mulmoterminal 1.7.1 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/README.md +9 -4
  2. package/bin/mulmoterminal.js +22 -34
  3. package/bin/npx-cache-hint.d.ts +2 -0
  4. package/bin/npx-cache-hint.js +69 -0
  5. package/bin/wait-ready.d.ts +26 -0
  6. package/bin/wait-ready.js +61 -0
  7. package/common/modelPresets.ts +11 -6
  8. package/common/terminalSubmit.ts +70 -0
  9. package/dist/assets/{abnfDiagram-VRR7QNED-RjiYivmv-0ZB3Q7Mv.js → abnfDiagram-VRR7QNED-RjiYivmv-Dv9Yctvh.js} +1 -1
  10. package/dist/assets/architecture-TIHT7OUA-0NsD2xW1-D3TA8fTd.js +1 -0
  11. package/dist/assets/{architectureDiagram-ZJ3FMSHR-3nWA91tG-BGEQ26kW.js → architectureDiagram-ZJ3FMSHR-3nWA91tG-BfLpJ-Hj.js} +1 -1
  12. package/dist/assets/{blockDiagram-677ZJIJ3-BPuAJQRW-CfG6VjIt.js → blockDiagram-677ZJIJ3-BPuAJQRW-mrEl8u2o.js} +1 -1
  13. package/dist/assets/{c4Diagram-LMCZKHZV-C0LAqQso-5Y3QAEOO.js → c4Diagram-LMCZKHZV-C0LAqQso-CccnvKxn.js} +1 -1
  14. package/dist/assets/channel-7wUqSdoX-DzBsOdzu.js +1 -0
  15. package/dist/assets/{chunk-32BRIVSS-BRrYpgtb-LKWk21XQ.js → chunk-32BRIVSS-BRrYpgtb-z64_xEfL.js} +1 -1
  16. package/dist/assets/{chunk-52WLFC77-BJ-ss3Xr-DQ0c_tel.js → chunk-52WLFC77-BJ-ss3Xr-BbTx44vc.js} +1 -1
  17. package/dist/assets/{chunk-C7G6YPKG-BZEucKEL-D1ScG-A9.js → chunk-C7G6YPKG-BZEucKEL-UIcrTcAw.js} +1 -1
  18. package/dist/assets/{chunk-EX3LRPZG-DLS6FBN1-CM3AIHWh.js → chunk-EX3LRPZG-DLS6FBN1-EqsJFk0Y.js} +1 -1
  19. package/dist/assets/{chunk-FWX5IMBZ-DHLSFw1H-C7tdhIeD.js → chunk-FWX5IMBZ-DHLSFw1H-BHANfZHz.js} +2 -2
  20. package/dist/assets/{chunk-HOUHSVGY-Bhlt8hXJ-Cz_lCkPW.js → chunk-HOUHSVGY-Bhlt8hXJ-s4l_kLKK.js} +1 -1
  21. package/dist/assets/{chunk-ICXQ74PX-DwgHBX_g-BiaXcLCM.js → chunk-ICXQ74PX-DwgHBX_g-Cwv0aNBS.js} +1 -1
  22. package/dist/assets/{chunk-MOJQB5TN-CMZRaeqt-DynKnij8.js → chunk-MOJQB5TN-CMZRaeqt-Dv_IeGiN.js} +1 -1
  23. package/dist/assets/{chunk-OGEWGWER-8Qy4a8b5-BHp0IyoO.js → chunk-OGEWGWER-8Qy4a8b5-C_128Gk1.js} +1 -1
  24. package/dist/assets/{chunk-PUDLZKDR-DcrWQRYh-CgXHo6E0.js → chunk-PUDLZKDR-DcrWQRYh-SIXDzNuv.js} +1 -1
  25. package/dist/assets/{chunk-Q4XR5HBZ-ZXVGkG8Z-BEB0ANC3.js → chunk-Q4XR5HBZ-ZXVGkG8Z-C6CR9UX-.js} +1 -1
  26. package/dist/assets/{chunk-V7JOEXUC-DmGdheTX-Ct5XDuM1.js → chunk-V7JOEXUC-DmGdheTX-N9ND9AI0.js} +1 -1
  27. package/dist/assets/{chunk-VAUOI2AC-CS9QJ4yz-BkjFKKFo.js → chunk-VAUOI2AC-CS9QJ4yz-C8yjyNAb.js} +1 -1
  28. package/dist/assets/{chunk-VR4S4FIN-C6a91eNY-Cvguj9hE.js → chunk-VR4S4FIN-C6a91eNY-BaQ02h-J.js} +1 -1
  29. package/dist/assets/{chunk-WYO6CB5R-BlzOfotS-Dm5-lJPj.js → chunk-WYO6CB5R-BlzOfotS-BuYOtRW-.js} +1 -1
  30. package/dist/assets/{chunk-ZGVPDNZ5-BYwxNFTK-hzHMTH8r.js → chunk-ZGVPDNZ5-BYwxNFTK-BCkOnDkO.js} +1 -1
  31. package/dist/assets/classDiagram-OUVF2IWQ-BYlRi6YQ-DbagYY9o.js +1 -0
  32. package/dist/assets/classDiagram-v2-EOCWNBFH-BCHaYHxj-DbagYY9o.js +1 -0
  33. package/dist/assets/cynefin-VYW2F7L2-CGYr2F6h-UB5w6uC7.js +1 -0
  34. package/dist/assets/{cynefinDiagram-TSTJHNR4-DHA9iPo--9Vfx1RL5.js → cynefinDiagram-TSTJHNR4-DHA9iPo--D5F9W2o2.js} +1 -1
  35. package/dist/assets/{dagre-VKFMJZFB--oJKqXBZ-DVMiDNUa.js → dagre-VKFMJZFB--oJKqXBZ-88RUfNlU.js} +1 -1
  36. package/dist/assets/{diagram-FQU43EPY-CcJCB9bG-Dw8uXi5J.js → diagram-FQU43EPY-CcJCB9bG-jbLemh0e.js} +1 -1
  37. package/dist/assets/{diagram-G47NLZAW-C_o-WGG1-BiirKYwU.js → diagram-G47NLZAW-C_o-WGG1-xlNez2pC.js} +1 -1
  38. package/dist/assets/{diagram-NH7WQ7WH-CXJCYvY--CxJ-W_AV.js → diagram-NH7WQ7WH-CXJCYvY--C3CqFZaV.js} +1 -1
  39. package/dist/assets/{diagram-OA4YK3LP-BMzeJ87A-C66U-WpA.js → diagram-OA4YK3LP-BMzeJ87A-sCnQ29Ip.js} +1 -1
  40. package/dist/assets/{diagram-WEI45ONY-D_93NKqo-C5wgOP3m.js → diagram-WEI45ONY-D_93NKqo-DSd8lYcR.js} +1 -1
  41. package/dist/assets/{ebnfDiagram-CCIWWBDH-CVai1Ii9-XxRZOagG.js → ebnfDiagram-CCIWWBDH-CVai1Ii9-DL5PrRfS.js} +1 -1
  42. package/dist/assets/{erDiagram-Q63AITRT-CiABnA0s-C3SFUgS_.js → erDiagram-Q63AITRT-CiABnA0s-rpAyy6Aw.js} +1 -1
  43. package/dist/assets/eventmodeling-45OFAUF4-CWmqhm3a-DkBecorT.js +1 -0
  44. package/dist/assets/flowDiagram-23GEKE2U-DKSB5AiY-CXJxjn_t.js +1 -0
  45. package/dist/assets/{ganttDiagram-NO4QXBWP-DQZvdFo1-CZAkzl2_.js → ganttDiagram-NO4QXBWP-DQZvdFo1-C7F2aPpB.js} +1 -1
  46. package/dist/assets/gitGraph-TEB2WS4Q-BR9qPwdN-Du_E4hcC.js +1 -0
  47. package/dist/assets/{gitGraphDiagram-IHSO6WYX-C2ovBouh-CXNXoPSF.js → gitGraphDiagram-IHSO6WYX-C2ovBouh-C_2xuNvU.js} +1 -1
  48. package/dist/assets/index-D3DaJte4.css +1 -0
  49. package/dist/assets/index-DKufSRgm.js +617 -0
  50. package/dist/assets/info-DKCQHKI2-Dplx5kMp-BUCYnVUr.js +1 -0
  51. package/dist/assets/{infoDiagram-FWYZ7A6U-7UnoB5AP-GVMnr0TB.js → infoDiagram-FWYZ7A6U-7UnoB5AP-BDJn_gZ2.js} +1 -1
  52. package/dist/assets/{ishikawaDiagram-FXEZZL3T-ByUDM_N2-BmKgPmF9.js → ishikawaDiagram-FXEZZL3T-ByUDM_N2-84Ld9x5-.js} +1 -1
  53. package/dist/assets/{journeyDiagram-5HDEW3XC-c5xIah9o-BOoOb2Up.js → journeyDiagram-5HDEW3XC-c5xIah9o-BKkQO5ur.js} +1 -1
  54. package/dist/assets/{kanban-definition-HUTT4EX6-Cnt6loYD-CV1DfLpD.js → kanban-definition-HUTT4EX6-Cnt6loYD-BsG3BxVw.js} +1 -1
  55. package/dist/assets/{lib-BfLKLNAW.js → lib-zuZr9fkI.js} +1 -1
  56. package/dist/assets/{line-D7ziSjKi-qjBgm7Wg.js → line-D7ziSjKi-C5w4gPUQ.js} +1 -1
  57. package/dist/assets/{marp-BPQ2jYa4.js → marp-FzmYUP9b.js} +1 -1
  58. package/dist/assets/{mermaid-parser.core-DxEa8E3F-bFNCoAMV.js → mermaid-parser.core-DxEa8E3F-DjFZ0x4N.js} +2 -2
  59. package/dist/assets/{mermaid.core-V0OYwIz3-bDvzm251.js → mermaid.core-V0OYwIz3-1iJt6Fje.js} +3 -3
  60. package/dist/assets/{mindmap-definition-LN4V7U3C-CexN3O6L-BRp2Mb--.js → mindmap-definition-LN4V7U3C-CexN3O6L-DP9kmAzP.js} +1 -1
  61. package/dist/assets/packet-7NZHBO7P-CQI3flND-ewcXElnr.js +1 -0
  62. package/dist/assets/{pegDiagram-2B236MQR-BiM4G0cW--kWTOoR2.js → pegDiagram-2B236MQR-BiM4G0cW-BlnrD6NT.js} +1 -1
  63. package/dist/assets/pie-RZYD4A2V-CnmKE2Wa-CCyUxET4.js +1 -0
  64. package/dist/assets/{pieDiagram-ENE6RG2P-B_fBBS-2-BOFV4ngY.js → pieDiagram-ENE6RG2P-B_fBBS-2-BKuZUm01.js} +1 -1
  65. package/dist/assets/{quadrantDiagram-ABIIQ3AL-BigGVxCR-jyaO71dB.js → quadrantDiagram-ABIIQ3AL-BigGVxCR-DkfLj9Xh.js} +1 -1
  66. package/dist/assets/radar-I7S5WNFK-us6x-Z9R-DrKUISae.js +1 -0
  67. package/dist/assets/railroad-3IZDKUUU-BKdz6pdN-CFKVwjnX.js +1 -0
  68. package/dist/assets/railroad-abnf-AHOZXSZD-m_CSSIfd-jmHmUIiI.js +1 -0
  69. package/dist/assets/railroad-ebnf-EBAXGLYW-Nu3nAG7C-xTNqkilU.js +1 -0
  70. package/dist/assets/railroad-peg-LSFZ7HO6--Y3mD4R_-BTE-hywQ.js +1 -0
  71. package/dist/assets/{railroadDiagram-RFXS5EU6--lNIlG64-D8DkHqgs.js → railroadDiagram-RFXS5EU6--lNIlG64-BEkxjf_N.js} +1 -1
  72. package/dist/assets/{requirementDiagram-TGXJPOKE-D-B0Y5Wu-d5Jr6_2i.js → requirementDiagram-TGXJPOKE-D-B0Y5Wu-CJBVdYgb.js} +1 -1
  73. package/dist/assets/{sankeyDiagram-HTMAVEWB-DlutaXDv-VGEBaos9.js → sankeyDiagram-HTMAVEWB-DlutaXDv-CFbAwuwF.js} +1 -1
  74. package/dist/assets/{sequenceDiagram-DBY2YBRQ-BOLgtggH-hpEall7s.js → sequenceDiagram-DBY2YBRQ-BOLgtggH-DLEygrdq.js} +1 -1
  75. package/dist/assets/{stateDiagram-2N3HPSRC-jf_dXUEU-CDm7zgbz.js → stateDiagram-2N3HPSRC-jf_dXUEU-Cd5481-w.js} +1 -1
  76. package/dist/assets/stateDiagram-v2-6OUMAXLB-CuzgCL2v-BP3lZGTn.js +1 -0
  77. package/dist/assets/{swimlanes-5IMT3BWC-CYjtALQH-Bn3Yx5s4.js → swimlanes-5IMT3BWC-CYjtALQH-Cp0_qORx.js} +1 -1
  78. package/dist/assets/swimlanesDiagram-G3AALYLV-3bmSBtKs-BqvZyp1I.js +8 -0
  79. package/dist/assets/{timeline-definition-FHXFAJF6-DYJ4oUm8-CtWavdRU.js → timeline-definition-FHXFAJF6-DYJ4oUm8-BR9TSleK.js} +1 -1
  80. package/dist/assets/treeView-QDETBFTQ-D27HWG6Q-DBPuc9zP.js +1 -0
  81. package/dist/assets/treemap-6X3UGDF4-CmGIU3H5-opVOIgkg.js +1 -0
  82. package/dist/assets/{vennDiagram-L72KCM5P-CdKAHoek-D-tozz5H.js → vennDiagram-L72KCM5P-CdKAHoek-B1GbPicN.js} +1 -1
  83. package/dist/assets/wardley-OPB4EBWU-CbHWsb2k-oryHi9la.js +1 -0
  84. package/dist/assets/{wardleyDiagram-EHGQE667-BF6c4_CW-Bd3huzRu.js → wardleyDiagram-EHGQE667-BF6c4_CW-CS4IOktG.js} +1 -1
  85. package/dist/assets/{xychartDiagram-FW5EYKEG-CGiKngj7-PiOfLnci.js → xychartDiagram-FW5EYKEG-CGiKngj7-WkBklR-a.js} +1 -1
  86. package/dist/index.html +2 -2
  87. package/package.json +3 -3
  88. package/server/backends/byte-range.ts +18 -8
  89. package/server/backends/collections.ts +7 -1
  90. package/server/backends/files.ts +42 -23
  91. package/server/backends/html.ts +2 -2
  92. package/server/backends/image-gen.ts +13 -2
  93. package/server/backends/mutateStatus.ts +9 -0
  94. package/server/backends/remoteHost/attachment-path.ts +4 -1
  95. package/server/backends/remoteHost/attachmentStore.spec.ts +14 -1
  96. package/server/backends/remoteHost/attachmentStore.ts +3 -1
  97. package/server/backends/remoteHost/getFeed.spec.ts +2 -1
  98. package/server/backends/remoteHost/handlers.spec.ts +70 -4
  99. package/server/backends/remoteHost/handlers.ts +31 -8
  100. package/server/backends/remoteHost/index.ts +1 -0
  101. package/server/backends/remoteHost/ingestAttachments.spec.ts +11 -7
  102. package/server/backends/remoteHost/ingestAttachments.ts +29 -19
  103. package/server/backends/remoteHost/skills.ts +4 -2
  104. package/server/backends/remoteHost/stagedStorageIds.ts +5 -2
  105. package/server/backends/remoteHost/terminalInput.spec.ts +41 -0
  106. package/server/backends/remoteHost/terminalInput.ts +7 -1
  107. package/server/backends/remoteHost/terminalScreen.ts +6 -2
  108. package/server/backends/streamFile.ts +26 -0
  109. package/server/backends/thumbnailStore.ts +1 -1
  110. package/server/backends/worklog.ts +5 -5
  111. package/server/config/app-config.ts +15 -0
  112. package/server/config/config-routes.ts +7 -0
  113. package/server/config/config-schema.ts +6 -1
  114. package/server/config/header-config.ts +11 -4
  115. package/server/config/header-context.ts +10 -3
  116. package/server/files/files-browse.ts +1 -49
  117. package/server/files/pathContainment.ts +79 -0
  118. package/server/git/pr-for-branch.ts +10 -5
  119. package/server/git/spawn-collect.ts +18 -9
  120. package/server/git/worktree-diff.ts +16 -3
  121. package/server/git/worktree-pr.ts +17 -1
  122. package/server/git/worktrees.ts +21 -5
  123. package/server/index.ts +19 -4
  124. package/server/infra/plugins-registry.ts +6 -2
  125. package/server/infra/tmux-routes.ts +17 -1
  126. package/server/routes/app-routes.ts +7 -4
  127. package/server/routes/plugin-narration.ts +4 -2
  128. package/dist/assets/architecture-TIHT7OUA-0NsD2xW1-9X3_cRqd.js +0 -1
  129. package/dist/assets/channel-7wUqSdoX-vfZkO_uL.js +0 -1
  130. package/dist/assets/classDiagram-OUVF2IWQ-BYlRi6YQ-CeGPKz7I.js +0 -1
  131. package/dist/assets/classDiagram-v2-EOCWNBFH-BCHaYHxj-CeGPKz7I.js +0 -1
  132. package/dist/assets/cynefin-VYW2F7L2-CGYr2F6h-0kc9OM8m.js +0 -1
  133. package/dist/assets/eventmodeling-45OFAUF4-CWmqhm3a-CjeZJqe8.js +0 -1
  134. package/dist/assets/flowDiagram-23GEKE2U-DKSB5AiY-B6tkM4yH.js +0 -1
  135. package/dist/assets/gitGraph-TEB2WS4Q-BR9qPwdN-BnYaIjat.js +0 -1
  136. package/dist/assets/index-C-I-KZAf.js +0 -617
  137. package/dist/assets/index-Cue6y3NP.css +0 -1
  138. package/dist/assets/info-DKCQHKI2-Dplx5kMp-DYTQnnkg.js +0 -1
  139. package/dist/assets/packet-7NZHBO7P-CQI3flND-TB9IxewR.js +0 -1
  140. package/dist/assets/pie-RZYD4A2V-CnmKE2Wa-DfFUYw37.js +0 -1
  141. package/dist/assets/radar-I7S5WNFK-us6x-Z9R-CfuJM-7B.js +0 -1
  142. package/dist/assets/railroad-3IZDKUUU-BKdz6pdN-d15bc0qE.js +0 -1
  143. package/dist/assets/railroad-abnf-AHOZXSZD-m_CSSIfd-CyjJaPJJ.js +0 -1
  144. package/dist/assets/railroad-ebnf-EBAXGLYW-Nu3nAG7C-CWODrAAC.js +0 -1
  145. package/dist/assets/railroad-peg-LSFZ7HO6--Y3mD4R_-uM99LygW.js +0 -1
  146. package/dist/assets/stateDiagram-v2-6OUMAXLB-CuzgCL2v-BJkHeYpz.js +0 -1
  147. package/dist/assets/swimlanesDiagram-G3AALYLV-3bmSBtKs-Dq42c4u7.js +0 -8
  148. package/dist/assets/treeView-QDETBFTQ-D27HWG6Q-DVQpaIF5.js +0 -1
  149. package/dist/assets/treemap-6X3UGDF4-CmGIU3H5-DpXrJ_Vl.js +0 -1
  150. package/dist/assets/wardley-OPB4EBWU-CbHWsb2k-DS8lznjT.js +0 -1
