@danielsimonjr/mathts-workbook 0.1.7 → 0.2.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/dist/cli.js CHANGED
@@ -1,107 +1,1678 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ SCHEMA_VERSION,
4
+ Session,
5
+ VERSION,
6
+ addCell,
7
+ buildDependencyGraph,
8
+ capabilitiesInfo,
3
9
  createExecutor,
4
- parseWorkbook
5
- } from "./chunk-L7UWFWMV.js";
10
+ describeData,
11
+ detectCycles,
12
+ editCell,
13
+ formatResult,
14
+ handleRequest,
15
+ importWorkbook,
16
+ listFunctions,
17
+ moveCell,
18
+ parseWorkbook,
19
+ parseYamlHardened,
20
+ removeCell,
21
+ renameCell,
22
+ serializeWorkbook,
23
+ setMetadata,
24
+ stripOutputs,
25
+ toDOT,
26
+ toMermaid,
27
+ writeFileAtomic
28
+ } from "./chunk-TS2WDJ7W.js";
6
29
 
7
30
  // src/cli.ts
31
+ import { readFileSync, writeFileSync, lstatSync, realpathSync } from "fs";
32
+ import { pathToFileURL } from "url";
33
+ import { basename } from "path";
34
+ import { createInterface } from "readline";
35
+ import * as mathFunctions from "@danielsimonjr/mathts-functions";
36
+
37
+ // src/html.ts
38
+ import { mathMLDocument, mathMLError } from "@danielsimonjr/mathts-expression";
39
+
40
+ // src/markdown.ts
41
+ function esc(s) {
42
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
43
+ }
44
+ function sanitizeHrefRaw(href) {
45
+ const h = href.trim();
46
+ let probe = h.replace(/&amp;/g, "&");
47
+ try {
48
+ probe = decodeURIComponent(probe);
49
+ } catch {
50
+ }
51
+ probe = probe.trim().toLowerCase();
52
+ if (probe.startsWith("//")) return null;
53
+ if (probe.startsWith("http://") || probe.startsWith("https://") || probe.startsWith("mailto:"))
54
+ return h;
55
+ if (probe.startsWith("/") || probe.startsWith("./") || probe.startsWith("../") || probe.startsWith("#"))
56
+ return h;
57
+ if (/^[a-z][a-z0-9+.-]*:/.test(probe)) return null;
58
+ return null;
59
+ }
60
+ function sanitizeHref(href) {
61
+ const raw = sanitizeHrefRaw(href);
62
+ return raw === null ? null : raw.replace(/"/g, "&quot;");
63
+ }
64
+ function inline(escaped) {
65
+ let s = escaped.replace(/`([^`]+)`/g, (_m, code) => `<code>${code}</code>`);
66
+ s = s.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
67
+ s = s.replace(/\*([^*]+)\*/g, "<em>$1</em>");
68
+ s = s.replace(/\[([^\]]*)\]\(([^)\s]+)\)/g, (_m, text, href) => {
69
+ const safe = sanitizeHref(href);
70
+ return safe ? `<a href="${safe}">${text}</a>` : text;
71
+ });
72
+ return s;
73
+ }
74
+ var HR = /^ {0,3}(-{3,}|\*{3,}|_{3,})\s*$/;
75
+ var HEADING = /^(#{1,6})\s+(.*)$/;
76
+ var UL = /^\s*[-*+]\s+/;
77
+ var OL = /^\s*\d+\.\s+/;
78
+ function isBlockStart(line2) {
79
+ return line2.trim() === "" || /^```/.test(line2.trim()) || HEADING.test(line2) || UL.test(line2) || OL.test(line2) || HR.test(line2);
80
+ }
81
+ function markdownToHtml(src) {
82
+ if (!src) return "";
83
+ const lines = src.replace(/\r\n?/g, "\n").split("\n");
84
+ const out = [];
85
+ let i = 0;
86
+ while (i < lines.length) {
87
+ const line2 = lines[i];
88
+ if (/^```/.test(line2.trim())) {
89
+ const buf = [];
90
+ i++;
91
+ while (i < lines.length && !/^```/.test(lines[i].trim())) {
92
+ buf.push(lines[i]);
93
+ i++;
94
+ }
95
+ i++;
96
+ out.push(`<pre><code>${esc(buf.join("\n"))}</code></pre>`);
97
+ continue;
98
+ }
99
+ if (line2.trim() === "") {
100
+ i++;
101
+ continue;
102
+ }
103
+ if (HR.test(line2)) {
104
+ out.push("<hr>");
105
+ i++;
106
+ continue;
107
+ }
108
+ const h = HEADING.exec(line2);
109
+ if (h) {
110
+ const lvl = h[1].length;
111
+ out.push(`<h${lvl}>${inline(esc(h[2].trim()))}</h${lvl}>`);
112
+ i++;
113
+ continue;
114
+ }
115
+ if (UL.test(line2)) {
116
+ const items = [];
117
+ while (i < lines.length && UL.test(lines[i])) {
118
+ items.push(`<li>${inline(esc(lines[i].replace(UL, "")))}</li>`);
119
+ i++;
120
+ }
121
+ out.push(`<ul>${items.join("")}</ul>`);
122
+ continue;
123
+ }
124
+ if (OL.test(line2)) {
125
+ const items = [];
126
+ while (i < lines.length && OL.test(lines[i])) {
127
+ items.push(`<li>${inline(esc(lines[i].replace(OL, "")))}</li>`);
128
+ i++;
129
+ }
130
+ out.push(`<ol>${items.join("")}</ol>`);
131
+ continue;
132
+ }
133
+ const para = [];
134
+ while (i < lines.length && !isBlockStart(lines[i])) {
135
+ para.push(lines[i]);
136
+ i++;
137
+ }
138
+ out.push(`<p>${inline(esc(para.join(" ")))}</p>`);
139
+ }
140
+ return out.join("\n");
141
+ }
142
+ function texEscape(s) {
143
+ return s.replace(/[\\&%$#_{}~^]/g, (c) => {
144
+ switch (c) {
145
+ case "\\":
146
+ return "\\textbackslash{}";
147
+ case "~":
148
+ return "\\textasciitilde{}";
149
+ case "^":
150
+ return "\\textasciicircum{}";
151
+ default:
152
+ return `\\${c}`;
153
+ }
154
+ });
155
+ }
156
+ function escapeInlineNonLink(text) {
157
+ let s = texEscape(text);
158
+ s = s.replace(/`([^`]+)`/g, (_m, code) => `\\texttt{${code}}`);
159
+ s = s.replace(/\*\*([^*]+)\*\*/g, "\\textbf{$1}");
160
+ s = s.replace(/\*([^*]+)\*/g, "\\emph{$1}");
161
+ return s;
162
+ }
163
+ function inlineTex(text) {
164
+ const linkRe = /\[([^\]]*)\]\(([^)\s]+)\)/g;
165
+ let out = "";
166
+ let last = 0;
167
+ let m;
168
+ while ((m = linkRe.exec(text)) !== null) {
169
+ out += escapeInlineNonLink(text.slice(last, m.index));
170
+ const raw = sanitizeHrefRaw(m[2]);
171
+ out += raw ? `\\href{${texEscape(raw)}}{${escapeInlineNonLink(m[1])}}` : escapeInlineNonLink(m[1]);
172
+ last = linkRe.lastIndex;
173
+ }
174
+ out += escapeInlineNonLink(text.slice(last));
175
+ return out;
176
+ }
177
+ function markdownToTex(src) {
178
+ if (!src) return "";
179
+ const lines = src.replace(/\r\n?/g, "\n").split("\n");
180
+ const out = [];
181
+ let i = 0;
182
+ const SEC = ["\\section*", "\\subsection*", "\\subsubsection*", "\\paragraph"];
183
+ while (i < lines.length) {
184
+ const line2 = lines[i];
185
+ if (/^```/.test(line2.trim())) {
186
+ const buf = [];
187
+ i++;
188
+ while (i < lines.length && !/^```/.test(lines[i].trim())) {
189
+ buf.push(lines[i]);
190
+ i++;
191
+ }
192
+ i++;
193
+ out.push(`\\begin{lstlisting}
194
+ ${buf.join("\n")}
195
+ \\end{lstlisting}`);
196
+ continue;
197
+ }
198
+ if (line2.trim() === "") {
199
+ i++;
200
+ continue;
201
+ }
202
+ if (HR.test(line2)) {
203
+ out.push("\\par\\noindent\\rule{\\linewidth}{0.4pt}\\par");
204
+ i++;
205
+ continue;
206
+ }
207
+ const h = HEADING.exec(line2);
208
+ if (h) {
209
+ const lvl = Math.min(h[1].length, 4);
210
+ out.push(`${SEC[lvl - 1]}{${inlineTex(h[2].trim())}}`);
211
+ i++;
212
+ continue;
213
+ }
214
+ if (UL.test(line2)) {
215
+ const items = [];
216
+ while (i < lines.length && UL.test(lines[i])) {
217
+ items.push(` \\item ${inlineTex(lines[i].replace(UL, ""))}`);
218
+ i++;
219
+ }
220
+ out.push(`\\begin{itemize}
221
+ ${items.join("\n")}
222
+ \\end{itemize}`);
223
+ continue;
224
+ }
225
+ if (OL.test(line2)) {
226
+ const items = [];
227
+ while (i < lines.length && OL.test(lines[i])) {
228
+ items.push(` \\item ${inlineTex(lines[i].replace(OL, ""))}`);
229
+ i++;
230
+ }
231
+ out.push(`\\begin{enumerate}
232
+ ${items.join("\n")}
233
+ \\end{enumerate}`);
234
+ continue;
235
+ }
236
+ const para = [];
237
+ while (i < lines.length && !isBlockStart(lines[i])) {
238
+ para.push(lines[i]);
239
+ i++;
240
+ }
241
+ out.push(inlineTex(para.join(" ")));
242
+ }
243
+ return out.join("\n\n");
244
+ }
245
+
246
+ // src/html.ts
247
+ function esc2(s) {
248
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
249
+ }
250
+ function renderEquation(content, parse3) {
251
+ let math;
252
+ if (!parse3) {
253
+ math = mathMLError(content);
254
+ } else {
255
+ try {
256
+ math = mathMLDocument(parse3(content));
257
+ } catch {
258
+ math = mathMLError(content);
259
+ }
260
+ }
261
+ return `<figure class="cell cell-eq">${math}</figure>`;
262
+ }
263
+ function renderCell(cell, parse3) {
264
+ const cap = cell.id ? `<figcaption>${esc2(cell.id)}</figcaption>` : "";
265
+ switch (cell.type) {
266
+ case "markdown":
267
+ return `<section class="cell cell-md">${markdownToHtml(cell.content)}</section>`;
268
+ case "equation":
269
+ return renderEquation(cell.content, parse3);
270
+ case "code": {
271
+ const src = `<pre class="src"><code>${esc2(cell.content)}</code></pre>`;
272
+ let res = "";
273
+ if (cell.error !== void 0) res = `<div class="cell-error">${esc2(cell.error)}</div>`;
274
+ else if (cell.output !== void 0)
275
+ res = `<div class="cell-output">${esc2(cell.output)}</div>`;
276
+ return `<figure class="cell cell-code">${cap}${src}${res}</figure>`;
277
+ }
278
+ case "test": {
279
+ const err = cell.error;
280
+ let badge;
281
+ let cls;
282
+ if (err !== void 0) {
283
+ badge = "\u26A0";
284
+ cls = "err";
285
+ } else if (cell.passed === true) {
286
+ badge = "\u2713";
287
+ cls = "pass";
288
+ } else if (cell.passed === false) {
289
+ badge = "\u2717";
290
+ cls = "fail";
291
+ } else {
292
+ badge = "\u25CB";
293
+ cls = "nrun";
294
+ }
295
+ const note = err !== void 0 ? ` <span class="note">${esc2(err)}</span>` : "";
296
+ return `<figure class="cell cell-test ${cls}"><span class="badge">${badge}</span> <code>${esc2(cell.content)}</code>${note}</figure>`;
297
+ }
298
+ case "data": {
299
+ const val = cell.error !== void 0 ? esc2(cell.error) : esc2(cell.output ?? "");
300
+ return `<figure class="cell cell-data">${cap}<pre><code>${val}</code></pre></figure>`;
301
+ }
302
+ case "chart": {
303
+ const note = cell.note ? `<p class="note">\u26A0 ${esc2(cell.note)}</p>` : "";
304
+ return `<figure class="cell cell-chart">${cap}${cell.chartSvg ?? '<p class="note">no chart</p>'}${note}</figure>`;
305
+ }
306
+ default:
307
+ return `<figure class="cell"><pre><code>${esc2(cell.content)}</code></pre></figure>`;
308
+ }
309
+ }
310
+ function toCSS() {
311
+ return [
312
+ ":root{--fg:#1a1a2e;--muted:#5a5a72;--bg:#fff;--code-bg:#f4f4f8;--border:#e0e0ea;--accent:#2a4d8f;--pass:#1a7f37;--fail:#cf222e;}",
313
+ "*{box-sizing:border-box}",
314
+ "body{margin:0;color:var(--fg);background:var(--bg);font:16px/1.6 system-ui,-apple-system,Segoe UI,Roboto,sans-serif}",
315
+ "main{max-width:48rem;margin:0 auto;padding:2rem 1.25rem}",
316
+ "header{border-bottom:2px solid var(--border);margin-bottom:1.5rem;padding-bottom:1rem}",
317
+ ".doc-title{margin:0 0 .25rem;font-size:1.9rem}",
318
+ ".doc-meta,.doc-desc{color:var(--muted);margin:.15rem 0}",
319
+ ".tag{display:inline-block;background:var(--code-bg);border:1px solid var(--border);border-radius:1rem;padding:.05rem .55rem;font-size:.8rem;margin-right:.3rem}",
320
+ ".cell{margin:1.1rem 0}",
321
+ "h1,h2,h3,h4{line-height:1.25}",
322
+ "code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}",
323
+ "pre{background:var(--code-bg);border:1px solid var(--border);border-radius:6px;padding:.7rem .9rem;overflow:auto}",
324
+ ".cell-code{border-left:3px solid var(--accent);padding-left:.9rem}",
325
+ ".cell-code figcaption{color:var(--muted);font-size:.8rem;font-family:ui-monospace,monospace;margin-bottom:.2rem}",
326
+ ".cell-output{background:#fafaff;border:1px solid var(--border);border-radius:6px;padding:.5rem .9rem;margin-top:.4rem;white-space:pre-wrap}",
327
+ ".cell-error{background:#fff0f0;border:1px solid #f3c0c0;color:var(--fail);border-radius:6px;padding:.5rem .9rem;margin-top:.4rem}",
328
+ ".cell-eq{text-align:center;margin:1.3rem 0}",
329
+ "math{font-size:1.15rem}",
330
+ ".cell-test{display:flex;align-items:center;gap:.5rem;font-size:.95rem}",
331
+ ".cell-test .badge{font-weight:700;width:1.4rem;height:1.4rem;display:inline-flex;align-items:center;justify-content:center;border-radius:50%;color:#fff}",
332
+ ".cell-test.pass .badge{background:var(--pass)}",
333
+ ".cell-test.fail .badge{background:var(--fail)}",
334
+ ".cell-test.err .badge{background:#9a6700}",
335
+ ".cell-test.nrun .badge{background:#8a8a9a}",
336
+ ".cell-test .note{color:var(--fail);font-size:.85rem}",
337
+ ".cell-chart{text-align:center}",
338
+ ".note{color:var(--muted)}"
339
+ ].join("");
340
+ }
341
+ function toHTML(doc, options = {}) {
342
+ const title = doc.title ?? "Workbook";
343
+ const headerBits = [`<h1 class="doc-title">${esc2(title)}</h1>`];
344
+ if (doc.author) headerBits.push(`<p class="doc-meta">by ${esc2(doc.author)}</p>`);
345
+ if (doc.description) headerBits.push(`<p class="doc-desc">${esc2(doc.description)}</p>`);
346
+ if (doc.tags && doc.tags.length) {
347
+ headerBits.push(
348
+ `<p class="doc-tags">${doc.tags.map((t) => `<span class="tag">${esc2(t)}</span>`).join("")}</p>`
349
+ );
350
+ }
351
+ const body = doc.cells.map((c) => renderCell(c, options.parse)).join("\n");
352
+ return [
353
+ "<!doctype html>",
354
+ '<html lang="en">',
355
+ "<head>",
356
+ '<meta charset="utf-8">',
357
+ '<meta name="viewport" content="width=device-width, initial-scale=1">',
358
+ `<title>${esc2(title)}</title>`,
359
+ `<style>${toCSS()}</style>`,
360
+ "</head>",
361
+ "<body>",
362
+ "<main>",
363
+ `<header>${headerBits.join("")}</header>`,
364
+ body,
365
+ "</main>",
366
+ "</body>",
367
+ "</html>"
368
+ ].join("\n");
369
+ }
370
+
371
+ // src/tex.ts
372
+ var PREAMBLE = [
373
+ "\\documentclass{article}",
374
+ "\\usepackage{amsmath}",
375
+ "\\usepackage{tikz}",
376
+ "\\usepackage{listings}",
377
+ "\\usepackage{xcolor}",
378
+ "\\usepackage[margin=1in]{geometry}",
379
+ "\\usepackage{hyperref}"
380
+ ].join("\n");
381
+ function renderEquationTex(content, parse3) {
382
+ if (parse3) {
383
+ try {
384
+ const node = parse3(content);
385
+ return `\\[ ${node.toTex()} \\]`;
386
+ } catch {
387
+ }
388
+ }
389
+ return `\\texttt{${texEscape(content)}}`;
390
+ }
391
+ function renderCellTex(cell, parse3) {
392
+ switch (cell.type) {
393
+ case "markdown":
394
+ return markdownToTex(cell.content);
395
+ case "equation":
396
+ return renderEquationTex(cell.content, parse3);
397
+ case "code": {
398
+ const cap = cell.id ? `\\textit{${texEscape(cell.id)}}\\\\
399
+ ` : "";
400
+ const src = `\\begin{lstlisting}
401
+ ${cell.content}
402
+ \\end{lstlisting}`;
403
+ let res = "";
404
+ if (cell.error !== void 0)
405
+ res = `
406
+
407
+ \\textcolor{red}{\\texttt{${texEscape(cell.error)}}}`;
408
+ else if (cell.output !== void 0)
409
+ res = `
410
+
411
+ \\begin{verbatim}
412
+ ${cell.output}
413
+ \\end{verbatim}`;
414
+ return `${cap}${src}${res}`;
415
+ }
416
+ case "test": {
417
+ const label = `\\texttt{${texEscape(cell.content)}}`;
418
+ if (cell.error !== void 0)
419
+ return `\\textcolor{orange}{[ERROR]} ${label} --- ${texEscape(cell.error)}`;
420
+ if (cell.passed === true) return `\\textcolor{green!60!black}{[PASS]} ${label}`;
421
+ if (cell.passed === false) return `\\textcolor{red}{[FAIL]} ${label}`;
422
+ return `\\textcolor{gray}{[NOT RUN]} ${label}`;
423
+ }
424
+ case "data": {
425
+ const val = cell.error !== void 0 ? cell.error : cell.output ?? "";
426
+ const cap = cell.id ? `\\textit{${texEscape(cell.id)}}\\\\
427
+ ` : "";
428
+ return `${cap}\\begin{verbatim}
429
+ ${val}
430
+ \\end{verbatim}`;
431
+ }
432
+ case "chart": {
433
+ const chart = cell.chartTikz ?? "% no chart";
434
+ const note = cell.note ? `
435
+
436
+ \\textit{${texEscape(cell.note)}}` : "";
437
+ return `\\begin{center}
438
+ ${chart}
439
+ \\end{center}${note}`;
440
+ }
441
+ default:
442
+ return `\\begin{verbatim}
443
+ ${cell.content}
444
+ \\end{verbatim}`;
445
+ }
446
+ }
447
+ function toTeX(doc, options = {}) {
448
+ const body = doc.cells.map((c) => renderCellTex(c, options.parse)).join("\n\n");
449
+ if (options.fragment) return body;
450
+ const meta = [];
451
+ if (doc.title) meta.push(`\\title{${texEscape(doc.title)}}`);
452
+ if (doc.author) meta.push(`\\author{${texEscape(doc.author)}}`);
453
+ const maketitle = doc.title ? "\\maketitle\n" : "";
454
+ return [PREAMBLE, ...meta, "\\begin{document}", `${maketitle}${body}`, "\\end{document}"].join(
455
+ "\n"
456
+ );
457
+ }
458
+
459
+ // src/pdf.ts
460
+ import { latexToPdf } from "@danielsimonjr/mathts-plot/render";
461
+ function toPDF(doc, outPath, options = {}) {
462
+ const { parse: parse3, ...renderOpts } = options;
463
+ return latexToPdf(toTeX(doc, { parse: parse3, fragment: false }), outPath, renderOpts);
464
+ }
465
+
466
+ // src/svg.ts
467
+ import { line, scatter, bar } from "@danielsimonjr/mathts-plot";
468
+ function coerce(v) {
469
+ if (typeof v === "number") return v;
470
+ if (typeof v === "bigint") return Number(v);
471
+ const u = v;
472
+ if (u && typeof u.toNumeric === "function") {
473
+ try {
474
+ return Number(u.toNumeric());
475
+ } catch {
476
+ return NaN;
477
+ }
478
+ }
479
+ return Number(v);
480
+ }
481
+ function toNums(raw) {
482
+ let arr = raw;
483
+ const m = raw;
484
+ if (m && typeof m.toArray === "function") {
485
+ try {
486
+ arr = m.toArray();
487
+ } catch {
488
+ arr = raw;
489
+ }
490
+ }
491
+ return Array.isArray(arr) ? arr.flat(Infinity).map(coerce) : [];
492
+ }
493
+ function renderChart(spec, xRaw, yRaw, format = "svg") {
494
+ const xs = toNums(xRaw);
495
+ const ys = toNums(yRaw);
496
+ const opts = format === "tikz" ? {
497
+ title: spec.title,
498
+ xLabel: spec.xLabel,
499
+ yLabel: spec.yLabel,
500
+ format: "tikz",
501
+ tikz: { standalone: false }
502
+ } : { title: spec.title, xLabel: spec.xLabel, yLabel: spec.yLabel };
503
+ if (spec.type === "scatter") return scatter(xs, ys, opts);
504
+ if (spec.type === "bar") return bar(xs, ys, opts);
505
+ return line(xs, ys, opts);
506
+ }
507
+
508
+ // src/cli.ts
509
+ var parse2 = mathFunctions.parse;
8
510
  var HELP = `
9
511
  mtsw - MathTS Workbook CLI
10
512
 
11
513
  Usage:
12
- mtsw run <file> Execute a workbook
13
- mtsw run <file> -c <cell> Run specific cell
14
- mtsw run <file> -v Verbose output
15
- mtsw watch <file> Watch and re-run on changes
16
- mtsw validate <file> Validate workbook structure
17
- mtsw graph <file> Show dependency graph
18
- mtsw graph <file> -f mermaid Export as Mermaid diagram
19
- mtsw strip <file> Strip outputs (for git)
20
- mtsw new <name> Create new workbook
21
- mtsw new <name> -t <template> Use template (basic|tensor-physics|data-science)
22
- mtsw export <file> -f <fmt> Export to format (html|pdf|ipynb|latex)
514
+ mtsw run <file> [-c <id>] [-v] [--json] [--write]
515
+ Execute a workbook (or one cell + its
516
+ deps with -c/--cell). -v: events,
517
+ --json: machine output, --write: persist
518
+ outputs back to the file.
519
+ mtsw describe <file> [--json] Structured document model (cells, graph)
520
+ mtsw validate <file> [--json] Validate structure (ids, deps, cycles)
521
+ mtsw graph <file> [-f mermaid|dot] Print the dependency graph
522
+ mtsw strip <file> [-w|--write] Strip outputs (stdout, or -w to rewrite)
523
+ mtsw new <name> [-t basic|empty|chart] [--empty] [-o <path>] [--force]
524
+ Scaffold a new workbook (-o for any path)
525
+ mtsw import [<file>] [-o out.mtsw] [--json]
526
+ Build a .mtsw from a JSON/YAML doc
527
+ ({metadata,cells:[{id,type,content,dependsOn}]};
528
+ file or stdin; stdout if no -o)
529
+ mtsw capabilities [--json] Engine version + feature flags
530
+ mtsw templates [--json] List scaffold templates
531
+ mtsw cell <verb> <file> ... Edit cells (atomic, in-place):
532
+ cell add <file> --type <t> --id <id> [--content <s> | --content-file <p|->]
533
+ [--depends-on a,b] [--before|--after <id> | --at <n>]
534
+ cell edit <file> <id> [--content <s> | --content-file <p|->] [--type <t>] [--depends-on a,b]
535
+ cell rm <file> <id> [--force] (--force detaches dependents)
536
+ cell move <file> <id> (--before|--after <id> | --at <n>)
537
+ cell rename <file> <oldId> <newId>
538
+ (any cell verb accepts --json and --dry-run)
539
+ mtsw functions [--json] List functions/constants cells can call
540
+ mtsw meta get <file> [--json] Show workbook metadata
541
+ mtsw meta set <file> [--title s] [--author s] [--description s] [--tags a,b]
542
+ mtsw export <file> [--format html|tex|json] [--fragment] [-o out] [--no-run]
543
+ Render to a self-contained HTML or LaTeX
544
+ document (MathML/TikZ equations + charts,
545
+ no external deps), or emit the executed
546
+ run report as JSON. --fragment (tex only)
547
+ omits the preamble for \\input; --no-run is
548
+ incompatible with --format json.
549
+ mtsw serve [<file>] JSON-RPC over stdio (persistent session
550
+ w/ streaming events + incremental run)
23
551
 
24
552
  Options:
25
553
  -h, --help Show this help
26
- -v, --verbose Verbose output
27
554
  -V, --version Show version
555
+
556
+ Notes:
557
+ --json output is a single envelope on stdout: { schemaVersion, command, ok,
558
+ data, problems }. The envelope is emitted even on failure; exit code mirrors
559
+ 'ok' (GUI clients should read 'ok', not the exit code).
560
+ Writes (run --write, strip -w, new) are atomic but drop YAML comments / key
561
+ order. Dependency scope is direct-only (non-transitive); cell ids must be
562
+ valid identifiers ([A-Za-z_][A-Za-z0-9_]*).
563
+ `.trimStart();
564
+ function jsonEnvelope(command, ok, data, problems = []) {
565
+ const seen = /* @__PURE__ */ new WeakSet();
566
+ const replacer = (_key, value) => {
567
+ if (typeof value === "bigint") return `${value}n`;
568
+ if (value !== null && typeof value === "object") {
569
+ if (seen.has(value)) return "[Circular]";
570
+ seen.add(value);
571
+ }
572
+ return value;
573
+ };
574
+ return JSON.stringify(
575
+ { schemaVersion: SCHEMA_VERSION, command, ok, data, problems: problems ?? [] },
576
+ replacer,
577
+ 2
578
+ );
579
+ }
580
+ var VALUE_FLAGS = /* @__PURE__ */ new Set([
581
+ "-f",
582
+ "--format",
583
+ "-t",
584
+ "--template",
585
+ "-c",
586
+ "--cell",
587
+ "--id",
588
+ "--content",
589
+ "--content-file",
590
+ "--depends-on",
591
+ "--before",
592
+ "--after",
593
+ "--at",
594
+ "--title",
595
+ "--author",
596
+ "--description",
597
+ "--tags",
598
+ "-o",
599
+ "--output"
600
+ ]);
601
+ function firstPositional(args) {
602
+ for (let i = 0; i < args.length; i++) {
603
+ const arg = args[i];
604
+ if (VALUE_FLAGS.has(arg)) {
605
+ i++;
606
+ continue;
607
+ }
608
+ if (!arg.startsWith("-")) return arg;
609
+ }
610
+ return void 0;
611
+ }
612
+ function flagValue(args, flag) {
613
+ const i = args.indexOf(flag);
614
+ return i >= 0 ? args[i + 1] : void 0;
615
+ }
616
+ function readFile(file) {
617
+ try {
618
+ return { content: readFileSync(file, "utf-8") };
619
+ } catch (error) {
620
+ return {
621
+ error: `Cannot read file '${file}': ${error instanceof Error ? error.message : String(error)}`
622
+ };
623
+ }
624
+ }
625
+ var BASIC_TEMPLATE = `version: "1.0"
626
+ metadata:
627
+ title: "<NAME>"
628
+ runtime:
629
+ engine: mathts
630
+ execution: reactive
631
+ cells:
632
+ - markdown: |
633
+ # <NAME>
634
+
635
+ A new MathTS workbook. Edit the cells, then run:
636
+ mtsw run <NAME>.mtsw
637
+ id: intro
638
+
639
+ - code: "1 + 1"
640
+ id: example
641
+
642
+ - test: "example == 2"
643
+ id: checkExample
644
+ depends_on: [example]
28
645
  `;
29
- async function main() {
30
- const args = process.argv.slice(2);
31
- if (args.length === 0 || args.includes("-h") || args.includes("--help")) {
32
- console.log(HELP);
33
- process.exit(0);
34
- }
35
- if (args.includes("-V") || args.includes("--version")) {
36
- console.log("mtsw version 0.1.0");
37
- process.exit(0);
38
- }
39
- const command = args[0];
40
- switch (command) {
41
- case "run":
42
- await runCommand(args.slice(1));
43
- break;
44
- case "validate":
45
- await validateCommand(args.slice(1));
46
- break;
47
- case "graph":
48
- await graphCommand(args.slice(1));
49
- break;
50
- case "new":
51
- await newCommand(args.slice(1));
52
- break;
53
- default:
54
- console.error(`Unknown command: ${command}`);
55
- console.log(HELP);
56
- process.exit(1);
646
+ function bullets(items) {
647
+ return items.map((item) => ` - ${item}`).join("\n");
648
+ }
649
+ function statusMark(status) {
650
+ return status === "success" || status === "pass" ? "\u2713" : "\u2717";
651
+ }
652
+ function humanCellLine(cell) {
653
+ const mark = statusMark(cell.status);
654
+ const body = cell.status === "error" || cell.status === "fail" ? cell.error ?? "(failed)" : formatResult(cell.output);
655
+ return `${mark} ${cell.id} (${cell.type}): ${body}`;
656
+ }
657
+ function failureSummary(cells) {
658
+ const failures = cells.filter((c) => c.status === "error" || c.status === "fail");
659
+ if (failures.length === 0) return "";
660
+ return `${failures.length} cell(s) failed:
661
+ ${bullets(failures.map((c) => `${c.id}: ${c.error ?? "(failed)"}`))}`;
662
+ }
663
+ function errMessage(error) {
664
+ return error instanceof Error ? error.message : String(error);
665
+ }
666
+ function applyResults(workbook, results) {
667
+ for (const result of results) {
668
+ const cell = workbook.cells.find((c) => c.id === result.id);
669
+ if (!cell) continue;
670
+ cell.output = result.output;
671
+ cell.error = result.error;
57
672
  }
58
673
  }
674
+ function jsonCells(cells) {
675
+ return cells.map((c) => ({
676
+ id: c.id,
677
+ type: c.type,
678
+ status: c.status,
679
+ output: c.output ?? null,
680
+ error: c.error ?? null
681
+ }));
682
+ }
683
+ function failureList(cells) {
684
+ return cells.filter((c) => c.status === "error" || c.status === "fail").map((c) => `${c.id}: ${c.error ?? "(failed)"}`);
685
+ }
59
686
  async function runCommand(args) {
60
- const file = args[0];
687
+ const json = args.includes("--json");
688
+ const fail = (problems2, data = null) => json ? { stdout: jsonEnvelope("run", false, data, problems2), stderr: "", exitCode: 1 } : { stdout: "", stderr: problems2.join("\n"), exitCode: 1 };
689
+ const file = firstPositional(args);
61
690
  if (!file) {
62
- console.error("Usage: mtsw run <file>");
63
- process.exit(1);
64
- }
65
- console.log(`Running workbook: ${file}`);
66
- const content = "";
67
- const result = parseWorkbook(content);
68
- if (!result.success) {
69
- console.error("Parse errors:", result.errors);
70
- process.exit(1);
71
- }
72
- if (result.workbook) {
73
- const executor = createExecutor(result.workbook);
74
- executor.on((event) => {
75
- console.log(`[${event.type}] ${event.cellId ?? ""}`);
76
- });
77
- await executor.runAll();
691
+ return json ? fail(["Usage: mtsw run <file> [-c <id>] [-v] [--json] [--write]"]) : {
692
+ stdout: "",
693
+ stderr: "Usage: mtsw run <file> [-c <id>] [-v] [--json] [--write]",
694
+ exitCode: 1
695
+ };
78
696
  }
697
+ const read = readFile(file);
698
+ if (read.error) return fail([read.error]);
699
+ const parsed = parseWorkbook(read.content);
700
+ if (!parsed.success) {
701
+ const problems2 = parsed.errors ?? [];
702
+ return json ? fail(problems2, { cells: [] }) : {
703
+ stdout: "",
704
+ stderr: `Parse errors:
705
+ ${bullets(problems2)}`,
706
+ exitCode: 1
707
+ };
708
+ }
709
+ const cellId = flagValue(args, "-c") ?? flagValue(args, "--cell");
710
+ if (cellId !== void 0 && !parsed.workbook.cells.some((c) => c.id === cellId)) {
711
+ return json ? fail([`No such cell: '${cellId}'`], { cells: [] }) : {
712
+ stdout: "",
713
+ stderr: `No such cell: '${cellId}'`,
714
+ exitCode: 1
715
+ };
716
+ }
717
+ const verbose = args.includes("-v") || args.includes("--verbose");
718
+ const executor = createExecutor(parsed.workbook);
719
+ const events = [];
720
+ if (verbose) {
721
+ executor.on((event) => events.push(`[${event.type}] ${event.cellId ?? ""}`.trimEnd()));
722
+ }
723
+ const report = await executor.runReport(cellId !== void 0 ? { only: cellId } : {});
724
+ const problems = report.ok ? [] : failureList(report.cells);
725
+ const humanFailures = report.ok ? "" : failureSummary(report.cells);
726
+ if (args.includes("--write")) {
727
+ applyResults(parsed.workbook, report.cells);
728
+ try {
729
+ writeFileAtomic(file, serializeWorkbook(parsed.workbook));
730
+ } catch (error) {
731
+ return fail([`Failed to write '${file}': ${errMessage(error)}`], {
732
+ cells: jsonCells(report.cells)
733
+ });
734
+ }
735
+ if (json) {
736
+ const stdout2 = jsonEnvelope(
737
+ "run",
738
+ report.ok,
739
+ { cells: jsonCells(report.cells), written: file },
740
+ problems
741
+ );
742
+ return { stdout: stdout2, stderr: "", exitCode: report.ok ? 0 : 1 };
743
+ }
744
+ return {
745
+ stdout: "",
746
+ stderr: [`Updated ${file}`, humanFailures].filter(Boolean).join("\n"),
747
+ exitCode: report.ok ? 0 : 1
748
+ };
749
+ }
750
+ if (json) {
751
+ const stdout2 = jsonEnvelope("run", report.ok, { cells: jsonCells(report.cells) }, problems);
752
+ return { stdout: stdout2, stderr: "", exitCode: report.ok ? 0 : 1 };
753
+ }
754
+ const body = report.cells.map(humanCellLine).join("\n");
755
+ const stdout = verbose && events.length > 0 ? `${events.join("\n")}
756
+
757
+ ${body}` : body;
758
+ return { stdout, stderr: humanFailures, exitCode: report.ok ? 0 : 1 };
759
+ }
760
+ function validateChartSpecs(workbook) {
761
+ const problems = [];
762
+ const ids = new Set(workbook.cells.map((c) => c.id));
763
+ for (const cell of workbook.cells) {
764
+ if (cell.type !== "visualization") continue;
765
+ let spec;
766
+ try {
767
+ spec = parseYamlHardened(cell.content);
768
+ } catch (error) {
769
+ problems.push(`Cell "${cell.id}": invalid chart spec (${errMessage(error)})`);
770
+ continue;
771
+ }
772
+ if (!spec || typeof spec !== "object" || Array.isArray(spec)) {
773
+ problems.push(`Cell "${cell.id}": chart spec must be a mapping with type/x/y`);
774
+ continue;
775
+ }
776
+ const s = spec;
777
+ if (s.type !== void 0 && !["line", "scatter", "bar"].includes(String(s.type))) {
778
+ problems.push(`Cell "${cell.id}": chart type must be line|scatter|bar`);
779
+ }
780
+ for (const axis of ["x", "y"]) {
781
+ const a = s[axis];
782
+ if (!a || typeof a !== "object") {
783
+ problems.push(`Cell "${cell.id}": chart "${axis}" must be a mapping with a "data" field`);
784
+ continue;
785
+ }
786
+ const data = a.data;
787
+ if (data === void 0) {
788
+ problems.push(`Cell "${cell.id}": chart ${axis}.data is required`);
789
+ } else if (typeof data === "string" && !ids.has(data)) {
790
+ problems.push(`Cell "${cell.id}": chart ${axis}.data references unknown cell "${data}"`);
791
+ }
792
+ }
793
+ }
794
+ return problems;
79
795
  }
80
- async function validateCommand(args) {
81
- const file = args[0];
796
+ function computeProblems(parsed) {
797
+ const problems = [...parsed.errors ?? []];
798
+ if (parsed.success && parsed.workbook) {
799
+ const graph = buildDependencyGraph(parsed.workbook.cells);
800
+ for (const cycle of detectCycles(graph)) {
801
+ problems.push(`Dependency cycle: ${cycle.join(" -> ")}`);
802
+ }
803
+ problems.push(...validateChartSpecs(parsed.workbook));
804
+ }
805
+ return problems;
806
+ }
807
+ function validateCommand(args) {
808
+ const json = args.includes("--json");
809
+ const file = firstPositional(args);
810
+ if (!file) {
811
+ return json ? {
812
+ stdout: jsonEnvelope("validate", false, null, ["Usage: mtsw validate <file>"]),
813
+ stderr: "",
814
+ exitCode: 1
815
+ } : { stdout: "", stderr: "Usage: mtsw validate <file>", exitCode: 1 };
816
+ }
817
+ const read = readFile(file);
818
+ if (read.error) {
819
+ return json ? { stdout: jsonEnvelope("validate", false, null, [read.error]), stderr: "", exitCode: 1 } : { stdout: "", stderr: read.error, exitCode: 1 };
820
+ }
821
+ const parsed = parseWorkbook(read.content);
822
+ const problems = computeProblems(parsed);
823
+ const ok = problems.length === 0;
824
+ const cellCount = parsed.workbook ? parsed.workbook.cells.length : 0;
825
+ if (json) {
826
+ return {
827
+ stdout: jsonEnvelope("validate", ok, { cellCount }, problems),
828
+ stderr: "",
829
+ exitCode: ok ? 0 : 1
830
+ };
831
+ }
832
+ if (ok) {
833
+ return { stdout: `OK: '${file}' is valid (${cellCount} cell(s))`, stderr: "", exitCode: 0 };
834
+ }
835
+ return { stdout: "", stderr: `Invalid workbook:
836
+ ${bullets(problems)}`, exitCode: 1 };
837
+ }
838
+ function describeCommand(args) {
839
+ const json = args.includes("--json");
840
+ const file = firstPositional(args);
82
841
  if (!file) {
83
- console.error("Usage: mtsw validate <file>");
84
- process.exit(1);
842
+ return json ? {
843
+ stdout: jsonEnvelope("describe", false, null, ["Usage: mtsw describe <file>"]),
844
+ stderr: "",
845
+ exitCode: 1
846
+ } : { stdout: "", stderr: "Usage: mtsw describe <file>", exitCode: 1 };
847
+ }
848
+ const read = readFile(file);
849
+ if (read.error) {
850
+ return json ? { stdout: jsonEnvelope("describe", false, null, [read.error]), stderr: "", exitCode: 1 } : { stdout: "", stderr: read.error, exitCode: 1 };
851
+ }
852
+ const parsed = parseWorkbook(read.content);
853
+ const problems = computeProblems(parsed);
854
+ const ok = parsed.success && problems.length === 0;
855
+ const data = describeData(parsed.workbook);
856
+ const cells = data.cells;
857
+ if (json) {
858
+ return {
859
+ stdout: jsonEnvelope("describe", ok, data, problems),
860
+ stderr: "",
861
+ exitCode: ok ? 0 : 1
862
+ };
863
+ }
864
+ if (!parsed.success) {
865
+ return { stdout: "", stderr: `Parse errors:
866
+ ${bullets(problems)}`, exitCode: 1 };
867
+ }
868
+ const byType = cells.reduce((acc, c) => {
869
+ acc[c.type] = (acc[c.type] ?? 0) + 1;
870
+ return acc;
871
+ }, {});
872
+ const summary = [
873
+ `${data.metadata.title ?? "(untitled)"} \u2014 ${cells.length} cell(s)`,
874
+ ...Object.entries(byType).map(([t, n]) => ` ${t}: ${n}`),
875
+ problems.length ? `${problems.length} problem(s)` : "no problems"
876
+ ].join("\n");
877
+ return { stdout: summary, stderr: "", exitCode: ok ? 0 : 1 };
878
+ }
879
+ function capabilitiesCommand(args) {
880
+ const data = capabilitiesInfo();
881
+ if (args.includes("--json")) {
882
+ return { stdout: jsonEnvelope("capabilities", true, data, []), stderr: "", exitCode: 0 };
883
+ }
884
+ const lines = [
885
+ `mtsw ${data.version} (schema ${SCHEMA_VERSION.major}.${SCHEMA_VERSION.minor})`,
886
+ `supported cells: ${data.cellTypes.supported.join(", ")}`,
887
+ `deferred cells: ${data.cellTypes.deferred.join(", ")}`,
888
+ `commands: ${data.commands.join(", ")}`
889
+ ];
890
+ return { stdout: lines.join("\n"), stderr: "", exitCode: 0 };
891
+ }
892
+ function templatesCommand(args) {
893
+ const templates = Object.entries(TEMPLATES).map(([name, t]) => ({
894
+ name,
895
+ description: t.description
896
+ }));
897
+ if (args.includes("--json")) {
898
+ return { stdout: jsonEnvelope("templates", true, { templates }, []), stderr: "", exitCode: 0 };
899
+ }
900
+ return {
901
+ stdout: templates.map((t) => `${t.name} - ${t.description}`).join("\n"),
902
+ stderr: "",
903
+ exitCode: 0
904
+ };
905
+ }
906
+ function functionsCommand(args) {
907
+ const data = listFunctions();
908
+ if (args.includes("--json")) {
909
+ return { stdout: jsonEnvelope("functions", true, data, []), stderr: "", exitCode: 0 };
910
+ }
911
+ const lines = [
912
+ `functions (${data.functions.length}): ${data.functions.join(", ")}`,
913
+ `constants (${data.constants.length}): ${data.constants.join(", ")}`
914
+ ];
915
+ return { stdout: lines.join("\n"), stderr: "", exitCode: 0 };
916
+ }
917
+ function metaCommand(args) {
918
+ const json = args.includes("--json");
919
+ const fail = (problems) => json ? { stdout: jsonEnvelope("meta", false, null, problems), stderr: "", exitCode: 1 } : { stdout: "", stderr: problems.join("\n"), exitCode: 1 };
920
+ const verb = args[0];
921
+ if (verb !== "get" && verb !== "set") return fail(["Usage: mtsw meta <get|set> <file> ..."]);
922
+ const vargs = args.slice(1);
923
+ const file = positionals(vargs)[0];
924
+ if (!file) return fail([`Usage: mtsw meta ${verb} <file> ...`]);
925
+ const read = readFile(file);
926
+ if (read.error) return fail([read.error]);
927
+ const parsed = parseWorkbook(read.content);
928
+ if (!parsed.success) return fail(parsed.errors ?? ["Parse error"]);
929
+ const wb = parsed.workbook;
930
+ if (verb === "get") {
931
+ if (json)
932
+ return { stdout: jsonEnvelope("meta", true, wb.metadata, []), stderr: "", exitCode: 0 };
933
+ const lines = Object.entries(wb.metadata).map(
934
+ ([k, v]) => `${k}: ${Array.isArray(v) ? v.join(", ") : String(v)}`
935
+ );
936
+ return { stdout: lines.join("\n") || "(no metadata)", stderr: "", exitCode: 0 };
937
+ }
938
+ const changes = {};
939
+ const title = flagValue(vargs, "--title");
940
+ if (title !== void 0) changes.title = title;
941
+ const author = flagValue(vargs, "--author");
942
+ if (author !== void 0) changes.author = author;
943
+ const description = flagValue(vargs, "--description");
944
+ if (description !== void 0) changes.description = description;
945
+ const tags = flagValue(vargs, "--tags");
946
+ if (tags !== void 0)
947
+ changes.tags = tags.split(",").map((s) => s.trim()).filter(Boolean);
948
+ if (Object.keys(changes).length === 0) {
949
+ return fail(["meta set requires at least one of --title/--author/--description/--tags"]);
950
+ }
951
+ let next;
952
+ try {
953
+ next = setMetadata(wb, changes);
954
+ } catch (error) {
955
+ return fail([errMessage(error)]);
956
+ }
957
+ try {
958
+ writeFileAtomic(file, serializeWorkbook(next));
959
+ } catch (error) {
960
+ return fail([`Failed to write '${file}': ${errMessage(error)}`]);
961
+ }
962
+ if (json)
963
+ return { stdout: jsonEnvelope("meta", true, next.metadata, []), stderr: "", exitCode: 0 };
964
+ return { stdout: "", stderr: `Updated metadata in ${file}`, exitCode: 0 };
965
+ }
966
+ function buildRenderDoc(workbook, byId, format = "svg") {
967
+ const lookup = (ref) => {
968
+ if (typeof ref !== "string") return ref;
969
+ return byId?.get(ref)?.output ?? workbook.cells.find((c) => c.id === ref)?.output;
970
+ };
971
+ const cells = workbook.cells.map((c) => {
972
+ const rc = { type: c.type, content: c.content, id: c.id };
973
+ if (c.type === "markdown" || c.type === "equation") return rc;
974
+ if (c.type === "visualization") {
975
+ rc.type = "chart";
976
+ try {
977
+ const spec = parseYamlHardened(c.content);
978
+ const rendered = renderChart(
979
+ { type: spec?.type, title: spec?.title, xLabel: spec?.x?.label, yLabel: spec?.y?.label },
980
+ lookup(spec?.x?.data),
981
+ lookup(spec?.y?.data),
982
+ format
983
+ );
984
+ if (format === "tikz") rc.chartTikz = rendered;
985
+ else rc.chartSvg = rendered;
986
+ const unresolved = [];
987
+ for (const ref of [spec?.x?.data, spec?.y?.data]) {
988
+ if (typeof ref !== "string") continue;
989
+ if (byId) {
990
+ const r2 = byId.get(ref);
991
+ if (!r2 || r2.status === "error" || r2.status === "fail") unresolved.push(ref);
992
+ } else if (workbook.cells.find((x) => x.id === ref)?.output === void 0) {
993
+ unresolved.push(ref);
994
+ }
995
+ }
996
+ if (unresolved.length > 0) {
997
+ rc.note = `chart data did not resolve: ${unresolved.join(", ")}${byId ? "" : " (try without --no-run)"}`;
998
+ }
999
+ } catch (error2) {
1000
+ const placeholder = renderChart({}, [], [], format);
1001
+ if (format === "tikz") rc.chartTikz = placeholder;
1002
+ else rc.chartSvg = placeholder;
1003
+ rc.note = `invalid chart spec: ${errMessage(error2)}`;
1004
+ }
1005
+ return rc;
1006
+ }
1007
+ const r = byId?.get(c.id);
1008
+ const status = r?.status;
1009
+ const output = r ? r.output : c.output;
1010
+ const error = r ? r.error : c.error;
1011
+ if (c.type === "test") {
1012
+ if (status === "error") rc.error = error ?? "error";
1013
+ else if (status !== void 0) rc.passed = status === "pass";
1014
+ else if (typeof output === "boolean") rc.passed = output;
1015
+ return rc;
1016
+ }
1017
+ if (status === "error" || status === "fail" || status === void 0 && error !== void 0) {
1018
+ rc.error = error ?? "error";
1019
+ } else if (output !== void 0) {
1020
+ rc.output = formatResult(output);
1021
+ }
1022
+ return rc;
1023
+ });
1024
+ return {
1025
+ title: workbook.metadata.title,
1026
+ author: workbook.metadata.author,
1027
+ description: workbook.metadata.description,
1028
+ tags: workbook.metadata.tags,
1029
+ cells
1030
+ };
1031
+ }
1032
+ async function exportCommand(args) {
1033
+ const json = args.includes("--json");
1034
+ const fail = (problems) => json ? { stdout: jsonEnvelope("export", false, null, problems), stderr: "", exitCode: 1 } : { stdout: "", stderr: problems.join("\n"), exitCode: 1 };
1035
+ const format = flagValue(args, "--format") ?? "html";
1036
+ if (format !== "html" && format !== "tex" && format !== "json" && format !== "pdf") {
1037
+ return fail([`Unknown format '${format}' (supported: html, tex, json, pdf)`]);
1038
+ }
1039
+ const file = firstPositional(args);
1040
+ if (!file)
1041
+ return fail([
1042
+ "Usage: mtsw export <file> [--format html|tex|json|pdf] [--fragment] [-o out] [--no-run]"
1043
+ ]);
1044
+ const read = readFile(file);
1045
+ if (read.error) return fail([read.error]);
1046
+ const parsed = parseWorkbook(read.content);
1047
+ if (!parsed.success) return fail(parsed.errors ?? ["Parse error"]);
1048
+ const workbook = parsed.workbook;
1049
+ let byId = null;
1050
+ let report = null;
1051
+ if (!args.includes("--no-run")) {
1052
+ report = await createExecutor(workbook).runReport();
1053
+ const fatal = report.cells.find((r) => r.id === "(workbook)");
1054
+ if (fatal) return fail([fatal.error ?? "workbook run failed"]);
1055
+ byId = new Map(report.cells.map((r) => [r.id, r]));
1056
+ }
1057
+ if (format === "json") {
1058
+ if (!report) {
1059
+ return fail(["--format json requires running the notebook (remove --no-run)"]);
1060
+ }
1061
+ const payload = {
1062
+ ok: report.ok,
1063
+ cells: report.cells.map((c) => ({
1064
+ id: c.id,
1065
+ type: c.type,
1066
+ status: c.status,
1067
+ output: c.output === void 0 ? void 0 : formatResult(c.output),
1068
+ error: c.error
1069
+ }))
1070
+ };
1071
+ const jsonStr = JSON.stringify(payload, null, 2);
1072
+ const outPath2 = flagValue(args, "-o") ?? flagValue(args, "--output");
1073
+ if (outPath2) {
1074
+ try {
1075
+ writeFileAtomic(outPath2, jsonStr);
1076
+ } catch (error) {
1077
+ return fail([`Failed to write '${outPath2}': ${errMessage(error)}`]);
1078
+ }
1079
+ if (json)
1080
+ return {
1081
+ stdout: jsonEnvelope(
1082
+ "export",
1083
+ true,
1084
+ { path: outPath2, bytes: Buffer.byteLength(jsonStr) },
1085
+ []
1086
+ ),
1087
+ stderr: "",
1088
+ exitCode: 0
1089
+ };
1090
+ return {
1091
+ stdout: "",
1092
+ stderr: `Exported ${file} -> ${outPath2} (${Buffer.byteLength(jsonStr)} bytes)`,
1093
+ exitCode: 0
1094
+ };
1095
+ }
1096
+ return { stdout: jsonStr, stderr: "", exitCode: 0 };
1097
+ }
1098
+ if (format === "pdf") {
1099
+ const outPath2 = flagValue(args, "-o") ?? flagValue(args, "--output");
1100
+ if (!outPath2) return fail(["--format pdf requires an output path: -o <file.pdf>"]);
1101
+ try {
1102
+ await toPDF(buildRenderDoc(workbook, byId, "tikz"), outPath2, { parse: parse2 });
1103
+ } catch (error) {
1104
+ return fail([`PDF export failed: ${errMessage(error)}`]);
1105
+ }
1106
+ return json ? { stdout: jsonEnvelope("export", true, { path: outPath2 }, []), stderr: "", exitCode: 0 } : { stdout: "", stderr: `Exported ${file} -> ${outPath2}`, exitCode: 0 };
1107
+ }
1108
+ const fragment = args.includes("--fragment");
1109
+ const rendered = format === "tex" ? toTeX(buildRenderDoc(workbook, byId, "tikz"), { parse: parse2, fragment }) : toHTML(buildRenderDoc(workbook, byId), { parse: parse2 });
1110
+ const bytes = Buffer.byteLength(rendered, "utf-8");
1111
+ const outPath = flagValue(args, "-o") ?? flagValue(args, "--output");
1112
+ if (outPath) {
1113
+ try {
1114
+ writeFileAtomic(outPath, rendered);
1115
+ } catch (error) {
1116
+ return fail([`Failed to write '${outPath}': ${errMessage(error)}`]);
1117
+ }
1118
+ if (json)
1119
+ return {
1120
+ stdout: jsonEnvelope("export", true, { path: outPath, bytes }, []),
1121
+ stderr: "",
1122
+ exitCode: 0
1123
+ };
1124
+ return { stdout: "", stderr: `Exported ${file} -> ${outPath} (${bytes} bytes)`, exitCode: 0 };
1125
+ }
1126
+ if (json) return { stdout: jsonEnvelope("export", true, { bytes }, []), stderr: "", exitCode: 0 };
1127
+ return { stdout: rendered, stderr: "", exitCode: 0 };
1128
+ }
1129
+ function runServer(input = process.stdin, output = process.stdout) {
1130
+ const session = new Session();
1131
+ const rl = createInterface({ input, crlfDelay: Infinity });
1132
+ const write = (obj) => {
1133
+ output.write(`${JSON.stringify(obj)}
1134
+ `);
1135
+ };
1136
+ return new Promise((resolve) => {
1137
+ let done = false;
1138
+ const finish = () => {
1139
+ if (done) return;
1140
+ done = true;
1141
+ process.removeListener("SIGINT", finish);
1142
+ process.removeListener("SIGTERM", finish);
1143
+ rl.close();
1144
+ resolve();
1145
+ };
1146
+ const processLine = async (line2) => {
1147
+ if (done) return;
1148
+ const text = line2.trim();
1149
+ if (!text) return;
1150
+ let request;
1151
+ try {
1152
+ request = JSON.parse(text);
1153
+ } catch {
1154
+ write({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error" } });
1155
+ return;
1156
+ }
1157
+ if (Array.isArray(request)) {
1158
+ write({
1159
+ jsonrpc: "2.0",
1160
+ id: null,
1161
+ error: { code: -32600, message: "Batch requests are not supported" }
1162
+ });
1163
+ return;
1164
+ }
1165
+ try {
1166
+ const { response, events, shutdown } = await handleRequest(
1167
+ session,
1168
+ request
1169
+ );
1170
+ for (const event of events) write(event);
1171
+ write(response);
1172
+ if (shutdown) finish();
1173
+ } catch (error) {
1174
+ write({
1175
+ jsonrpc: "2.0",
1176
+ id: null,
1177
+ error: { code: -32603, message: error instanceof Error ? error.message : String(error) }
1178
+ });
1179
+ }
1180
+ };
1181
+ let queue = Promise.resolve();
1182
+ rl.on("line", (line2) => {
1183
+ queue = queue.then(() => processLine(line2));
1184
+ });
1185
+ rl.on("close", () => {
1186
+ void queue.then(finish);
1187
+ });
1188
+ process.once("SIGINT", finish);
1189
+ process.once("SIGTERM", finish);
1190
+ });
1191
+ }
1192
+ async function serveCommand() {
1193
+ await runServer();
1194
+ return { stdout: "", stderr: "", exitCode: 0 };
1195
+ }
1196
+ function graphCommand(args) {
1197
+ if (args.includes("--json")) {
1198
+ return {
1199
+ stdout: jsonEnvelope("graph", false, null, [
1200
+ "graph has no --json output; use `describe --json` for structured graph data"
1201
+ ]),
1202
+ stderr: "",
1203
+ exitCode: 1
1204
+ };
1205
+ }
1206
+ const file = firstPositional(args);
1207
+ if (!file) {
1208
+ return { stdout: "", stderr: "Usage: mtsw graph <file> [-f mermaid|dot]", exitCode: 1 };
1209
+ }
1210
+ const read = readFile(file);
1211
+ if (read.error) return { stdout: "", stderr: read.error, exitCode: 1 };
1212
+ const parsed = parseWorkbook(read.content);
1213
+ if (!parsed.success) {
1214
+ return { stdout: "", stderr: `Parse errors:
1215
+ ${bullets(parsed.errors ?? [])}`, exitCode: 1 };
85
1216
  }
86
- console.log(`Validating: ${file}`);
1217
+ const graph = buildDependencyGraph(parsed.workbook.cells);
1218
+ if (flagValue(args, "-f") === "mermaid" || flagValue(args, "--format") === "mermaid") {
1219
+ return { stdout: toMermaid(graph), stderr: "", exitCode: 0 };
1220
+ }
1221
+ if (flagValue(args, "-f") === "dot" || flagValue(args, "--format") === "dot") {
1222
+ return { stdout: toDOT(graph), stderr: "", exitCode: 0 };
1223
+ }
1224
+ const lines = [...graph.nodes].map(([id, node]) => `${id}: [${node.dependencies.join(", ")}]`);
1225
+ return { stdout: lines.join("\n"), stderr: "", exitCode: 0 };
87
1226
  }
88
- async function graphCommand(args) {
89
- const file = args[0];
1227
+ function stripCommand(args) {
1228
+ const file = firstPositional(args);
90
1229
  if (!file) {
91
- console.error("Usage: mtsw graph <file>");
92
- process.exit(1);
1230
+ return { stdout: "", stderr: "Usage: mtsw strip <file> [-w|--write]", exitCode: 1 };
1231
+ }
1232
+ const read = readFile(file);
1233
+ if (read.error) return { stdout: "", stderr: read.error, exitCode: 1 };
1234
+ const parsed = parseWorkbook(read.content);
1235
+ if (!parsed.success) {
1236
+ return { stdout: "", stderr: `Parse errors:
1237
+ ${bullets(parsed.errors ?? [])}`, exitCode: 1 };
1238
+ }
1239
+ const yaml = serializeWorkbook(stripOutputs(parsed.workbook));
1240
+ if (args.includes("-w") || args.includes("--write")) {
1241
+ try {
1242
+ writeFileAtomic(file, yaml);
1243
+ } catch (error) {
1244
+ return { stdout: "", stderr: `Failed to write '${file}': ${errMessage(error)}`, exitCode: 1 };
1245
+ }
1246
+ return { stdout: "", stderr: `Stripped outputs -> ${file}`, exitCode: 0 };
1247
+ }
1248
+ return { stdout: yaml, stderr: "", exitCode: 0 };
1249
+ }
1250
+ var EMPTY_TEMPLATE = `version: "1.0"
1251
+ metadata:
1252
+ title: "<NAME>"
1253
+ runtime:
1254
+ engine: mathts
1255
+ execution: reactive
1256
+ cells: []
1257
+ `;
1258
+ var CHART_TEMPLATE = `version: "1.0"
1259
+ metadata:
1260
+ title: "<NAME>"
1261
+ runtime:
1262
+ engine: mathts
1263
+ execution: reactive
1264
+ cells:
1265
+ - data: "[1, 2, 3, 4]"
1266
+ id: xs
1267
+
1268
+ - data: "[1, 4, 9, 16]"
1269
+ id: ys
1270
+
1271
+ - visualization: |
1272
+ type: line
1273
+ title: "Sample chart"
1274
+ x: { label: "x", data: xs }
1275
+ y: { label: "y", data: ys }
1276
+ id: chart
1277
+ depends_on: [xs, ys]
1278
+ `;
1279
+ var TEMPLATES = {
1280
+ basic: {
1281
+ content: BASIC_TEMPLATE,
1282
+ description: "Markdown intro + a code cell + a passing test cell"
1283
+ },
1284
+ empty: { content: EMPTY_TEMPLATE, description: "A blank workbook (no cells)" },
1285
+ chart: {
1286
+ content: CHART_TEMPLATE,
1287
+ description: "A line chart over two data cells (visualization example)"
93
1288
  }
94
- console.log(`Dependency graph for: ${file}`);
1289
+ };
1290
+ var RESERVED_DEVICE_NAMES = /* @__PURE__ */ new Set([
1291
+ "CON",
1292
+ "PRN",
1293
+ "AUX",
1294
+ "NUL",
1295
+ ...Array.from({ length: 9 }, (_unused, i) => `COM${i + 1}`),
1296
+ ...Array.from({ length: 9 }, (_unused, i) => `LPT${i + 1}`)
1297
+ ]);
1298
+ function newCommand(args) {
1299
+ const outPath = flagValue(args, "-o") ?? flagValue(args, "--output");
1300
+ const name = firstPositional(args);
1301
+ const templateName = args.includes("--empty") ? "empty" : flagValue(args, "-t") ?? flagValue(args, "--template") ?? "basic";
1302
+ const template = TEMPLATES[templateName];
1303
+ if (!template) {
1304
+ return {
1305
+ stdout: "",
1306
+ stderr: `Unknown template '${templateName}'. Available: ${Object.keys(TEMPLATES).join(", ")}`,
1307
+ exitCode: 1
1308
+ };
1309
+ }
1310
+ let target;
1311
+ let bare;
1312
+ if (outPath) {
1313
+ target = outPath.endsWith(".mtsw") ? outPath : `${outPath}.mtsw`;
1314
+ bare = basename(target).slice(0, -".mtsw".length);
1315
+ } else {
1316
+ if (!name) {
1317
+ return {
1318
+ stdout: "",
1319
+ stderr: "Usage: mtsw new <name> [-t basic|empty|chart] [--empty] [-o <path>] [--force]",
1320
+ exitCode: 1
1321
+ };
1322
+ }
1323
+ if (!/^[A-Za-z0-9._-]+$/.test(name)) {
1324
+ return {
1325
+ stdout: "",
1326
+ stderr: `Invalid name '${name}': must be a bare filename \u2014 or use -o <path> for an explicit location`,
1327
+ exitCode: 1
1328
+ };
1329
+ }
1330
+ bare = name.endsWith(".mtsw") ? name.slice(0, -".mtsw".length) : name;
1331
+ if (bare === "" || bare === "." || bare === ".." || RESERVED_DEVICE_NAMES.has(bare.toUpperCase())) {
1332
+ return { stdout: "", stderr: `Invalid name '${name}'`, exitCode: 1 };
1333
+ }
1334
+ target = `${bare}.mtsw`;
1335
+ }
1336
+ const content = template.content.replace(/<NAME>/g, bare);
1337
+ const force = args.includes("--force");
1338
+ try {
1339
+ if (force) {
1340
+ try {
1341
+ if (lstatSync(target).isSymbolicLink()) {
1342
+ return { stdout: "", stderr: `Refusing to overwrite symlink '${target}'`, exitCode: 1 };
1343
+ }
1344
+ } catch {
1345
+ }
1346
+ writeFileAtomic(target, content);
1347
+ } else {
1348
+ writeFileSync(target, content, { encoding: "utf-8", flag: "wx" });
1349
+ }
1350
+ } catch (error) {
1351
+ const e = error;
1352
+ if (e.code === "EEXIST") {
1353
+ return {
1354
+ stdout: "",
1355
+ stderr: `File '${target}' already exists (use --force to overwrite)`,
1356
+ exitCode: 1
1357
+ };
1358
+ }
1359
+ return {
1360
+ stdout: "",
1361
+ stderr: `Failed to create '${target}': ${errMessage(error)}`,
1362
+ exitCode: 1
1363
+ };
1364
+ }
1365
+ return { stdout: `Created ${target}`, stderr: "", exitCode: 0 };
1366
+ }
1367
+ function importCommand(args) {
1368
+ const json = args.includes("--json");
1369
+ const fail = (problems) => json ? { stdout: jsonEnvelope("import", false, null, problems), stderr: "", exitCode: 1 } : { stdout: "", stderr: problems.join("\n"), exitCode: 1 };
1370
+ const file = firstPositional(args);
1371
+ let input;
1372
+ if (!file || file === "-") {
1373
+ if (process.stdin.isTTY) return fail(["Provide an input file, or pipe JSON/YAML to stdin"]);
1374
+ try {
1375
+ input = readFileSync(0, "utf-8");
1376
+ } catch (error) {
1377
+ return fail([`Failed to read stdin: ${errMessage(error)}`]);
1378
+ }
1379
+ } else {
1380
+ const read = readFile(file);
1381
+ if (read.error) return fail([read.error]);
1382
+ input = read.content;
1383
+ }
1384
+ const result = importWorkbook(input);
1385
+ if (!result.success || !result.workbook) return fail(result.errors ?? ["Import failed"]);
1386
+ const content = serializeWorkbook(result.workbook);
1387
+ const count = result.workbook.cells.length;
1388
+ const outPath = flagValue(args, "-o") ?? flagValue(args, "--output");
1389
+ if (outPath) {
1390
+ try {
1391
+ writeFileAtomic(outPath, content);
1392
+ } catch (error) {
1393
+ return fail([`Failed to write '${outPath}': ${errMessage(error)}`]);
1394
+ }
1395
+ if (json)
1396
+ return {
1397
+ stdout: jsonEnvelope("import", true, { path: outPath, cells: count }, []),
1398
+ stderr: "",
1399
+ exitCode: 0
1400
+ };
1401
+ return { stdout: "", stderr: `Imported ${count} cell(s) -> ${outPath}`, exitCode: 0 };
1402
+ }
1403
+ if (json)
1404
+ return {
1405
+ stdout: jsonEnvelope("import", true, { content, cells: count }, []),
1406
+ stderr: "",
1407
+ exitCode: 0
1408
+ };
1409
+ return { stdout: content, stderr: "", exitCode: 0 };
1410
+ }
1411
+ function positionals(args) {
1412
+ const out = [];
1413
+ for (let i = 0; i < args.length; i++) {
1414
+ const a = args[i];
1415
+ if (VALUE_FLAGS.has(a)) {
1416
+ i++;
1417
+ continue;
1418
+ }
1419
+ if (!a.startsWith("-")) out.push(a);
1420
+ }
1421
+ return out;
1422
+ }
1423
+ function parseDependsOn(args) {
1424
+ const value = flagValue(args, "--depends-on");
1425
+ if (value === void 0) return void 0;
1426
+ return [
1427
+ ...new Set(
1428
+ value.split(",").map((s) => s.trim()).filter(Boolean)
1429
+ )
1430
+ ];
95
1431
  }
96
- async function newCommand(args) {
97
- const name = args[0];
98
- if (!name) {
99
- console.error("Usage: mtsw new <name>");
100
- process.exit(1);
1432
+ function parsePosition(args) {
1433
+ const before = flagValue(args, "--before");
1434
+ if (before !== void 0) return { before };
1435
+ const after = flagValue(args, "--after");
1436
+ if (after !== void 0) return { after };
1437
+ const at = flagValue(args, "--at");
1438
+ if (at !== void 0) {
1439
+ const n = Number(at);
1440
+ if (!Number.isInteger(n)) throw new Error(`--at must be an integer (got '${at}')`);
1441
+ return { at: n };
101
1442
  }
102
- console.log(`Creating new workbook: ${name}.mtsw`);
1443
+ return void 0;
1444
+ }
1445
+ function resolveContent(args) {
1446
+ const inline2 = flagValue(args, "--content");
1447
+ const fromFile = flagValue(args, "--content-file");
1448
+ if (inline2 !== void 0 && fromFile !== void 0) {
1449
+ return { error: "Use only one of --content / --content-file" };
1450
+ }
1451
+ if (inline2 !== void 0) return { content: inline2 };
1452
+ if (fromFile !== void 0) {
1453
+ if (fromFile === "-") {
1454
+ if (process.stdin.isTTY) {
1455
+ return {
1456
+ error: "No piped input for --content-file - (pipe content via stdin, or use --content)"
1457
+ };
1458
+ }
1459
+ try {
1460
+ return { content: readFileSync(0, "utf-8") };
1461
+ } catch (error) {
1462
+ return { error: `Cannot read stdin: ${errMessage(error)}` };
1463
+ }
1464
+ }
1465
+ const r = readFile(fromFile);
1466
+ return r.error ? { error: r.error } : { content: r.content };
1467
+ }
1468
+ return {};
1469
+ }
1470
+ var CELL_VERBS = ["add", "edit", "rm", "move", "rename"];
1471
+ function cellCommand(args) {
1472
+ const json = args.includes("--json");
1473
+ const fail = (problems) => json ? { stdout: jsonEnvelope("cell", false, null, problems), stderr: "", exitCode: 1 } : { stdout: "", stderr: problems.join("\n"), exitCode: 1 };
1474
+ const verb = args[0];
1475
+ if (!verb || !CELL_VERBS.includes(verb)) {
1476
+ return fail([`Usage: mtsw cell <${CELL_VERBS.join("|")}> <file> ...`]);
1477
+ }
1478
+ const vargs = args.slice(1);
1479
+ const pos = positionals(vargs);
1480
+ const file = pos[0];
1481
+ if (!file) return fail([`Usage: mtsw cell ${verb} <file> ...`]);
1482
+ const read = readFile(file);
1483
+ if (read.error) return fail([read.error]);
1484
+ const parsed = parseWorkbook(read.content);
1485
+ if (!parsed.success) return fail(parsed.errors ?? ["Parse error"]);
1486
+ const wb = parsed.workbook;
1487
+ let result;
1488
+ let changedCells = [];
1489
+ try {
1490
+ switch (verb) {
1491
+ case "add": {
1492
+ const id = flagValue(vargs, "--id");
1493
+ const type = flagValue(vargs, "--type") ?? flagValue(vargs, "-t");
1494
+ if (!id) return fail(["cell add requires --id"]);
1495
+ if (!type) return fail(["cell add requires --type"]);
1496
+ const content = resolveContent(vargs);
1497
+ if (content.error) return fail([content.error]);
1498
+ result = addCell(
1499
+ wb,
1500
+ {
1501
+ id,
1502
+ type,
1503
+ content: content.content,
1504
+ dependsOn: parseDependsOn(vargs)
1505
+ },
1506
+ parsePosition(vargs)
1507
+ );
1508
+ break;
1509
+ }
1510
+ case "edit": {
1511
+ const id = pos[1];
1512
+ if (!id) return fail(["cell edit requires <id>"]);
1513
+ const content = resolveContent(vargs);
1514
+ if (content.error) return fail([content.error]);
1515
+ const type = flagValue(vargs, "--type") ?? flagValue(vargs, "-t");
1516
+ const deps = parseDependsOn(vargs);
1517
+ const changes = {};
1518
+ if (content.content !== void 0) changes.content = content.content;
1519
+ if (type !== void 0) changes.type = type;
1520
+ if (deps !== void 0) changes.dependsOn = deps;
1521
+ result = editCell(wb, id, changes);
1522
+ break;
1523
+ }
1524
+ case "rm": {
1525
+ const id = pos[1];
1526
+ if (!id) return fail(["cell rm requires <id>"]);
1527
+ const removed = removeCell(wb, id, { force: vargs.includes("--force") });
1528
+ result = removed.workbook;
1529
+ changedCells = removed.changedCells;
1530
+ break;
1531
+ }
1532
+ case "move": {
1533
+ const id = pos[1];
1534
+ if (!id) return fail(["cell move requires <id>"]);
1535
+ const position = parsePosition(vargs);
1536
+ if (!position) return fail(["cell move requires --before/--after/--at"]);
1537
+ result = moveCell(wb, id, position);
1538
+ break;
1539
+ }
1540
+ case "rename": {
1541
+ const oldId = pos[1];
1542
+ const newId = pos[2];
1543
+ if (!oldId || !newId) return fail(["cell rename requires <oldId> <newId>"]);
1544
+ result = renameCell(wb, oldId, newId);
1545
+ break;
1546
+ }
1547
+ default:
1548
+ return fail([`Unknown cell verb: ${verb}`]);
1549
+ }
1550
+ } catch (error) {
1551
+ return fail([errMessage(error)]);
1552
+ }
1553
+ const after = serializeWorkbook(result);
1554
+ const changed = after !== serializeWorkbook(wb);
1555
+ if (vargs.includes("--dry-run")) {
1556
+ if (json) {
1557
+ const data = {
1558
+ dryRun: true,
1559
+ changed,
1560
+ content: after,
1561
+ ...changedCells.length ? { changedCells } : {}
1562
+ };
1563
+ return { stdout: jsonEnvelope("cell", true, data, []), stderr: "", exitCode: 0 };
1564
+ }
1565
+ return { stdout: after, stderr: "", exitCode: 0 };
1566
+ }
1567
+ if (changed) {
1568
+ try {
1569
+ writeFileAtomic(file, after);
1570
+ } catch (error) {
1571
+ return fail([`Failed to write '${file}': ${errMessage(error)}`]);
1572
+ }
1573
+ }
1574
+ if (json) {
1575
+ const data = {
1576
+ changed,
1577
+ ...changedCells.length ? { changedCells } : {},
1578
+ ...describeData(result)
1579
+ };
1580
+ return { stdout: jsonEnvelope("cell", true, data, []), stderr: "", exitCode: 0 };
1581
+ }
1582
+ const note = changed ? `Updated ${file}` : `No change (${file})`;
1583
+ const detached = changedCells.length ? `
1584
+ Detached from: ${changedCells.join(", ")}` : "";
1585
+ return { stdout: "", stderr: note + detached, exitCode: 0 };
1586
+ }
1587
+ async function dispatch(argv) {
1588
+ if (argv.length === 0 || argv.includes("-h") || argv.includes("--help")) {
1589
+ return { stdout: HELP, stderr: "", exitCode: 0 };
1590
+ }
1591
+ if (argv.includes("-V") || argv.includes("--version")) {
1592
+ return { stdout: `mtsw version ${VERSION}`, stderr: "", exitCode: 0 };
1593
+ }
1594
+ const [command, ...rest] = argv;
1595
+ try {
1596
+ switch (command) {
1597
+ case "run":
1598
+ return await runCommand(rest);
1599
+ case "validate":
1600
+ return validateCommand(rest);
1601
+ case "graph":
1602
+ return graphCommand(rest);
1603
+ case "describe":
1604
+ return describeCommand(rest);
1605
+ case "strip":
1606
+ return stripCommand(rest);
1607
+ case "new":
1608
+ return newCommand(rest);
1609
+ case "capabilities":
1610
+ return capabilitiesCommand(rest);
1611
+ case "templates":
1612
+ return templatesCommand(rest);
1613
+ case "cell":
1614
+ return cellCommand(rest);
1615
+ case "functions":
1616
+ return functionsCommand(rest);
1617
+ case "meta":
1618
+ return metaCommand(rest);
1619
+ case "import":
1620
+ return importCommand(rest);
1621
+ case "export":
1622
+ return exportCommand(rest);
1623
+ case "serve":
1624
+ return serveCommand();
1625
+ default:
1626
+ return { stdout: "", stderr: `Unknown command: ${command}
1627
+ ${HELP}`, exitCode: 1 };
1628
+ }
1629
+ } catch (error) {
1630
+ const message = error instanceof Error ? error.message : String(error);
1631
+ if (rest.includes("--json")) {
1632
+ return {
1633
+ stdout: jsonEnvelope(command, false, null, [`Internal error: ${message}`]),
1634
+ stderr: "",
1635
+ exitCode: 1
1636
+ };
1637
+ }
1638
+ return { stdout: "", stderr: `Internal error: ${message}`, exitCode: 1 };
1639
+ }
1640
+ }
1641
+ async function main() {
1642
+ const result = await dispatch(process.argv.slice(2));
1643
+ if (result.stdout) process.stdout.write(`${result.stdout}
1644
+ `);
1645
+ if (result.stderr) process.stderr.write(`${result.stderr}
1646
+ `);
1647
+ process.exit(result.exitCode);
1648
+ }
1649
+ function isEntryPoint() {
1650
+ const entry = process.argv[1];
1651
+ if (!entry) return false;
1652
+ try {
1653
+ return import.meta.url === pathToFileURL(realpathSync(entry)).href;
1654
+ } catch {
1655
+ return import.meta.url === pathToFileURL(entry).href;
1656
+ }
1657
+ }
1658
+ if (isEntryPoint()) {
1659
+ void main();
103
1660
  }
104
- main().catch((error) => {
105
- console.error("Error:", error);
106
- process.exit(1);
107
- });
1661
+ export {
1662
+ capabilitiesCommand,
1663
+ cellCommand,
1664
+ describeCommand,
1665
+ dispatch,
1666
+ exportCommand,
1667
+ functionsCommand,
1668
+ graphCommand,
1669
+ importCommand,
1670
+ metaCommand,
1671
+ newCommand,
1672
+ runCommand,
1673
+ runServer,
1674
+ serveCommand,
1675
+ stripCommand,
1676
+ templatesCommand,
1677
+ validateCommand
1678
+ };