mulmoterminal 4.3.0 → 4.4.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 (142) hide show
  1. package/README.md +45 -18
  2. package/common/workComment.ts +141 -14
  3. package/common/workCommentFailure.ts +16 -0
  4. package/dist/assets/{abnfDiagram-VRR7QNED-RjiYivmv-Bdog-CiH.js → abnfDiagram-VRR7QNED-RjiYivmv-C43gBAG2.js} +1 -1
  5. package/dist/assets/architecture-TIHT7OUA-0NsD2xW1-DRANrMDe.js +1 -0
  6. package/dist/assets/{architectureDiagram-ZJ3FMSHR-3nWA91tG-DY8jZRJO.js → architectureDiagram-ZJ3FMSHR-3nWA91tG-BONcd9-U.js} +1 -1
  7. package/dist/assets/{blockDiagram-677ZJIJ3-BPuAJQRW-CpOQ2oxU.js → blockDiagram-677ZJIJ3-BPuAJQRW-CM2STruI.js} +1 -1
  8. package/dist/assets/{c4Diagram-LMCZKHZV-C0LAqQso-CWAcQCwl.js → c4Diagram-LMCZKHZV-C0LAqQso-B5vNwdXA.js} +1 -1
  9. package/dist/assets/channel-7wUqSdoX-qLt0VyIe.js +1 -0
  10. package/dist/assets/{chunk-32BRIVSS-BRrYpgtb-BzcHZdCO.js → chunk-32BRIVSS-BRrYpgtb-CoxhWbB9.js} +1 -1
  11. package/dist/assets/{chunk-52WLFC77-BJ-ss3Xr-Qq7iypNS.js → chunk-52WLFC77-BJ-ss3Xr-CId6SWzh.js} +1 -1
  12. package/dist/assets/{chunk-C7G6YPKG-BZEucKEL-DU94jTaQ.js → chunk-C7G6YPKG-BZEucKEL-P5M5Yvpn.js} +1 -1
  13. package/dist/assets/{chunk-EX3LRPZG-DLS6FBN1-B8t9ONXm.js → chunk-EX3LRPZG-DLS6FBN1-DyLWbVhu.js} +1 -1
  14. package/dist/assets/{chunk-FWX5IMBZ-DHLSFw1H-72qiJJFi.js → chunk-FWX5IMBZ-DHLSFw1H-DWcCl8Yk.js} +2 -2
  15. package/dist/assets/{chunk-HOUHSVGY-Bhlt8hXJ-Dh_S4m7L.js → chunk-HOUHSVGY-Bhlt8hXJ-vKYnv8Bj.js} +1 -1
  16. package/dist/assets/{chunk-ICXQ74PX-DwgHBX_g-i4M4RWjR.js → chunk-ICXQ74PX-DwgHBX_g-CS9xzHHA.js} +1 -1
  17. package/dist/assets/{chunk-MOJQB5TN-CMZRaeqt-ePkETRCM.js → chunk-MOJQB5TN-CMZRaeqt-gTPiZssc.js} +1 -1
  18. package/dist/assets/{chunk-OGEWGWER-8Qy4a8b5-BMPSaD1n.js → chunk-OGEWGWER-8Qy4a8b5-C9Osj4JZ.js} +1 -1
  19. package/dist/assets/{chunk-PUDLZKDR-DcrWQRYh-CUfUNRZU.js → chunk-PUDLZKDR-DcrWQRYh-CZwJPYWX.js} +1 -1
  20. package/dist/assets/{chunk-Q4XR5HBZ-ZXVGkG8Z-vRCdUFJt.js → chunk-Q4XR5HBZ-ZXVGkG8Z-Cco8LyAc.js} +1 -1
  21. package/dist/assets/{chunk-V7JOEXUC-DmGdheTX-BPzQ8JEc.js → chunk-V7JOEXUC-DmGdheTX-CL2oW-GL.js} +1 -1
  22. package/dist/assets/{chunk-VAUOI2AC-CS9QJ4yz-DDJgABqC.js → chunk-VAUOI2AC-CS9QJ4yz-DhyLweXm.js} +1 -1
  23. package/dist/assets/{chunk-VR4S4FIN-C6a91eNY-DZS-oJom.js → chunk-VR4S4FIN-C6a91eNY-kS-cvTnu.js} +1 -1
  24. package/dist/assets/{chunk-WYO6CB5R-BlzOfotS-DMj-dLn7.js → chunk-WYO6CB5R-BlzOfotS-B-BAzlIB.js} +1 -1
  25. package/dist/assets/{chunk-ZGVPDNZ5-BYwxNFTK-tmcZjNpX.js → chunk-ZGVPDNZ5-BYwxNFTK-B_5ITunz.js} +1 -1
  26. package/dist/assets/classDiagram-OUVF2IWQ-BYlRi6YQ-ClENF_Zt.js +1 -0
  27. package/dist/assets/classDiagram-v2-EOCWNBFH-BCHaYHxj-ClENF_Zt.js +1 -0
  28. package/dist/assets/cynefin-VYW2F7L2-CGYr2F6h-Xw6wbIlg.js +1 -0
  29. package/dist/assets/{cynefinDiagram-TSTJHNR4-DHA9iPo--BpiB89n2.js → cynefinDiagram-TSTJHNR4-DHA9iPo--CsvHy5QQ.js} +1 -1
  30. package/dist/assets/{dagre-VKFMJZFB--oJKqXBZ-Ddms1XzR.js → dagre-VKFMJZFB--oJKqXBZ-B_Vz2rxF.js} +1 -1
  31. package/dist/assets/{diagram-FQU43EPY-CcJCB9bG-BAHWVmAY.js → diagram-FQU43EPY-CcJCB9bG-3PKZ2Ka3.js} +1 -1
  32. package/dist/assets/{diagram-G47NLZAW-C_o-WGG1-DdaYFG9H.js → diagram-G47NLZAW-C_o-WGG1-BMfEdJEI.js} +1 -1
  33. package/dist/assets/{diagram-NH7WQ7WH-CXJCYvY--CHgaNPap.js → diagram-NH7WQ7WH-CXJCYvY--DMS7vR7_.js} +1 -1
  34. package/dist/assets/{diagram-OA4YK3LP-BMzeJ87A-C7QgoRtB.js → diagram-OA4YK3LP-BMzeJ87A-yVWZgC-A.js} +1 -1
  35. package/dist/assets/{diagram-WEI45ONY-D_93NKqo-DK7fOC2g.js → diagram-WEI45ONY-D_93NKqo-JY2oY6TV.js} +1 -1
  36. package/dist/assets/{dist-BvnEqyXN.js → dist-B5vu6eeE.js} +1 -1
  37. package/dist/assets/{dist-1yVb2v37.js → dist-By4s_hax.js} +1 -1
  38. package/dist/assets/{dist-N43w-1Uv.js → dist-CAS06ruL.js} +1 -1
  39. package/dist/assets/{dist-B8QpBddG.js → dist-cuEw-vsy.js} +1 -1
  40. package/dist/assets/{ebnfDiagram-CCIWWBDH-CVai1Ii9-CVGfPNmr.js → ebnfDiagram-CCIWWBDH-CVai1Ii9-CSODjPeQ.js} +1 -1
  41. package/dist/assets/{erDiagram-Q63AITRT-CiABnA0s-DY1yINPc.js → erDiagram-Q63AITRT-CiABnA0s-DMaAYLYB.js} +1 -1
  42. package/dist/assets/eventmodeling-45OFAUF4-CWmqhm3a-CDRF15wA.js +1 -0
  43. package/dist/assets/flowDiagram-23GEKE2U-DKSB5AiY-nMfF1thc.js +1 -0
  44. package/dist/assets/{ganttDiagram-NO4QXBWP-DQZvdFo1-BFu7XeTn.js → ganttDiagram-NO4QXBWP-DQZvdFo1-t6QKW7r_.js} +1 -1
  45. package/dist/assets/gitGraph-TEB2WS4Q-BR9qPwdN-BpVP-1CD.js +1 -0
  46. package/dist/assets/{gitGraphDiagram-IHSO6WYX-C2ovBouh-CVNiO1pR.js → gitGraphDiagram-IHSO6WYX-C2ovBouh-8CB_sdi8.js} +1 -1
  47. package/dist/assets/index-BfZe-OnL.js +614 -0
  48. package/dist/assets/index-slI4QJ8P.css +1 -0
  49. package/dist/assets/info-DKCQHKI2-Dplx5kMp-ugP0JbLq.js +1 -0
  50. package/dist/assets/{infoDiagram-FWYZ7A6U-7UnoB5AP-C93AGObv.js → infoDiagram-FWYZ7A6U-7UnoB5AP-DCV7LBpV.js} +1 -1
  51. package/dist/assets/{ishikawaDiagram-FXEZZL3T-ByUDM_N2-B2Dn2jAL.js → ishikawaDiagram-FXEZZL3T-ByUDM_N2-DbEPqoeF.js} +1 -1
  52. package/dist/assets/{journeyDiagram-5HDEW3XC-c5xIah9o-DQk5r-7H.js → journeyDiagram-5HDEW3XC-c5xIah9o-YWImF3-J.js} +1 -1
  53. package/dist/assets/{kanban-definition-HUTT4EX6-Cnt6loYD-1TZVG0gD.js → kanban-definition-HUTT4EX6-Cnt6loYD-Cu6iQUPe.js} +1 -1
  54. package/dist/assets/{lib-JfWkHMZA.js → lib-D3KO7fnC.js} +1 -1
  55. package/dist/assets/{line-D7ziSjKi-DhbbwYBs.js → line-D7ziSjKi-rKNlfotQ.js} +1 -1
  56. package/dist/assets/{marp-BfY_ww9b.js → marp-ssHueLtD.js} +1 -1
  57. package/dist/assets/{mermaid-parser.core-DxEa8E3F-D6oVwac4.js → mermaid-parser.core-DxEa8E3F-CTOK7EeF.js} +2 -2
  58. package/dist/assets/{mermaid.core-V0OYwIz3-B6uMFLWJ.js → mermaid.core-V0OYwIz3-ikGsVp05.js} +3 -3
  59. package/dist/assets/{mindmap-definition-LN4V7U3C-CexN3O6L-B1FJ1HVs.js → mindmap-definition-LN4V7U3C-CexN3O6L-BAQB7j75.js} +1 -1
  60. package/dist/assets/packet-7NZHBO7P-CQI3flND-ChXy_hCK.js +1 -0
  61. package/dist/assets/{pegDiagram-2B236MQR-BiM4G0cW--wr3I5uW.js → pegDiagram-2B236MQR-BiM4G0cW-HDlX2gZc.js} +1 -1
  62. package/dist/assets/pie-RZYD4A2V-CnmKE2Wa-DeFim35k.js +1 -0
  63. package/dist/assets/{pieDiagram-ENE6RG2P-B_fBBS-2-_2feGczD.js → pieDiagram-ENE6RG2P-B_fBBS-2-C7-OBdMS.js} +1 -1
  64. package/dist/assets/{quadrantDiagram-ABIIQ3AL-BigGVxCR-BrtQISoY.js → quadrantDiagram-ABIIQ3AL-BigGVxCR-C0w-ELAc.js} +1 -1
  65. package/dist/assets/radar-I7S5WNFK-us6x-Z9R-D-Y4D0Ax.js +1 -0
  66. package/dist/assets/railroad-3IZDKUUU-BKdz6pdN-tvzxXql8.js +1 -0
  67. package/dist/assets/railroad-abnf-AHOZXSZD-m_CSSIfd-CMCeKqcK.js +1 -0
  68. package/dist/assets/railroad-ebnf-EBAXGLYW-Nu3nAG7C-CM7KSH9R.js +1 -0
  69. package/dist/assets/railroad-peg-LSFZ7HO6--Y3mD4R_-Ds4sbE-D.js +1 -0
  70. package/dist/assets/{railroadDiagram-RFXS5EU6--lNIlG64-Cl00PZJ3.js → railroadDiagram-RFXS5EU6--lNIlG64-CaMY_GQ1.js} +1 -1
  71. package/dist/assets/{requirementDiagram-TGXJPOKE-D-B0Y5Wu-BBU6_YVY.js → requirementDiagram-TGXJPOKE-D-B0Y5Wu-DWHcMDxg.js} +1 -1
  72. package/dist/assets/{sankeyDiagram-HTMAVEWB-DlutaXDv-DDG1vVfm.js → sankeyDiagram-HTMAVEWB-DlutaXDv-BXstTNJK.js} +1 -1
  73. package/dist/assets/{sequenceDiagram-DBY2YBRQ-BOLgtggH-BLRc-c61.js → sequenceDiagram-DBY2YBRQ-BOLgtggH-DNq_A2us.js} +1 -1
  74. package/dist/assets/{stateDiagram-2N3HPSRC-jf_dXUEU-IElGdQ_H.js → stateDiagram-2N3HPSRC-jf_dXUEU-BcuA2rt-.js} +1 -1
  75. package/dist/assets/stateDiagram-v2-6OUMAXLB-CuzgCL2v-CngDb_q-.js +1 -0
  76. package/dist/assets/{swimlanes-5IMT3BWC-CYjtALQH-DWw64pD1.js → swimlanes-5IMT3BWC-CYjtALQH-CFAIsGQE.js} +1 -1
  77. package/dist/assets/swimlanesDiagram-G3AALYLV-3bmSBtKs-ktA_ajC9.js +8 -0
  78. package/dist/assets/{timeline-definition-FHXFAJF6-DYJ4oUm8-CmkjOn4R.js → timeline-definition-FHXFAJF6-DYJ4oUm8-_przvOVq.js} +1 -1
  79. package/dist/assets/treeView-QDETBFTQ-D27HWG6Q-BVr9yA0C.js +1 -0
  80. package/dist/assets/treemap-6X3UGDF4-CmGIU3H5-dwog38AV.js +1 -0
  81. package/dist/assets/{vennDiagram-L72KCM5P-CdKAHoek-Ch4eXyRT.js → vennDiagram-L72KCM5P-CdKAHoek-nDlMmrar.js} +1 -1
  82. package/dist/assets/wardley-OPB4EBWU-CbHWsb2k-BcHLy1xv.js +1 -0
  83. package/dist/assets/{wardleyDiagram-EHGQE667-BF6c4_CW-B2NgeiNl.js → wardleyDiagram-EHGQE667-BF6c4_CW-CCl9h9eK.js} +1 -1
  84. package/dist/assets/{xychartDiagram-FW5EYKEG-CGiKngj7-B544fj9E.js → xychartDiagram-FW5EYKEG-CGiKngj7-B6LaWKwe.js} +1 -1
  85. package/dist/index.html +2 -2
  86. package/package.json +8 -8
  87. package/server/agents/claude-args.ts +23 -14
  88. package/server/config/header-config.ts +18 -11
  89. package/server/git/forge-failure.ts +42 -0
  90. package/server/git/gh.ts +6 -1
  91. package/server/git/glab-items.ts +17 -3
  92. package/server/git/glab.ts +22 -1
  93. package/server/git/issue-work.ts +2 -15
  94. package/server/git/work-comment.ts +225 -60
  95. package/server/infra/jsonl-file.ts +96 -2
  96. package/server/infra/serialize-per-key.ts +27 -0
  97. package/server/mcp/broker.ts +6 -3
  98. package/server/mcp/tool-gate.ts +20 -1
  99. package/server/routes/dir-routes.ts +11 -4
  100. package/server/routes/mcp-routes.ts +11 -4
  101. package/server/routes/ws-routes.ts +25 -8
  102. package/server/session/all-tools-log.ts +54 -0
  103. package/server/session/cost.ts +60 -19
  104. package/server/session/decision-scan.ts +38 -19
  105. package/server/session/decisions.ts +61 -60
  106. package/server/session/file-cache.ts +47 -18
  107. package/server/session/header-hook.ts +10 -2
  108. package/server/session/issue-spawn-options.ts +2 -2
  109. package/server/session/launcher-gui-mcp.ts +25 -10
  110. package/server/session/mcp-config.ts +9 -5
  111. package/server/session/registry.ts +66 -7
  112. package/server/session/session-reads.ts +163 -64
  113. package/server/session/spawn-claude.ts +13 -6
  114. package/server/session/spawn-codex.ts +9 -4
  115. package/server/session/summary-scan.ts +82 -47
  116. package/server/session/transcript-fold.ts +89 -0
  117. package/server/session/transcript-sidecar.ts +136 -0
  118. package/server/session/transcript.ts +70 -38
  119. package/server/skills/mulmoterminal-header/SKILL.md +27 -12
  120. package/dist/assets/architecture-TIHT7OUA-0NsD2xW1-rsT_LNkQ.js +0 -1
  121. package/dist/assets/channel-7wUqSdoX-C_3NKRG1.js +0 -1
  122. package/dist/assets/classDiagram-OUVF2IWQ-BYlRi6YQ-I_77uR8C.js +0 -1
  123. package/dist/assets/classDiagram-v2-EOCWNBFH-BCHaYHxj-I_77uR8C.js +0 -1
  124. package/dist/assets/cynefin-VYW2F7L2-CGYr2F6h-BASJNDQ6.js +0 -1
  125. package/dist/assets/eventmodeling-45OFAUF4-CWmqhm3a-DYbmMHSK.js +0 -1
  126. package/dist/assets/flowDiagram-23GEKE2U-DKSB5AiY-DxbRE59B.js +0 -1
  127. package/dist/assets/gitGraph-TEB2WS4Q-BR9qPwdN-DDSyOJ1L.js +0 -1
  128. package/dist/assets/index-C-UrTIoI.css +0 -1
  129. package/dist/assets/index-CHZiol4M.js +0 -614
  130. package/dist/assets/info-DKCQHKI2-Dplx5kMp-bM6jK96q.js +0 -1
  131. package/dist/assets/packet-7NZHBO7P-CQI3flND-M2i4X2qk.js +0 -1
  132. package/dist/assets/pie-RZYD4A2V-CnmKE2Wa-Bd_XkMs5.js +0 -1
  133. package/dist/assets/radar-I7S5WNFK-us6x-Z9R-C3vBieyU.js +0 -1
  134. package/dist/assets/railroad-3IZDKUUU-BKdz6pdN-CGQcIWsR.js +0 -1
  135. package/dist/assets/railroad-abnf-AHOZXSZD-m_CSSIfd-jWYezWwF.js +0 -1
  136. package/dist/assets/railroad-ebnf-EBAXGLYW-Nu3nAG7C-Csc9w5bS.js +0 -1
  137. package/dist/assets/railroad-peg-LSFZ7HO6--Y3mD4R_-D5bhRRBY.js +0 -1
  138. package/dist/assets/stateDiagram-v2-6OUMAXLB-CuzgCL2v-KNFxI9rB.js +0 -1
  139. package/dist/assets/swimlanesDiagram-G3AALYLV-3bmSBtKs-CjVzBocH.js +0 -8
  140. package/dist/assets/treeView-QDETBFTQ-D27HWG6Q-Db61iIhG.js +0 -1
  141. package/dist/assets/treemap-6X3UGDF4-CmGIU3H5-DX1IC1yH.js +0 -1
  142. package/dist/assets/wardley-OPB4EBWU-CbHWsb2k-IFR8KKBh.js +0 -1
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # mulmoterminal
1
+ # MulmoTerminal
2
2
 
