mulmoterminal 2.3.0 → 2.5.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 (144) hide show
  1. package/README.md +39 -3
  2. package/bin/cli-args.d.ts +1 -0
  3. package/bin/cli-args.js +16 -0
  4. package/bin/mulmoterminal.js +2 -1
  5. package/common/dirConfigSource.ts +101 -0
  6. package/common/prPhase.ts +65 -0
  7. package/common/rateLimits.ts +37 -0
  8. package/common/terminalScrollSpeed.ts +31 -0
  9. package/dist/assets/{abnfDiagram-VRR7QNED-RjiYivmv-B8nu2pUR.js → abnfDiagram-VRR7QNED-RjiYivmv-ZKZeabOo.js} +1 -1
  10. package/dist/assets/architecture-TIHT7OUA-0NsD2xW1-BlV5di_4.js +1 -0
  11. package/dist/assets/{architectureDiagram-ZJ3FMSHR-3nWA91tG-ByxXyiOP.js → architectureDiagram-ZJ3FMSHR-3nWA91tG-CzywLoPM.js} +1 -1
  12. package/dist/assets/{blockDiagram-677ZJIJ3-BPuAJQRW-Brt9e7H_.js → blockDiagram-677ZJIJ3-BPuAJQRW-aOYmvIDN.js} +1 -1
  13. package/dist/assets/{c4Diagram-LMCZKHZV-C0LAqQso-D598g8JV.js → c4Diagram-LMCZKHZV-C0LAqQso-DrRq0Tz1.js} +1 -1
  14. package/dist/assets/channel-7wUqSdoX-DBiTAZ-Z.js +1 -0
  15. package/dist/assets/{chunk-32BRIVSS-BRrYpgtb-BEOPPcME.js → chunk-32BRIVSS-BRrYpgtb-D-VyP-2M.js} +1 -1
  16. package/dist/assets/{chunk-52WLFC77-BJ-ss3Xr-BmZ-jgWV.js → chunk-52WLFC77-BJ-ss3Xr-xsQKbX6Y.js} +1 -1
  17. package/dist/assets/{chunk-C7G6YPKG-BZEucKEL-DhvCnMVg.js → chunk-C7G6YPKG-BZEucKEL-LmuOyVM4.js} +1 -1
  18. package/dist/assets/{chunk-EX3LRPZG-DLS6FBN1-CxeaHZOV.js → chunk-EX3LRPZG-DLS6FBN1-D_ZX8lXD.js} +1 -1
  19. package/dist/assets/{chunk-FWX5IMBZ-DHLSFw1H-COykUZUW.js → chunk-FWX5IMBZ-DHLSFw1H-DABXVNFD.js} +2 -2
  20. package/dist/assets/{chunk-HOUHSVGY-Bhlt8hXJ-DFKJuoMh.js → chunk-HOUHSVGY-Bhlt8hXJ-Clcclm_e.js} +1 -1
  21. package/dist/assets/{chunk-ICXQ74PX-DwgHBX_g-CFoH6Yjf.js → chunk-ICXQ74PX-DwgHBX_g-DVrDEih_.js} +1 -1
  22. package/dist/assets/{chunk-MOJQB5TN-CMZRaeqt-Dy6S_sS_.js → chunk-MOJQB5TN-CMZRaeqt-B6_lajzj.js} +1 -1
  23. package/dist/assets/{chunk-OGEWGWER-8Qy4a8b5-XcjK3kCc.js → chunk-OGEWGWER-8Qy4a8b5-BK8v7oHr.js} +1 -1
  24. package/dist/assets/{chunk-PUDLZKDR-DcrWQRYh-CFVQ5Aq8.js → chunk-PUDLZKDR-DcrWQRYh-BpZVxE3I.js} +1 -1
  25. package/dist/assets/{chunk-Q4XR5HBZ-ZXVGkG8Z-D2iTH8ap.js → chunk-Q4XR5HBZ-ZXVGkG8Z-BJub83F1.js} +1 -1
  26. package/dist/assets/{chunk-V7JOEXUC-DmGdheTX-Dfius2MW.js → chunk-V7JOEXUC-DmGdheTX-BwD2Cu1l.js} +1 -1
  27. package/dist/assets/{chunk-VAUOI2AC-CS9QJ4yz-Dd_VpRsu.js → chunk-VAUOI2AC-CS9QJ4yz-DYmUgyge.js} +1 -1
  28. package/dist/assets/{chunk-VR4S4FIN-C6a91eNY-U_jwmwMS.js → chunk-VR4S4FIN-C6a91eNY-CZWE7I5r.js} +1 -1
  29. package/dist/assets/{chunk-WYO6CB5R-BlzOfotS-04XZ1Jfd.js → chunk-WYO6CB5R-BlzOfotS-CoeWhGPT.js} +1 -1
  30. package/dist/assets/{chunk-ZGVPDNZ5-BYwxNFTK-DBPsh-p3.js → chunk-ZGVPDNZ5-BYwxNFTK-DHPaQ6Oa.js} +1 -1
  31. package/dist/assets/classDiagram-OUVF2IWQ-BYlRi6YQ-DEqD9yXA.js +1 -0
  32. package/dist/assets/classDiagram-v2-EOCWNBFH-BCHaYHxj-DEqD9yXA.js +1 -0
  33. package/dist/assets/cynefin-VYW2F7L2-CGYr2F6h-rrcpRbiY.js +1 -0
  34. package/dist/assets/{cynefinDiagram-TSTJHNR4-DHA9iPo--D-iTSsn0.js → cynefinDiagram-TSTJHNR4-DHA9iPo--BVz-2Ntu.js} +1 -1
  35. package/dist/assets/{dagre-VKFMJZFB--oJKqXBZ-G72EKlyP.js → dagre-VKFMJZFB--oJKqXBZ-CgKI-Did.js} +1 -1
  36. package/dist/assets/{diagram-FQU43EPY-CcJCB9bG-DgT-eqMs.js → diagram-FQU43EPY-CcJCB9bG-DIRlUnKd.js} +1 -1
  37. package/dist/assets/{diagram-G47NLZAW-C_o-WGG1-D_JxH6o2.js → diagram-G47NLZAW-C_o-WGG1-DNgB0vZj.js} +1 -1
  38. package/dist/assets/{diagram-NH7WQ7WH-CXJCYvY--pVhp9uBi.js → diagram-NH7WQ7WH-CXJCYvY--DtULNtOi.js} +1 -1
  39. package/dist/assets/{diagram-OA4YK3LP-BMzeJ87A-CFoCoA1K.js → diagram-OA4YK3LP-BMzeJ87A-Betvk9-o.js} +1 -1
  40. package/dist/assets/{diagram-WEI45ONY-D_93NKqo-COhtVv5k.js → diagram-WEI45ONY-D_93NKqo-DpjErInC.js} +1 -1
  41. package/dist/assets/{ebnfDiagram-CCIWWBDH-CVai1Ii9-cHbupDvE.js → ebnfDiagram-CCIWWBDH-CVai1Ii9-CkKEjG4D.js} +1 -1
  42. package/dist/assets/{erDiagram-Q63AITRT-CiABnA0s-Ch6F8aLL.js → erDiagram-Q63AITRT-CiABnA0s-BOytIrTd.js} +1 -1
  43. package/dist/assets/eventmodeling-45OFAUF4-CWmqhm3a-lhGPdKsg.js +1 -0
  44. package/dist/assets/flowDiagram-23GEKE2U-DKSB5AiY-Ce3_LeXQ.js +1 -0
  45. package/dist/assets/{ganttDiagram-NO4QXBWP-DQZvdFo1-DcmaS_P9.js → ganttDiagram-NO4QXBWP-DQZvdFo1-Bv6Ol2Sx.js} +1 -1
  46. package/dist/assets/gitGraph-TEB2WS4Q-BR9qPwdN-DgqDgNXY.js +1 -0
  47. package/dist/assets/{gitGraphDiagram-IHSO6WYX-C2ovBouh-CgaMpUqZ.js → gitGraphDiagram-IHSO6WYX-C2ovBouh-aUQOq1B7.js} +1 -1
  48. package/dist/assets/index-BwvrN1j0.css +1 -0
  49. package/dist/assets/{index-QEGG3YTz.js → index-CbGZuCmY.js} +202 -202
  50. package/dist/assets/info-DKCQHKI2-Dplx5kMp-CbcoQy2P.js +1 -0
  51. package/dist/assets/{infoDiagram-FWYZ7A6U-7UnoB5AP-f2daXl9J.js → infoDiagram-FWYZ7A6U-7UnoB5AP-D5riRt52.js} +1 -1
  52. package/dist/assets/{ishikawaDiagram-FXEZZL3T-ByUDM_N2-DYE7Ld9-.js → ishikawaDiagram-FXEZZL3T-ByUDM_N2-DUHr3ImH.js} +1 -1
  53. package/dist/assets/{journeyDiagram-5HDEW3XC-c5xIah9o-B0woNVtP.js → journeyDiagram-5HDEW3XC-c5xIah9o-BmYfNX5M.js} +1 -1
  54. package/dist/assets/{kanban-definition-HUTT4EX6-Cnt6loYD-Dm8evmYs.js → kanban-definition-HUTT4EX6-Cnt6loYD-CYSjS-Kr.js} +1 -1
  55. package/dist/assets/{lib-DtgnGSMW.js → lib-4D_VJiWI.js} +1 -1
  56. package/dist/assets/{line-D7ziSjKi-CRwDdfSt.js → line-D7ziSjKi-BSuJe7JA.js} +1 -1
  57. package/dist/assets/{marp-Djuncd6G.js → marp-aQPALUt4.js} +1 -1
  58. package/dist/assets/{mermaid-parser.core-DxEa8E3F-BILJo_fK.js → mermaid-parser.core-DxEa8E3F-BAMiAMZz.js} +2 -2
  59. package/dist/assets/{mermaid.core-V0OYwIz3-CabnyPOO.js → mermaid.core-V0OYwIz3-0t7TpKVw.js} +3 -3
  60. package/dist/assets/{mindmap-definition-LN4V7U3C-CexN3O6L-DF1aNqGu.js → mindmap-definition-LN4V7U3C-CexN3O6L-Y5ILic5l.js} +1 -1
  61. package/dist/assets/packet-7NZHBO7P-CQI3flND-Bzib0qjU.js +1 -0
  62. package/dist/assets/{pegDiagram-2B236MQR-BiM4G0cW-q-89nsVP.js → pegDiagram-2B236MQR-BiM4G0cW-BcRtbtj2.js} +1 -1
  63. package/dist/assets/pie-RZYD4A2V-CnmKE2Wa-sgdov0Km.js +1 -0
  64. package/dist/assets/{pieDiagram-ENE6RG2P-B_fBBS-2-B3pcC0Pg.js → pieDiagram-ENE6RG2P-B_fBBS-2-BoLxHrrD.js} +1 -1
  65. package/dist/assets/{quadrantDiagram-ABIIQ3AL-BigGVxCR-BBKSbiVD.js → quadrantDiagram-ABIIQ3AL-BigGVxCR-CaBUCRTr.js} +1 -1
  66. package/dist/assets/radar-I7S5WNFK-us6x-Z9R-HeF-YUqy.js +1 -0
  67. package/dist/assets/railroad-3IZDKUUU-BKdz6pdN-CVEaRb6p.js +1 -0
  68. package/dist/assets/railroad-abnf-AHOZXSZD-m_CSSIfd-Da0o3UJC.js +1 -0
  69. package/dist/assets/railroad-ebnf-EBAXGLYW-Nu3nAG7C-d8_8RJsZ.js +1 -0
  70. package/dist/assets/railroad-peg-LSFZ7HO6--Y3mD4R_-CJVm33Iy.js +1 -0
  71. package/dist/assets/{railroadDiagram-RFXS5EU6--lNIlG64-Do0ZI2kT.js → railroadDiagram-RFXS5EU6--lNIlG64-BZODWkTp.js} +1 -1
  72. package/dist/assets/{requirementDiagram-TGXJPOKE-D-B0Y5Wu-B4oci7Dv.js → requirementDiagram-TGXJPOKE-D-B0Y5Wu-CTG1__VO.js} +1 -1
  73. package/dist/assets/{sankeyDiagram-HTMAVEWB-DlutaXDv-KRXYn9rF.js → sankeyDiagram-HTMAVEWB-DlutaXDv-BHkDstqV.js} +1 -1
  74. package/dist/assets/{sequenceDiagram-DBY2YBRQ-BOLgtggH-zw0J8MZu.js → sequenceDiagram-DBY2YBRQ-BOLgtggH-B47PY7W5.js} +1 -1
  75. package/dist/assets/{stateDiagram-2N3HPSRC-jf_dXUEU-vFGH-mvy.js → stateDiagram-2N3HPSRC-jf_dXUEU-4bMMcfLt.js} +1 -1
  76. package/dist/assets/stateDiagram-v2-6OUMAXLB-CuzgCL2v-BhlNWKJ_.js +1 -0
  77. package/dist/assets/{swimlanes-5IMT3BWC-CYjtALQH-DhegOTK_.js → swimlanes-5IMT3BWC-CYjtALQH-Dkkp7Xaf.js} +1 -1
  78. package/dist/assets/swimlanesDiagram-G3AALYLV-3bmSBtKs-BIUkJIiD.js +8 -0
  79. package/dist/assets/{timeline-definition-FHXFAJF6-DYJ4oUm8-a7d-hF5Y.js → timeline-definition-FHXFAJF6-DYJ4oUm8-CKcCEyil.js} +1 -1
  80. package/dist/assets/treeView-QDETBFTQ-D27HWG6Q-BTmguQlN.js +1 -0
  81. package/dist/assets/treemap-6X3UGDF4-CmGIU3H5-C69GNR2Y.js +1 -0
  82. package/dist/assets/{vennDiagram-L72KCM5P-CdKAHoek-La4xWHGC.js → vennDiagram-L72KCM5P-CdKAHoek-D4FLVcsc.js} +1 -1
  83. package/dist/assets/wardley-OPB4EBWU-CbHWsb2k-4mdSyYTy.js +1 -0
  84. package/dist/assets/{wardleyDiagram-EHGQE667-BF6c4_CW-M6-Z6fe5.js → wardleyDiagram-EHGQE667-BF6c4_CW-CRhp69sj.js} +1 -1
  85. package/dist/assets/{xychartDiagram-FW5EYKEG-CGiKngj7-BtTeKKIV.js → xychartDiagram-FW5EYKEG-CGiKngj7-CqYw8fCB.js} +1 -1
  86. package/dist/index.html +2 -2
  87. package/package.json +4 -4
  88. package/server/agents/claude-args.ts +10 -1
  89. package/server/agents/codex-rate-limits.spec.ts +74 -0
  90. package/server/agents/codex-rate-limits.ts +94 -0
  91. package/server/agents/codex-rollout.ts +79 -0
  92. package/server/agents/pr-clone-prompt.ts +30 -0
  93. package/server/agents/rate-limit-persist.ts +59 -0
  94. package/server/agents/rate-limit-probe.spec.ts +66 -0
  95. package/server/agents/rate-limit-probe.ts +114 -0
  96. package/server/agents/rate-limit-routes.ts +66 -0
  97. package/server/agents/rate-limit-store.spec.ts +91 -0
  98. package/server/agents/rate-limit-store.ts +73 -0
  99. package/server/agents/statusline.spec.ts +66 -0
  100. package/server/agents/statusline.ts +48 -0
  101. package/server/cli-init.ts +2 -2
  102. package/server/config/app-config.ts +48 -4
  103. package/server/config/config-routes.ts +10 -2
  104. package/server/config/config-schema.ts +1 -1
  105. package/server/config/dir-config.ts +83 -0
  106. package/server/config/env.ts +12 -0
  107. package/server/config/header-context.ts +9 -8
  108. package/server/config/workspace.ts +17 -7
  109. package/server/git/gitRemote.ts +14 -31
  110. package/server/git/pr-footer.ts +17 -4
  111. package/server/git/prPhase.ts +40 -12
  112. package/server/git/remote-ref.ts +50 -0
  113. package/server/git/repo-root-sync.ts +55 -0
  114. package/server/git/worktree-pr.ts +1 -0
  115. package/server/index.ts +42 -5
  116. package/server/infra/allowed-origin.ts +106 -25
  117. package/server/infra/hide-error-stacks.ts +15 -0
  118. package/server/infra/spa-fallback.ts +18 -0
  119. package/server/infra/tmux.ts +40 -0
  120. package/server/routes/app-routes.ts +7 -2
  121. package/server/routes/dir-routes.ts +19 -3
  122. package/server/session/spawn-claude.ts +39 -16
  123. package/dist/assets/architecture-TIHT7OUA-0NsD2xW1-TY_v-qbq.js +0 -1
  124. package/dist/assets/channel-7wUqSdoX-DRUj8A2e.js +0 -1
  125. package/dist/assets/classDiagram-OUVF2IWQ-BYlRi6YQ-BVAZDjb0.js +0 -1
  126. package/dist/assets/classDiagram-v2-EOCWNBFH-BCHaYHxj-BVAZDjb0.js +0 -1
  127. package/dist/assets/cynefin-VYW2F7L2-CGYr2F6h-CFSruy5n.js +0 -1
  128. package/dist/assets/eventmodeling-45OFAUF4-CWmqhm3a-B09FMUUq.js +0 -1
  129. package/dist/assets/flowDiagram-23GEKE2U-DKSB5AiY-BswknzuF.js +0 -1
  130. package/dist/assets/gitGraph-TEB2WS4Q-BR9qPwdN-Bg_TZB3f.js +0 -1
  131. package/dist/assets/index-DWePsXYt.css +0 -1
  132. package/dist/assets/info-DKCQHKI2-Dplx5kMp-BAI4sdgi.js +0 -1
  133. package/dist/assets/packet-7NZHBO7P-CQI3flND-Bqib4t7m.js +0 -1
  134. package/dist/assets/pie-RZYD4A2V-CnmKE2Wa-C7f4Ij-s.js +0 -1
  135. package/dist/assets/radar-I7S5WNFK-us6x-Z9R-C0_E6OiV.js +0 -1
  136. package/dist/assets/railroad-3IZDKUUU-BKdz6pdN-C2_X_Txm.js +0 -1
  137. package/dist/assets/railroad-abnf-AHOZXSZD-m_CSSIfd-wMka3Tbm.js +0 -1
  138. package/dist/assets/railroad-ebnf-EBAXGLYW-Nu3nAG7C-B7uu2NPX.js +0 -1
  139. package/dist/assets/railroad-peg-LSFZ7HO6--Y3mD4R_-D4Sj2mdo.js +0 -1
  140. package/dist/assets/stateDiagram-v2-6OUMAXLB-CuzgCL2v-PiMBzK3e.js +0 -1
  141. package/dist/assets/swimlanesDiagram-G3AALYLV-3bmSBtKs-ByceWEns.js +0 -8
  142. package/dist/assets/treeView-QDETBFTQ-D27HWG6Q-Bf6uz3xV.js +0 -1
  143. package/dist/assets/treemap-6X3UGDF4-CmGIU3H5-CLzvNsC2.js +0 -1
  144. package/dist/assets/wardley-OPB4EBWU-CbHWsb2k-OXtZqto9.js +0 -1