package/README.md CHANGED
@@ -410,13 +410,16 @@ The Settings modal (⚙) persists per-user UI choices to `~/.mulmoterminal/confi
410
410
  | `pushEnabled` | `true` to send a **Web Push** to your registered devices when a background task finishes. Off by default; only sends while the **RemoteHost** channel is connected (see below). |
411
411
  | `worklogEnabled` | `true` to run the built-in **dev worklog** batch (see below). Off by default (each run spawns an LLM session, so it costs tokens). |
412
412
  | `worklogIntervalHours` | Worklog cadence in hours (default `6`, clamped to `1`–`168`). |
413
+ | `terminalSubmit` | Which bytes Claude reads as **submit** vs **newline**: `"cr"` (default — Enter submits, Shift+Enter makes a newline) or `"esc-cr"` (for a Claude Code rebound the other way). Applies to the keyboard **and** the phone remote-view submit, for **Claude sessions only** (shell/codex keep plain Enter). See the [Configuration guide](https://receptron.github.io/mulmoterminal/guide/en/config.html#terminal-submit). |
413
414
 
414
415
  #### Header buttons
415
416
 
416
417
  Each terminal header shows configurable **action buttons**. Omitting `buttons` (globally or per-dir)
417
- keeps the built-in defaults a **file-path picker** (📎) and an **OS file-manager reveal** (📂).
418
- Setting `buttons` (at either level) **replaces** the defaults with your list, so you can drop, reorder,
419
- or swap them e.g. add the in-app **📁 file explorer** with `"open": { "files": "${dir}" }`.
418
+ keeps the built-in **starter set**: a file-path picker (📎), an OS file-manager reveal (📂), an in-app
419
+ file explorer (📁), a new terminal here (🖥), this branch's PR (🔗, git repos, only when a PR exists),
420
+ and open-on-GitHub (🌐, git repos). Setting `buttons` (at either level) **replaces the whole default
421
+ set** with your list (it is not merged on top), so listing your own — even a **shorter** one — is how
422
+ you drop, reorder, or swap them.
420
423
  A button has an `id`, `label`, and a `run` of `"shell"` (run a command), `"input"` (send text to the
421
424
  agent), or `"open"`. An `open` button targets one of `url` / `reveal` (OS file manager) / `files`
422
425
  (in-app explorer) / `view` (a built-in overlay) / `terminal` (a dir → a new cell running `$SHELL`,
@@ -793,7 +796,9 @@ Favorited collections get their own toolbar buttons.
793
796
  - **Themes** — four terminal palettes (midnight / nord / daylight / solarized), your pick
794
797
  remembered; a project's `.mulmoterminal.json` can override per directory.
795
798
  - **Editing niceties** — **Shift+Enter** inserts a newline in the prompt, and on macOS
796
- **Option** is treated as Meta so Claude's Alt-key bindings work.
799
+ **Option** is treated as Meta so Claude's Alt-key bindings work. If your Claude Code is
800
+ rebound so Enter and Shift+Enter behave backwards, flip them with
801
+ [`terminalSubmit`](https://receptron.github.io/mulmoterminal/guide/en/config.html#terminal-submit).
797
802
 
798
803
  ---
799
804
 
@@ -7,13 +7,14 @@
7
7
 
8
8
  import { execSync, spawn } from "node:child_process";
9
9
  import { existsSync, statSync } from "node:fs";
10
- import { get as httpGet } from "node:http";
11
10
  import { createRequire } from "node:module";
12
11
  import { createServer } from "node:net";
13
12
  import { dirname, join, resolve } from "node:path";
14
13
  import { createInterface } from "node:readline";
15
14
  import { fileURLToPath } from "node:url";
16
15
  import { computeUpdateNotice, isUpdateCheckDisabled } from "./update-check.js";
16
+ import { detectNpxCacheDir, npxCacheHintLines } from "./npx-cache-hint.js";
17
+ import { waitUntilReady } from "./wait-ready.js";
17
18
  import {
18
19
  chooseCwd,
19
20
  parsePortArg,
@@ -30,10 +31,12 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
30
31
  const PKG_DIR = join(__dirname, "..");
31
32
  const SERVER_ENTRY = join(PKG_DIR, "server", "index.ts");
32
33
  const DEFAULT_PORT = 34567;
33
- const READY_TIMEOUT_MS = 15_000;
34
34
  // Server exit code meaning "port taken at bind time" — keep in sync with
35
35
  // server/index.ts (PORT_IN_USE_EXIT_CODE).
36
36
  const PORT_IN_USE_EXIT_CODE = 75;
37
+ // Only the end of stderr matters for the crash diagnosis; a long-lived server can log
38
+ // arbitrarily much before dying, so the tail is bounded.
39
+ const STDERR_TAIL_MAX_BYTES = 64 * 1024;
37
40
 
38
41
  // Single source of truth: read the version from the shipped package.json so
39
42
  // `--version` never drifts from the published version.
@@ -170,36 +173,6 @@ function isPortFree(port) {
170
173
  });
171
174
  }
172
175
 
173
- // Poll the server until it answers, then call onReady; give up after the timeout
174
- // so the launcher never hangs on a crash loop. Returns a cancel function — a
175
- // raced/abandoned attempt stops polling so it can't fire a stale banner.
176
- function waitUntilReady(port, onReady) {
177
- const startedAt = Date.now();
178
- let timer = null;
179
- let cancelled = false;
180
- const attempt = () => {
181
- if (cancelled) return;
182
- const req = httpGet({ host: "127.0.0.1", port, path: "/", timeout: 1000 }, (res) => {
183
- res.resume();
184
- if (!cancelled) onReady();
185
- });
186
- req.on("error", retry);
187
- req.on("timeout", () => {
188
- req.destroy();
189
- retry();
190
- });
191
- };
192
- const retry = () => {
193
- if (cancelled || Date.now() - startedAt > READY_TIMEOUT_MS) return;
194
- timer = setTimeout(attempt, 300);
195
- };
196
- attempt();
197
- return () => {
198
- cancelled = true;
199
- if (timer) clearTimeout(timer);
200
- };
201
- }
202
-
203
176
  function printReadyBanner(url) {
204
177
  const bar = "\x1b[32m" + "─".repeat(48) + "\x1b[0m";
205
178
  console.log(`\n${bar}`);
@@ -272,10 +245,18 @@ async function choosePort(requested, explicit) {
272
245
  function runServer(port, noOpen, cwd, onChild) {
273
246
  return new Promise((resolveExit) => {
274
247
  log(`Starting MulmoTerminal on port ${port}...`);
248
+ // stderr is piped (and passed through) so a fatal boot error can be inspected once the
249
+ // child closes — a half-unpacked npx cache entry crashes here with ERR_MODULE_NOT_FOUND,
250
+ // and without reading stderr the launcher cannot tell that from a real bug.
275
251
  const server = spawn(process.execPath, ["--import", "tsx", SERVER_ENTRY], {
276
252
  cwd: PKG_DIR,
277
253
  env: { ...process.env, NODE_ENV: "production", PORT: String(port), CLAUDE_CWD: cwd },
278
- stdio: "inherit",
254
+ stdio: ["inherit", "inherit", "pipe"],
255
+ });
256
+ let stderrTail = "";
257
+ server.stderr.on("data", (chunk) => {
258
+ process.stderr.write(chunk);
259
+ stderrTail = (stderrTail + chunk.toString()).slice(-STDERR_TAIL_MAX_BYTES);
279
260
  });
280
261
  onChild(server);
281
262
 
@@ -292,7 +273,10 @@ function runServer(port, noOpen, cwd, onChild) {
292
273
  }
293
274
  });
294
275
 
295
- server.on("exit", (code) => {
276
+ // `close`, not `exit`: it fires only once the piped stderr has fully drained, so the
277
+ // whole crash output — including a trailing `_npx/<hash>` line that can arrive after
278
+ // `exit` — is in `stderrTail` before we inspect it.
279
+ server.on("close", (code) => {
296
280
  cancelReady();
297
281
  // Exit code 75 means this child failed to bind (EADDRINUSE) and never
298
282
  // served — always retriable, regardless of what a probe to the port saw
@@ -301,6 +285,10 @@ function runServer(port, noOpen, cwd, onChild) {
301
285
  resolveExit();
302
286
  return;
303
287
  }
288
+ if (code !== 0) {
289
+ const cacheDir = detectNpxCacheDir(stderrTail);
290
+ if (cacheDir) npxCacheHintLines(cacheDir, process.platform).forEach((line) => error(line));
291
+ }
304
292
  process.exit(code ?? 1);
305
293
  });
306
294
  });
@@ -0,0 +1,2 @@
1
+ export declare function detectNpxCacheDir(stderrText: string): string | null;
2
+ export declare function npxCacheHintLines(cacheDir: string, platform: NodeJS.Platform): string[];
@@ -0,0 +1,69 @@
1
+ // Turning a confusing crash into a one-line fix. When an `npx mulmoterminal` install is
2
+ // aborted mid-unpack (npm's npx lock race — `ECOMPROMISED: Lock compromised`), the next run
3
+ // reuses the half-unpacked `~/.npm/_npx/<hash>/node_modules` and dies at boot with
4
+ // ERR_MODULE_NOT_FOUND for some transitive dependency. That reads as a broken package, but
5
+ // the published tarball is fine — the local npx cache entry is corrupted, and removing it
6
+ // is the whole fix. Nothing here reads the filesystem; the caller feeds it stderr text.
7
+
8
+ const MISSING_MODULE = /ERR_MODULE_NOT_FOUND|Cannot find (?:package|module)/;
9
+
10
+ // The cache entry itself: `<sep>_npx<sep><hash>` followed by another separator. Anchored on
11
+ // the literal `_npx` rather than on a leading `[^\s'"]*` wildcard — that prefix can also match
12
+ // separators, which makes the match ambiguous and the regex backtrack super-linearly on a long
13
+ // stack trace. The directory part before it is recovered by scanning, not by matching.
14
+ const NPX_ENTRY = /[/\\]_npx[/\\][0-9a-f]+(?=[/\\])/;
15
+
16
+ // What ends the path token a stack trace embeds: whitespace, or the quotes Node wraps
17
+ // specifiers in.
18
+ const PATH_BOUNDARIES = [" ", "\t", "\n", "\r", "'", '"'];
19
+
20
+ // Where the path token containing the entry begins: just past the last boundary before it.
21
+ const pathStartIndex = (textBefore) => Math.max(-1, ...PATH_BOUNDARIES.map((ch) => textBefore.lastIndexOf(ch))) + 1;
22
+
23
+ /**
24
+ * The corrupted npx cache entry a failed boot points at, or null.
25
+ * Fires only when BOTH signals are present — a module-resolution error and a path inside
26
+ * an `_npx/<hash>` cache entry — so an ordinary crash, or a resolution error in a normal
27
+ * install, never triggers the hint.
28
+ */
29
+ export function detectNpxCacheDir(stderrText) {
30
+ if (typeof stderrText !== "string" || !MISSING_MODULE.test(stderrText)) return null;
31
+ const match = NPX_ENTRY.exec(stderrText);
32
+ if (!match) return null;
33
+ return stderrText.slice(pathStartIndex(stderrText.slice(0, match.index)), match.index + match[0].length);
34
+ }
35
+
36
+ // Shell-quote the cache dir so a path containing quote characters (a home dir with an
37
+ // apostrophe on POSIX is the realistic case) produces a command that is still valid — and
38
+ // safe — to copy-paste, rather than one that breaks or runs something unintended.
39
+
40
+ // POSIX single-quoting: wrap in '…' and turn every embedded ' into '\'' (close, escaped
41
+ // quote, reopen). Nothing inside single quotes is special, so this is injection-proof.
42
+ const posixQuote = (s) => `'${s.replace(/'/g, "'\\''")}'`;
43
+
44
+ // PowerShell single-quoting: '…' with each ' doubled. Single quotes suppress $-interpolation
45
+ // (a double-quoted path containing $ would otherwise expand a variable), so this is safe.
46
+ const powershellQuote = (s) => `'${s.replace(/'/g, "''")}'`;
47
+
48
+ // cmd has no way to escape a " inside "…", but " is an illegal character in a Windows path,
49
+ // so double-quoting a real cache dir is safe; it protects the spaces that do occur.
50
+ const cmdQuote = (s) => `"${s}"`;
51
+
52
+ // The delete command differs per platform, and a wrong one is worse than none: `rm -rf` is not
53
+ // a command on Windows, and cmd's `rmdir /s /q` is a syntax error in PowerShell (where `rmdir`
54
+ // aliases Remove-Item), so Windows gets both of its shells spelled out.
55
+ const removalCommands = (cacheDir, platform) =>
56
+ platform === "win32"
57
+ ? [` cmd: rmdir /s /q ${cmdQuote(cacheDir)}`, ` PowerShell: Remove-Item -Recurse -Force ${powershellQuote(cacheDir)}`]
58
+ : [` rm -rf ${posixQuote(cacheDir)}`];
59
+
60
+ /** The recovery instructions shown under the crash, one string per line. */
61
+ export function npxCacheHintLines(cacheDir, platform) {
62
+ return [
63
+ "This looks like a corrupted npx cache, not a MulmoTerminal bug.",
64
+ 'An interrupted `npx` install (npm\'s "Lock compromised" race) can leave this entry half-unpacked:',
65
+ ` ${cacheDir}`,
66
+ "Remove it and run the same command again:",
67
+ ...removalCommands(cacheDir, platform),
68
+ ];
69
+ }
@@ -0,0 +1,26 @@
1
+ // Only the surface probeOnce actually touches — narrower than http.ClientRequest /
2
+ // IncomingMessage on purpose, so a test fake and the real node:http both satisfy it.
3
+ export interface ProbeRequest {
4
+ on(event: "error" | "timeout", listener: (...args: unknown[]) => void): unknown;
5
+ destroy(): void;
6
+ }
7
+
8
+ export interface ProbeResponse {
9
+ resume(): void;
10
+ }
11
+
12
+ export type ProbeGet = (opts: { host: string; port: number; path: string; timeout: number }, cb: (res: ProbeResponse) => void) => ProbeRequest;
13
+
14
+ export declare function probeOnce(get: ProbeGet, port: number, timeoutMs?: number): Promise<"ready" | "retry">;
15
+
16
+ export declare function waitUntilReady(
17
+ port: number,
18
+ onReady: () => void,
19
+ deps?: {
20
+ get?: ProbeGet;
21
+ now?: () => number;
22
+ timeoutMs?: number;
23
+ intervalMs?: number;
24
+ readyTimeoutMs?: number;
25
+ },
26
+ ): () => void;
@@ -0,0 +1,61 @@
1
+ // Polling the launched server until it answers `/`, then firing onReady exactly once.
2
+ // Split out of mulmoterminal.js so the retry state machine is testable without a socket.
3
+
4
+ import { get as httpGet } from "node:http";
5
+
6
+ const PROBE_TIMEOUT_MS = 1000;
7
+ const RETRY_INTERVAL_MS = 300;
8
+ const DEFAULT_READY_TIMEOUT_MS = 15_000;
9
+
10
+ // One probe of the server: resolves "ready" if it answers, else "retry". It NEVER
11
+ // rejects, and yields exactly ONE outcome per call — the crucial guarantee. Destroying a
12
+ // timed-out request itself emits an 'error', so a single timeout would otherwise trigger
13
+ // both the timeout handler AND the error handler; each schedules a retry, the poll loop
14
+ // forks in two, and every fork fires onReady when the server finally answers — printing
15
+ // the banner and opening the browser several times. The `settled` latch collapses each
16
+ // request to one outcome.
17
+ export function probeOnce(get, port, timeoutMs = PROBE_TIMEOUT_MS) {
18
+ return new Promise((resolve) => {
19
+ let settled = false;
20
+ const done = (outcome) => {
21
+ if (settled) return;
22
+ settled = true;
23
+ resolve(outcome);
24
+ };
25
+ const req = get({ host: "127.0.0.1", port, path: "/", timeout: timeoutMs }, (res) => {
26
+ res.resume();
27
+ done("ready");
28
+ });
29
+ req.on("error", () => done("retry"));
30
+ req.on("timeout", () => {
31
+ req.destroy();
32
+ done("retry");
33
+ });
34
+ });
35
+ }
36
+
37
+ // Poll until the server answers, then call onReady once; give up after readyTimeoutMs so
38
+ // the launcher never hangs on a crash loop. Returns a cancel function — a raced/abandoned
39
+ // attempt stops polling so it can't fire a stale banner. `deps` is injectable for tests.
40
+ export function waitUntilReady(port, onReady, deps = {}) {
41
+ const { get = httpGet, now = Date.now, timeoutMs = PROBE_TIMEOUT_MS, intervalMs = RETRY_INTERVAL_MS, readyTimeoutMs = DEFAULT_READY_TIMEOUT_MS } = deps;
42
+ const startedAt = now();
43
+ let cancelled = false;
44
+ let timer = null;
45
+ const loop = async () => {
46
+ if (cancelled) return;
47
+ const outcome = await probeOnce(get, port, timeoutMs);
48
+ if (cancelled) return;
49
+ if (outcome === "ready") {
50
+ onReady();
51
+ return;
52
+ }
53
+ if (now() - startedAt > readyTimeoutMs) return;
54
+ timer = setTimeout(loop, intervalMs);
55
+ };
56
+ loop();
57
+ return () => {
58
+ cancelled = true;
59
+ if (timer) clearTimeout(timer);
60
+ };
61
+ }
@@ -197,7 +197,7 @@ export const MODEL_PRESETS: readonly ModelPreset[] = [
197
197
  provider: "openrouter",
198
198
  id: "nvidia/nemotron-3-ultra-550b-a55b",
199
199
  label: "Nemotron 3 Ultra 550B",
200
- contextLength: 512_288,
200
+ contextLength: 524_288, // 512 KiB = 512 * 1024; was 512_288 (a typo)
201
201
  pricePerMTok: { input: 0.6, output: 3.6 },
202
202
  trials: measured(3, 3, 13),
203
203
  },
@@ -300,14 +300,19 @@ export const MODEL_PRESETS: readonly ModelPreset[] = [
300
300
 
301
301
  // Presets for one provider, plus whatever the user added to that provider's `models`.
302
302
  // A user entry with the same id as a preset keeps the preset's measured numbers rather
303
- // than appearing twice.
303
+ // than appearing twice — and two user entries for the same id (even differing only in
304
+ // case) collapse to one, so a dropdown never shows the same model twice.
304
305
  export function presetsForProvider(providerId: string, userModels: readonly string[] = []): ModelPreset[] {
305
306
  const presets = MODEL_PRESETS.filter((preset) => preset.provider === providerId);
306
307
  // Match `presetFor` (modelOption.ts), which compares ids case-insensitively: a user
307
308
  // entry that only differs from a preset in case is the same model, not a new one.
308
- const known = new Set(presets.map((preset) => preset.id.toLowerCase()));
309
- const added: ModelPreset[] = userModels
310
- .filter((id) => !known.has(id.toLowerCase()))
311
- .map((id) => ({ provider: providerId, id, label: id, contextLength: 0, pricePerMTok: { input: 0, output: 0 }, trials: { status: "unmeasured" } }));
309
+ const seen = new Set(presets.map((preset) => preset.id.toLowerCase()));
310
+ const added: ModelPreset[] = [];
311
+ for (const id of userModels) {
312
+ const key = id.toLowerCase();
313
+ if (seen.has(key)) continue; // already a preset OR an earlier user entry
314
+ seen.add(key);
315
+ added.push({ provider: providerId, id, label: id, contextLength: 0, pricePerMTok: { input: 0, output: 0 }, trials: { status: "unmeasured" } });
316
+ }
312
317
  return [...presets, ...added];
313
318
  }
@@ -0,0 +1,70 @@
1
+ // How the host's Claude Code / TUI maps received bytes to "submit this prompt" vs
2
+ // "insert a newline". The TUI decides from the bytes, and that mapping is environment-
3
+ // dependent (Claude Code lets a user rebind it), so the SAME fact has to reach two
4
+ // unrelated places: the browser's xterm key handler and the phone remote-view's submit.
5
+ // One value, consumed by both, keeps them from drifting apart.
6
+ //
7
+ // "cr" — the standard binding: CR (\r) submits, ESC+CR (\x1b\r, = Alt/Meta+Enter)
8
+ // makes a newline. This is MulmoTerminal's default and unchanged behaviour.
9
+ // "esc-cr" — the reversed binding some users configure: ESC+CR submits, CR makes a
10
+ // newline.
11
+ export const TERMINAL_SUBMIT_MODES = ["cr", "esc-cr"] as const;
12
+ export type TerminalSubmitMode = (typeof TERMINAL_SUBMIT_MODES)[number];
13
+ export const DEFAULT_TERMINAL_SUBMIT_MODE: TerminalSubmitMode = "cr";
14
+
15
+ export const isTerminalSubmitMode = (value: unknown): value is TerminalSubmitMode =>
16
+ typeof value === "string" && (TERMINAL_SUBMIT_MODES as readonly string[]).includes(value);
17
+
18
+ // CR on its own. ESC+CR is what a terminal emits for Alt/Meta+Enter.
19
+ const CR = "\r";
20
+ const ESC_CR = "\x1b\r";
21
+
22
+ // The byte(s) the host reads as "submit this prompt" in the given mode.
23
+ export const submitSequence = (mode: TerminalSubmitMode): string => (mode === "esc-cr" ? ESC_CR : CR);
24
+ // The byte(s) it reads as "insert a newline" — the other one.
25
+ export const newlineSequence = (mode: TerminalSubmitMode): string => (mode === "esc-cr" ? CR : ESC_CR);
26
+
27
+ // The submit byte(s) for a specific session. `terminalSubmit` describes the user's CLAUDE
28
+ // binding, so it applies ONLY to Claude sessions; every other agent (shell, codex, a
29
+ // one-shot command) keeps plain CR — a reversed setting must never rewrite a shell's Enter,
30
+ // where ESC+CR is Alt+Enter, not submit.
31
+ export const submitSequenceForAgent = (agent: string | undefined, mode: TerminalSubmitMode): string => (agent === "claude" ? submitSequence(mode) : CR);
32
+
33
+ // The structural shape of a keydown the override needs. A real DOM KeyboardEvent satisfies
34
+ // it, and so does a plain test object — no DOM dependency, so this stays testable and shared.
35
+ export interface EnterKeyEvent {
36
+ type: string;
37
+ key: string;
38
+ shiftKey: boolean;
39
+ altKey: boolean;
40
+ ctrlKey: boolean;
41
+ metaKey: boolean;
42
+ isComposing?: boolean;
43
+ }
44
+
45
+ // The bytes to emit for an Enter-family keydown (the caller then suppresses xterm's own
46
+ // \r and sends these instead), or null to let xterm handle the key natively.
47
+ //
48
+ // The SEMANTICS are the same in both modes — a bare Enter SUBMITS, Shift/Alt(Option)+Enter
49
+ // make a NEWLINE — only which bytes carry each meaning differs, because that is the
50
+ // environment fact `mode` encodes.
51
+ //
52
+ // In "cr" mode only Shift+Enter is overridden, exactly as before the setting existed: a
53
+ // bare Enter falls through to xterm's native \r (submit) and Option+Enter to
54
+ // macOptionIsMeta's ESC+CR (newline). Leaving the bare-Enter path native keeps IME
55
+ // candidate-confirm and the hottest key untouched in the default configuration.
56
+ export const enterKeyOverride = (mode: TerminalSubmitMode, e: EnterKeyEvent): string | null => {
57
+ if (e.type !== "keydown" || e.key !== "Enter") return null;
58
+ // IME composition (Japanese and other CJK input): this Enter confirms a candidate — it
59
+ // is never the user's submit/newline, so never intercept it.
60
+ if (e.isComposing) return null;
61
+ const bare = !e.shiftKey && !e.altKey && !e.ctrlKey && !e.metaKey;
62
+ const shiftOnly = e.shiftKey && !e.altKey && !e.ctrlKey && !e.metaKey;
63
+ const altHeld = e.altKey && !e.ctrlKey && !e.metaKey; // Option/Alt (with or without Shift)
64
+ if (mode === "cr") return shiftOnly ? newlineSequence(mode) : null;
65
+ // "esc-cr": a bare Enter must emit the ESC+CR that submits here; Shift/Alt+Enter must
66
+ // emit the CR that makes a newline (the Alt case also overrides macOptionIsMeta's ESC+CR).
67
+ if (bare) return submitSequence(mode);
68
+ if (shiftOnly || altHeld) return newlineSequence(mode);
69
+ return null;
70
+ };
@@ -1 +1 @@
1
- import{n as e}from"./chunk-Y2CYZVJY-Bdt8pFDJ-DsF7k-Jl.js";import{f as t}from"./src-Brzfja-q-ch1VcTiG.js";import{_ as n,g as r}from"./mermaid-parser.core-DxEa8E3F-bFNCoAMV.js";import"./chunk-WYO6CB5R-BlzOfotS-Dm5-lJPj.js";import"./chunk-VAUOI2AC-CS9QJ4yz-BkjFKKFo.js";import{n as i,r as a,t as o}from"./chunk-MOJQB5TN-CMZRaeqt-DynKnij8.js";import{t as s}from"./chunk-JWPE2WC7-BQ3zXr2k-DSd3Ct95.js";var c=r().RailroadAbnf.parser.LangiumParser,l=e(e=>{let t=e.alternatives.map(u);return t.length===1?t[0]:{type:`choice`,alternatives:t}},`transformAlternation`),u=e(e=>{let t=e.elements.map(f);return t.length===1?t[0]:{type:`sequence`,elements:t}},`transformConcatenation`),d=e(e=>{if(e.includes(`*`)){let[t,n]=e.split(`*`);return{min:t?parseInt(t,10):0,max:n?parseInt(n,10):1/0}}let t=parseInt(e,10);return{min:t,max:t}},`parseRepeat`),f=e(e=>{let t=p(e.primary);if(!e.repeat)return t;let{min:n,max:r}=d(e.repeat);return n===0&&r===1?{type:`optional`,element:t}:{type:`repetition`,element:t,min:n,max:r}},`transformElement`),p=e(e=>{switch(e.$type){case`AbnfStringLiteral`:return{type:`terminal`,value:e.value};case`AbnfNumVal`:return{type:`terminal`,value:e.value};case`AbnfRuleName`:return{type:`nonterminal`,name:e.name};case`AbnfGroup`:return l(e.element);case`AbnfOptionalGroup`:return{type:`optional`,element:l(e.element)};default:throw Error(`Unsupported ABNF primary node: ${e.$type}`)}},`transformPrimary`),m=e(e=>({name:e.name,definition:l(e.definition)}),`transformRule`),h=e(e=>{s(e,a),e.title&&a.setTitle(e.title),e.rules.map(e=>a.addRule(m(e)))},`populateDb`),g={parser:{parse:e(e=>{a.clear(),t.debug(`[ABNF Parser] Starting Langium parse`);let r=c.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new n(r);let i=r.value;t.debug(`[ABNF Parser] Parsed rules:`,i.rules.length),h(i),t.debug(`[ABNF Parser] Parse complete`)},`parse`),parser:{yy:a}},db:a,renderer:o,styles:i};export{g as diagram};
1
+ import{n as e}from"./chunk-Y2CYZVJY-Bdt8pFDJ-DsF7k-Jl.js";import{f as t}from"./src-Brzfja-q-ch1VcTiG.js";import{_ as n,g as r}from"./mermaid-parser.core-DxEa8E3F-DjFZ0x4N.js";import"./chunk-WYO6CB5R-BlzOfotS-BuYOtRW-.js";import"./chunk-VAUOI2AC-CS9QJ4yz-C8yjyNAb.js";import{n as i,r as a,t as o}from"./chunk-MOJQB5TN-CMZRaeqt-Dv_IeGiN.js";import{t as s}from"./chunk-JWPE2WC7-BQ3zXr2k-DSd3Ct95.js";var c=r().RailroadAbnf.parser.LangiumParser,l=e(e=>{let t=e.alternatives.map(u);return t.length===1?t[0]:{type:`choice`,alternatives:t}},`transformAlternation`),u=e(e=>{let t=e.elements.map(f);return t.length===1?t[0]:{type:`sequence`,elements:t}},`transformConcatenation`),d=e(e=>{if(e.includes(`*`)){let[t,n]=e.split(`*`);return{min:t?parseInt(t,10):0,max:n?parseInt(n,10):1/0}}let t=parseInt(e,10);return{min:t,max:t}},`parseRepeat`),f=e(e=>{let t=p(e.primary);if(!e.repeat)return t;let{min:n,max:r}=d(e.repeat);return n===0&&r===1?{type:`optional`,element:t}:{type:`repetition`,element:t,min:n,max:r}},`transformElement`),p=e(e=>{switch(e.$type){case`AbnfStringLiteral`:return{type:`terminal`,value:e.value};case`AbnfNumVal`:return{type:`terminal`,value:e.value};case`AbnfRuleName`:return{type:`nonterminal`,name:e.name};case`AbnfGroup`:return l(e.element);case`AbnfOptionalGroup`:return{type:`optional`,element:l(e.element)};default:throw Error(`Unsupported ABNF primary node: ${e.$type}`)}},`transformPrimary`),m=e(e=>({name:e.name,definition:l(e.definition)}),`transformRule`),h=e(e=>{s(e,a),e.title&&a.setTitle(e.title),e.rules.map(e=>a.addRule(m(e)))},`populateDb`),g={parser:{parse:e(e=>{a.clear(),t.debug(`[ABNF Parser] Starting Langium parse`);let r=c.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new n(r);let i=r.value;t.debug(`[ABNF Parser] Parsed rules:`,i.rules.length),h(i),t.debug(`[ABNF Parser] Parse complete`)},`parse`),parser:{yy:a}},db:a,renderer:o,styles:i};export{g as diagram};
@@ -0,0 +1 @@
1
+ import{f as e}from"./mermaid-parser.core-DxEa8E3F-DjFZ0x4N.js";export{e as createArchitectureServices};