@hardikg/mdview 0.7.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +72 -6
  3. package/bin/mdview.mjs +924 -212
  4. package/dist/client/assets/abnfDiagram-VCTEODGH-DafRYfx3.js +1 -0
  5. package/dist/client/assets/{arc-ycQp8V_W.js → arc-BqKZ1e2b.js} +1 -1
  6. package/dist/client/assets/architectureDiagram-5GKGNRK7-_94hPNdL.js +36 -0
  7. package/dist/client/assets/blockDiagram-NRAW4CY4-BegJqAzx.js +129 -0
  8. package/dist/client/assets/c4Diagram-UCG6FXSJ-DYBJe_j-.js +38 -0
  9. package/dist/client/assets/channel-CzMcbnuI.js +1 -0
  10. package/dist/client/assets/{chunk-QZHKN3VN-OHnkQvdC.js → chunk-2Q5K7J3B-CarNPuoG.js} +1 -1
  11. package/dist/client/assets/{chunk-FMBD7UC4-IM3YKAF3.js → chunk-5VM5RSS4-Bmb8JM_j.js} +1 -1
  12. package/dist/client/assets/{chunk-ND2GUHAM-UF3KQYdE.js → chunk-F27PBJKO-BhLIUyGL.js} +1 -1
  13. package/dist/client/assets/chunk-G27WJ6UU-B_7t7iym.js +231 -0
  14. package/dist/client/assets/{chunk-4BX2VUAB-BXnUK3rw.js → chunk-JWPE2WC7-6WDfZVE5.js} +1 -1
  15. package/dist/client/assets/{chunk-727SXJPM-DKPIb06m.js → chunk-LCL6LL3I-BnM13fhY.js} +4 -4
  16. package/dist/client/assets/chunk-POPQ4Y6H-DQMWRZKQ.js +1 -0
  17. package/dist/client/assets/chunk-SVP7TREG-DPpw4ke1.js +88 -0
  18. package/dist/client/assets/{chunk-55IACEB6-BkF4Ba-V.js → chunk-XXDRQBXY-ByC-zeqG.js} +1 -1
  19. package/dist/client/assets/classDiagram-DTDB5LWJ-RvAHOW66.js +1 -0
  20. package/dist/client/assets/classDiagram-v2-JRS7N3AN-RvAHOW66.js +1 -0
  21. package/dist/client/assets/{cose-bilkent-S5V4N54A-D0XjEe4_.js → cose-bilkent-JH36ORCC-CiRnFlQp.js} +1 -1
  22. package/dist/client/assets/cynefin-OW5HDTMX-DqTFYole.js +178 -0
  23. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-B1WAhR06.js +62 -0
  24. package/dist/client/assets/cytoscape.esm-BjAbmLdb.js +331 -0
  25. package/dist/client/assets/dagre-3AP2YEHR-DQK5a43-.js +4 -0
  26. package/dist/client/assets/diagram-S7CK7UJ4-BMQ8dao_.js +30 -0
  27. package/dist/client/assets/diagram-UQ7AKVKN-CT7KIFG4.js +41 -0
  28. package/dist/client/assets/{diagram-KO2AKTUF-Zu0g_DOV.js → diagram-VSXAHHWV-By0SbfXs.js} +3 -3
  29. package/dist/client/assets/diagram-VX7I27RA-DwshpqNi.js +24 -0
  30. package/dist/client/assets/{diagram-LMA3HP47-bPbOm4sL.js → diagram-Z3DM3KII-CG7wA7Kr.js} +2 -2
  31. package/dist/client/assets/ebnfDiagram-PWID7BFC-XlNRbCZw.js +1 -0
  32. package/dist/client/assets/erDiagram-SSCWMZ5O-BtDUPrpL.js +99 -0
  33. package/dist/client/assets/flowDiagram-A5DVABFB-DKJ5hkO-.js +168 -0
  34. package/dist/client/assets/ganttDiagram-EL5Y4UJY-B8rnbHA1.js +292 -0
  35. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-Du_KX2ik.js → gitGraphDiagram-WWUBYQGX-Conc20s7.js} +7 -7
  36. package/dist/client/assets/index-CCiGr1hz.js +2 -0
  37. package/dist/client/assets/index-CJ5_G3gC.css +1 -0
  38. package/dist/client/assets/infoDiagram-RXCK75RN-DNuHlrdu.js +2 -0
  39. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-DwkHFgb8.js → ishikawaDiagram-5VMMS53U-DkYaQbSy.js} +5 -5
  40. package/dist/client/assets/{journeyDiagram-JHISSGLW-nEajRNFZ.js → journeyDiagram-EYS64GPL-BdoQruin.js} +1 -1
  41. package/dist/client/assets/{kanban-definition-UN3LZRKU-Dvn5P58B.js → kanban-definition-3QL26DDD-BD3QQjwp.js} +8 -8
  42. package/dist/client/assets/katex-C5jXJg4s.js +257 -0
  43. package/dist/client/assets/katex.min-BYZTj3zW.css +1 -0
  44. package/dist/client/assets/katex.min-DXaO1tdm.js +1 -0
  45. package/dist/client/assets/layout-DKu9QfZ6.js +1 -0
  46. package/dist/client/assets/{linear-BYNRmGYt.js → linear-DMmTRDu2.js} +1 -1
  47. package/dist/client/assets/mermaid.core-2ESgyZ95.js +312 -0
  48. package/dist/client/assets/{mindmap-definition-RKZ34NQL-WIFgsLLz.js → mindmap-definition-FBJOCRG2-CokK9_yg.js} +1 -1
  49. package/dist/client/assets/pegDiagram-XKGWAZYB-CUAn26Uv.js +1 -0
  50. package/dist/client/assets/pieDiagram-E7YTZNPT-BT-XLIyl.js +39 -0
  51. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-B0Gh1bMy.js → quadrantDiagram-AXDQQJYC-dDr-ZgjR.js} +3 -3
  52. package/dist/client/assets/railroadDiagram-O6MQD6OU-B5rCSICc.js +1 -0
  53. package/dist/client/assets/{requirementDiagram-4Y6WPE33-DcIINxfV.js → requirementDiagram-EFPCY7ZU-DyI3FGB5.js} +5 -5
  54. package/dist/client/assets/sankeyDiagram-P5KCCOFB-CGXj8eCe.js +40 -0
  55. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-C3yr2FVq.js +162 -0
  56. package/dist/client/assets/sizeCapture-X5ZJPWSS-CCnkSxko.js +1 -0
  57. package/dist/client/assets/stateDiagram-HBIQ2CUA-BG5Dshx6.js +1 -0
  58. package/dist/client/assets/stateDiagram-v2-4QOOHH4V-BdraFugH.js +1 -0
  59. package/dist/client/assets/swimlanes-XN3QIQJK-MGfvzMlQ.js +1 -0
  60. package/dist/client/assets/swimlanesDiagram-VK2B7HYN-LQu07Za2.js +8 -0
  61. package/dist/client/assets/{timeline-definition-PNZ67QCA-DYWt9asA.js → timeline-definition-24CTP7MA-Bj3gMrOq.js} +3 -3
  62. package/dist/client/assets/vennDiagram-4TSXK5OY-CiIW_j8g.js +34 -0
  63. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-Ch7IIKOr.js → wardleyDiagram-VM6X3IG4-DQTIyknP.js} +4 -4
  64. package/dist/client/assets/xychartDiagram-S5SC5T6Z-Bg0tzmhJ.js +7 -0
  65. package/dist/client/index.html +2 -2
  66. package/package.json +2 -2
  67. package/dist/client/assets/architectureDiagram-3BPJPVTR-CGT8FovT.js +0 -36
  68. package/dist/client/assets/blockDiagram-GPEHLZMM-N8A4M5KF.js +0 -132
  69. package/dist/client/assets/c4Diagram-AAUBKEIU-GOIrZjrm.js +0 -10
  70. package/dist/client/assets/channel-DsJAWOzB.js +0 -1
  71. package/dist/client/assets/chunk-2J33WTMH-DMiX8NYo.js +0 -1
  72. package/dist/client/assets/chunk-AQP2D5EJ-BFOc87IV.js +0 -231
  73. package/dist/client/assets/classDiagram-4FO5ZUOK-BD7T43t0.js +0 -1
  74. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-BD7T43t0.js +0 -1
  75. package/dist/client/assets/cytoscape.esm-D_LviqZs.js +0 -331
  76. package/dist/client/assets/dagre-BM42HDAG-1wR5aSmA.js +0 -4
  77. package/dist/client/assets/diagram-2AECGRRQ-B8L1OTOq.js +0 -43
  78. package/dist/client/assets/diagram-5GNKFQAL-BPGoGDNx.js +0 -10
  79. package/dist/client/assets/diagram-OG6HWLK6-DWW4aJ4C.js +0 -24
  80. package/dist/client/assets/erDiagram-TEJ5UH35-CUsMs05D.js +0 -85
  81. package/dist/client/assets/flowDiagram-I6XJVG4X-DZCURXAw.js +0 -162
  82. package/dist/client/assets/ganttDiagram-6RSMTGT7-CzMfUd_H.js +0 -292
  83. package/dist/client/assets/graph-CAnANduQ.js +0 -1
  84. package/dist/client/assets/index-BCdIdRge.js +0 -2
  85. package/dist/client/assets/index-_GInkOX2.css +0 -1
  86. package/dist/client/assets/infoDiagram-5YYISTIA-D81JEsUe.js +0 -2
  87. package/dist/client/assets/katex-DkKDou_j.js +0 -257
  88. package/dist/client/assets/katex.min-CfVKi3_s.css +0 -1
  89. package/dist/client/assets/katex.min-DBnXG_UH.js +0 -1
  90. package/dist/client/assets/layout-DGIYPm2g.js +0 -1
  91. package/dist/client/assets/mermaid.core-BJg17JjK.js +0 -308
  92. package/dist/client/assets/pieDiagram-4H26LBE5-DMgTQVGJ.js +0 -30
  93. package/dist/client/assets/sankeyDiagram-5OEKKPKP-CDnHepHs.js +0 -40
  94. package/dist/client/assets/sequenceDiagram-3UESZ5HK-D7MMEHjm.js +0 -162
  95. package/dist/client/assets/stateDiagram-AJRCARHV-Dvdvjjk8.js +0 -1
  96. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-I2g3TqMC.js +0 -1
  97. package/dist/client/assets/vennDiagram-CIIHVFJN-Bt0VUni_.js +0 -34
  98. package/dist/client/assets/wardley-L42UT6IY-DVy2v9KS.js +0 -173
  99. package/dist/client/assets/xychartDiagram-2RQKCTM6-Byds5kce.js +0 -7
