mulmoterminal 2.4.0 → 2.5.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 (141) hide show
  1. package/README.md +31 -4
  2. package/common/codeBlocks.ts +88 -0
  3. package/common/prPhase.ts +65 -0
  4. package/common/rateLimits.ts +37 -0
  5. package/common/terminalScrollSpeed.ts +31 -0
  6. package/common/workComment.ts +44 -0
  7. package/dist/assets/{abnfDiagram-VRR7QNED-RjiYivmv-CqId6L6N.js → abnfDiagram-VRR7QNED-RjiYivmv-BHMJcX-D.js} +1 -1
  8. package/dist/assets/architecture-TIHT7OUA-0NsD2xW1-BQjHKl14.js +1 -0
  9. package/dist/assets/{architectureDiagram-ZJ3FMSHR-3nWA91tG-CWhhLcfM.js → architectureDiagram-ZJ3FMSHR-3nWA91tG-izYrzJUx.js} +1 -1
  10. package/dist/assets/{blockDiagram-677ZJIJ3-BPuAJQRW-89yfYblY.js → blockDiagram-677ZJIJ3-BPuAJQRW-BD7LRmIw.js} +1 -1
  11. package/dist/assets/{c4Diagram-LMCZKHZV-C0LAqQso-Cq70UauP.js → c4Diagram-LMCZKHZV-C0LAqQso-daMFkQYF.js} +1 -1
  12. package/dist/assets/channel-7wUqSdoX-i1Oxds2I.js +1 -0
  13. package/dist/assets/{chunk-32BRIVSS-BRrYpgtb-Bj5FdJ3A.js → chunk-32BRIVSS-BRrYpgtb-Lp6IvDwQ.js} +1 -1
  14. package/dist/assets/{chunk-52WLFC77-BJ-ss3Xr-Cf6wft_5.js → chunk-52WLFC77-BJ-ss3Xr-CIA8l6cL.js} +1 -1
  15. package/dist/assets/{chunk-C7G6YPKG-BZEucKEL-CLA5COUu.js → chunk-C7G6YPKG-BZEucKEL-DBfTQdYh.js} +1 -1
  16. package/dist/assets/{chunk-EX3LRPZG-DLS6FBN1-ChsLH6_y.js → chunk-EX3LRPZG-DLS6FBN1-CmqVBBJi.js} +1 -1
  17. package/dist/assets/{chunk-FWX5IMBZ-DHLSFw1H-BIvnIbPU.js → chunk-FWX5IMBZ-DHLSFw1H-Br6MRw8l.js} +2 -2
  18. package/dist/assets/{chunk-HOUHSVGY-Bhlt8hXJ-Ck-Dxtw-.js → chunk-HOUHSVGY-Bhlt8hXJ-BDsryiai.js} +1 -1
  19. package/dist/assets/{chunk-ICXQ74PX-DwgHBX_g-CSlWVcWs.js → chunk-ICXQ74PX-DwgHBX_g-Dtq6hkL5.js} +1 -1
  20. package/dist/assets/{chunk-MOJQB5TN-CMZRaeqt-CYfpp2V7.js → chunk-MOJQB5TN-CMZRaeqt-xCxis4AD.js} +1 -1
  21. package/dist/assets/{chunk-OGEWGWER-8Qy4a8b5-DPIlRee4.js → chunk-OGEWGWER-8Qy4a8b5-CEKkrNHl.js} +1 -1
  22. package/dist/assets/{chunk-PUDLZKDR-DcrWQRYh-J86vFVAN.js → chunk-PUDLZKDR-DcrWQRYh-CNZIIdNp.js} +1 -1
  23. package/dist/assets/{chunk-Q4XR5HBZ-ZXVGkG8Z-D-40tbjk.js → chunk-Q4XR5HBZ-ZXVGkG8Z-BfnUuIf2.js} +1 -1
  24. package/dist/assets/{chunk-V7JOEXUC-DmGdheTX-7gJmS-F9.js → chunk-V7JOEXUC-DmGdheTX-Xhz9vrZQ.js} +1 -1
  25. package/dist/assets/{chunk-VAUOI2AC-CS9QJ4yz-DFjCsQc6.js → chunk-VAUOI2AC-CS9QJ4yz-CDhJXpTw.js} +1 -1
  26. package/dist/assets/{chunk-VR4S4FIN-C6a91eNY-Ba059rhB.js → chunk-VR4S4FIN-C6a91eNY-C6K2Be2R.js} +1 -1
  27. package/dist/assets/{chunk-WYO6CB5R-BlzOfotS-CTDodsnw.js → chunk-WYO6CB5R-BlzOfotS-C6J_J8TJ.js} +1 -1
  28. package/dist/assets/{chunk-ZGVPDNZ5-BYwxNFTK-CMF9FJ7K.js → chunk-ZGVPDNZ5-BYwxNFTK-DM76gWAP.js} +1 -1
  29. package/dist/assets/classDiagram-OUVF2IWQ-BYlRi6YQ-w-oQvzN_.js +1 -0
  30. package/dist/assets/classDiagram-v2-EOCWNBFH-BCHaYHxj-w-oQvzN_.js +1 -0
  31. package/dist/assets/cynefin-VYW2F7L2-CGYr2F6h-BbR1aAzz.js +1 -0
  32. package/dist/assets/{cynefinDiagram-TSTJHNR4-DHA9iPo--BUwfErNr.js → cynefinDiagram-TSTJHNR4-DHA9iPo--CygDupfu.js} +1 -1
  33. package/dist/assets/{dagre-VKFMJZFB--oJKqXBZ-FEoLipSZ.js → dagre-VKFMJZFB--oJKqXBZ-DKaBHoMD.js} +1 -1
  34. package/dist/assets/{diagram-FQU43EPY-CcJCB9bG-DwH2nER4.js → diagram-FQU43EPY-CcJCB9bG-BCdZM1XW.js} +1 -1
  35. package/dist/assets/{diagram-G47NLZAW-C_o-WGG1-BuSqgofP.js → diagram-G47NLZAW-C_o-WGG1-E50OCsch.js} +1 -1
  36. package/dist/assets/{diagram-NH7WQ7WH-CXJCYvY--CoMsubYQ.js → diagram-NH7WQ7WH-CXJCYvY--CmG9YdUu.js} +1 -1
  37. package/dist/assets/{diagram-OA4YK3LP-BMzeJ87A-Ds_49Q4I.js → diagram-OA4YK3LP-BMzeJ87A-CxPVNELp.js} +1 -1
  38. package/dist/assets/{diagram-WEI45ONY-D_93NKqo-DeDTEgI-.js → diagram-WEI45ONY-D_93NKqo-lP3U0GxX.js} +1 -1
  39. package/dist/assets/{ebnfDiagram-CCIWWBDH-CVai1Ii9-B84fKP1h.js → ebnfDiagram-CCIWWBDH-CVai1Ii9-DEA-ZvYv.js} +1 -1
  40. package/dist/assets/{erDiagram-Q63AITRT-CiABnA0s-QGRolPYH.js → erDiagram-Q63AITRT-CiABnA0s-uFB2JxeG.js} +1 -1
  41. package/dist/assets/eventmodeling-45OFAUF4-CWmqhm3a-DXHpv7ri.js +1 -0
  42. package/dist/assets/flowDiagram-23GEKE2U-DKSB5AiY-BAXt5ZcK.js +1 -0
  43. package/dist/assets/{ganttDiagram-NO4QXBWP-DQZvdFo1-BBXcle76.js → ganttDiagram-NO4QXBWP-DQZvdFo1-CnuwHsCh.js} +1 -1
  44. package/dist/assets/gitGraph-TEB2WS4Q-BR9qPwdN-C5VWobaj.js +1 -0
  45. package/dist/assets/{gitGraphDiagram-IHSO6WYX-C2ovBouh-BxGBV6Hj.js → gitGraphDiagram-IHSO6WYX-C2ovBouh-DMwS2Tlh.js} +1 -1
  46. package/dist/assets/index-BL1ivfaK.css +1 -0
  47. package/dist/assets/{index-_XRnaxAj.js → index-CIUVcLpS.js} +201 -199
  48. package/dist/assets/info-DKCQHKI2-Dplx5kMp-dN3WlTdS.js +1 -0
  49. package/dist/assets/{infoDiagram-FWYZ7A6U-7UnoB5AP-DCqG4iB9.js → infoDiagram-FWYZ7A6U-7UnoB5AP-D1UXK3im.js} +1 -1
  50. package/dist/assets/{ishikawaDiagram-FXEZZL3T-ByUDM_N2-Dkzr63Uo.js → ishikawaDiagram-FXEZZL3T-ByUDM_N2-eU53AoG2.js} +1 -1
  51. package/dist/assets/{journeyDiagram-5HDEW3XC-c5xIah9o-3K64pBTH.js → journeyDiagram-5HDEW3XC-c5xIah9o-BQNy69_H.js} +1 -1
  52. package/dist/assets/{kanban-definition-HUTT4EX6-Cnt6loYD-B0U7Mkg9.js → kanban-definition-HUTT4EX6-Cnt6loYD-C3Ya6-uo.js} +1 -1
  53. package/dist/assets/{lib-rFkGOb1O.js → lib-XZe86Xbo.js} +1 -1
  54. package/dist/assets/{line-D7ziSjKi-D25izB30.js → line-D7ziSjKi-DhimY3vn.js} +1 -1
  55. package/dist/assets/{marp-79PhoGbq.js → marp-DE0BYdnE.js} +1 -1
  56. package/dist/assets/{mermaid-parser.core-DxEa8E3F-BzSqp82H.js → mermaid-parser.core-DxEa8E3F-C43vNJPp.js} +2 -2
  57. package/dist/assets/{mermaid.core-V0OYwIz3-0p62b-zk.js → mermaid.core-V0OYwIz3-v8Oxyjlo.js} +3 -3
  58. package/dist/assets/{mindmap-definition-LN4V7U3C-CexN3O6L-BzFX06oy.js → mindmap-definition-LN4V7U3C-CexN3O6L-D3QMGDaH.js} +1 -1
  59. package/dist/assets/packet-7NZHBO7P-CQI3flND-0aWqmW17.js +1 -0
  60. package/dist/assets/{pegDiagram-2B236MQR-BiM4G0cW-BMRJsJZN.js → pegDiagram-2B236MQR-BiM4G0cW-Bt6FgrWE.js} +1 -1
  61. package/dist/assets/pie-RZYD4A2V-CnmKE2Wa-BuudBowH.js +1 -0
  62. package/dist/assets/{pieDiagram-ENE6RG2P-B_fBBS-2-Bl2zNUvh.js → pieDiagram-ENE6RG2P-B_fBBS-2-CEmdDz9t.js} +1 -1
  63. package/dist/assets/{quadrantDiagram-ABIIQ3AL-BigGVxCR-BzQ392_S.js → quadrantDiagram-ABIIQ3AL-BigGVxCR-4kYUj0VK.js} +1 -1
  64. package/dist/assets/radar-I7S5WNFK-us6x-Z9R-CoI_TbnK.js +1 -0
  65. package/dist/assets/railroad-3IZDKUUU-BKdz6pdN-D-Aq_3zs.js +1 -0
  66. package/dist/assets/railroad-abnf-AHOZXSZD-m_CSSIfd-CYcDcmr_.js +1 -0
  67. package/dist/assets/railroad-ebnf-EBAXGLYW-Nu3nAG7C-D-PAMLpZ.js +1 -0
  68. package/dist/assets/railroad-peg-LSFZ7HO6--Y3mD4R_-CAkKdvRU.js +1 -0
  69. package/dist/assets/{railroadDiagram-RFXS5EU6--lNIlG64-D-Yn0Dpg.js → railroadDiagram-RFXS5EU6--lNIlG64-BS12t8Yr.js} +1 -1
  70. package/dist/assets/{requirementDiagram-TGXJPOKE-D-B0Y5Wu-DIYnBCWZ.js → requirementDiagram-TGXJPOKE-D-B0Y5Wu-B1_SLwFo.js} +1 -1
  71. package/dist/assets/{sankeyDiagram-HTMAVEWB-DlutaXDv-Dsr5VJzO.js → sankeyDiagram-HTMAVEWB-DlutaXDv-CFF-xYsd.js} +1 -1
  72. package/dist/assets/{sequenceDiagram-DBY2YBRQ-BOLgtggH-mR1rIlJQ.js → sequenceDiagram-DBY2YBRQ-BOLgtggH-DJXV6qUI.js} +1 -1
  73. package/dist/assets/{stateDiagram-2N3HPSRC-jf_dXUEU-Cq2A7Udg.js → stateDiagram-2N3HPSRC-jf_dXUEU-BD6GsGtH.js} +1 -1
  74. package/dist/assets/stateDiagram-v2-6OUMAXLB-CuzgCL2v-CGUWQotU.js +1 -0
  75. package/dist/assets/{swimlanes-5IMT3BWC-CYjtALQH-CtgZ4USf.js → swimlanes-5IMT3BWC-CYjtALQH-CHqwZOyR.js} +1 -1
  76. package/dist/assets/swimlanesDiagram-G3AALYLV-3bmSBtKs-YHo-FlZ7.js +8 -0
  77. package/dist/assets/{timeline-definition-FHXFAJF6-DYJ4oUm8-CpuMCUFK.js → timeline-definition-FHXFAJF6-DYJ4oUm8-Dsee2TjT.js} +1 -1
  78. package/dist/assets/treeView-QDETBFTQ-D27HWG6Q-XRfUQQF3.js +1 -0
  79. package/dist/assets/treemap-6X3UGDF4-CmGIU3H5-DV-AlqRD.js +1 -0
  80. package/dist/assets/{vennDiagram-L72KCM5P-CdKAHoek-B-FSFzf3.js → vennDiagram-L72KCM5P-CdKAHoek-d0nAypXX.js} +1 -1
  81. package/dist/assets/wardley-OPB4EBWU-CbHWsb2k-Dvuabdt5.js +1 -0
  82. package/dist/assets/{wardleyDiagram-EHGQE667-BF6c4_CW-CB01oT0C.js → wardleyDiagram-EHGQE667-BF6c4_CW-BStK630L.js} +1 -1
  83. package/dist/assets/{xychartDiagram-FW5EYKEG-CGiKngj7-D1vr6bQ-.js → xychartDiagram-FW5EYKEG-CGiKngj7--qXz8KqF.js} +1 -1
  84. package/dist/index.html +2 -2
  85. package/package.json +4 -4
  86. package/server/agents/claude-args.ts +10 -1
  87. package/server/agents/codex-rate-limits.spec.ts +74 -0
  88. package/server/agents/codex-rate-limits.ts +94 -0
  89. package/server/agents/codex-rollout.ts +79 -0
  90. package/server/agents/pr-clone-prompt.ts +30 -0
  91. package/server/agents/rate-limit-persist.ts +59 -0
  92. package/server/agents/rate-limit-probe.spec.ts +66 -0
  93. package/server/agents/rate-limit-probe.ts +114 -0
  94. package/server/agents/rate-limit-routes.ts +66 -0
  95. package/server/agents/rate-limit-store.spec.ts +91 -0
  96. package/server/agents/rate-limit-store.ts +73 -0
  97. package/server/agents/statusline.spec.ts +66 -0
  98. package/server/agents/statusline.ts +48 -0
  99. package/server/cli-init.ts +2 -2
  100. package/server/config/app-config.ts +60 -4
  101. package/server/config/config-routes.ts +10 -1
  102. package/server/config/config-schema.ts +1 -1
  103. package/server/config/header-context.ts +9 -8
  104. package/server/files/pick-file.ts +5 -3
  105. package/server/files/win-folder-dialog.ts +112 -0
  106. package/server/git/gitRemote.ts +14 -31
  107. package/server/git/pr-footer.ts +17 -4
  108. package/server/git/prPhase.ts +40 -12
  109. package/server/git/remote-ref.ts +50 -0
  110. package/server/git/repo-root-sync.ts +55 -0
  111. package/server/git/work-comment.ts +126 -0
  112. package/server/git/worktree-pr.ts +1 -0
  113. package/server/index.ts +32 -0
  114. package/server/infra/tmux.ts +40 -0
  115. package/server/routes/app-routes.ts +5 -0
  116. package/server/routes/dir-routes.ts +54 -3
  117. package/server/session/last-turn.ts +4 -0
  118. package/server/session/session-reads.ts +17 -1
  119. package/server/session/spawn-claude.ts +39 -16
  120. package/dist/assets/architecture-TIHT7OUA-0NsD2xW1-CfmmIOkB.js +0 -1
  121. package/dist/assets/channel-7wUqSdoX-Bc9EmVWS.js +0 -1
  122. package/dist/assets/classDiagram-OUVF2IWQ-BYlRi6YQ-Dk_90vZD.js +0 -1
  123. package/dist/assets/classDiagram-v2-EOCWNBFH-BCHaYHxj-Dk_90vZD.js +0 -1
  124. package/dist/assets/cynefin-VYW2F7L2-CGYr2F6h-B1MlaJhj.js +0 -1
  125. package/dist/assets/eventmodeling-45OFAUF4-CWmqhm3a-C0eB3veQ.js +0 -1
  126. package/dist/assets/flowDiagram-23GEKE2U-DKSB5AiY-BAfA7eXR.js +0 -1
  127. package/dist/assets/gitGraph-TEB2WS4Q-BR9qPwdN-CotjI6SA.js +0 -1
  128. package/dist/assets/index-B7LbAi10.css +0 -1
  129. package/dist/assets/info-DKCQHKI2-Dplx5kMp-DKHWzOor.js +0 -1
  130. package/dist/assets/packet-7NZHBO7P-CQI3flND-DyAt_QAV.js +0 -1
  131. package/dist/assets/pie-RZYD4A2V-CnmKE2Wa-nlXXCSHg.js +0 -1
  132. package/dist/assets/radar-I7S5WNFK-us6x-Z9R-Dnn5SKpT.js +0 -1
  133. package/dist/assets/railroad-3IZDKUUU-BKdz6pdN-DjF0R5Zr.js +0 -1
  134. package/dist/assets/railroad-abnf-AHOZXSZD-m_CSSIfd-GH7jGfhq.js +0 -1
  135. package/dist/assets/railroad-ebnf-EBAXGLYW-Nu3nAG7C-CH8TSRFp.js +0 -1
  136. package/dist/assets/railroad-peg-LSFZ7HO6--Y3mD4R_-QfsXYBPl.js +0 -1
  137. package/dist/assets/stateDiagram-v2-6OUMAXLB-CuzgCL2v-D7ipapeO.js +0 -1
  138. package/dist/assets/swimlanesDiagram-G3AALYLV-3bmSBtKs-D1yGYNNh.js +0 -8
  139. package/dist/assets/treeView-QDETBFTQ-D27HWG6Q-nxCnsDvO.js +0 -1
  140. package/dist/assets/treemap-6X3UGDF4-CmGIU3H5-7UfnOcZW.js +0 -1
  141. package/dist/assets/wardley-OPB4EBWU-CbHWsb2k-DHZIQf1x.js +0 -1