package/README.md CHANGED
@@ -21,6 +21,13 @@ ping to your phone when a task finishes. One `npx` command, no Electron, no conf
21
21
  npx mulmoterminal # starts on http://localhost:34567 and opens your browser
22
22
  ```
23
23
 
24
+ > **Something looks wrong?** Type `/mulmoterminal-bug-report` in any MulmoTerminal session. The
25
+ > bundled skill hears the symptom out, checks your **real** config, schema and version to see
26
+ > whether the behaviour is configuration or by design, searches the existing issues — and only
27
+ > helps you file one if none of that explains it, with the environment collected and secrets
28
+ > masked. Getting you unstuck is the goal; an issue is what is left when the first three steps
29
+ > fail.
30
+
24
31
  ![MulmoTerminal's grid view — four live Claude sessions running side by side, each in its own color-coded project](https://raw.githubusercontent.com/receptron/mulmoterminal/main/docs/guide/images/grid-2x2-live.png)
25
32
 
26
33
  *The grid is a **cockpit for parallel agents** — here, four live Claude sessions, each in its own color-coded project. Every cell's header carries what you need to triage at a glance: **model · context %**, **token counts** (`⇡in ⇣out`), the **git branch / changes** chip, and an AI summary of what the agent is doing. A cell's **border color signals state** — working / done (blue), needs-you (amber — e.g. waiting on a permission), idle — with an attention chime so a stuck cell off-screen still pulls you back. Supervise many; only step in where you're called.*
@@ -93,6 +100,13 @@ than as bytes (files within the session's working directory only):
93
100
  | source, config, logs, and `.txt` — 46 extensions | the app's own **Files** view (`/files?path=`), where CodeMirror highlights it, the tree is right there, and it can be edited |
94
101
  | everything else — images, PDF, SVG, HTML, video | raw bytes in a new tab, which the browser renders better than an editor would |
95
102
 
103
+ **While a grid cell is enlarged, the [Files pane](#files-view-browse--edit) takes the click first** — every
104
+ row above except the last one, since the pane is the same editor plus a Markdown preview. The
105
+ file opens *beside* the terminal that printed it, and the pane opens itself if it was closed.
106
+ It declines, leaving the routing above untouched, when nothing is enlarged, when the path is
107
+ not under that cell's own directory (the pane cannot walk above its root), or for the raw-bytes
108
+ row, where it would only show an empty editor.
109
+
96
110
  Highlighting in the Files view covers the JS/TS family, JSON and Markdown (the modes
97
111
  `cmEditor.ts` bundles); other languages open as plain text.
98
112
 
@@ -448,7 +462,7 @@ The Settings modal (⚙) persists per-user UI choices to `~/.mulmoterminal/confi
448
462
 
