mulmoterminal 2.5.2 → 2.6.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 (190) hide show
  1. package/README.md +12 -10
  2. package/bin/mulmoterminal.js +4 -1
  3. package/common/keymap.ts +96 -18
  4. package/common/prPhase.ts +11 -1
  5. package/common/toolGroups.ts +98 -0
  6. package/dist/assets/{abnfDiagram-VRR7QNED-RjiYivmv-CFwKVB03.js → abnfDiagram-VRR7QNED-RjiYivmv-2UuooAL1.js} +1 -1
  7. package/dist/assets/architecture-TIHT7OUA-0NsD2xW1-BgkdTJQE.js +1 -0
  8. package/dist/assets/{architectureDiagram-ZJ3FMSHR-3nWA91tG-mE1s94XZ.js → architectureDiagram-ZJ3FMSHR-3nWA91tG-Cadi87eN.js} +1 -1
  9. package/dist/assets/{blockDiagram-677ZJIJ3-BPuAJQRW-DBDXdKIt.js → blockDiagram-677ZJIJ3-BPuAJQRW-DqOBzK4r.js} +1 -1
  10. package/dist/assets/{c4Diagram-LMCZKHZV-C0LAqQso-RJl7QI1d.js → c4Diagram-LMCZKHZV-C0LAqQso-Czc9LUxD.js} +1 -1
  11. package/dist/assets/channel-7wUqSdoX-C1cie6OA.js +1 -0
  12. package/dist/assets/{chunk-32BRIVSS-BRrYpgtb-C3OsDfXW.js → chunk-32BRIVSS-BRrYpgtb-DPioxLNJ.js} +1 -1
  13. package/dist/assets/{chunk-52WLFC77-BJ-ss3Xr-tZqd3bLR.js → chunk-52WLFC77-BJ-ss3Xr-BGc0pGjo.js} +1 -1
  14. package/dist/assets/{chunk-C7G6YPKG-BZEucKEL-B8NVLAkB.js → chunk-C7G6YPKG-BZEucKEL-C4mkPiqB.js} +1 -1
  15. package/dist/assets/{chunk-EX3LRPZG-DLS6FBN1-BdMYjq_3.js → chunk-EX3LRPZG-DLS6FBN1-C3Wn7cGn.js} +1 -1
  16. package/dist/assets/chunk-FWX5IMBZ-DHLSFw1H-C8DnOSSj.js +2 -0
  17. package/dist/assets/{chunk-HOUHSVGY-Bhlt8hXJ-DZ4w7WP6.js → chunk-HOUHSVGY-Bhlt8hXJ-BT_GFYXY.js} +1 -1
  18. package/dist/assets/{chunk-ICXQ74PX-DwgHBX_g-DwfVM3-4.js → chunk-ICXQ74PX-DwgHBX_g-DY-sDZ2-.js} +1 -1
  19. package/dist/assets/{chunk-MOJQB5TN-CMZRaeqt-DDn-QIzL.js → chunk-MOJQB5TN-CMZRaeqt-DvOpObYg.js} +1 -1
  20. package/dist/assets/{chunk-OGEWGWER-8Qy4a8b5-DsrblYpm.js → chunk-OGEWGWER-8Qy4a8b5-BxAWKfDq.js} +1 -1
  21. package/dist/assets/{chunk-PUDLZKDR-DcrWQRYh-Dz5x4gGU.js → chunk-PUDLZKDR-DcrWQRYh-HeYWroWP.js} +1 -1
  22. package/dist/assets/{chunk-Q4XR5HBZ-ZXVGkG8Z-Dg8P4fgN.js → chunk-Q4XR5HBZ-ZXVGkG8Z-Cqn2FjJe.js} +1 -1
  23. package/dist/assets/{chunk-V7JOEXUC-DmGdheTX-CSESRbzZ.js → chunk-V7JOEXUC-DmGdheTX-WySfFBLu.js} +1 -1
  24. package/dist/assets/{chunk-VAUOI2AC-CS9QJ4yz-r_1UiH65.js → chunk-VAUOI2AC-CS9QJ4yz-xhVI0AT_.js} +1 -1
  25. package/dist/assets/{chunk-VR4S4FIN-C6a91eNY-BiFSHZig.js → chunk-VR4S4FIN-C6a91eNY-Dawtxx1M.js} +1 -1
  26. package/dist/assets/{chunk-WYO6CB5R-BlzOfotS-DenoOpP3.js → chunk-WYO6CB5R-BlzOfotS-cPQQuq_p.js} +1 -1
  27. package/dist/assets/{chunk-ZGVPDNZ5-BYwxNFTK-DIJf6S9Q.js → chunk-ZGVPDNZ5-BYwxNFTK-CwEy22EH.js} +1 -1
  28. package/dist/assets/classDiagram-OUVF2IWQ-BYlRi6YQ-Do5m3IoG.js +1 -0
  29. package/dist/assets/classDiagram-v2-EOCWNBFH-BCHaYHxj-Do5m3IoG.js +1 -0
  30. package/dist/assets/cynefin-VYW2F7L2-CGYr2F6h-DwN-y4W_.js +1 -0
  31. package/dist/assets/{cynefinDiagram-TSTJHNR4-DHA9iPo--CMG2swe1.js → cynefinDiagram-TSTJHNR4-DHA9iPo--6gP3lGiQ.js} +1 -1
  32. package/dist/assets/{dagre-VKFMJZFB--oJKqXBZ-DptBydHT.js → dagre-VKFMJZFB--oJKqXBZ-Ogb4ZCJY.js} +1 -1
  33. package/dist/assets/{diagram-FQU43EPY-CcJCB9bG-Dzr4eBwY.js → diagram-FQU43EPY-CcJCB9bG-En7Wv4VU.js} +1 -1
  34. package/dist/assets/{diagram-G47NLZAW-C_o-WGG1-DyxwgGcN.js → diagram-G47NLZAW-C_o-WGG1-CvfDKE9B.js} +1 -1
  35. package/dist/assets/{diagram-NH7WQ7WH-CXJCYvY--C7_0SJUX.js → diagram-NH7WQ7WH-CXJCYvY--D_SuUUUL.js} +1 -1
  36. package/dist/assets/{diagram-OA4YK3LP-BMzeJ87A-CQoIcPi7.js → diagram-OA4YK3LP-BMzeJ87A-COaQX8ss.js} +1 -1
  37. package/dist/assets/{diagram-WEI45ONY-D_93NKqo-6c4HxiEH.js → diagram-WEI45ONY-D_93NKqo-B5qjqS3m.js} +1 -1
  38. package/dist/assets/dist-CBx3SN5Y.js +9 -0
  39. package/dist/assets/dist-CXRReMvS.js +1 -0
  40. package/dist/assets/dist-CeY8Kgqb.js +1 -0
  41. package/dist/assets/dist-Ci7pnEU6.js +11 -0
  42. package/dist/assets/dist-DyY2Odgu.js +1 -0
  43. package/dist/assets/dist-FzB0wwv6.js +1 -0
  44. package/dist/assets/dist-IEKl3ifz.js +11 -0
  45. package/dist/assets/dist-Lnp6Clqz.js +1 -0
  46. package/dist/assets/dist-berHSMWa.js +1 -0
  47. package/dist/assets/dist-kXEjeiqx.js +2 -0
  48. package/dist/assets/{ebnfDiagram-CCIWWBDH-CVai1Ii9-DJcz6F1R.js → ebnfDiagram-CCIWWBDH-CVai1Ii9-Bbl_CbGD.js} +1 -1
  49. package/dist/assets/{erDiagram-Q63AITRT-CiABnA0s-D-sOjz5Q.js → erDiagram-Q63AITRT-CiABnA0s-DtNjq60k.js} +1 -1
  50. package/dist/assets/eventmodeling-45OFAUF4-CWmqhm3a-EIcAkJu9.js +1 -0
  51. package/dist/assets/flowDiagram-23GEKE2U-DKSB5AiY-BLvmawCu.js +1 -0
  52. package/dist/assets/{ganttDiagram-NO4QXBWP-DQZvdFo1-B8qQdIm8.js → ganttDiagram-NO4QXBWP-DQZvdFo1-Dtybxb5k.js} +1 -1
  53. package/dist/assets/gitGraph-TEB2WS4Q-BR9qPwdN-CWvVulXn.js +1 -0
  54. package/dist/assets/{gitGraphDiagram-IHSO6WYX-C2ovBouh-DshsJsEP.js → gitGraphDiagram-IHSO6WYX-C2ovBouh-CibskzGI.js} +1 -1
  55. package/dist/assets/index-B1LdU5cE.js +610 -0
  56. package/dist/assets/index-Dwz79YwY.css +1 -0
  57. package/dist/assets/info-DKCQHKI2-Dplx5kMp-CBgSXevA.js +1 -0
  58. package/dist/assets/{infoDiagram-FWYZ7A6U-7UnoB5AP-8s4vJMqL.js → infoDiagram-FWYZ7A6U-7UnoB5AP-WyZaP5rz.js} +1 -1
  59. package/dist/assets/{ishikawaDiagram-FXEZZL3T-ByUDM_N2-z-9aPM5O.js → ishikawaDiagram-FXEZZL3T-ByUDM_N2-C_Q9CmwS.js} +1 -1
  60. package/dist/assets/{journeyDiagram-5HDEW3XC-c5xIah9o-Cgg8FxSO.js → journeyDiagram-5HDEW3XC-c5xIah9o-D-Ru3_wb.js} +1 -1
  61. package/dist/assets/{kanban-definition-HUTT4EX6-Cnt6loYD-BNCxCgbs.js → kanban-definition-HUTT4EX6-Cnt6loYD-eWJL56Xd.js} +1 -1
  62. package/dist/assets/lib-CTNrhK1N.js +9 -0
  63. package/dist/assets/{line-D7ziSjKi-VRCegTMF.js → line-D7ziSjKi-CEMKL39T.js} +1 -1
  64. package/dist/assets/{marp-CMroV6s8.js → marp-C4na0vHp.js} +37 -37
  65. package/dist/assets/{mermaid-parser.core-DxEa8E3F-DvKDRZGq.js → mermaid-parser.core-DxEa8E3F-3f1dQMCy.js} +2 -2
  66. package/dist/assets/{mermaid.core-V0OYwIz3-DU0Mr4W1.js → mermaid.core-V0OYwIz3-Dg1HWQ_i.js} +3 -3
  67. package/dist/assets/{mindmap-definition-LN4V7U3C-CexN3O6L-DMZlX-GZ.js → mindmap-definition-LN4V7U3C-CexN3O6L-CyxV57-U.js} +1 -1
  68. package/dist/assets/packet-7NZHBO7P-CQI3flND-BlXZ8rUp.js +1 -0
  69. package/dist/assets/{pegDiagram-2B236MQR-BiM4G0cW-CsQ2THPf.js → pegDiagram-2B236MQR-BiM4G0cW-B40BLiy-.js} +1 -1
  70. package/dist/assets/pie-RZYD4A2V-CnmKE2Wa-elRgXCv3.js +1 -0
  71. package/dist/assets/{pieDiagram-ENE6RG2P-B_fBBS-2-BQVfIkOE.js → pieDiagram-ENE6RG2P-B_fBBS-2-j0wsTlPA.js} +1 -1
  72. package/dist/assets/{quadrantDiagram-ABIIQ3AL-BigGVxCR-DWtRt8Ki.js → quadrantDiagram-ABIIQ3AL-BigGVxCR-BVxlsDNI.js} +1 -1
  73. package/dist/assets/radar-I7S5WNFK-us6x-Z9R-BZqQUr3N.js +1 -0
  74. package/dist/assets/railroad-3IZDKUUU-BKdz6pdN-Cb5GXZCb.js +1 -0
  75. package/dist/assets/railroad-abnf-AHOZXSZD-m_CSSIfd-BddD0JXg.js +1 -0
  76. package/dist/assets/railroad-ebnf-EBAXGLYW-Nu3nAG7C-7VkImbqm.js +1 -0
  77. package/dist/assets/railroad-peg-LSFZ7HO6--Y3mD4R_-DHko1-Zq.js +1 -0
  78. package/dist/assets/{railroadDiagram-RFXS5EU6--lNIlG64-DhhBhlYi.js → railroadDiagram-RFXS5EU6--lNIlG64-7RnYoafQ.js} +1 -1
  79. package/dist/assets/{requirementDiagram-TGXJPOKE-D-B0Y5Wu-CX6hh2lX.js → requirementDiagram-TGXJPOKE-D-B0Y5Wu-xH07LQI_.js} +1 -1
  80. package/dist/assets/{sankeyDiagram-HTMAVEWB-DlutaXDv-D4lvqkh6.js → sankeyDiagram-HTMAVEWB-DlutaXDv-BUmys5rr.js} +1 -1
  81. package/dist/assets/{sequenceDiagram-DBY2YBRQ-BOLgtggH-DBzQDGiD.js → sequenceDiagram-DBY2YBRQ-BOLgtggH-BYyf27T3.js} +1 -1
  82. package/dist/assets/{stateDiagram-2N3HPSRC-jf_dXUEU-DAoAJ2BI.js → stateDiagram-2N3HPSRC-jf_dXUEU-DGzqzTG5.js} +1 -1
  83. package/dist/assets/stateDiagram-v2-6OUMAXLB-CuzgCL2v-DDeTaew1.js +1 -0
  84. package/dist/assets/{swimlanes-5IMT3BWC-CYjtALQH-DOQhVlBd.js → swimlanes-5IMT3BWC-CYjtALQH-BD-WjB9j.js} +1 -1
  85. package/dist/assets/swimlanesDiagram-G3AALYLV-3bmSBtKs-DZLi_c2t.js +8 -0
  86. package/dist/assets/{timeline-definition-FHXFAJF6-DYJ4oUm8-GXp4cKym.js → timeline-definition-FHXFAJF6-DYJ4oUm8-BL8HXryX.js} +1 -1
  87. package/dist/assets/treeView-QDETBFTQ-D27HWG6Q-C9kkOUqZ.js +1 -0
  88. package/dist/assets/treemap-6X3UGDF4-CmGIU3H5-zjv1syce.js +1 -0
  89. package/dist/assets/{vennDiagram-L72KCM5P-CdKAHoek-BozSpm60.js → vennDiagram-L72KCM5P-CdKAHoek-_pUekkpD.js} +1 -1
  90. package/dist/assets/wardley-OPB4EBWU-CbHWsb2k-DE_WhSD_.js +1 -0
  91. package/dist/assets/{wardleyDiagram-EHGQE667-BF6c4_CW-BhXZxWJk.js → wardleyDiagram-EHGQE667-BF6c4_CW-BVNPNGsg.js} +1 -1
  92. package/dist/assets/{xychartDiagram-FW5EYKEG-CGiKngj7-DG_02eZy.js → xychartDiagram-FW5EYKEG-CGiKngj7-C5EYDD6T.js} +1 -1
  93. package/dist/index.html +3 -2
  94. package/package.json +12 -1
  95. package/server/agents/claude-args.ts +13 -3
  96. package/server/agents/codex-args.ts +30 -5
  97. package/server/agents/codex-rollout.spec.ts +46 -0
  98. package/server/agents/codex-rollout.ts +13 -23
  99. package/server/agents/probe-session.spec.ts +45 -0
  100. package/server/agents/probe-session.ts +34 -0
  101. package/server/agents/probe-transcript.spec.ts +282 -0
  102. package/server/agents/probe-transcript.ts +190 -0
  103. package/server/agents/rate-limit-persist.spec.ts +64 -0
  104. package/server/agents/rate-limit-persist.ts +21 -6
  105. package/server/agents/rate-limit-probe.spec.ts +52 -0
  106. package/server/agents/rate-limit-probe.ts +20 -6
  107. package/server/agents/rate-limit-routes.ts +13 -3
  108. package/server/agents/rate-limit-store.spec.ts +151 -7
  109. package/server/agents/rate-limit-store.ts +124 -7
  110. package/server/agents/session-summary-prompt.ts +10 -0
  111. package/server/agents/statusline.spec.ts +8 -3
  112. package/server/agents/statusline.ts +6 -4
  113. package/server/backends/remoteHost/firestoreSafeResult.ts +72 -0
  114. package/server/backends/remoteHost/index.ts +20 -11
  115. package/server/backends/remoteHost/presenceProbe.ts +67 -0
  116. package/server/backends/remoteHost/resilientRunner.ts +57 -0
  117. package/server/backends/remoteHost/terminalScreen.ts +36 -5
  118. package/server/backends/whisper.ts +5 -6
  119. package/server/config/app-config.ts +13 -0
  120. package/server/config/config-routes.ts +5 -0
  121. package/server/config/cwd-presets.ts +28 -5
  122. package/server/config/header-title.ts +7 -1
  123. package/server/config/workspace.ts +16 -1
  124. package/server/git/prPhase.ts +33 -9
  125. package/server/index.ts +148 -17
  126. package/server/infra/gui-mcp-registration.ts +127 -0
  127. package/server/infra/has-binary.ts +27 -0
  128. package/server/infra/install-bundled-skills.ts +1 -1
  129. package/server/infra/jsonl-file.ts +88 -0
  130. package/server/infra/path-within.ts +14 -0
  131. package/server/infra/plugins-registry.ts +22 -2
  132. package/server/infra/spawnCapture.ts +48 -3
  133. package/server/infra/tmux.ts +10 -2
  134. package/server/mcp/broker.ts +14 -6
  135. package/server/mcp/tool-gate.ts +24 -3
  136. package/server/routes/app-routes.ts +8 -2
  137. package/server/routes/decision-routes.ts +21 -100
  138. package/server/routes/gui-mcp-routes.ts +44 -0
  139. package/server/routes/mcp-routes.ts +47 -6
  140. package/server/routes/plugin-routes.ts +1 -1
  141. package/server/routes/session-routes.ts +2 -1
  142. package/server/routes/tool-routes.ts +55 -6
  143. package/server/routes/ws-routes.ts +80 -20
  144. package/server/session/cost.ts +23 -10
  145. package/server/session/decision-digest-file.ts +57 -0
  146. package/server/session/decision-digest.ts +166 -0
  147. package/server/session/decision-scan.ts +93 -0
  148. package/server/session/dev-terminal-cwds.ts +68 -0
  149. package/server/session/early-frames.ts +39 -0
  150. package/server/session/last-turn.ts +6 -1
  151. package/server/session/launcher-gui-mcp.ts +55 -0
  152. package/server/session/mcp-config.ts +57 -0
  153. package/server/session/pty-spawn.ts +31 -7
  154. package/server/session/registry.ts +110 -1
  155. package/server/session/session-list.ts +4 -3
  156. package/server/session/session-reads.ts +62 -50
  157. package/server/session/session-title.ts +14 -6
  158. package/server/session/session-tool-groups.ts +79 -0
  159. package/server/session/spawn-claude.ts +34 -7
  160. package/server/session/spawn-codex.ts +20 -5
  161. package/server/session/spawn-deps.ts +3 -0
  162. package/server/session/summary-scan.ts +93 -0
  163. package/server/session/transcript.ts +35 -22
  164. package/server/skills/mulmoterminal-config/SKILL.md +35 -0
  165. package/server/skills/mulmoterminal-decisions/SKILL.md +77 -0
  166. package/dist/assets/architecture-TIHT7OUA-0NsD2xW1-BEV01Wk-.js +0 -1
  167. package/dist/assets/channel-7wUqSdoX-Czd43Gvk.js +0 -1
  168. package/dist/assets/chunk-FWX5IMBZ-DHLSFw1H-DbaszCXG.js +0 -2
  169. package/dist/assets/classDiagram-OUVF2IWQ-BYlRi6YQ-CNqTeJKX.js +0 -1
  170. package/dist/assets/classDiagram-v2-EOCWNBFH-BCHaYHxj-CNqTeJKX.js +0 -1
  171. package/dist/assets/cynefin-VYW2F7L2-CGYr2F6h-HnBHTRUw.js +0 -1
  172. package/dist/assets/eventmodeling-45OFAUF4-CWmqhm3a-BLoYpG_j.js +0 -1
  173. package/dist/assets/flowDiagram-23GEKE2U-DKSB5AiY-7Y5BNJk8.js +0 -1
  174. package/dist/assets/gitGraph-TEB2WS4Q-BR9qPwdN-BbSOpldz.js +0 -1
  175. package/dist/assets/index-BAKRQHkN.js +0 -620
  176. package/dist/assets/index-BAZX7G64.css +0 -1
  177. package/dist/assets/info-DKCQHKI2-Dplx5kMp-BGK6F5n3.js +0 -1
  178. package/dist/assets/lib-IyO-7kbx.js +0 -9
  179. package/dist/assets/packet-7NZHBO7P-CQI3flND-Pi7SfNjX.js +0 -1
  180. package/dist/assets/pie-RZYD4A2V-CnmKE2Wa-CCC7e9os.js +0 -1
  181. package/dist/assets/radar-I7S5WNFK-us6x-Z9R-CXlDDsf0.js +0 -1
  182. package/dist/assets/railroad-3IZDKUUU-BKdz6pdN-CtqN4OYA.js +0 -1
  183. package/dist/assets/railroad-abnf-AHOZXSZD-m_CSSIfd-Cx7DnzdQ.js +0 -1
  184. package/dist/assets/railroad-ebnf-EBAXGLYW-Nu3nAG7C-CZCUMFL6.js +0 -1
  185. package/dist/assets/railroad-peg-LSFZ7HO6--Y3mD4R_-DEZTHe61.js +0 -1
  186. package/dist/assets/stateDiagram-v2-6OUMAXLB-CuzgCL2v-CZK_6dwu.js +0 -1
  187. package/dist/assets/swimlanesDiagram-G3AALYLV-3bmSBtKs-MQB8RGJN.js +0 -8
  188. package/dist/assets/treeView-QDETBFTQ-D27HWG6Q-DhjAUXqS.js +0 -1
  189. package/dist/assets/treemap-6X3UGDF4-CmGIU3H5-C3IZTO8B.js +0 -1
  190. package/dist/assets/wardley-OPB4EBWU-CbHWsb2k-BrvClPld.js +0 -1