package/README.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  **Run a whole team of coding agents from your browser — and actually keep up with them.**
4
4
 
5
+ A **browser terminal** for **parallel AI coding agents**: several **Claude Code** and **Codex**
6
+ sessions side by side, each in its own cell, with the one that needs you marked in colour. Vibe
7
+ coding with a single agent needs nothing but a shell — this is for when you run several and lose
8
+ track of which is waiting. Sessions survive a reload (tmux), work isolates in **git worktrees**,
9
+ and a **phone push** reaches you when a turn finishes.
10
+
5
11
  ### 📖 Documentation — **[receptron.github.io/mulmoterminal](https://receptron.github.io/mulmoterminal/)**
6
12
 
7
13
  - **User guide:** [English](https://receptron.github.io/mulmoterminal/guide/en/) — the grid
@@ -21,6 +27,13 @@ ping to your phone when a task finishes. One `npx` command, no Electron, no conf
21
27
  npx mulmoterminal # starts on http://localhost:34567 and opens your browser
22
28
  ```
23
29
 
30
+ > **Something looks wrong?** Type `/mulmoterminal-bug-report` in any MulmoTerminal session. The
31
+ > bundled skill hears the symptom out, checks your **real** config, schema and version to see
32
+ > whether the behaviour is configuration or by design, searches the existing issues — and only
33
+ > helps you file one if none of that explains it, with the environment collected and secrets
34
+ > masked. Getting you unstuck is the goal; an issue is what is left when the first three steps
35
+ > fail.
36
+
24
37
  ![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
38
 
26
39
  *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.*
@@ -455,7 +468,7 @@ The Settings modal (⚙) persists per-user UI choices to `~/.mulmoterminal/confi
455
468
 
456
469
  ![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)
457
470
 
458
- *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.*
471
+ *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.*
459
472
 
460
473
  | Field | Meaning |
461
474
  | ------------ | ------- |
@@ -468,16 +481,24 @@ The Settings modal (⚙) persists per-user UI choices to `~/.mulmoterminal/confi
468
481
  | `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. |
469
482
  | `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. |
470
483
  | `buttons` | Header action buttons — see [Header buttons](#header-buttons). Omit to keep the defaults; set to replace them. |
471
- | `chips` | Header info chips. Omit to keep the default set; `[]` hides all built-ins. |
484
+ | `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). |
472
485
  | `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. |
473
486
  | `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. |