449
463
  ![The Settings modal — theme, notification sound, PR repos, launch commands, and MCP servers](https://raw.githubusercontent.com/receptron/mulmoterminal/main/docs/guide/images/settings.png)
450
464
 
451
- *Open it from the ⚙ button in the toolbar. Pick a **theme**, set the **terminal font size**, set a custom **attention sound**, list the repos the cross-repo **PRs & Issues** view should aggregate, add **launch commands** for grid cells, and register your own **MCP servers** — no need to hand-edit the config file. Note that **theme and font size are stored per browser** (they're display preferences, so a phone and a desktop keep their own); the rest live in `~/.mulmoterminal/config.json` and are shared by every client.*
465
+ *Open it from the ⚙ button in the toolbar. Pick a **theme**, set the **terminal font size** and **scroll speed**, set a custom **attention sound**, list the repos the cross-repo **PRs & Issues** view should aggregate, add **launch commands** for grid cells, and register your own **MCP servers** — no need to hand-edit the config file. Note that **theme, font size and scroll speed are stored per browser** (they're display preferences, so a phone and a desktop keep their own); the rest live in `~/.mulmoterminal/config.json` and are shared by every client.*
452
466
 
453
467
  | Field | Meaning |
454
468
  | ------------ | ------- |
@@ -461,16 +475,23 @@ The Settings modal (⚙) persists per-user UI choices to `~/.mulmoterminal/confi
461
475
  | `quickCommands` | `{ label, text, agents? }` phrases the **phone** offers as chips on a session's terminal view. Tapping one puts `text` in the input box; it is not sent until you press send. `agents` (`"claude"` / `"codex"` / `"shell"`) scopes a chip to session kinds — omit it to offer the chip everywhere. Empty by default. |
462
476
  | `userMcpServers` | `{ id, url }` HTTP MCP servers merged into the **single-view** Claude session's `--mcp-config` (a `localhost` URL is reached over `host.docker.internal` in the Docker sandbox). Takes effect on the next session. |
463
477
  | `buttons` | Header action buttons — see [Header buttons](#header-buttons). Omit to keep the defaults; set to replace them. |
464
- | `chips` | Header info chips. Omit to keep the default set; `[]` hides all built-ins. |
478
+ | `chips` | Header info chips (`dir` / `git` / `work` / `diff` / `ctx` / `usage` / `status` / `tools`, or custom text). Omit to keep the default set; `[]` hides all built-ins. `work` shows which PR / issue the cell is on (`#977 → #966`) and clears itself when the PR merges — see the [Configuration guide](https://receptron.github.io/mulmoterminal/guide/en/config.html#work-chip). |
465
479
  | `pushEnabled` | `true` to send a **Web Push** to your registered devices. Off by default; only sends while the **RemoteHost** channel is connected (see below). The master switch — `pushKinds` picks which moments. |
466
480
  | `pushKinds` | Which moments push: `"finished"` (a turn ended, ✅) and/or `"waiting"` (the agent stopped to ask — a permission prompt or a question, ❓, **once per prompt**). Omit to keep both; `[]` for none. A kind added in a later version stays off until you tick it. |
467
481
  | `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). |
468
482
  | `worklogIntervalHours` | Worklog cadence in hours (default `6`, clamped to `1`–`168`). |
469
483
  | `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). |
470
484
  | `copyOnSelect` | `true` puts a **mouse selection on the clipboard the moment it settles**, with no key pressed (the PuTTY / iTerm2 behaviour). **Off by default** — it changes the clipboard when you may only have meant to highlight something. No Settings UI: edit the file and reload the tab. Composes with the `copy` keymap action rather than replacing it. Over plain `http://` the browser gives a page no clipboard access, so a fallback asks xterm to copy instead; see the [Configuration guide](https://receptron.github.io/mulmoterminal/guide/en/config.html#copy-on-select). |
471
- | `prWorkdirFooter` | Ends the body of a PR **⧉ Open PR** creates with `work in <clone>` — the directory name of the clone the work happened in, so a PR says which of several side-by-side checkouts produced it. **On by default**; set `false` to opt out — read from the file per PR, so no restart is needed (there is no Settings control for it). Only applied to PRs this app creates (pressing the button again on an existing PR never re-appends). |
485
+ | `prWorkdirFooter` | Ends a PR body with `work in <clone>` — the directory name of the clone the work happened in, so a PR says which of several side-by-side checkouts produced it. Applies to **both** paths that open PRs here: **⧉ Open PR** appends it to the PR it creates, and every Claude session is told to end the bodies it writes with the same line (the name is resolved by the server, so a session inside a managed worktree still names the main checkout). **On by default**; set `false` to opt out — read per PR and per session spawn, so no restart is needed (there is no Settings control for it). Appending is idempotent: an existing PR never gets a second copy. |
472
486
  | `fontFamily` | The **terminal font** every session renders in — a CSS font-family stack, e.g. `"'Cica', 'MS Gothic', monospace"`. No Settings UI: edit the file, then **restart** (this config is read once at startup). Unset uses the built-in stack (JetBrains Mono / Fira Code / Menlo / Consolas, then CJK faces for Japanese, Korean and Chinese). Unlike the per-browser font **size**, this is one value for the whole host — it names fonts, and which fonts exist is a property of the machine. A directory can override it. See the [Configuration guide](https://receptron.github.io/mulmoterminal/guide/en/config.html#font-family). |
473
487
 
488
+ Every MulmoTerminal on the machine shares this one file, so an older build could save over a key a
489
+ newer one wrote. It doesn't: a **top-level key this version doesn't recognise is written back
490
+ untouched**, which is what makes running two versions side by side — or downgrading for a while —
491
+ safe. A mistyped key survives on the same rule, which is deliberate: a line you can still see is
492
+ easier to debug than one that silently vanished. See the
493
+ [Configuration guide](https://receptron.github.io/mulmoterminal/guide/en/config.html#unknown-keys).
494
+
474
495
  #### Header buttons
475
496
 
476
497
  Each terminal header shows configurable **action buttons**. Omitting `buttons` (globally or per-dir)
@@ -620,6 +641,12 @@ as the reload signal, so colors, palette, font size and grid order update withou
620
641
  anything. There is no filesystem watcher, so an edit made **outside** a session (your own
621
642
  editor) is picked up when the terminal is next opened.
622
643
 
644
+ **Checking what took effect.** Settings → **Directory settings** lists your recent directories
645
+ and expands each one to the values in force, with a swatch per color and the path of the file
646
+ they came from. It also names the keys it **dropped** (a color that isn't `#rrggbb`, a size out
647
+ of range) and the keys it doesn't read at all (`badgeColour`, a global-only setting) — which is
648
+ what tells "I never set that" apart from "I set it and it didn't take".
649
+
623
650
  ---
624
651
 
625
652
  ## Running
@@ -771,6 +798,10 @@ shrinks the pane rather than reflowing xterm into garbage. It works in both zoom
771
798
  (cockpit roster and thumbnail filmstrip), the pane re-roots as you walk the zoom between
772
799
  terminals, and whether it's open plus how wide it is are remembered per browser.
773
800
 
801
+ The toggle is not the only way in: while a cell is enlarged, **clicking a file path the agent
802
+ printed** opens it here too, rather than in a new tab or full-screen — see
803
+ [Clicking a file path](#clicking-a-file-path).
804
+
774
805
  All reads and writes go through `GET/PUT /api/files/browse/*?cwd=&path=`, and every
775
806
  `path` is **contained within the project root** (server-side) — `..`/absolute escapes
776
807
  are rejected for reads and writes alike, so editing can't reach outside the directory
@@ -941,6 +972,10 @@ Favorited collections get their own toolbar buttons.
941
972
  **Option** is treated as Meta so Claude's Alt-key bindings work. If your Claude Code is
942
973
  rebound so Enter and Shift+Enter behave backwards, flip them with
943
974
  [`terminalSubmit`](https://receptron.github.io/mulmoterminal/guide/en/config.html#terminal-submit).
975
+ - **Scroll speed** — one wheel notch or trackpad swipe moves the terminal the same distance
976
+ whether you're reading a shell's scrollback or a full-screen app like Claude Code. If a
977
+ two-finger scroll on a Mac trackpad flies past what you were reading, turn **terminal scroll
978
+ speed** down in Settings (0.25×–3×, per browser — it's a property of the pointing device).
944
979
  - **No accidental page zoom** — `Ctrl`+wheel and a trackpad pinch would rescale the whole
945
980
  page and drag the layout and the terminal's fit along with it, so both are ignored.
946
981
  Keyboard zoom (`Cmd`/`Ctrl` `+` / `-`) still works when you mean it, and a phone's finger
@@ -1137,6 +1172,7 @@ same-origin-guarded.
1137
1172
  | -------- | ------- |
1138
1173
  | `GET\|POST /api/config` | User UI config (`cwdPresets`, `soundFile`, `soundKinds`, `sounds`, `prRepos`, `launchers`, `quickCommands`, `userMcpServers`, `providers`). |
1139
1174
  | `GET /api/sound?kind=` · `/api/dir-sound?cwd=&kind=` · `/api/sound-preset/:id` · `/api/dir-config?cwd=` | Custom / per-directory / preset attention sound + per-dir config. `kind` selects a config entry, never a path. |
1175
+ | `GET /api/dir-config-detail?cwd=` | The same per-dir config, **plus** the settings a running terminal doesn't need (`provider`, `model`, `skills`, `addDirs`, header button/chip **labels**), **plus** which keys the file set and how each fared (applied / dropped in validation / not a setting at all). Read-only; backs the Settings modal's **Directory settings** preview. Unlike the other `?cwd=` routes this one does **not** fall back to the default workspace — it reports on the directory it was asked about, so a path that no longer exists comes back as `exists:false`. Sound paths and button commands stay server-side. |
1140
1176
  | `GET /api/launch-options` | The Anthropic-compatible backends this server can reach, each with its models and — when it can't — the reason. Reports the **name** of the env var a key is read from, never the key. |
1141
1177
  | `GET /api/notifications`(`/history`) · `POST /api/notifications/:id/clear` | Notification feed. |
1142
1178
  | `POST /api/transcribe`(`/model`…) | Voice-input transcription (Whisper, macOS). |
package/bin/cli-args.d.ts CHANGED
@@ -10,3 +10,4 @@ export declare const SECOND_INSTANCE_NOTE: string;
10
10
  export declare const MIN_NODE_LABEL: string;
11
11
  export declare function nodeMeetsMinimum(version: string): boolean;
12
12
  export declare function serverNodeArgs(serverEntry: string, launchDir: string): string[];
13
+ export declare function serverSpawnEnv(env: Record<string, string | undefined>, port: number, cwd: string): Record<string, string | undefined>;
package/bin/cli-args.js CHANGED
@@ -150,3 +150,19 @@ export function nodeMeetsMinimum(version) {
150
150
  export function serverNodeArgs(serverEntry, launchDir) {
151
151
  return ["--import", "tsx", `--env-file-if-exists=${join(launchDir, ".env")}`, serverEntry];
152
152
  }
153
+
154
+ /**
155
+ * The environment the launcher spawns the server with.
156
+ *
157
+ * Only the two values the server cannot work out for itself. In particular NOT `NODE_ENV`:
158
+ * the server hands its own environment to every PTY it spawns, so a `NODE_ENV=production`
159
+ * set here reaches every terminal in every cell — where yarn v1 reads it and installs
160
+ * WITHOUT devDependencies while still reporting success (#955). Express is pinned to
161
+ * production separately, in the server, so nothing here needs to say it.
162
+ *
163
+ * A `NODE_ENV` the user exported themselves passes through untouched: this decides what the
164
+ * launcher adds, not what it removes.
165
+ */
166
+ export function serverSpawnEnv(env, port, cwd) {
167
+ return { ...env, PORT: String(port), CLAUDE_CWD: cwd };
168
+ }
@@ -26,6 +26,7 @@ import {
26
26
  nodeMeetsMinimum,
27
27
  MIN_NODE_LABEL,
28
28
  serverNodeArgs,
29
+ serverSpawnEnv,
29
30
  } from "./cli-args.js";
30
31
 
31
32
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -272,7 +273,7 @@ function runServer(port, noOpen, cwd, onChild) {
272
273
  // package directory, so serverNodeArgs makes that path absolute (#795).
273
274
  const server = spawn(process.execPath, serverNodeArgs(SERVER_ENTRY, process.cwd()), {
274
275
  cwd: PKG_DIR,
275
- env: { ...process.env, NODE_ENV: "production", PORT: String(port), CLAUDE_CWD: cwd },
276
+ env: serverSpawnEnv(process.env, port, cwd),
276
277
  stdio: ["inherit", "inherit", "pipe"],
277
278
  });
278
279
  let stderrTail = "";
@@ -0,0 +1,101 @@
1
+ // Where a directory's settings actually came from, for the settings modal's preview.
2
+ //
3
+ // The rest of the app only ever needs the RESOLVED config — the values a terminal adopts.
4
+ // That is exactly what makes a mistyped or rejected setting invisible: the file is on disk,
5
+ // the app reads it, and the value silently isn't there. This says which keys the file set,
6
+ // which of them survived validation, and which the app doesn't read at all.
7
+
8
+ // Every key `loadDirConfig` reads. A key outside this list is a typo as far as the app is
9
+ // concerned, however sensible it looks — `server/config/dir-config.spec.ts` pins the two
10
+ // together so a field added to the loader can't quietly go missing here.
11
+ export const DIR_CONFIG_KEYS = [
12
+ "name",
13
+ "badgeColor",
14
+ "headerColor",
15
+ "headerTextColor",
16
+ "cellColor",
17
+ "cellBorderColor",
18
+ "dotColor",
19
+ "buttonColor",
20
+ "fontSize",
21
+ "fontFamily",
22
+ "orderPriority",
23
+ "theme",
24
+ "colors",
25
+ "sound",
26
+ "sounds",
27
+ "buttons",
28
+ "chips",
29
+ "skills",
30
+ "provider",
31
+ "model",
32
+ "addDirs",
33
+ ] as const;
34
+
35
+ export interface DirConfigSource {
36
+ // Keys the file set that the app is using.
37
+ applied: string[];
38
+ // Keys the file set that the app read and then dropped — an out-of-range number, a colour
39
+ // that isn't #rrggbb, a sound outside the directory. The value is gone; the key is the clue.
40
+ ignored: string[];
41
+ // Keys the app doesn't read at all: a misspelling (`badgeColour`), or a setting that belongs
42
+ // in the global config instead of a directory's file.
43
+ unknown: string[];
44
+ }
45
+
46
+ export const EMPTY_DIR_CONFIG_SOURCE: DirConfigSource = { applied: [], ignored: [], unknown: [] };
47
+
48
+ // The settings a directory can make that the per-cell config deliberately doesn't carry — the
49
+ // browser has no use for them while running a terminal, but the preview has to SHOW them or a
50
+ // key like `provider` can only be named as applied, never read back.
51
+ //
52
+ // Labels only for buttons and chips: their `cmd` / `text` is what the button would type into
53
+ // the session, which is not what "did my config take effect" needs, and is the sort of thing a
54
+ // screenshot of the settings screen shouldn't carry. Sound paths stay out for the same reason
55
+ // they stay out of the per-cell config — that path is the confinement boundary.
56
+ export interface DirConfigExtras {
57
+ provider: string | null;
58
+ model: string | null;
59
+ skills: string[] | null;
60
+ addDirs: string[] | null;
61
+ buttonLabels: string[];
62
+ chipLabels: string[];
63
+ }
64
+
65
+ export const EMPTY_DIR_CONFIG_EXTRAS: DirConfigExtras = {
66
+ provider: null,
67
+ model: null,
68
+ skills: null,
69
+ addDirs: null,
70
+ buttonLabels: [],
71
+ chipLabels: [],
72
+ };
73
+
74
+ // "The loader kept nothing for this key" — null/undefined, but also the empty collections the
75
+ // loader returns when every entry in a map or list was rejected.
76
+ function isEmptyValue(value: unknown): boolean {
77
+ if (value === null || value === undefined) return true;
78
+ if (Array.isArray(value)) return value.length === 0;
79
+ if (typeof value === "object") return Object.keys(value as Record<string, unknown>).length === 0;
80
+ return false;
81
+ }
82
+
83
+ // The keys a resolved config actually holds a value for. Taken as a SET by describeDirConfig
84
+ // rather than the config object itself: the loader's type is an interface with named fields,
85
+ // which no amount of `Record<string, unknown>` will accept without a cast.
86
+ export function keysWithValue(resolved: object): Set<string> {
87
+ const entries: [string, unknown][] = Object.entries(resolved);
88
+ return new Set(entries.filter(([, value]) => !isEmptyValue(value)).map(([key]) => key));
89
+ }
90
+
91
+ // `raw` is the file as parsed; `kept` is which keys survived the loader (see keysWithValue).
92
+ export function describeDirConfig(raw: Record<string, unknown>, kept: ReadonlySet<string>): DirConfigSource {
93
+ const known = new Set<string>(DIR_CONFIG_KEYS);
94
+ const source: DirConfigSource = { applied: [], ignored: [], unknown: [] };
95
+ for (const key of Object.keys(raw)) {
96
+ if (!known.has(key)) source.unknown.push(key);
97
+ else if (kept.has(key)) source.applied.push(key);
98
+ else source.ignored.push(key);
99
+ }
100
+ return source;
101
+ }
@@ -0,0 +1,65 @@
1
+ // What a cell is working on, as it travels between the two sides: the branch's PR, its phase in
2
+ // the review loop, and the issue that PR closes. Shared because BOTH sides decide from it — the
3
+ // server derives it from `gh`, the client decides what the header chip shows — and it used to be
4
+ // two declarations with a "keep them in sync" comment on one of them (src/components/rosterPhase.ts).
5
+
6
+ // Ordered roughly along the lifecycle so the client can pick a colour/label per phase.
7
+ // `none` = no PR for this branch yet (still local work); `ready` = open, CI green, no
8
+ // changes requested — i.e. waiting to merge.
9
+ export type PrPhase = "none" | "draft" | "ci-failing" | "changes-requested" | "ci-running" | "ready" | "merged" | "closed";
10
+
11
+ export const PR_PHASES: readonly PrPhase[] = ["none", "draft", "ci-failing", "changes-requested", "ci-running", "ready", "merged", "closed"];
12
+
13
+ export const isPrPhase = (v: unknown): v is PrPhase => typeof v === "string" && (PR_PHASES as readonly string[]).includes(v);
14
+
15
+ // The /api/pr-phase response. `phase` and `prUrl` predate the rest and the roster reads them;
16
+ // the numbers are what lets a cell say WHICH work it is on rather than just how far along it is.
17
+ export interface WorkItem {
18
+ phase: PrPhase;
19
+ pr: number | null;
20
+ prUrl: string | null;
21
+ issue: number | null;
22
+ issueUrl: string | null;
23
+ }
24
+
25
+ export const EMPTY_WORK_ITEM: Readonly<WorkItem> = { phase: "none", pr: null, prUrl: null, issue: null, issueUrl: null };
26
+
27
+ // GitHub's closing keywords, as GitHub itself matches them: any of these, then optional
28
+ // whitespace/colon, then #N. Anything else in the body is a mention, not a link — a PR that
29
+ // says "related to #12" is not working on #12.
30
+ // One character class for the separator, not `\s*:?\s+`: two adjacent whitespace quantifiers
31
+ // backtrack super-linearly on a long body, and a PR description is user input.
32
+ //
33
+ // `[1-9]\d*` rather than `\d+`: there is no issue #0, and `#0123` is not issue 123 — both are
34
+ // typos, and a typo that renders as a link to somebody else's issue is worse than no chip.
35
+ const CLOSING_KEYWORD = /\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)[:\s]+#([1-9]\d*)/i;
36
+
37
+ // Digits from a body or a branch name are unbounded, and `Number("9".repeat(20))` is 1e20 — which
38
+ // would render in the chip as "#1e+20" and link nowhere. Anything past the safe-integer range is
39
+ // not an issue number (found by Codex review).
40
+ function toIssueNumber(digits: string): number | null {
41
+ const n = Number(digits);
42
+ return Number.isSafeInteger(n) && n > 0 ? n : null;
43
+ }
44
+
45
+ // The issue a PR body says it closes, or null. Deliberately blind to the full-URL form
46
+ // (`Fixes https://github.com/o/r/issues/12`): that can name ANOTHER repository, and a number
47
+ // shown next to this cell's PR has to belong to the same repo to be clickable and true.
48
+ export function issueRefFromPrBody(body: string | null | undefined): number | null {
49
+ const found = typeof body === "string" ? CLOSING_KEYWORD.exec(body) : null;
50
+ return found ? toIssueNumber(found[1]) : null;
51
+ }
52
+
53
+ // A branch named after its issue the way this repo names them: `fix/966-preserve-unknown-keys`.
54
+ // Requires a type prefix and a hyphen after the digits, so `chore/dep-updates-20260728` is not
55
+ // read as issue #20260728.
56
+ const BRANCH_ISSUE = /^[a-z][a-z-]*\/([1-9]\d*)-/;
57
+
58
+ // A CANDIDATE, never an answer: `release/2026-07-28-hotfix` yields 2026 here, and no pattern can
59
+ // tell that apart from a branch for issue #2026 — the year and the number have the same shape.
60
+ // The caller must confirm the issue exists before showing it, or a cell claims to be working on
61
+ // somebody else's issue. Named for the doubt so a call site can't forget it.
62
+ export function issueCandidateFromBranch(branch: string | null | undefined): number | null {
63
+ const found = typeof branch === "string" ? BRANCH_ISSUE.exec(branch) : null;
64
+ return found ? toIssueNumber(found[1]) : null;
65
+ }
@@ -0,0 +1,37 @@
1
+ // The rate-limit wire shape, and the one parser for it (#387).
2
+ //
3
+ // Here rather than on either side because BOTH decide from it: the server reads it back out of its
4
+ // own cache file, and the browser reads it off `/api/rate-limits`. Written twice it was written
5
+ // twice differently — jscpd caught the copies before they had a chance to drift.
6
+ //
7
+ // Everything is optional on purpose. A window can be absent because the agent is not installed,
8
+ // because the plan is API-key billed, because no session has answered yet, or because upstream
9
+ // dropped the field (anthropics/claude-code#40094). None of those are zero, and a gauge reading
10
+ // 0% when the truth is 83% is the worst thing this data can do.
11
+
12
+ export interface RateLimitWindow {
13
+ usedPercentage: number; // 0-100, fractional
14
+ resetsAt_sec: number | null; // Unix epoch seconds
15
+ }
16
+
17
+ export interface RateLimits {
18
+ fiveHour: RateLimitWindow | null;
19
+ sevenDay: RateLimitWindow | null;
20
+ }
21
+
22
+ const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === "object" && value !== null;
23
+ const finiteNumber = (value: unknown): number | null => (typeof value === "number" && Number.isFinite(value) ? value : null);
24
+
25
+ export function parseRateLimitWindow(raw: unknown): RateLimitWindow | null {
26
+ if (!isRecord(raw)) return null;
27
+ const used = finiteNumber(raw.usedPercentage);
28
+ return used === null ? null : { usedPercentage: used, resetsAt_sec: finiteNumber(raw.resetsAt_sec) };
29
+ }
30
+
31
+ /** Null when neither window survives, so a caller can tell "nothing to show" from "0% used". */
32
+ export function parseRateLimits(raw: unknown): RateLimits | null {
33
+ if (!isRecord(raw)) return null;
34
+ const fiveHour = parseRateLimitWindow(raw.fiveHour);
35
+ const sevenDay = parseRateLimitWindow(raw.sevenDay);
36
+ return fiveHour || sevenDay ? { fiveHour, sevenDay } : null;
37
+ }
@@ -0,0 +1,31 @@
1
+ // How far one wheel/trackpad gesture scrolls the terminal, as a multiplier on the xterm
2
+ // default (1 = xterm's own speed). Shared because both sides decide from the same numbers:
3
+ // the Settings stepper bounds its own buttons, and the client re-validates what it reads back
4
+ // out of localStorage.
5
+ //
6
+ // It drives BOTH scrolling paths, which are different code: xterm's `scrollSensitivity` for
7
+ // the normal buffer's scrollback, and the wheel-report accumulator in ./mouseReports for a
8
+ // full-screen TUI in the alternate buffer (Claude Code). One setting, because to the person
9
+ // scrolling they are the same gesture.
10
+ export const TERMINAL_SCROLL_SPEED_DEFAULT = 1;
11
+ // A macOS trackpad emits a wheel event every few pixels, so the useful range sits below 1 —
12
+ // 0.25 is "a swipe moves a paragraph", still fast enough to cross a long transcript. Above 3
13
+ // a single flick outruns the renderer and lands nowhere readable, which is the complaint this
14
+ // setting exists to answer.
15
+ export const TERMINAL_SCROLL_SPEED_MIN = 0.25;
16
+ export const TERMINAL_SCROLL_SPEED_MAX = 3;
17
+ export const TERMINAL_SCROLL_SPEED_STEP = 0.25;
18
+
19
+ // Clamp rather than reject an out-of-range number, for the same reason the font size does:
20
+ // honouring the direction the user asked for reads as working, where snapping back to the
21
+ // default reads as ignored. Non-numeric input means "nothing set here" and stays null so the
22
+ // caller can fall back. Values are snapped to the step so the label stays readable (0.75, not
23
+ // 0.7499999) and so the stepper can't drift off its own grid.
24
+ export function normalizeScrollSpeed(input: unknown): number | null {
25
+ if (typeof input !== "number" || !Number.isFinite(input)) return null;
26
+ const clamped = Math.min(TERMINAL_SCROLL_SPEED_MAX, Math.max(TERMINAL_SCROLL_SPEED_MIN, input));
27
+ const snapped = Math.round(clamped / TERMINAL_SCROLL_SPEED_STEP) * TERMINAL_SCROLL_SPEED_STEP;
28
+ // The multiply above reintroduces binary-float noise (0.30000000000000004); two decimals is
29
+ // more than the step needs and leaves the value safe to compare and to render.
30
+ return Math.round(snapped * 100) / 100;
31
+ }
@@ -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-BILJo_fK.js";import"./chunk-WYO6CB5R-BlzOfotS-04XZ1Jfd.js";import"./chunk-VAUOI2AC-CS9QJ4yz-Dd_VpRsu.js";import{n as i,r as a,t as o}from"./chunk-MOJQB5TN-CMZRaeqt-Dy6S_sS_.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-BAMiAMZz.js";import"./chunk-WYO6CB5R-BlzOfotS-CoeWhGPT.js";import"./chunk-VAUOI2AC-CS9QJ4yz-DYmUgyge.js";import{n as i,r as a,t as o}from"./chunk-MOJQB5TN-CMZRaeqt-B6_lajzj.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-BAMiAMZz.js";export{e as createArchitectureServices};