package/README.md CHANGED
@@ -24,7 +24,7 @@ color-coded so you see at a glance which are **working**, which **need you**, an
24
24
  ping to your phone when a task finishes. One `npx` command, no Electron, no config.
25
25
 
26
26
  ```bash
27
- npx mulmoterminal # starts on http://localhost:34567 and opens your browser
27
+ npx mulmoterminal@latest # starts on http://localhost:34567 and opens your browser
28
28
  ```
29
29
 
30
30
  > **Something looks wrong?** Type `/mulmoterminal-bug-report` in any MulmoTerminal session. The
@@ -146,17 +146,17 @@ Needs **Node ≥ 22.9**, plus these CLIs on your `PATH`:
146
146
 
147
147
  The server starts without any of the non-required rows; you just lose that row's feature,
148
148
  and the header/panel for it says so. `git` and `gh` are marked required because losing them
149
- costs whole views rather than one button. `npx mulmoterminal init` (below) reports which of
149
+ costs whole views rather than one button. `npx mulmoterminal@latest init` (below) reports which of
150
150
  these it can find.
151
151
 
152
152
  ```bash
153
- npx mulmoterminal # start on http://localhost:34567 and open the browser
153
+ npx mulmoterminal@latest # start on http://localhost:34567 and open the browser
154
154
  # or install globally:
155
155
  npm install -g mulmoterminal
156
156
  mulmoterminal
157
157
  ```