package/CHANGELOG.md CHANGED
@@ -7,6 +7,52 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.8.1] — 2026-08-24
11
+
12
+ ### Fixed
13
+ - Live reload stopped working for any URL that named a file without its root id — a bookmark from before 0.8.0, or the VS Code extension sending a workspace-relative path. Such a request resolves against the primary root and the server answers with the workspace-scoped path, but the page kept comparing watch events against the path it had asked for, so nothing ever matched and the document silently stopped updating on save. The comparison now uses the path the server actually served. The first load always worked, which is why this got past the 0.8.0 checks.
14
+
15
+ ## [0.8.0] — 2026-08-24
16
+
17
+ One background server, many folders. `mdview <path>` no longer holds the terminal.
18
+
19
+ ### Added
20
+ - **`mdview <path>` returns the shell.** It starts one detached background server the first time it's needed; every later `mdview <path>`, in any repo, adds that folder to the same server and opens it. So unrelated folders — `~/work/api/docs` and `~/notes` — can be read side by side in one window at one stable URL. `--foreground` (`-f`) runs a server in the terminal as before.
21
+ - `mdview ls` (open folders, server pid/port, log path), `mdview rm <name|path>…` (stop serving a folder), `mdview stop` (stop the server).
22
+ - Folders persist in `~/.config/mdview/workspace.json` until removed, across restarts and reboots. Capped at 8, because each holds a live recursive watcher. Opening a file or subfolder inside an already-open folder reuses it instead of adding a duplicate.
23
+ - The background server runs until `mdview stop` or a reboot. `MDVIEW_IDLE_TIMEOUT` opts into having it exit after that many minutes with no browser tab connected (it clears its record, and the next `mdview` starts it again) — off by default, since a server that stays put keeps the URL reliable.
24
+ - `--port` and `--palette` now imply `--foreground`: each overrides a setting belonging to a whole server, and the background one is shared. A useful side effect is that every invocation that worked before behaves exactly as it did — only the bare `mdview [path]` form changed.
25
+ - The printed and opened URL is now `http://mdview.localhost:7331/` instead of `http://127.0.0.1:7331/`. `*.localhost` is reserved to loopback by RFC 6761 and resolves with no setup, no `/etc/hosts` entry and no privileges; the server still binds `127.0.0.1` only. If the local resolver doesn't answer the name with a loopback address the CLI falls back to the literal address rather than printing a URL that could leave the machine. `--vscode` keeps `127.0.0.1` unconditionally, because the extension validates that exact prefix.
26
+ - `PORT` is honoured as the default port when `--port` is omitted, so mdview runs unchanged under a local reverse proxy such as [portless](https://github.com/vercel-labs/portless) for a port-free URL. Invalid values are ignored rather than fatal.
27
+
28
+ ### Changed
29
+ - **Paths in URLs are now workspace-scoped**: `?file=docs/api.md` becomes `?file=<rootId>/docs/api.md`, and asset URLs become `/__asset/<rootId>/<path>`. This is what lets one server host several unrelated folders at once. An unprefixed path still resolves against the first root, so existing links and existing bookmarks keep working, as does the VS Code extension.
30
+ - `/api/tree` now returns `{ roots, tree, config }`. With a single root its contents stay at the top level exactly as before; a second root is what introduces a folder node per root. Window-level settings (palette, font, line width) come from the first root; `ignore` stays per-root.
31
+ - Folder search covers every open folder root and labels each hit with its root. The 200-hit cap is a budget for the whole search rather than per root.
32
+ - With more than one folder open the sidebar groups each under its own heading, expanded by default. `⌘P` and folder search span every folder and show which one each result is in. Live reload learns a `workspace` event, so a folder opened or closed elsewhere appears in tabs that are already open without a reload. Closing the last folder shows an empty state rather than an endless skeleton.
33
+
34
+ ### Security
35
+ - There is deliberately **no HTTP endpoint that changes which folders are served**. The CLI writes `workspace.json` and the server watches it, so adding a root requires filesystem access, which a web page does not have — nothing to CSRF, and no shared token to store or leak. `/api/health` is read-only and reports ids, a pid and a start time, never absolute paths.
36
+ - `mdview stop` checks the recorded pid **and** its start time against what the process reports before signalling it, so a recycled pid belonging to an unrelated process is never killed.
37
+ - State files are written `0600` inside a `0700` directory, each through a temp file and one rename so a reader never sees a half-written file. `workspace.json` is validated defensively on read: a hand-edited or truncated file degrades to fewer folders, never to a crash or an unchecked path.
38
+ - Concurrent `mdview` invocations are serialised through a lock file, so two shells can't race into two servers fighting over the port.
39
+ - Every request's `Host` header is now checked against a loopback allow-list (`localhost`, `*.localhost`, the loopback literals) in an `onRequest` hook, and anything else gets a bare `403`. Closes a DNS-rebinding hole: binding `127.0.0.1` never stopped a page the user was already browsing from re-resolving its own domain to loopback and reading every file mdview serves. Verified against a running server — `Host: evil.com` returned `200` before this change.
40
+ - `@fastify/static` 9 → 10 (major). Closes two high-severity advisories in the component that serves the SPA: authorization bypass via non-canonical URL paths (GHSA-8pvw-jcv7-9cmj) and route-guard bypass via path traversal (GHSA-83w8-p2f5-377r). The plugin is registered at a single call site with a `root` + `prefix` only, and v10 changes nothing we use — its diff against v9 is `fastify-plugin` ^5 → ^6 and `content-disposition` ^1 → ^2.
41
+ - Lockfile-only bumps clearing five further high-severity shipped-dep advisories: `brace-expansion` (DoS via unbounded expansion), `fast-uri` (host confusion via backslash authority delimiter), `find-my-way` (HTTP/2 DoS), `js-yaml` (quadratic CPU in `!!omap`), `linkify-it` (quadratic `mailto:` scan). `npm audit --omit=dev` is back to zero.
42
+ - The one remaining advisory is the `low`, dev-only `esbuild` one already deferred in 0.7.1: it is reachable only through `tsup`/`vite`, is absent from shipped deps, and never enters the published tarball. `npm audit fix` still cannot resolve it without a breaking parent bump.
43
+
44
+ ## [0.7.1] — 2026-07-01
45
+
46
+ ### Security
47
+ - Dependency security bumps applied via `npm audit fix` (lockfile-only, SemVer-compatible — no `package.json` range changes): `markdown-it`, `dompurify`, `linkify-it`, `js-yaml`, `ws`, and dev tooling. Clears all shipped-dep advisories (moderate/high DoS & complexity issues in the render path). One `low`, dev-only `esbuild` advisory (reachable solely through `tsup`'s Windows dev server, never in the published package) has no non-breaking fix and is knowingly deferred; the quality gate now audits shipped deps only (`npm audit --omit=dev`).
48
+
49
+ ### Changed
50
+ - Under `--vscode`, the HTML response now **omits** the `frame-ancestors` CSP directive entirely (previously `frame-ancestors *`). Chromium 142 (shipped in VS Code 1.126) narrowed `frame-ancestors *` to match only network-scheme origins (`http`/`https`/`ws`/`wss`), which excludes the webview's non-network `vscode-webview:` parent — so `*` blocked the extension from framing the preview at all. No CSP value can name the dynamic webview origin, so the directive is dropped in embed mode; the loopback bind (`127.0.0.1`) remains the real network boundary. Default browser runs still emit `frame-ancestors 'none'` byte-for-byte.
51
+
52
+ ### Fixed
53
+ - **Narrow-width layout.** The file-tree and outline panes now collapse into overlay drawers at narrow viewport widths instead of leaving blank, full-width grid columns that squeezed the document into an unreadable vertical sliver. Root cause: the pane-width CSS variables are set inline on `.app-shell` (needed for the drag-resizers), and inline styles overrode the responsive `@media` zeroing; the breakpoints now win via `!important`. Affects the plain browser CLI and the VS Code webview alike.
54
+ - **Embed mode** no longer reserves a blank left column for the (never-rendered) file tree, and the outline now opens correctly in the narrow VS Code preview instead of appearing empty.
55
+
10
56
  ## [0.7.0] — 2026-05-28
11
57
 
12
58
  CLI prereqs for an upcoming VS Code extension. New, additive surface — browser-mode runs are unchanged.
package/README.md CHANGED
@@ -50,26 +50,92 @@ node bin/mdview.mjs ./docs
50
50
  ### Share with friends without publishing
51
51
 
52
52
  ```bash
53
- npm run build && npm pack # produces mdview-0.6.0.tgz
53
+ npm run build && npm pack # produces hardikg-mdview-<version>.tgz
54
54
  ```
55
55
 
56
56
  Send them the `.tgz` (Slack/Drive/AirDrop). They install with:
57
57
 
58
58
  ```bash
59
- npm install -g ./mdview-0.6.0.tgz
59
+ npm install -g ./hardikg-mdview-<version>.tgz
60
60
  ```
61
61
 
62
62
  ## Usage
63
63
 
64
64
  ```bash
65
- mdview <path> # file or folder
65
+ mdview <path> # open a file or folder; the shell comes straight back
66
66
  mdview # current directory
67
- mdview --port 9000 # custom port (default 7331; auto-fallback unless --port is explicit)
67
+ mdview ls # what's open, and where the server is
68
+ mdview rm <name> # stop serving a folder
69
+ mdview stop # stop the background server
70
+ mdview --foreground # run in this terminal instead (Ctrl-C to stop)
68
71
  mdview --no-open # don't auto-launch the browser
72
+ mdview --port 9000 # one-off server on a specific port (implies --foreground)
73
+ mdview --palette nord # palette for this run (implies --foreground)
69
74
  mdview --help # show usage
70
75
  ```
71
76
 
72
- Closes when you `Ctrl-C` or `kill` the process. Set `MDVIEW_DEBUG=1` for full stack traces on unexpected errors.
77
+ ### One server, many folders
78
+
79
+ `mdview` does not hold your terminal. It starts a single background server the
80
+ first time you need one, and every later `mdview <path>` — in any repo — adds
81
+ that folder to the same server and opens it. So you can read
82
+ `~/work/api/docs` and `~/notes` side by side in one window, with one stable
83
+ URL, whether or not the folders are anywhere near each other.
84
+
85
+ ```console
86
+ $ mdview ~/work/api
87
+ mdview → http://mdview.localhost:7331/?root=api
88
+ added: /Users/you/work/api (api)
89
+ $ cd ~/notes && mdview .
90
+ mdview → http://mdview.localhost:7331/?root=notes
91
+ added: /Users/you/notes (notes)
92
+ $ mdview ls
93
+ ● api ~/work/api (primary)
94
+ ● notes ~/notes
95
+
96
+ server: http://mdview.localhost:7331/ (pid 40113)
97
+ logs: ~/.config/mdview/daemon.log
98
+ ```
99
+
100
+ Folders stay open until you remove them with `mdview rm <name>`, across restarts
101
+ and reboots. Up to 8 at a time — each one holds a live file watcher, so the
102
+ limit is there to keep mdview from quietly watching your whole disk. Opening a
103
+ file or subfolder inside a folder that's already open reuses it rather than
104
+ adding a duplicate.
105
+
106
+ The server keeps running until you `mdview stop` it, or you reboot. If you'd
107
+ rather it stood down on its own, set `MDVIEW_IDLE_TIMEOUT` to a number of
108
+ minutes and it will exit that long after the last browser tab disconnects; the
109
+ next `mdview` starts it again.
110
+
111
+ Its state lives in `~/.config/mdview/` (`daemon.json`, `workspace.json`,
112
+ `daemon.log`) and nowhere else. `workspace.json` is also how the CLI talks to
113
+ the server: `mdview <path>` writes the file and the server picks the change up,
114
+ which is why there is no HTTP endpoint that can change what's being served.
115
+
116
+ The URL is `http://mdview.localhost:7331/` — `*.localhost` always resolves to
117
+ loopback (RFC 6761) with no setup, and the server still listens on `127.0.0.1`
118
+ only. If your resolver doesn't answer the name, mdview falls back to printing
119
+ `http://127.0.0.1:<port>/`. Setting `PORT` changes the default port, so mdview
120
+ works as-is behind a local proxy like
121
+ [portless](https://github.com/vercel-labs/portless) if you want the port gone
122
+ entirely.
123
+
124
+ `--palette` takes `classic`, `paper`, `nord`, `solarized`, or `high-contrast`.
125
+ It wins over both `.mdview.json` and the global config, and never writes to
126
+ either. There is also a `--vscode` flag, used by the VS Code extension to run
127
+ mdview as a sidecar; it implies `--no-open` and prints a single JSON ready line
128
+ instead of the human-readable output.
129
+
130
+ `--port` and `--palette` both override a setting that belongs to a whole server,
131
+ and the background one is shared with every other folder you have open — so each
132
+ implies `--foreground`, giving you a server of your own. That also means every
133
+ invocation that worked before still behaves exactly as it did; only the bare
134
+ `mdview [path]` form returns the shell now.
135
+
136
+ `--foreground` closes when you `Ctrl-C` or `kill` it. The background server is
137
+ stopped with `mdview stop`. Set `MDVIEW_DEBUG=1` for full stack traces on
138
+ unexpected errors.
73
139
 
74
140
  ## Keyboard shortcuts
75
141
 
@@ -186,7 +252,7 @@ Node 20+, TypeScript, Fastify 5, markdown-it 14, Shiki 1, gray-matter, chokidar
186
252
  ## Tests
187
253
 
188
254
  ```bash
189
- npm test # vitest, server + client (289 tests)
255
+ npm test # vitest, server + client
190
256
  npm run typecheck # both tsconfigs
191
257
  npm run build # vite (client) + tsup (server CLI)
192
258
  ```