474
487
  | `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). |
475
488
  | `worklogIntervalHours` | Worklog cadence in hours (default `6`, clamped to `1`–`168`). |
476
489
  | `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). |
477
490
  | `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). |
478
- | `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). |
491
+ | `issueWorkComments` | Let a cell **comment on the issue it is working on**: once when it starts, and again when its PR merges (closing the issue if GitHub has not already). The comment names the working **directory** it happened in the folder name only, never the path so a reader can tell which clone. **Off by default**; it writes to GitHub, often on somebody else's issue. Needs `gh` logged in. See the [Configuration guide](https://receptron.github.io/mulmoterminal/guide/en/config.html#issue-work-comments). |
492
+ | `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. |
479
493
  | `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). |
480
494
 
495
+ Every MulmoTerminal on the machine shares this one file, so an older build could save over a key a
496
+ newer one wrote. It doesn't: a **top-level key this version doesn't recognise is written back
497
+ untouched**, which is what makes running two versions side by side — or downgrading for a while —
498
+ safe. A mistyped key survives on the same rule, which is deliberate: a line you can still see is
499
+ easier to debug than one that silently vanished. See the
500
+ [Configuration guide](https://receptron.github.io/mulmoterminal/guide/en/config.html#unknown-keys).
501
+
481
502
  #### Header buttons
482
503
 
483
504
  Each terminal header shows configurable **action buttons**. Omitting `buttons` (globally or per-dir)
@@ -862,7 +883,9 @@ Each grid cell's header shows two badges for its session, refreshed when a turn
862
883
  current-gen Opus / Sonnet / Fable / Mythos, **200k** otherwise). A session running on a