158
158
 
159
- **First-run setup (optional).** `npx mulmoterminal init` checks your environment (Node ≥ 22.9
159
+ **First-run setup (optional).** `npx mulmoterminal@latest init` checks your environment (Node ≥ 22.9
160
160
  and every CLI in the table above), seeds the launcher's **directory
161
161
  presets** from the projects in your Claude Code history, and writes `~/.mulmoterminal/config.json`.
162
162
  It's **idempotent** — re-run it any time to refresh the presets; it overwrites the managed parts
@@ -166,7 +166,7 @@ and keeps your other settings. When `claude` is installed it can hand off to the
166
166
  **Google account (optional).** Link a Google account to enable the chat's `google` tool and the
167
167
  phone's `google.calendar.*` commands: read/create events on any calendar (not just your primary),
168
168
  list the calendars you've subscribed to, and read the colour palettes. Sign in from
169
- **Settings → Google account**, or run `npx mulmoterminal google login` — the CLI is the fallback
169
+ **Settings → Google account**, or run `npx mulmoterminal@latest google login` — the CLI is the fallback
170
170
  for when you're driving MulmoTerminal from another machine, since consent finishes on a loopback
171
171
  listener and needs a browser **on the host**. Either way it needs a Desktop OAuth client JSON saved
172
172
  as `~/.secrets/client_secret_*.json`; the refresh token lands in `~/.config/mulmo/google-token.json`
@@ -200,11 +200,11 @@ directory you run the command from), `--port <n>` (default 34567), `--no-open`,
200
200
  `--version`, `--help`.