3
3
  **Run multiple Claude Code and Codex sessions in parallel — and see which one needs you.**
4
4
 
@@ -34,6 +34,10 @@ ping to your phone when a task finishes. One `npx` command, no Electron, no conf
34
34
  npx mulmoterminal@latest # starts on http://localhost:34567 and opens your browser
35
35
  ```
36
36
 
37
+ Built by **[receptron](https://github.com/receptron)** — **[Satoshi Nakajima](https://x.com/snakajima)**,
38
+ software architect for **Windows 95** at Microsoft, and **[Isamu Arimoto](https://github.com/isamu)**;
39
+ the same two behind **[GraphAI](https://github.com/receptron/graphai)**. [More ↓](#who-builds-this)
40
+
37
41
  > **Something looks wrong?** Type `/mulmoterminal-bug-report` in any MulmoTerminal session. The
38
42
  > bundled skill hears the symptom out, checks your **real** config, schema and version to see
39
43
  > whether the behaviour is configuration or by design, searches the existing issues — and only
@@ -393,7 +397,7 @@ today — **Claude Code** (the default), **Codex**, and **Antigravity** (`agy`).
393
397
  file exists, and injects activity hooks per spawn (see
394
398
  [Claude hook injection](#claude-hook-injection)) plus the
395
399
  [closing summary](#closing-summary) instruction.
396
- The **whole** GUI MCP (`--mcp-config` + `--strict-mcp-config`) goes only to a session that is not a grid cell, or to a grid cell whose cwd IS the workspace — `carriesFullGuiMcp` in `server/session/spawn-claude.ts`, which is what gives a workspace cell the tools the single view had before 4.0.0 removed it. That equivalence is about what the session *carries*: a workspace cell is still a grid cell in every other respect. A cell in a project directory attaches neither flag, so Claude Code loads MCP servers the ordinary way — the directory's own local scope, any `.mcp.json` up the tree, and your global ones including whichever [Canvas switches](#wiki-collections--the-gui-panel) are registered for it.
400
+ The **whole** GUI MCP (`--mcp-config`, on one all-tools URL) goes only to a session that is not a grid cell, or to a grid cell whose cwd IS the workspace — `claimFullGuiMcp` in `server/session/registry.ts`, which is what gives a workspace cell the tools the single view had before 4.0.0 removed it. That equivalence is about what the session *carries*: a workspace cell is still a grid cell in every other respect. A cell in a project directory attaches none of ours, so its GUI tools come from whichever [Canvas switches](#wiki-collections--the-gui-panel) are registered for it. **Either way, Claude Code loads your own MCP servers normally** — the directory's local scope, any `.mcp.json` up the tree, your global ones and your claude.ai connectors. It did not always: `--strict-mcp-config` used to ride along with `--mcp-config`, which hid all of that from the very sessions meant to be the most capable ([#1338](https://github.com/receptron/mulmoterminal/issues/1338), [#1385](https://github.com/receptron/mulmoterminal/issues/1385)).
397
401
  - **Codex** — spawned as `codex` (override with `CODEX_BIN`; `CODEX_MODEL` sets
398
402
  `--model`). Codex runs on its own WebSocket (`/ws/codex`) and its sessions appear in the
399
403
  cockpit roster next to Claude's. Because Codex only mints its rollout id **after** the first
@@ -562,7 +566,7 @@ The Settings modal (⚙) persists per-user UI choices to `~/.mulmoterminal/confi
562
566
  | `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). |
563
567
  | `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). |