863
884
  [provider model](#agents-claude--codex) shows that model's name and its published window
864
885
  (`Kimi K2.7 Code · ctx 12%`); a model in neither list keeps the label and hides the %,
865
- since the window is never guessed.
886
+ since the window is never guessed. A reading **past 100%** shows `ctx ?` instead of the
887
+ number: the window is a hard cap, so an impossible percentage means the built-in window
888
+ table is out of date for that model rather than that the session is over-full.
866
889
  - **Token badge** — `⇡<in> ⇣<out>`: cumulative input (fresh + cache-read + cache-creation)
867
890
  and output tokens for the session, k/M-formatted, with a full breakdown in the tooltip.
868
891
 
@@ -958,6 +981,10 @@ Favorited collections get their own toolbar buttons.
958
981
  **Option** is treated as Meta so Claude's Alt-key bindings work. If your Claude Code is
959
982
  rebound so Enter and Shift+Enter behave backwards, flip them with
960
983
  [`terminalSubmit`](https://receptron.github.io/mulmoterminal/guide/en/config.html#terminal-submit).
984
+ - **Scroll speed** — one wheel notch or trackpad swipe moves the terminal the same distance
985
+ whether you're reading a shell's scrollback or a full-screen app like Claude Code. If a
986
+ two-finger scroll on a Mac trackpad flies past what you were reading, turn **terminal scroll
987
+ speed** down in Settings (0.25×–3×, per browser — it's a property of the pointing device).
961
988
  - **No accidental page zoom** — `Ctrl`+wheel and a trackpad pinch would rescale the whole
962
989
  page and drag the layout and the terminal's fit along with it, so both are ignored.
963
990
  Keyboard zoom (`Cmd`/`Ctrl` `+` / `-`) still works when you mean it, and a phone's finger
@@ -0,0 +1,88 @@
1
+ // Fenced code blocks in a markdown string (#865).
2
+ //
3
+ // The point is to hand another app EXACTLY what the agent wrote. Selecting the same text off
4
+ // the terminal picks up the leading padding xterm draws with it, which is what breaks a paste
5
+ // into Discord or Slack — so the source here is the agent's own transcript markdown, never the
6
+ // screen.
7
+ //
8
+ // Deliberately not a markdown parser. It answers one question — where do the fences sit — and
9
+ // the cases below are the ones a real reply produces:
10
+ //
11
+ // - ``` and ~~~ are both fences, and a run of 3+ of the same character opens one
12
+ // - a fence closes only on the SAME character, at least as long as the opener. That is what
13
+ // lets a shell snippet contain ``` inside a ~~~~ block without cutting it short
14
+ // - an info string (```ts, ```bash) names the language; the rest of that line is dropped
15
+ // - a fence may be indented up to 3 spaces, per CommonMark
16
+ // - an UNCLOSED fence still yields a block, running to the end of the text. An agent whose
17
+ // output was cut off mid-block is exactly when someone reaches for this, and refusing to
18
+ // return anything would read as "there is no code here"
19
+
20
+ export interface FencedBlock {
21
+ /** The info string's first word, lower-cased — `ts`, `bash` — or null when absent. */
22
+ lang: string | null;
23
+ /** The block's contents, verbatim apart from the trailing newline. */
24
+ body: string;
25
+ }
26
+
27
+ interface Fence {
28
+ /** The run itself, so its LENGTH can gate what is allowed to close it. */
29
+ run: string;
30
+ /** Everything after the run — the info string on an opener, empty on a closer. */
31
+ info: string;
32
+ }
33
+
34
+ // Scanned by hand rather than with a pattern: the obvious regex (` {0,3}(`{3,}|~{3,})(.*)`)
35
+ // nests two quantifiers under an alternation, which the linter flags for super-linear
36
+ // backtracking. This walk is plainly one pass, and reads closer to the rule it implements.
37
+ // Up to three leading spaces are allowed (CommonMark); a fourth makes it an indented code
38
+ // block, not a fence.
39
+ function fenceOf(line: string): Fence | null {
40
+ let i = 0;
41
+ while (i < 3 && line[i] === " ") i++;
42
+ const char = line[i];
43
+ if (char !== "`" && char !== "~") return null;
44
+ let length = 0;
45
+ while (line[i + length] === char) length++;
46
+ if (length < 3) return null;
47
+ return { run: line.slice(i, i + length), info: line.slice(i + length) };
48
+ }
49
+
50
+ // A closing fence is the same character, at least as long, with nothing after it. Compared
51
+ // after `.trim()` because trailing whitespace is invisible and would otherwise leave the block
52
+ // silently unclosed.
53
+ const closes = (line: string, opener: string): boolean => {
54
+ const fence = fenceOf(line);
55
+ return !!fence && fence.run[0] === opener[0] && fence.run.length >= opener.length && fence.info.trim() === "";
56
+ };
57
+
58
+ export function fencedBlocks(markdown: string): FencedBlock[] {
59
+ const lines = markdown.split("\n");
60
+ const blocks: FencedBlock[] = [];
61
+ let i = 0;
62
+ while (i < lines.length) {
63
+ const open = fenceOf(lines[i]);
64
+ if (!open) {
65
+ i++;
66
+ continue;
67
+ }
68
+ const fence = open.run;
69
+ const info = open.info.trim();
70
+ const body: string[] = [];
71
+ i++;
72
+ while (i < lines.length && !closes(lines[i], fence)) {
73
+ body.push(lines[i]);
74
+ i++;
75
+ }
76
+ i++; // step over the closing fence (or past the end, for an unclosed block)
77
+ blocks.push({ lang: info ? (info.split(/\s+/)[0].toLowerCase() ?? null) : null, body: body.join("\n") });
78
+ }
79
+ return blocks;
80
+ }
81
+
82
+ /** The block a reader means by "the code you just gave me": the last one. Null when the reply
83
+ * has no fenced block, or when the only ones are empty — an empty clipboard would look
84
+ * exactly like a copy that silently failed. */
85
+ export function lastFencedBlock(markdown: string): FencedBlock | null {
86
+ const withBody = fencedBlocks(markdown).filter((b) => b.body.trim() !== "");
87
+ return withBody.length ? withBody[withBody.length - 1] : null;
88
+ }
@@ -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
+ }
@@ -0,0 +1,44 @@
1
+ // The comments MulmoTerminal leaves on an issue: "a cell is on this" and "it was merged" (#979).
2
+ // Pure, and shared, because the marker is the whole idempotency story — the server writes it and
3
+ // the server reads it back, and a mismatch between the two would post the same comment on every
4
+ // poll of every tab.
5
+
6
+ export type WorkCommentKind = "start" | "merged";
7
+
8
+ // An HTML comment, so it is invisible in the rendered issue but survives a round-trip through the
9
+ // GitHub API. Keyed by kind AND directory: the same issue worked on from a second clone is a
10
+ // second, honest line in the thread, not a duplicate to suppress.
11
+ //
12
+ // The directory is percent-encoded, not interpolated raw: a folder may legally be called
13
+ // `foo-->bar`, and that string ends the HTML comment early — the rest spills into the rendered
14
+ // issue as text (Codex review). Encoding also keeps a newline or a backtick in a path from
15
+ // reshaping the comment. Ordinary names encode to themselves, so markers already posted still
16
+ // match.
17
+ export function workCommentMarker(kind: WorkCommentKind, dir: string): string {
18
+ return `<!-- mulmoterminal:work:${kind} dir=${encodeURIComponent(dir)} -->`;
19
+ }
20
+
21
+ // The directory a comment names. The BASENAME only: the point is "which of my clones", and a full
22
+ // path on a public issue leaks the machine's layout (and, on a work machine, project names).
23
+ export function workCommentDirLabel(cwd: string): string {
24
+ // Split-and-take-last rather than trimming a trailing separator with a regex: an anchored
25
+ // `[/\\]+$` backtracks super-linearly, and a path is user input.
26
+ const parts = cwd.split(/[/\\]/).filter((part) => part !== "");
27
+ return parts[parts.length - 1] ?? cwd;
28
+ }
29
+
30
+ // `pr` is the number the merge came in as, when there is one — a reader of the issue should not
31
+ // have to go looking for which PR closed it.
32
+ export function workCommentBody(kind: WorkCommentKind, dir: string, pr: number | null): string {
33
+ const marker = workCommentMarker(kind, dir);
34
+ if (kind === "start") return `Working on this in \`${dir}\`.\n\n${marker}`;
35
+ const where = pr === null ? "Merged." : `Merged in #${pr}.`;
36
+ return `${where} Work done in \`${dir}\`.\n\n${marker}`;
37
+ }
38
+
39
+ // Has this exact comment already been left? Matching on the marker rather than the prose means an
40
+ // edited comment, or a change to the wording above, still counts as "already said".
41
+ export function alreadyCommented(bodies: readonly string[], kind: WorkCommentKind, dir: string): boolean {
42
+ const marker = workCommentMarker(kind, dir);
43
+ return bodies.some((body) => typeof body === "string" && body.includes(marker));
44
+ }
@@ -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-BzSqp82H.js";import"./chunk-WYO6CB5R-BlzOfotS-CTDodsnw.js";import"./chunk-VAUOI2AC-CS9QJ4yz-DFjCsQc6.js";import{n as i,r as a,t as o}from"./chunk-MOJQB5TN-CMZRaeqt-CYfpp2V7.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-C43vNJPp.js";import"./chunk-WYO6CB5R-BlzOfotS-C6J_J8TJ.js";import"./chunk-VAUOI2AC-CS9QJ4yz-CDhJXpTw.js";import{n as i,r as a,t as o}from"./chunk-MOJQB5TN-CMZRaeqt-xCxis4AD.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-C43vNJPp.js";export{e as createArchitectureServices};