201
201
 
202
202
  ```bash
203
- npx mulmoterminal --cwd ./my-project # work in a specific directory
203
+ npx mulmoterminal@latest --cwd ./my-project # work in a specific directory
204
204
  ```
205
205
 
206
206
  The published package ships the server (run via `tsx`) plus the pre-built web UI;
207
- `npx mulmoterminal` checks for the `claude` CLI, picks a free port, starts the
207
+ `npx mulmoterminal@latest` checks for the `claude` CLI, picks a free port, starts the
208
208
  server, and opens the browser. For local development from a clone, see
209
209
  [Running](#running).
210
210
 
@@ -426,7 +426,7 @@ Requires **Node ≥ 22.9** (uses `node --env-file-if-exists`) and the `claude` C
426
426
  ## Configuration
427
427
 
428
428
  The server is configured entirely through environment variables, optionally
429
- loaded from a `.env` file. `npx mulmoterminal` reads the `.env` **in the
429
+ loaded from a `.env` file. `npx mulmoterminal@latest` reads the `.env` **in the
430
430
  directory you run it from**; the npm scripts read the one in the repo root. The
431
431
  `.env` is optional — every variable below has a default, so the server runs
432
432
  without one.
@@ -441,7 +441,7 @@ the `claude` / `codex` sessions themselves.
441
441
  | `PORT` | `34567` | Backend HTTP/WebSocket port (prod: the URL you open). |
442
442
  | `CLIENT_PORT` | `6856` | Vite dev-server port (dev only: the URL you open with `yarn dev`). |
443
443
  | `CLAUDE_BIN` | `claude` | The Claude Code binary to spawn. On Windows a bare name is resolved on `PATH` before it reaches the PTY layer (which matches file names exactly): to the `.exe` when there is one, otherwise to the `.cmd` shim an npm-global install leaves, run through `cmd.exe`. |
444
- | `CLAUDE_CWD` | current dir | Working directory each `claude` PTY runs in; determines which project's sessions the sidebar lists. Via `npx mulmoterminal` it defaults to the directory you ran the command from (override with `--cwd <dir>`, relative allowed); when the server is run directly it falls back to `~/mulmoclaude`. A value read from `.env` must be an absolute path (`~` is not expanded). |
444
+ | `CLAUDE_CWD` | current dir | Working directory each `claude` PTY runs in; determines which project's sessions the sidebar lists. Via `npx mulmoterminal@latest` it defaults to the directory you ran the command from (override with `--cwd <dir>`, relative allowed); when the server is run directly it falls back to `~/mulmoclaude`. A value read from `.env` must be an absolute path (`~` is not expanded). |
445
445
  | `CLAUDE_PERMISSION_MODE` | `auto` | Permission mode passed to each `claude` spawn. |
446
446
  | `MT_TITLE_MODEL` | `haiku` | Model used for the cell header's AI title (a cheap/fast model summarizing the recent turns). Accepts a `--model` alias or a full model id. |
447
447
  | `CODEX_BIN` | `codex` | The Codex CLI binary to spawn. |
@@ -488,6 +488,7 @@ The Settings modal (⚙) persists per-user UI choices to `~/.mulmoterminal/confi
488
488
  | `worklogIntervalHours` | Worklog cadence in hours (default `6`, clamped to `1`–`168`). |
489
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). |
490
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). |
491
+ | `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. |
491
492
  | `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
493
  | `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. |
493
494
  | `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). |
@@ -1145,6 +1146,7 @@ same-origin-guarded.
1145
1146
  | `GET /api/transcript/timeline?session=&cwd=` | Per-session activity timeline (tools run). |
1146
1147
  | `GET /api/transcript/last-turn?session=&cwd=&agent=` | A session's last completed exchange (`prompt`, `reply`) plus the `text` to paste into another terminal. `agent=codex` reads the codex rollout instead of the Claude transcript. |
1147
1148
  | `GET /api/decisions?cwd=&limit=` | The decisions a human was asked to make in this project, newest first — each question with the options it offered, their descriptions, and the answer. `answerKind` says whether the answer was one of the options, text the user wrote instead (the question was wrong), or never given. Read out of Claude's own transcripts; writes nothing. `scanned` reports how many transcripts were read (the scan is capped) and `unreadable` how many could not be, so a partial answer is visible rather than implied. A `cwd` that is not an existing directory answers an empty response rather than falling back to the default workspace. |
1149
+ | `GET /api/decisions/digest?cwd=` | The same decisions as Markdown, for an agent to read (`{ enabled, markdown }`). `enabled: false` means the `decisionDigest` setting is off — a different answer from an empty digest, so a reader can tell "switched off" from "nothing decided here". |
1148
1150
 
1149
1151
  **Git & worktrees**
1150
1152
 
@@ -1469,7 +1471,7 @@ server/
1469
1471
  mcp/ per-session MCP broker
1470
1472
  backends/ wiki, collections, feeds, accounting, notifier,
1471
1473
  translation, whisper, remote-host, html, files
1472
- skills/ bundled skills (mulmoterminal-config, mulmoterminal-bug-report)
1474
+ skills/ bundled skills (mulmoterminal-config, mulmoterminal-bug-report, mulmoterminal-decisions)
1473
1475
  fix-pty-perms.js postinstall: fixes node-pty binary permissions
1474
1476
  src/
1475
1477
  App.vue Layout; owns the active session + single/grid view
@@ -155,7 +155,10 @@ async function runInit(initArgs) {
155
155
  return;
156
156
  }
157
157
  if (hasClaude) log("Later: run `claude` in any project and use /mulmoterminal-config");
158
- log("Setup done. Start MulmoTerminal: npx mulmoterminal");
158
+ // Pinned to @latest: an unpinned `npx` reuses whatever it already has cached, so the very
159
+ // command printed for someone to type next would start an older version than the one they
160
+ // just set up with.
161
+ log("Setup done. Start MulmoTerminal: npx mulmoterminal@latest");
159
162
  }
160
163
 
161
164
  // `npx mulmoterminal google <command>` — Google account linking. Consent needs a
package/common/keymap.ts CHANGED
@@ -37,7 +37,21 @@ export const isKeymapAction = (value: unknown): value is KeymapAction => typeof
37
37
  // ^C. A handler that has already swallowed the key cannot change its mind.
38
38
  export const TERMINAL_SCOPED_ACTIONS: readonly KeymapAction[] = ["copy", "paste"];
39
39
 
40
- export type Keymap = Partial<Record<KeymapAction, string>>;
40
+ // A key that puts BYTES into the focused terminal instead of running an app action (#1005) —
41
+ // Cmd+Right as Ctrl+E for end-of-line, say, or Alt+B for word-back.
42
+ //
43
+ // A LIST, where every action above is a single field, because the two are shaped differently:
44
+ // an action is one behaviour that a key is pointed at, while every send binding carries its own
45
+ // payload. `{ "send": "\u0005" }` could only ever name one key.
46
+ //
47
+ // `bytes` goes to the PTY verbatim. Control characters are written the way JSON writes them
48
+ // (`"\u0005"` is Ctrl+E); nothing here interprets or re-escapes them.
49
+ export interface SendBinding {
50
+ key: string;
51
+ bytes: string;
52
+ }
53
+
54
+ export type Keymap = Partial<Record<KeymapAction, string>> & { send?: SendBinding[] };
41
55
 
42
56
  // A parsed binding. `key` is matched against `KeyboardEvent.key` exactly as the browser
43
57
  // reports it, so it is case-sensitive for printable characters ("a" and "A" differ, the
@@ -108,6 +122,26 @@ export function actionForKey(keymap: Keymap, e: KeymapKeyEvent): KeymapAction |
108
122
  return null;
109
123
  }
110
124
 
125
+ const isSendBinding = (value: unknown): value is SendBinding =>
126
+ typeof value === "object" && value !== null && typeof (value as SendBinding).key === "string" && typeof (value as SendBinding).bytes === "string";
127
+
128
+ // The bytes this keydown should put into the terminal, or null when it is not a send binding.
129
+ //
130
+ // Only reachable for keys the GRID did not claim: its handler listens on `window` in the capture
131
+ // phase and calls stopPropagation(), so an action and a send binding on one keystroke is not a
132
+ // race — the action always wins and the send silently never fires. validateKeymap warns about it
133
+ // for that reason.
134
+ //
135
+ // First match wins, so a keystroke listed twice uses the earlier entry.
136
+ export function sendBytesFor(keymap: Keymap, e: KeymapKeyEvent & { type: string; isComposing?: boolean }): string | null {
137
+ if (e.type !== "keydown" || e.isComposing) return null;
138
+ for (const entry of keymap.send ?? []) {
139
+ const binding = parseKeyBinding(entry.key);
140
+ if (binding && matchesBinding(binding, e)) return entry.bytes;
141
+ }
142
+ return null;
143
+ }
144
+
111
145
  // What is wrong with one `keymap` entry. `fatal` separates a typo the user clearly meant to
112
146
  // work (a binding we cannot parse — the shortcut would silently never fire) from an action
113
147
  // name we simply do not know, which is what a config written for a NEWER version looks like
@@ -127,38 +161,74 @@ export function validateKeymap(input: unknown): KeymapProblem[] {
127
161
  return [{ action: "keymap", binding: input, reason: "`keymap` must be an object of action -> key binding", fatal: true }];
128
162
  }
129
163
  const entries = Object.entries(input as Record<string, unknown>);
130
- const bound = new Map<string, { action: KeymapAction; binding: string }[]>();
164
+ // A claim on one keystroke. `rank` is DISPATCH order, so the winner can be named: every action
165
+ // outranks every send binding, because the grid's handler runs in the capture phase and stops
166
+ // the event before the terminal — see sendBytesFor.
167
+ const bound = new Map<string, Claim[]>();
168
+ const claim = (parsed: KeyBinding, entry: Claim): void => {
169
+ const key = canonicalBinding(parsed); // as PARSED: "Shift+PageUp" and "shift+pageup" are one keystroke
170
+ bound.set(key, [...(bound.get(key) ?? []), entry]);
171
+ };
131
172
  const problems = entries.flatMap(([action, binding]): KeymapProblem[] => {
173
+ if (action === "send") return sendProblems(binding, claim);
132
174
  if (!isKeymapAction(action)) {
133
- return [{ action, binding, reason: `unknown action (known: ${KEYMAP_ACTIONS.join(", ")})`, fatal: false }];
175
+ return [{ action, binding, reason: `unknown action (known: ${KEYMAP_ACTIONS.join(", ")}, send)`, fatal: false }];
134
176
  }
135
177
  if (typeof binding !== "string") return [{ action, binding, reason: "binding must be a string", fatal: true }];
136
178
  const parsed = parseKeyBinding(binding);
137
179
  if (parsed === null) {
138
180
  return [{ action, binding, reason: 'unparseable key binding — expected e.g. "PageDown" or "Shift+PageUp"', fatal: true }];
139
181
  }
140
- // Grouped as PARSED, since "Shift+PageUp" and "shift+pageup" are one keystroke.
141
- const key = canonicalBinding(parsed);
142
- bound.set(key, [...(bound.get(key) ?? []), { action, binding }]);
182
+ claim(parsed, { label: action, binding, rank: KEYMAP_ACTIONS.indexOf(action) });
143
183
  return [];
144
184
  });
145
185
  return [...problems, ...duplicateWarnings(bound)];
146
186
  }
147
187
 
148
- // Two actions on one keystroke: only one can fire, so the others silently never work.
188
+ interface Claim {
189
+ label: string;
190
+ binding: string;
191
+ rank: number;
192
+ }
193
+
194
+ // Everything wrong with the `send` list, claiming the keystrokes that are well-formed.
195
+ //
196
+ // Fatal throughout, for the reason the module header gives: a send binding is invisible until
197
+ // the key is pressed, so a dropped one is indistinguishable from a shortcut that "doesn't work".
198
+ // Empty `bytes` is fatal too — it would take the key away from the terminal and put nothing back.
199
+ function sendProblems(input: unknown, claim: (parsed: KeyBinding, entry: Claim) => void): KeymapProblem[] {
200
+ if (!Array.isArray(input)) {
201
+ return [{ action: "send", binding: input, reason: "`send` must be an array of { key, bytes }", fatal: true }];
202
+ }
203
+ return input.flatMap((entry, i): KeymapProblem[] => {
204
+ const label = `send[${i}]`;
205
+ if (!isSendBinding(entry)) return [{ action: label, binding: entry, reason: "expected { key: string, bytes: string }", fatal: true }];
206
+ const parsed = parseKeyBinding(entry.key);
207
+ if (parsed === null) {
208
+ return [{ action: label, binding: entry.key, reason: 'unparseable key binding — expected e.g. "Cmd+ArrowRight"', fatal: true }];
209
+ }
210
+ if (entry.bytes === "") {
211
+ return [{ action: label, binding: entry.key, reason: "`bytes` is empty — the key would be taken from the terminal and nothing sent", fatal: true }];
212
+ }
213
+ // Ranked after every action, matching who actually wins (see the `bound` comment above).
214
+ claim(parsed, { label, binding: entry.key, rank: KEYMAP_ACTIONS.length + i });
215
+ return [];
216
+ });
217
+ }
218
+
219
+ // Two claims on one keystroke: only one can fire, so the others silently never work.
149
220
  //
150
- // The winner is decided the way `actionForKey` decides it first in KEYMAP_ACTIONS order
151
- // NOT by which came first in the config file. Reporting the config-order winner would name
152
- // the wrong action whenever the two orders disagree, which is worse than not naming one.
153
- function duplicateWarnings(bound: Map<string, { action: KeymapAction; binding: string }[]>): KeymapProblem[] {
221
+ // The winner is the one DISPATCH picks, not the one written first in the config file. Reporting
222
+ // the config-order winner would name the wrong entry whenever the two orders disagree, which is
223
+ // worse than not naming one.
224
+ function duplicateWarnings(bound: Map<string, Claim[]>): KeymapProblem[] {
154
225
  return [...bound.values()].flatMap((claims) => {
155
226
  if (claims.length < 2) return [];
156
- const byDispatch = [...claims].sort((a, b) => KEYMAP_ACTIONS.indexOf(a.action) - KEYMAP_ACTIONS.indexOf(b.action));
157
- const [winner, ...losers] = byDispatch;
158
- return losers.map(({ action, binding }) => ({
159
- action,
227
+ const [winner, ...losers] = [...claims].sort((a, b) => a.rank - b.rank);
228
+ return losers.map(({ label, binding }) => ({
229
+ action: label,
160
230
  binding,
161
- reason: `same keystroke as \`${winner.action}\` — only \`${winner.action}\` will fire`,
231
+ reason: `same keystroke as \`${winner.label}\` — only \`${winner.label}\` will fire`,
162
232
  fatal: false,
163
233
  }));