564
568
  | `decisionDigest` | Keep a **Markdown digest of the decisions this project's sessions asked for**, refreshed at startup and every few hours, so an agent can read what has already been decided before asking something similar. Written to `~/.mulmoterminal/decisions/<project>.md` (never into your repository) and served to agents by the bundled `mulmoterminal-decisions` skill. **Off by default** — it is a vision-stage idea, and it writes a file that would otherwise not exist. The digest holds dated facts, never inferred rules. |
565
- | `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). |
569
+ | `issueWorkComments` | Let a cell **comment on the issue it is working on**: **one comment**, posted when the work starts and then **edited** as the PR opens and merges (closing the issue if GitHub has not already), each milestone stamped in UTC. The comment names the working **directory** it happened in — the folder name only, never the path — so a reader can tell which clone, and two terminals do not start the same issue twice. It says it came from MulmoTerminal. CI is deliberately not reported: it is on the PR already, and it flaps. When it **cannot** write — no `gh`, not logged in, or a login without write access — the cell says so next to the work chip rather than doing nothing silently; the work is unaffected either way. **Off by default**; it writes to GitHub, often on somebody else's issue. Needs `gh` logged in **with write access**. See the [Configuration guide](https://receptron.github.io/mulmoterminal/guide/en/config.html#issue-work-comments). |
566
570
  | `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. |
567
571
  | `appendSystemPrompt` | Whether a spawned Claude session is asked to end a reply with a **closing summary** — what was asked, what was achieved, what was not (see [Closing summary](#closing-summary)). **On by default**; set `false` to opt out, and a directory's `.mulmoterminal.json` outranks this. Read per spawn, so no restart is needed (there is no Settings control for it), though a session already running keeps what it was launched with. `true` / `false` only. |
568
572
  | `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). |
@@ -772,15 +776,17 @@ In dev, open the Vite URL; its proxy forwards `/ws`, `/ws/pubsub`, and `/api` to
772
776
 
773
777
  An empty grid cell's launcher sets the **Working directory** by typing, by a preset
774
778
  chip, or with the **📁 folder button** (a native OS folder dialog). The preset chips are
775
- the directories you have launched in; the **workspace** leads them always, marked with an
776
- icon, whether or not you have ever launched there — it is the one directory where a
777
- session reaches every GUI tool, so it is never a click you can lose. It has no remove
778
- button for the same reason.
779
+ the directories you have launched in; the **workspace** leads them always, labelled
780
+ **WORKSPACE** and marked with an icon, whether or not you have ever launched there — it is
781
+ the one directory where a session reaches every GUI tool, so it is never a click you can
782
+ lose. It has no remove button for the same reason. It is named for its role rather than its
783
+ folder, because the folder name (`~/mulmoclaude` by default, or wherever `CLAUDE_CWD`
784
+ points) says the least interesting true thing about it; the real path is on its hover.
779
785
 
780
786
  **The launcher is shorter in the workspace**, because two of its choices do not apply
781
787
  there. The per-directory **Canvas switches** are replaced by a line saying every GUI tool
782
- is already available — a session there is handed the whole GUI MCP at spawn, so switches
783
- would write a registration that `--strict-mcp-config` then ignores. And the **worktree**
788
+ is already available — a session there is handed the whole GUI MCP at spawn, so a switch
789
+ would register a group URL that then has nothing left to serve. And the **worktree**
784
790
  section is hidden: a worktree isolates work on one codebase onto a branch, while the
785
791
  workspace is what a session works *from* (the shared wiki, collections and accounting
786
792
  live there), which is precisely what a detached branch would cut it off from. Both come
@@ -970,6 +976,14 @@ and refused, where before it could be confirmed away — which detached whoever
970
976
  `mulmoterminal` process on this machine: the server answers from its own PTY table plus
971
977
  tmux, not from what one page can see.
972
978
 
979
+ **Change the directory and those lists empty immediately**, replaced by a single
980
+ `Loading this directory's sessions, worktrees and scripts…` row until the new ones arrive.
981
+ Everything the launch form offers below the field — **OR RESUME HERE**, the worktrees and
982
+ **OR RUN A SCRIPT** — belongs to the directory it was read for, and reading it costs a
983
+ debounce plus a round trip. Rows left standing through that wait would be the previous
984
+ directory's, listed under the new directory's name, and clicking one resumes exactly the
985
+ session it offers.
986
+
973
987
  A worktree started **from an issue** gets an `issue/<N>-<slug>` branch instead. The number
974
988
  in the name is what later tells the app which issue the work belongs to: the ⧉ Open PR
975
989
  button puts `Fixes #<N>` in the PR body, and the branch chip, the issue work comment and
@@ -1108,11 +1122,11 @@ A chip is a command line the user wrote, so the injection is a **rewrite of thei
1108
1122
  deliberately narrow: only a bare `claude` or `codex` is recognised, and anything else — a wrapper
1109
1123
  script, `FOO=1 claude` — is passed through unchanged.
1110
1124
 
1111
- A `claude` chip in the workspace is given `--strict-mcp-config`, same as the cell. That makes the
1112
- generated config the only source but it already contains your Settings `userMcpServers`, so
1113
- **those still load and are still pre-approved**. What stops contributing is a project directory's
1114
- per-folder `.mcp.json`, which in the workspace is the intent: that file is where the per-group URLs
1115
- live, and the chip is being handed the all-tools URL instead.
1125
+ A `claude` chip in the workspace is handed the all-tools URL, same as the cell, and like the cell it
1126
+ **does not** isolate the session: your own MCP servers and claude.ai connectors load as usual. Where
1127
+ a directory also registered per-group URLs in its `.mcp.json`, those groups stand down for that
1128
+ session rather than serving a second copy of the same tools the session already reaches all of
1129
+ them under `mt`.
1116
1130
 
1117
1131
  **The asymmetry is deliberate.** `mt` is ours to name: nothing on disk holds it, it is
1118
1132
  regenerated on every spawn, so it was shortened to stop paying 17 characters per tool name. The
@@ -1802,10 +1816,23 @@ and `fetch` are mocked so the tests run without a server.
1802
1816
 
1803
1817
  ## Who builds this
1804
1818
 
1805
- MulmoTerminal is built by the **[Singularity Society](https://singularitysociety.org)** team,
1806
- including **[Satoshi Nakajima](https://x.com/snakajima)** software architect for **Windows 95**,
1807
- **Windows 98** and **Internet Explorer 3.0 / 4.0** at Microsoft, later founder of UIEvolution /
1808
- Xevo, and still building from Seattle.
1819
+ MulmoTerminal is built by **[receptron](https://github.com/receptron)**
1820
+ **[Satoshi Nakajima](https://x.com/snakajima)** and **[Isamu Arimoto](https://github.com/isamu)**.
1821
+
1822
+ Satoshi was the software architect for **Windows 95**, **Windows 98** and **Internet Explorer
1823
+ 3.0 / 4.0** at Microsoft, later founded UIEvolution / Xevo, and still builds from Seattle.
1824
+
1825
+ The two have shipped open source together since 2015, and the core of each venture has been
1826
+ public every time:
1827
+
1828
+ | | |
1829
+ |---|---|
1830
+ | **[VideoShader](https://github.com/snakajima/videoshader)** (2015) | GPU video processing for iOS, built at Veemob |
1831
+ | **[Swipe](https://github.com/swipe-org/swipe)** (2015) | an animation runtime that made manga move on phones |
1832
+ | **[OwnPlate](https://github.com/Nakajima-Foundation/ownplate)** (2020) | takeout ordering for restaurants during COVID, run at the Singularity Society and launched with ITOCHU |
1833
+ | **[SlashGPT](https://github.com/receptron/SlashGPT)** (2023) · **[GraphAI](https://github.com/receptron/graphai)** (2024) · **[MulmoCast](https://github.com/receptron/mulmocast-cli)** (2025) | LLM agents, declarative dataflow, AI video |
1834
+
1835
+ MulmoTerminal is the seventh.
1809
1836
 
1810
1837
  It exists because we run several coding agents every day and kept losing track of which one was
1811
1838
  waiting on us. Everything here was built for that, then kept because it worked. MIT licensed.
@@ -1,9 +1,29 @@
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.
1
+ // The comment MulmoTerminal leaves on an issue: ONE per (issue, clone), edited as the work moves
2
+ // (#979, #1369). Pure, and shared, because the comment is its own storage — the server renders the
3
+ // body, reads it back on the next milestone, and appends to what it parsed. A mismatch between the
4
+ // two halves would either lose the earlier milestones or post a second comment on every poll.
5
+ import { GITHUB_REPO } from "./githubRepo.js";
5
6
 
6
- export type WorkCommentKind = "start" | "merged";
7
+ // A milestone worth telling the issue about. NOT every state the cell passes through: CI is on the
8
+ // pull request already, and it flaps, so it is deliberately absent.
9
+ export type WorkCommentKind = "start" | "pr" | "merged";
10
+
11
+ const WORK_COMMENT_KINDS: readonly WorkCommentKind[] = ["start", "pr", "merged"];
12
+
13
+ export const isWorkCommentKind = (v: unknown): v is WorkCommentKind => WORK_COMMENT_KINDS.some((kind) => kind === v);
14
+
15
+ /** One milestone. `pr` is the pull request the milestone is about, null when there is none. */
16
+ export interface WorkEvent {
17
+ kind: WorkCommentKind;
18
+ /** `YYYY-MM-DD HH:MM UTC`, from the SERVER's clock — see formatWorkTime. */
19
+ at: string;
20
+ pr: number | null;
21
+ }
22
+
23
+ // Only the two kinds that were ever written as a marker: `start` identifies the comment (see
24
+ // workAnchorMarker) and `merged` is the second comment older builds left, which is read back so an
25
+ // issue upgraded mid-flight does not get told about the same merge twice.
26
+ type WorkMarkerKind = "start" | "merged";
7
27
 
8
28
  // An HTML comment, so it is invisible in the rendered issue but survives a round-trip through the
9
29
  // GitHub API. Keyed by kind AND directory: the same issue worked on from a second clone is a
@@ -14,10 +34,15 @@ export type WorkCommentKind = "start" | "merged";
14
34
  // issue as text (Codex review). Encoding also keeps a newline or a backtick in a path from
15
35
  // reshaping the comment. Ordinary names encode to themselves, so markers already posted still
16
36
  // match.
17
- export function workCommentMarker(kind: WorkCommentKind, dir: string): string {
37
+ export function workCommentMarker(kind: WorkMarkerKind, dir: string): string {
18
38
  return `<!-- mulmoterminal:work:${kind} dir=${encodeURIComponent(dir)} -->`;
19
39
  }
20
40
 
41
+ /** The marker that IDENTIFIES this clone's comment, so later milestones can find it and edit it.
42
+ * Still spelled `:start` — that is what builds before #1369 wrote, and reusing it is what lets an
43
+ * already-posted comment be adopted instead of duplicated. */
44
+ export const workAnchorMarker = (dir: string): string => workCommentMarker("start", dir);
45
+
21
46
  // The directory a comment names. The BASENAME only: the point is "which of my clones", and a full
22
47
  // path on a public issue leaks the machine's layout (and, on a work machine, project names).
23
48
  export function workCommentDirLabel(cwd: string): string {
@@ -27,18 +52,120 @@ export function workCommentDirLabel(cwd: string): string {
27
52
  return parts[parts.length - 1] ?? cwd;
28
53
  }
29
54
 
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}`;
55
+ // Minutes, UTC, spelled out. A reader of the issue is asking "is this claim still alive" the
56
+ // answer needs a date more than it needs seconds, and a fixed zone means two clones in two
57
+ // timezones write comparable lines.
58
+ export const formatWorkTime = (at: Date): string => `${at.toISOString().slice(0, 16).replace("T", " ")} UTC`;
59
+
60
+ const TIME = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2} UTC$/;
61
+
62
+ // Says who wrote it, in the rendered comment rather than only in the marker: this lands on other
63
+ // people's issues, and a reader should not have to guess which tool is claiming their issue.
64
+ const SIGNATURE = `<sub>posted by [MulmoTerminal](https://github.com/${GITHUB_REPO})</sub>`;
65
+
66
+ // A directory name may legally contain newlines and control characters, and the name is written
67
+ // into the headline as-is. The marker below is percent-encoded, but a newline here would put a
68
+ // line of the user's choosing into the body — including one that reads back as a milestone.
69
+ const displayDir = (dir: string): string =>
70
+ dir
71
+ .replace(/[\p{Cc}\p{Cf}]/gu, " ")
72
+ .replace(/\s+/g, " ")
73
+ .trim();
74
+
75
+ // The widest number a milestone line can carry. Render and parse have to agree on it: a line this
76
+ // side wrote and the parser could not read would vanish from the comment on the NEXT edit, taking
77
+ // its milestone with it (Codex review). Ten digits is far more than any forge issues, and the
78
+ // pattern below reads exactly that back.
79
+ const MAX_LINE_PR = 9_999_999_999;
80
+
81
+ const writablePr = (pr: number | null): pr is number => pr !== null && Number.isSafeInteger(pr) && pr > 0 && pr <= MAX_LINE_PR;
82
+
83
+ // The wording of the two states, unchanged from the build that wrote one comment per state: an
84
+ // issue that already carries "Working on this in `x`." must not read differently after an edit.
85
+ function headline(dir: string, events: readonly WorkEvent[]): string {
86
+ const shown = displayDir(dir);
87
+ const merged = events.find((event) => event.kind === "merged");
88
+ if (!merged) return `Working on this in \`${shown}\`.`;
89
+ // The same bound as the line: the headline is not parsed, but naming a number the line beneath
90
+ // it had to drop would make the comment contradict itself.
91
+ const where = writablePr(merged.pr) ? `Merged in #${merged.pr}.` : "Merged.";
92
+ return `${where} Work done in \`${shown}\`.`;
93
+ }
94
+
95
+ // Null for an event that cannot be written as a line the parser would read back — a PR milestone
96
+ // with no number says nothing anyway.
97
+ function eventLine(event: WorkEvent): string | null {
98
+ if (event.kind === "start") return `- started — ${event.at}`;
99
+ if (event.kind === "pr") return writablePr(event.pr) ? `- PR #${event.pr} — ${event.at}` : null;
100
+ return writablePr(event.pr) ? `- merged in #${event.pr} — ${event.at}` : `- merged — ${event.at}`;
101
+ }
102
+
103
+ const isLine = (line: string | null): line is string => line !== null;
104
+
105
+ /** The whole comment body for a clone's milestones so far. Rendered from scratch every time, so
106
+ * the body is a function of the events and nothing else. */
107
+ export function renderWorkComment(dir: string, events: readonly WorkEvent[]): string {
108
+ const lines = events.map(eventLine).filter(isLine);
109
+ return [headline(dir, events), "", ...lines, "", SIGNATURE, "", workAnchorMarker(dir)].join("\n");
110
+ }
111
+
112
+ // MAX_LINE_PR, spelled as digits — the parse half of the same bound. It stops a run of digits out
113
+ // of an edited body from reaching the parser as something that would print as `1e+20`, and it
114
+ // keeps `\d*` from sitting inside an optional group, which `security/detect-unsafe-regex` reads as
115
+ // a backtracking risk (measured as linear here, but a bound is a better answer than an exception).
116
+ const PR_DIGITS = "[1-9]\\d{0,9}";
117
+ const START_LINE = /^- started — (.+)$/;
118
+ const PR_LINE = new RegExp(`^- PR #(${PR_DIGITS}) — (.+)$`);
119
+ const MERGED_LINE = new RegExp(`^- merged(?: in #(${PR_DIGITS}))? — (.+)$`);
120
+
121
+ // Strict about the timestamp on purpose. The body is editable by anyone reading the issue, and
122
+ // whatever this returns is written straight back into the next edit — so a line that is not
123
+ // exactly what render wrote is dropped rather than echoed.
124
+ const timed = (kind: WorkCommentKind, pr: number | null, at: string | undefined): WorkEvent | null =>
125
+ at !== undefined && TIME.test(at) ? { kind, at, pr } : null;
126
+
127
+ // `Number` is exact for anything the patterns above admit, so there is no second check here: a
128
+ // line whose number is longer than that does not match at all, and is dropped like every other
129
+ // line that is not exactly what render wrote.
130
+ const prNumber = (digits: string | undefined): number | null => (digits === undefined ? null : Number(digits));
131
+
132
+ function parseEventLine(line: string): WorkEvent | null {
133
+ const started = START_LINE.exec(line);
134
+ if (started) return timed("start", null, started[1]);
135
+ const pr = PR_LINE.exec(line);
136
+ if (pr) return timed("pr", prNumber(pr[1]), pr[2]);
137
+ const merged = MERGED_LINE.exec(line);
138
+ // A merge with no number is a line render writes and reads back, so null is an answer here
139
+ // rather than a failure.
140
+ return merged ? timed("merged", prNumber(merged[1]), merged[2]) : null;
141
+ }
142
+
143
+ /** The milestones a comment body records, in the order it lists them. Empty for a comment written
144
+ * before #1369 — those carry the marker and the headline but no lines, and the caller supplies
145
+ * the start from the comment's own creation time. */
146
+ export const parseWorkEvents = (body: string): WorkEvent[] =>
147
+ body
148
+ .split("\n")
149
+ // trimEnd, not trim: a body round-tripped through the forge can come back CRLF, and `\r` at the
150
+ // end of the timestamp would fail the strict check above.
151
+ .map((line) => parseEventLine(line.trimEnd()))
152
+ .filter((event): event is WorkEvent => event !== null);
153
+
154
+ /** `events` with `event` added, or null when the comment already records it — which is the normal
155
+ * answer, because the caller asks on every poll of every open tab. */
156
+ export function withWorkEvent(events: readonly WorkEvent[], event: WorkEvent): WorkEvent[] | null {
157
+ // A PR milestone is its number, and a number the line cannot carry is not a milestone this
158
+ // comment can record — refused here rather than added and then silently dropped by render.
159
+ if (event.kind === "pr" && !writablePr(event.pr)) return null;
160
+ // Matched on the PR number too: a second pull request for the same issue from the same clone —
161
+ // the first one closed unmerged, say — is a new milestone, not a repeat of the old one.
162
+ if (events.some((known) => known.kind === event.kind && known.pr === event.pr)) return null;
163
+ return [...events, event];
37
164
  }
38
165
 
39
166
  // Has this exact comment already been left? Matching on the marker rather than the prose means an
40
167
  // 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 {
168
+ export function alreadyCommented(bodies: readonly string[], kind: WorkMarkerKind, dir: string): boolean {
42
169
  const marker = workCommentMarker(kind, dir);
43
170
  return bodies.some((body) => typeof body === "string" && body.includes(marker));
44
171
  }
@@ -0,0 +1,16 @@
1
+ // Why MulmoTerminal could not write its work comment (#1369). Shared, because the server decides
2
+ // it from `gh`'s exit and the UI turns it into a sentence — a second copy of the list on the client
3
+ // would drift into wording a server can never send.
4
+ //
5
+ // Not commenting stays a normal outcome: the work carries on either way. What this adds is the
6
+ // REASON, which used to exist nowhere — a read-only `gh` login made the feature do nothing at all,
7
+ // with the same silence as having it switched off.
8
+
9
+ /** `permission` is the one this exists for: logged in, but not allowed to write on that repo. */
10
+ export type WorkCommentFailure = "cli-missing" | "auth" | "permission" | "unknown";
11
+
12
+ const FAILURES: readonly WorkCommentFailure[] = ["cli-missing", "auth", "permission", "unknown"];
13
+
14
+ // The value arrives over the wire, so a build skew (an older server naming a cause this one has
15
+ // never heard of) has to read as "no cause" rather than reach the UI's wording switch.
16
+ export const isWorkCommentFailure = (v: unknown): v is WorkCommentFailure => FAILURES.some((failure) => failure === v);
@@ -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-D6oVwac4.js";import"./chunk-WYO6CB5R-BlzOfotS-DMj-dLn7.js";import"./chunk-VAUOI2AC-CS9QJ4yz-DDJgABqC.js";import{n as i,r as a,t as o}from"./chunk-MOJQB5TN-CMZRaeqt-ePkETRCM.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-CTOK7EeF.js";import"./chunk-WYO6CB5R-BlzOfotS-B-BAzlIB.js";import"./chunk-VAUOI2AC-CS9QJ4yz-DhyLweXm.js";import{n as i,r as a,t as o}from"./chunk-MOJQB5TN-CMZRaeqt-gTPiZssc.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-CTOK7EeF.js";export{e as createArchitectureServices};