164
234
  });
@@ -172,8 +242,16 @@ const canonicalBinding = (b: KeyBinding): string => `${b.shift ? "S" : ""}${b.al
172
242
  // rest of the config treats one bad entry.
173
243
  export function sanitizeKeymap(input: unknown): Keymap {
174
244
  if (typeof input !== "object" || input === null || Array.isArray(input)) return {};
175
- const entries = Object.entries(input as Record<string, unknown>).filter(
245
+ const raw = input as Record<string, unknown>;
246
+ const entries = Object.entries(raw).filter(
176
247
  (entry): entry is [KeymapAction, string] => isKeymapAction(entry[0]) && typeof entry[1] === "string" && parseKeyBinding(entry[1]) !== null,
177
248
  );
178
- return Object.fromEntries(entries);
249
+ const send = sanitizeSendBindings(raw.send);
250
+ return { ...Object.fromEntries(entries), ...(send.length ? { send } : {}) };
179
251
  }
252
+
253
+ // An entry survives only if it names a parseable key AND carries bytes to send. An empty `send`
254
+ // is dropped entirely rather than kept as `[]`, so an absent and an emptied list look the same
255
+ // to everything downstream.
256
+ const sanitizeSendBindings = (input: unknown): SendBinding[] =>
257
+ Array.isArray(input) ? input.filter(isSendBinding).filter((entry) => entry.bytes !== "" && parseKeyBinding(entry.key) !== null) : [];
package/common/prPhase.ts CHANGED
@@ -20,9 +20,19 @@ export interface WorkItem {
20
20
  prUrl: string | null;
21
21
  issue: number | null;
22
22
  issueUrl: string | null;
23
+ // Titles, for the surfaces that have room for words rather than a number — the phone's session
24
+ // list above all, where "#987" alone says nothing about which request is running (#1014).
25
+ prTitle: string | null;
26
+ issueTitle: string | null;
23
27
  }
24
28
 
25
- export const EMPTY_WORK_ITEM: Readonly<WorkItem> = { phase: "none", pr: null, prUrl: null, issue: null, issueUrl: null };
29
+ export const EMPTY_WORK_ITEM: Readonly<WorkItem> = { phase: "none", pr: null, prUrl: null, issue: null, issueUrl: null, prTitle: null, issueTitle: null };
30
+
31
+ // One line for a surface with room for one: what the work is FOR beats what was done about it, so
32
+ // the issue's title wins when there is one. Null when neither side has a title to show.
33
+ export function workItemHeadline(item: WorkItem): string | null {
34
+ return item.issueTitle ?? item.prTitle ?? null;
35
+ }
26
36
 
27
37
  // GitHub's closing keywords, as GitHub itself matches them: any of these, then optional
28
38
  // whitespace/colon, then #N. Anything else in the body is a mention, not a link — a PR that
@@ -0,0 +1,98 @@
1
+ // The GUI MCP is served at one URL per GROUP of tools, so a user can turn a subset on for a
2
+ // directory with Claude Code's own per-folder MCP config (`.mcp.json` / `claude mcp add -s
3
+ // local`) instead of a MulmoTerminal setting. `.mcp.json` only switches whole SERVERS on and
4
+ // off — splitting the URL is what turns that into tool granularity.
5
+ //
6
+ // These names appear in the URL path AND in the server id users write in their own config
7
+ // (`mulmoterminal-<group>`), so they are effectively public API: renaming one breaks every
8
+ // config already written against it.
9
+
10
+ // The groups, ordered by how much damage a call can do.
11
+ // render — draws into the Canvas panel and stops there. No side effect outside it.
12
+ // data — reads/writes the workspace's structured data (collections, accounting).
13
+ // media — generation that is slow, costly, and lands files on disk.
14
+ // external — reaches a third-party account or API.
15
+ export const TOOL_GROUPS = ["render", "data", "media", "external"] as const;
16
+
17
+ export type ToolGroup = (typeof TOOL_GROUPS)[number];
18
+
19
+ export const isToolGroup = (value: unknown): value is ToolGroup => TOOL_GROUPS.some((group) => group === value);
20
+
21
+ // The groups the Canvas pane is made of. The launcher offers a switch per group, the panel's
22
+ // availability is decided by them, and the server routes on them — named here rather than
23
+ // written as `"render"` / `"media"` at each of those sites, so a rename cannot leave one of them
24
+ // silently pointing at a group that no longer exists.
25
+ //
26
+ // `media` is in because its tools DRAW: generateImage and presentMulmoScript land in the same
27
+ // panel a render tool does. It is a separate switch rather than part of one because the two
28
+ // differ in what a call costs — render stops at the pane, media is slow, paid and writes files —
29
+ // and that is exactly the line the grouping exists to draw. Neither media tool is in
30
+ // AUTO_ALLOWED_TOOLS, so enabling the group still leaves Claude Code's permission prompt in
31
+ // front of the spend (same reasoning as presentDocument below).
32
+ export const CANVAS_TOOL_GROUPS: readonly ToolGroup[] = ["render", "media"];
33
+
34
+ // Does a session/directory reach the Canvas at all? Asked of the group list the server reports,
35
+ // whose members arrive as plain strings — validated rather than cast, since an unknown name from
36
+ // a newer server must not count as a canvas group.
37
+ export const hasCanvasGroup = (groups: unknown): boolean =>
38
+ Array.isArray(groups) && groups.some((group) => isToolGroup(group) && CANVAS_TOOL_GROUPS.includes(group));
39
+
40
+ // Which group each GUI tool belongs to.
41
+ //
42
+ // A tool that is absent belongs to NO group and is therefore reachable only through the
43
+ // all-tools URL (`/api/mcp/:sessionId`, the single view). That is the deliberate failure
44
+ // mode: forgetting to classify a newly added plugin withholds it from the group URLs rather
45
+ // than leaking it into one — the map can go stale, but only ever closed.
46
+ //
47
+ // `spawnBackgroundChat` is absent ON PURPOSE, not by omission: it starts another session,
48
+ // which is neither drawing, data, media, nor an external call, and a grid cell has no
49
+ // business doing it silently.
50
+ // A Map, not a plain object — the same reason the plugin dispatch map is one: object index
51
+ // access reads through the prototype chain, so `constructor` / `__proto__` / `toString` would
52
+ // resolve to an Object.prototype member and report a truthy "group". Map.get only ever returns
53
+ // own entries.
54
+ const GROUP_BY_TOOL = new Map<string, ToolGroup>([
55
+ ["presentDocument", "render"],
56
+ ["presentForm", "render"],
57
+ ["presentChart", "render"],
58
+ ["presentHtml", "render"],
59
+
60
+ // presentCollection RENDERS, but it renders collection data and only makes sense next to
61
+ // manageCollection — a cell offered the view without the store gets a tool it cannot fill.
62
+ ["presentCollection", "data"],
63
+ ["manageCollection", "data"],
64
+ ["manageAccounting", "data"],
65
+
66
+ ["generateImage", "media"],
67
+ ["presentMulmoScript", "media"],
68
+
69
+ ["google", "external"],
70
+ ["readXPost", "external"],
71
+ ["searchX", "external"],
72
+ ]);
73
+
74
+ export const groupOfTool = (toolName: string): ToolGroup | null => GROUP_BY_TOOL.get(toolName) ?? null;
75
+
76
+ // Every tool in a group, in the order declared above. The Canvas panel's empty state names them,
77
+ // so a tool added to a group here reaches that list without a second edit — the list had been
78
+ // written out by hand and named two of the four.
79
+ export const toolsInGroup = (group: ToolGroup): string[] => [...GROUP_BY_TOOL].filter(([, g]) => g === group).map(([name]) => name);
80
+
81
+ // The MCP server id a group is expected to be registered under. `--allowedTools` matches on
82
+ // `mcp__<server id>__<tool>`, and the id comes from the USER's config key — so this is a
83
+ // convention the enable-it-for-this-folder affordance has to write, and a user who registers
84
+ // the same URL under another name simply gets permission prompts (nothing breaks).
85
+ export const toolGroupServerId = (group: ToolGroup): string => `mulmoterminal-${group}`;
86
+
87
+ // The tools MulmoTerminal pre-approves via `--allowedTools`, so they run without a permission
88
+ // prompt. A list of TOOLS, not of groups: a group says which tools a directory can reach, and
89
+ // that is not the same question as which may run unattended.
90
+ //
91
+ // `presentDocument` is the case that forces them apart, and it is deliberately ABSENT. Its
92
+ // execute runs `fillImages` before saving, which resolves every image placeholder in the
93
+ // markdown through the image backend — a PAID generation call. Auto-allowing it would let a
94
+ // model spend money silently under a switch the UI presents as "let the agent draw", so it
95
+ // keeps Claude Code's prompt (answer it once per project and the prompt stops).
96
+ //
97
+ // The three below save an artifact and draw it, and call nothing external.
98
+ export const AUTO_ALLOWED_TOOLS: readonly string[] = ["presentForm", "presentChart", "presentHtml"];
@@ -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-DvKDRZGq.js";import"./chunk-WYO6CB5R-BlzOfotS-DenoOpP3.js";import"./chunk-VAUOI2AC-CS9QJ4yz-r_1UiH65.js";import{n as i,r as a,t as o}from"./chunk-MOJQB5TN-CMZRaeqt-DDn-QIzL.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-3f1dQMCy.js";import"./chunk-WYO6CB5R-BlzOfotS-cPQQuq_p.js";import"./chunk-VAUOI2AC-CS9QJ4yz-xhVI0AT_.js";import{n as i,r as a,t as o}from"./chunk-MOJQB5TN-CMZRaeqt-DvOpObYg.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-3f1dQMCy.js";export{e as createArchitectureServices};