mulmoterminal 2.7.0 → 2.9.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 (206) hide show
  1. package/README.md +110 -29
  2. package/common/bundledSkills.ts +31 -0
  3. package/common/fileWriteChannel.ts +6 -0
  4. package/common/launchAgent.ts +1 -1
  5. package/common/modelPresets.ts +1 -1
  6. package/common/pastedImageTypes.ts +23 -0
  7. package/common/sessionAgent.ts +35 -1
  8. package/common/terminalSubmit.ts +29 -0
  9. package/dist/assets/{abnfDiagram-VRR7QNED-RjiYivmv-i7KLnuI-.js → abnfDiagram-VRR7QNED-RjiYivmv-FRF8kLeT.js} +1 -1
  10. package/dist/assets/architecture-TIHT7OUA-0NsD2xW1-DrblApEd.js +1 -0
  11. package/dist/assets/{architectureDiagram-ZJ3FMSHR-3nWA91tG-CL3Or8CZ.js → architectureDiagram-ZJ3FMSHR-3nWA91tG-D1pPQlOf.js} +1 -1
  12. package/dist/assets/{blockDiagram-677ZJIJ3-BPuAJQRW-DFu8zII3.js → blockDiagram-677ZJIJ3-BPuAJQRW-d5-ljc-N.js} +1 -1
  13. package/dist/assets/{c4Diagram-LMCZKHZV-C0LAqQso-CByG3jFJ.js → c4Diagram-LMCZKHZV-C0LAqQso-BGlEwCQt.js} +1 -1
  14. package/dist/assets/channel-7wUqSdoX-BgZgYLWN.js +1 -0
  15. package/dist/assets/{chunk-32BRIVSS-BRrYpgtb-Dc3x0Hgh.js → chunk-32BRIVSS-BRrYpgtb-SXiKhjnH.js} +1 -1
  16. package/dist/assets/{chunk-52WLFC77-BJ-ss3Xr-D6efVaUm.js → chunk-52WLFC77-BJ-ss3Xr-DKigFAu-.js} +1 -1
  17. package/dist/assets/{chunk-C7G6YPKG-BZEucKEL-CUdIiVHU.js → chunk-C7G6YPKG-BZEucKEL-Cb5wXOXv.js} +1 -1
  18. package/dist/assets/{chunk-EX3LRPZG-DLS6FBN1-BH94eoNV.js → chunk-EX3LRPZG-DLS6FBN1-CzTNcARU.js} +1 -1
  19. package/dist/assets/{chunk-FWX5IMBZ-DHLSFw1H-Dw8tAp6e.js → chunk-FWX5IMBZ-DHLSFw1H-BZvgg4po.js} +2 -2
  20. package/dist/assets/{chunk-HOUHSVGY-Bhlt8hXJ-CEurvfOt.js → chunk-HOUHSVGY-Bhlt8hXJ-DL5Y_3yg.js} +1 -1
  21. package/dist/assets/{chunk-ICXQ74PX-DwgHBX_g-DKrDQRbg.js → chunk-ICXQ74PX-DwgHBX_g-BDvSo2KT.js} +1 -1
  22. package/dist/assets/{chunk-MOJQB5TN-CMZRaeqt-Ch-WBypm.js → chunk-MOJQB5TN-CMZRaeqt-BPbi0qBZ.js} +1 -1
  23. package/dist/assets/{chunk-OGEWGWER-8Qy4a8b5-BfJ1_Ye1.js → chunk-OGEWGWER-8Qy4a8b5-BmITYHk7.js} +1 -1
  24. package/dist/assets/{chunk-PUDLZKDR-DcrWQRYh-NggLAE5k.js → chunk-PUDLZKDR-DcrWQRYh-CXK8yMcj.js} +1 -1
  25. package/dist/assets/{chunk-Q4XR5HBZ-ZXVGkG8Z-DmtrERIM.js → chunk-Q4XR5HBZ-ZXVGkG8Z-SCGIXxMq.js} +1 -1
  26. package/dist/assets/{chunk-V7JOEXUC-DmGdheTX-C8lSwSR3.js → chunk-V7JOEXUC-DmGdheTX-gZgY507i.js} +1 -1
  27. package/dist/assets/{chunk-VAUOI2AC-CS9QJ4yz-DNlwzQL1.js → chunk-VAUOI2AC-CS9QJ4yz-BQv37MCq.js} +1 -1
  28. package/dist/assets/{chunk-VR4S4FIN-C6a91eNY-DX2Te6ga.js → chunk-VR4S4FIN-C6a91eNY-ZNRLU9H7.js} +1 -1
  29. package/dist/assets/{chunk-WYO6CB5R-BlzOfotS-BAOXGFjf.js → chunk-WYO6CB5R-BlzOfotS-Bx9N57Se.js} +1 -1
  30. package/dist/assets/{chunk-ZGVPDNZ5-BYwxNFTK-CS40JTYJ.js → chunk-ZGVPDNZ5-BYwxNFTK-Bg7w-yJo.js} +1 -1
  31. package/dist/assets/classDiagram-OUVF2IWQ-BYlRi6YQ-0zi7Cgn2.js +1 -0
  32. package/dist/assets/classDiagram-v2-EOCWNBFH-BCHaYHxj-0zi7Cgn2.js +1 -0
  33. package/dist/assets/cynefin-VYW2F7L2-CGYr2F6h-Bi7e0oXc.js +1 -0
  34. package/dist/assets/{cynefinDiagram-TSTJHNR4-DHA9iPo--DUt8F5SJ.js → cynefinDiagram-TSTJHNR4-DHA9iPo--abCAh98z.js} +1 -1
  35. package/dist/assets/{dagre-VKFMJZFB--oJKqXBZ-BhcFRdM7.js → dagre-VKFMJZFB--oJKqXBZ-CA9PU3rn.js} +1 -1
  36. package/dist/assets/{diagram-FQU43EPY-CcJCB9bG-CBj7RNID.js → diagram-FQU43EPY-CcJCB9bG-BgmurMGW.js} +1 -1
  37. package/dist/assets/{diagram-G47NLZAW-C_o-WGG1-DCAfQJQ2.js → diagram-G47NLZAW-C_o-WGG1-CZ6ymH57.js} +1 -1
  38. package/dist/assets/{diagram-NH7WQ7WH-CXJCYvY--133061sd.js → diagram-NH7WQ7WH-CXJCYvY--5Ej1o0wj.js} +1 -1
  39. package/dist/assets/{diagram-OA4YK3LP-BMzeJ87A-DPt25PXB.js → diagram-OA4YK3LP-BMzeJ87A-Br_Yz55A.js} +1 -1
  40. package/dist/assets/{diagram-WEI45ONY-D_93NKqo-Bb2DJ3v3.js → diagram-WEI45ONY-D_93NKqo-C2UMN3qi.js} +1 -1
  41. package/dist/assets/{dist-CDpjkjbB.js → dist-C8-kYY50.js} +1 -1
  42. package/dist/assets/{dist-BmZVepIx.js → dist-DOaExQFB.js} +1 -1
  43. package/dist/assets/{dist-4eLWDv4V.js → dist-_cuIeZ2Q.js} +1 -1
  44. package/dist/assets/{dist-Do1AxSN7.js → dist-nsS3Kg7p.js} +1 -1
  45. package/dist/assets/{ebnfDiagram-CCIWWBDH-CVai1Ii9-CqvyVeoa.js → ebnfDiagram-CCIWWBDH-CVai1Ii9-DHqre3sw.js} +1 -1
  46. package/dist/assets/{erDiagram-Q63AITRT-CiABnA0s-jrNyKW5P.js → erDiagram-Q63AITRT-CiABnA0s-BO4koo_A.js} +1 -1
  47. package/dist/assets/eventmodeling-45OFAUF4-CWmqhm3a-BRn6nrMK.js +1 -0
  48. package/dist/assets/flowDiagram-23GEKE2U-DKSB5AiY-CnFfxuAw.js +1 -0
  49. package/dist/assets/{ganttDiagram-NO4QXBWP-DQZvdFo1-Dz1MAsy4.js → ganttDiagram-NO4QXBWP-DQZvdFo1-BpNj4GGQ.js} +1 -1
  50. package/dist/assets/gitGraph-TEB2WS4Q-BR9qPwdN-65Nmo9BB.js +1 -0
  51. package/dist/assets/{gitGraphDiagram-IHSO6WYX-C2ovBouh-CV3qv2yf.js → gitGraphDiagram-IHSO6WYX-C2ovBouh-C-DD21zp.js} +1 -1
  52. package/dist/assets/index-Fs9P1rOl.css +1 -0
  53. package/dist/assets/index-jKUTVgQT.js +616 -0
  54. package/dist/assets/info-DKCQHKI2-Dplx5kMp-BRtvzheB.js +1 -0
  55. package/dist/assets/{infoDiagram-FWYZ7A6U-7UnoB5AP-BZUR4_mA.js → infoDiagram-FWYZ7A6U-7UnoB5AP-Cjtq7aHi.js} +1 -1
  56. package/dist/assets/{ishikawaDiagram-FXEZZL3T-ByUDM_N2-BLGvRDOX.js → ishikawaDiagram-FXEZZL3T-ByUDM_N2-CJ7Dm1DX.js} +1 -1
  57. package/dist/assets/{journeyDiagram-5HDEW3XC-c5xIah9o-D9zqo5k7.js → journeyDiagram-5HDEW3XC-c5xIah9o-DmEyfShK.js} +1 -1
  58. package/dist/assets/{kanban-definition-HUTT4EX6-Cnt6loYD-BwfjKxOx.js → kanban-definition-HUTT4EX6-Cnt6loYD-CEonRig7.js} +1 -1
  59. package/dist/assets/{lib-BWq7d2p0.js → lib-BP6_kjsB.js} +1 -1
  60. package/dist/assets/{line-D7ziSjKi-ZOy-21z8.js → line-D7ziSjKi-ZN0lN31b.js} +1 -1
  61. package/dist/assets/{marp-IdFBx6Kk.js → marp-CUPxjfPv.js} +1 -1
  62. package/dist/assets/{mermaid-parser.core-DxEa8E3F-C_pDG6kL.js → mermaid-parser.core-DxEa8E3F-C7AoyBVj.js} +2 -2
  63. package/dist/assets/{mermaid.core-V0OYwIz3--tUcNILl.js → mermaid.core-V0OYwIz3-CXLOt97m.js} +3 -3
  64. package/dist/assets/{mindmap-definition-LN4V7U3C-CexN3O6L-B08uU8xg.js → mindmap-definition-LN4V7U3C-CexN3O6L-DOiqhmPh.js} +1 -1
  65. package/dist/assets/packet-7NZHBO7P-CQI3flND-CmU_E5Rs.js +1 -0
  66. package/dist/assets/{pegDiagram-2B236MQR-BiM4G0cW-CAmtHOOs.js → pegDiagram-2B236MQR-BiM4G0cW-Dxt7poMV.js} +1 -1
  67. package/dist/assets/pie-RZYD4A2V-CnmKE2Wa-CZEE10z2.js +1 -0
  68. package/dist/assets/{pieDiagram-ENE6RG2P-B_fBBS-2-1Te1k2OW.js → pieDiagram-ENE6RG2P-B_fBBS-2-COQUiiOs.js} +1 -1
  69. package/dist/assets/{quadrantDiagram-ABIIQ3AL-BigGVxCR-Bwmyhd3u.js → quadrantDiagram-ABIIQ3AL-BigGVxCR-B-eWI2XH.js} +1 -1
  70. package/dist/assets/radar-I7S5WNFK-us6x-Z9R-r15uJDA8.js +1 -0
  71. package/dist/assets/railroad-3IZDKUUU-BKdz6pdN-Dw_fxGlH.js +1 -0
  72. package/dist/assets/railroad-abnf-AHOZXSZD-m_CSSIfd-CKyTy_kj.js +1 -0
  73. package/dist/assets/railroad-ebnf-EBAXGLYW-Nu3nAG7C-DKG1VzHP.js +1 -0
  74. package/dist/assets/railroad-peg-LSFZ7HO6--Y3mD4R_-DfP1SMYD.js +1 -0
  75. package/dist/assets/{railroadDiagram-RFXS5EU6--lNIlG64-B7viF_TQ.js → railroadDiagram-RFXS5EU6--lNIlG64-DeaxglIg.js} +1 -1
  76. package/dist/assets/{requirementDiagram-TGXJPOKE-D-B0Y5Wu-C5VA5j5p.js → requirementDiagram-TGXJPOKE-D-B0Y5Wu-1CMezKi3.js} +1 -1
  77. package/dist/assets/{sankeyDiagram-HTMAVEWB-DlutaXDv-DfD-musw.js → sankeyDiagram-HTMAVEWB-DlutaXDv-D7DwjyX1.js} +1 -1
  78. package/dist/assets/{sequenceDiagram-DBY2YBRQ-BOLgtggH-DeYKXJK9.js → sequenceDiagram-DBY2YBRQ-BOLgtggH-CsJUqii0.js} +1 -1
  79. package/dist/assets/{stateDiagram-2N3HPSRC-jf_dXUEU-Bq6BSHXK.js → stateDiagram-2N3HPSRC-jf_dXUEU-DzQL5WUm.js} +1 -1
  80. package/dist/assets/stateDiagram-v2-6OUMAXLB-CuzgCL2v-CVaAK8RL.js +1 -0
  81. package/dist/assets/{swimlanes-5IMT3BWC-CYjtALQH-BwKcwh8j.js → swimlanes-5IMT3BWC-CYjtALQH-C0XGpuvl.js} +1 -1
  82. package/dist/assets/swimlanesDiagram-G3AALYLV-3bmSBtKs-Dgs9N-of.js +8 -0
  83. package/dist/assets/{timeline-definition-FHXFAJF6-DYJ4oUm8-D5NlLypk.js → timeline-definition-FHXFAJF6-DYJ4oUm8-CV7c2zBI.js} +1 -1
  84. package/dist/assets/treeView-QDETBFTQ-D27HWG6Q-DBE7pPBH.js +1 -0
  85. package/dist/assets/treemap-6X3UGDF4-CmGIU3H5-DTrYIIrW.js +1 -0
  86. package/dist/assets/{vennDiagram-L72KCM5P-CdKAHoek-BAq00OzF.js → vennDiagram-L72KCM5P-CdKAHoek-D3MiyDV3.js} +1 -1
  87. package/dist/assets/wardley-OPB4EBWU-CbHWsb2k-JRYl6PcW.js +1 -0
  88. package/dist/assets/{wardleyDiagram-EHGQE667-BF6c4_CW-Dvh8PYFx.js → wardleyDiagram-EHGQE667-BF6c4_CW-CJynIPoh.js} +1 -1
  89. package/dist/assets/{xychartDiagram-FW5EYKEG-CGiKngj7-C2sC6_Lu.js → xychartDiagram-FW5EYKEG-CGiKngj7-Co6wcyGy.js} +1 -1
  90. package/dist/index.html +2 -2
  91. package/package.json +14 -8
  92. package/server/agents/agent-resume.ts +27 -0
  93. package/server/agents/antigravity-args.ts +39 -0
  94. package/server/agents/antigravity-mcp.ts +122 -0
  95. package/server/agents/antigravity-session.ts +68 -0
  96. package/server/agents/antigravity.ts +7 -0
  97. package/server/agents/rate-limit-probe.spec.ts +55 -2
  98. package/server/agents/rate-limit-probe.ts +6 -1
  99. package/server/agents/registry.ts +2 -0
  100. package/server/agents/types.ts +1 -1
  101. package/server/backends/feed-summary.ts +1 -1
  102. package/server/backends/google.ts +3 -1
  103. package/server/backends/remoteHost/collectionPage.ts +10 -4
  104. package/server/backends/remoteHost/getFeed.spec.ts +2 -1
  105. package/server/backends/remoteHost/handlers/deps.ts +43 -0
  106. package/server/backends/remoteHost/handlers/getCollection.ts +17 -0
  107. package/server/backends/remoteHost/handlers/getFeed.ts +22 -0
  108. package/server/backends/remoteHost/handlers/getRemoteView.ts +18 -0
  109. package/server/backends/remoteHost/handlers/getRemoteViewItems.ts +27 -0
  110. package/server/backends/remoteHost/handlers/index.ts +62 -0
  111. package/server/backends/remoteHost/handlers/listAccountingBooks.ts +12 -0
  112. package/server/backends/remoteHost/handlers/listCollections.ts +11 -0
  113. package/server/backends/remoteHost/handlers/listFeeds.ts +18 -0
  114. package/server/backends/remoteHost/handlers/listShortcuts.ts +10 -0
  115. package/server/backends/remoteHost/handlers/listSkills.ts +16 -0
  116. package/server/backends/remoteHost/handlers/mutateRemoteView.ts +29 -0
  117. package/server/backends/remoteHost/handlers/startChat.ts +51 -0
  118. package/server/backends/remoteHost/handlers/terminalSession.ts +63 -0
  119. package/server/backends/remoteHost/handlers.spec.ts +2 -1
  120. package/server/backends/remoteHost/index.ts +2 -1
  121. package/server/backends/remoteHost/launchTerminal.ts +2 -2
  122. package/server/backends/remoteHost/routes.spec.ts +13 -1
  123. package/server/backends/remoteHost/routes.ts +6 -3
  124. package/server/backends/remoteHost/terminalInput.spec.ts +166 -19
  125. package/server/backends/remoteHost/terminalInput.ts +24 -2
  126. package/server/backends/remoteHost/terminalScreen.ts +59 -2
  127. package/server/backends/remoteView.ts +1 -1
  128. package/server/config/header-config.ts +1 -1
  129. package/server/config/header-context.ts +1 -1
  130. package/server/config/workspace.ts +45 -15
  131. package/server/files/dirRequest.ts +2 -1
  132. package/server/files/pick-file.ts +7 -2
  133. package/server/files/win-folder-dialog.ts +3 -0
  134. package/server/files/win-powershell-utf8.ts +17 -0
  135. package/server/git/worktree-routes.ts +5 -4
  136. package/server/index.ts +58 -15
  137. package/server/infra/install-bundled-skills.ts +7 -8
  138. package/server/infra/tmux-routes.ts +3 -2
  139. package/server/infra/tmux.ts +65 -1
  140. package/server/mcp/bridge.mjs +66 -0
  141. package/server/mcp/broker.ts +43 -8
  142. package/server/mcp/gui-call-history.ts +96 -0
  143. package/server/routes/app-routes.ts +33 -6
  144. package/server/routes/dir-routes.ts +30 -22
  145. package/server/routes/gui-mcp-routes.ts +6 -0
  146. package/server/routes/mcp-routes.ts +41 -1
  147. package/server/routes/plugin-routes.ts +14 -4
  148. package/server/routes/routeParams.ts +23 -3
  149. package/server/routes/same-origin-guard.ts +17 -3
  150. package/server/routes/session-routes.ts +13 -11
  151. package/server/routes/terminal-ws-path.ts +2 -1
  152. package/server/routes/tool-routes.ts +14 -4
  153. package/server/routes/ws-routes.ts +159 -39
  154. package/server/session/antigravity-conversations.ts +71 -0
  155. package/server/session/background-chat.ts +24 -10
  156. package/server/session/command-summary.ts +2 -1
  157. package/server/session/cost.ts +6 -3
  158. package/server/session/draft-injection.ts +34 -8
  159. package/server/session/last-turn.ts +0 -4
  160. package/server/session/lifecycle.ts +4 -0
  161. package/server/session/pty-connection.ts +24 -0
  162. package/server/session/pty-relay.ts +35 -0
  163. package/server/session/registry.ts +66 -0
  164. package/server/session/session-reads.ts +4 -7
  165. package/server/session/spawn-antigravity.ts +85 -0
  166. package/server/session/spawn-claude.ts +12 -4
  167. package/server/session/spawn-codex.ts +3 -19
  168. package/server/session/spawn-deps.ts +2 -0
  169. package/server/session/spawners.ts +2 -0
  170. package/server/session/task-push.ts +2 -2
  171. package/server/session/tmux-size-sync.ts +182 -0
  172. package/server/session/tool-store.ts +11 -5
  173. package/server/session/types.ts +4 -0
  174. package/server/skills/mulmoterminal-config/SKILL.md +110 -521
  175. package/server/skills/mulmoterminal-dirs/SKILL.md +227 -0
  176. package/server/skills/{mulmoterminal-config → mulmoterminal-dirs}/palettes.json +1 -1
  177. package/server/skills/mulmoterminal-header/SKILL.md +165 -0
  178. package/server/skills/mulmoterminal-keys/SKILL.md +153 -0
  179. package/server/skills/mulmoterminal-model/SKILL.md +102 -0
  180. package/server/skills/mulmoterminal-notify/SKILL.md +124 -0
  181. package/server/skills/mulmoterminal-theme/SKILL.md +128 -0
  182. package/dist/assets/architecture-TIHT7OUA-0NsD2xW1-DRxyAOZm.js +0 -1
  183. package/dist/assets/channel-7wUqSdoX-CryXErAP.js +0 -1
  184. package/dist/assets/classDiagram-OUVF2IWQ-BYlRi6YQ-Bh9HKUjT.js +0 -1
  185. package/dist/assets/classDiagram-v2-EOCWNBFH-BCHaYHxj-Bh9HKUjT.js +0 -1
  186. package/dist/assets/cynefin-VYW2F7L2-CGYr2F6h-CNhjzHRd.js +0 -1
  187. package/dist/assets/eventmodeling-45OFAUF4-CWmqhm3a-BzmuvEpa.js +0 -1
  188. package/dist/assets/flowDiagram-23GEKE2U-DKSB5AiY-D_olhK5u.js +0 -1
  189. package/dist/assets/gitGraph-TEB2WS4Q-BR9qPwdN-6E2-f_ab.js +0 -1
  190. package/dist/assets/index-CbJGZlpM.js +0 -616
  191. package/dist/assets/index-Zds0SlQi.css +0 -1
  192. package/dist/assets/info-DKCQHKI2-Dplx5kMp-DxJGF13X.js +0 -1
  193. package/dist/assets/packet-7NZHBO7P-CQI3flND-C1R5Sk6K.js +0 -1
  194. package/dist/assets/pie-RZYD4A2V-CnmKE2Wa-fnUCv-1K.js +0 -1
  195. package/dist/assets/radar-I7S5WNFK-us6x-Z9R-DfxsmsH_.js +0 -1
  196. package/dist/assets/railroad-3IZDKUUU-BKdz6pdN-Cc9H2ZqM.js +0 -1
  197. package/dist/assets/railroad-abnf-AHOZXSZD-m_CSSIfd-CedxYTf_.js +0 -1
  198. package/dist/assets/railroad-ebnf-EBAXGLYW-Nu3nAG7C-DFUbcN03.js +0 -1
  199. package/dist/assets/railroad-peg-LSFZ7HO6--Y3mD4R_-7OkZDahF.js +0 -1
  200. package/dist/assets/stateDiagram-v2-6OUMAXLB-CuzgCL2v-cQTUIwVP.js +0 -1
  201. package/dist/assets/swimlanesDiagram-G3AALYLV-3bmSBtKs-SzBmSlrT.js +0 -8
  202. package/dist/assets/treeView-QDETBFTQ-D27HWG6Q-mArgqXY_.js +0 -1
  203. package/dist/assets/treemap-6X3UGDF4-CmGIU3H5-DcjdNdM5.js +0 -1
  204. package/dist/assets/wardley-OPB4EBWU-CbHWsb2k-C9OSQ_B-.js +0 -1
  205. package/server/agents/codex-resume.ts +0 -26
  206. package/server/backends/remoteHost/handlers.ts +0 -311
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # mulmoterminal
2
2
 
3
- **Run a whole team of coding agents from your browser — and actually keep up with them.**
3
+ **Run multiple Claude Code and Codex sessions in parallel — and see which one needs you.**
4
4
 
5
5
  A **browser terminal** for **parallel AI coding agents**: several **Claude Code** and **Codex**
6
6
  sessions side by side, each in its own cell, with the one that needs you marked in colour. Vibe
@@ -14,6 +14,9 @@ and a **phone push** reaches you when a turn finishes.
14
14
  view, everyday workflows, the full feature list, configuration, and mobile push notifications.
15
15
  - **ユーザーガイド:** [日本語](https://receptron.github.io/mulmoterminal/guide/ja/) —
16
16
  グリッドの使い方・日々のワークフロー・機能一覧・設定・スマホ通知の設定はこちら。
17
+ - **Updates / アップデート情報:** new releases and features are announced **in Japanese** on X —
18
+ 新バージョンや新機能のお知らせは X の
19
+ [Singularity Society (@SingularitySoci)](https://x.com/SingularitySoci) で。
17
20
 
18
21
  ![MulmoTerminal — a grid of live Claude Code sessions, each color-coded by state, updating in real time](https://raw.githubusercontent.com/receptron/mulmoterminal/main/docs/guide/images/hero.gif)
19
22
 
@@ -71,15 +74,19 @@ npx mulmoterminal@latest # starts on http://localhost:34567 and opens you
71
74
 
72
75
  *Zoomed in, the **cockpit roster** replaces thumbnails with information: every session as a
73
76
  text row — directory, AI summary, your last prompt, the agent's latest reply, a status word,
74
- and the branch's **PR phase** badge. Click a row to swap the enlarged terminal.*
77
+ and the branch's **PR phase** badge. A row whose agent is **waiting on you** rings amber and
78
+ blinks; one that has merely **finished** rings green and stays still (Settings → Waiting rows
79
+ turns the movement off). Click a row to swap the enlarged terminal.*
75
80
 
76
81
  ### What it is, under the hood
77
82
 
78
83
  Each session runs as a real PTY on the server (the agent CLI in a pseudo-terminal) and is
79
84
  streamed to an [xterm.js](https://xtermjs.org/) terminal in the browser over a WebSocket. A
80
85
  sidebar lists every session for the project and reflects, in real time, which are **working**
81
- (the agent is thinking) and which **need attention** (waiting for input, or finished with
82
- output you haven't seen) driven by Claude/Codex activity hooks the server injects per spawn.
86
+ (the agent is thinking, a spinner), which are **waiting on you** (a permission prompt or a
87
+ question an amber dot; nothing proceeds until you answer) and which are **finished with output
88
+ you haven't seen** (a green dot) — driven by Claude/Codex activity hooks the server injects per
89
+ spawn. The horizontal tab bar carries the same two dots.
83
90
 
84
91
  ![Single view — one agent in focus, terminal on the left and a GUI panel on the right](https://raw.githubusercontent.com/receptron/mulmoterminal/main/docs/guide/images/single-view.png)
85
92
 
@@ -101,6 +108,17 @@ when the session ends, and any left by a crash are swept at the next start. Up t
101
108
  file — the same ceiling as a phone attachment. **A session already running when you upgrade
102
109
  was launched without that grant**, so drops into it still prompt; new sessions don't.
103
110
 
111
+ **Pasting a screenshot** — take a screenshot and paste it straight into the terminal
112
+ (`Cmd`/`Ctrl`+`V`). The image is saved to the session's own drop directory — the same place a
113
+ dropped file goes, with the same grant, the same 110 MiB ceiling and the same cleanup when the
114
+ session ends — and its **absolute path** is inserted at the cursor, so the agent can read it.
115
+ Unlike a drop, this does not need the browser to expose a path — the bytes are on the
116
+ clipboard — so it also covers Chrome, where dropping a file cannot insert a path. It works
117
+ wherever the browser puts the image on the clipboard as `image/png`, `image/jpeg`,
118
+ `image/gif`, or `image/webp`. Anything else is left to the terminal's own paste handling,
119
+ exactly as before — including a paste that carries **plain text** next to the image, which
120
+ copying from a web page usually does, so that pasting text keeps working.
121
+
104
122
  <a id="clicking-a-file-path"></a>
105
123
 
106
124
  **Clicking a file path** — the other direction. A path an agent *prints* becomes a link, and
@@ -215,7 +233,10 @@ and every CLI in the table above), seeds the launcher's **directory
215
233
  presets** from the projects in your Claude Code history, and writes `~/.mulmoterminal/config.json`.
216
234
  It's **idempotent** — re-run it any time to refresh the presets; it overwrites the managed parts
217
235
  and keeps your other settings. When `claude` is installed it can hand off to the
218
- `/mulmoterminal-config` skill for interactive tweaks.
236
+ `/mulmoterminal-config` skill for interactive tweaks — it routes to the one that owns what you
237
+ want to change. Once the app is up you can also reach them from **Settings**: each section that a
238
+ skill can write ends in a button that starts that skill in a new session, which is how the settings
239
+ with no UI (a theme of your own, `keymap`) get written without hand-editing JSON.
219
240
 
220
241
  **Google account (optional).** Link a Google account to enable the chat's `google` tool and the
221
242
  phone's `google.calendar.*` commands: read/create events on any calendar (not just your primary),
@@ -352,12 +373,12 @@ SDK; we drive the real interactive CLI and relay its TTY over the WebSocket.
352
373
 
353
374
  ---
354
375
 
355
- ## Agents: Claude & Codex
376
+ ## Agents: Claude, Codex & Antigravity
356
377
 
357
378
  MulmoTerminal drives **interactive coding-agent CLIs**, not just Claude. An
358
379
  `AgentAdapter` seam abstracts the per-agent bits (which binary to spawn, how it resumes)
359
- so the PTY, grid, persistence, and GUI-panel plumbing stay shared. Two adapters ship
360
- today — **Claude Code** (the default) and **Codex**.
380
+ so the PTY, grid, persistence, and GUI-panel plumbing stay shared. Three adapters ship
381
+ today — **Claude Code** (the default), **Codex**, and **Antigravity** (`agy`).
361
382
 
362
383
  - **Claude** — spawned as `claude` (override with `CLAUDE_BIN`). The server passes
363
384
  `--session-id <uuid>`, so it knows the live session's id even before its transcript
@@ -371,10 +392,36 @@ today — **Claude Code** (the default) and **Codex**.
371
392
  `CODEX_HOME`) and maps the new rollout to the session — attributed only when it's
372
393
  unambiguous, never by "newest wins". Resume reattaches a live PTY, adopts a surviving
373
394
  tmux session, or cold-resumes the rollout id.
395
+ - **Antigravity** — spawned as `agy` (override with `ANTIGRAVITY_BIN`; `ANTIGRAVITY_MODEL` sets
396
+ `--model`). Antigravity runs on its own WebSocket (`/ws/antigravity`). Like Codex it mints its
397
+ own conversation id, so the server watches `~/.gemini/antigravity-cli/brain/` (home overridable
398
+ via `ANTIGRAVITY_HOME`) for the directory the new conversation creates — attributed only when
399
+ unambiguous — and cold-resumes it with `--conversation <id>`. That mapping is appended to
400
+ `~/.mulmoterminal/antigravity-conversations.jsonl`, so a conversation is still resumable after
401
+ the server restarts.
402
+
403
+ Its **GUI tools work differently**, because `agy` takes no MCP flag: it reads its servers from
404
+ `.agents/mcp_config.json` in the working directory. MulmoTerminal writes that file from the
405
+ directory's [Canvas switches](#wiki-collections--the-gui-panel) — the same switches Claude's cells read — so
406
+ one switch serves every agent, and rewrites it whenever a switch flips or an agy session starts.
407
+ Servers in it that MulmoTerminal did not write are left alone, the file is removed once no group
408
+ is on, and it is kept out of your `git status` through `.git/info/exclude` — a local switch on a
409
+ local machine, so it never reaches a diff or your team. The entry runs `server/mcp/bridge.mjs`, a stdio-to-HTTP shim onto the same in-process
410
+ GUI MCP server the other agents call. The **session id is never written into that file** — it is
411
+ per directory and shared by every session running there — and reaches the bridge through the agy
412
+ process's own environment instead.
413
+
414
+ The Docker sandbox does NOT cover agy: it stays claude-only until `buildDockerRunArgs` is
415
+ generalized (see `plans/feat-multi-agent-support.md`, PR#5). `agy` also ships as a standalone
416
+ binary rather than an npm package, so the sandbox image has nothing to install.
374
417
 
375
418
  **Choosing an agent.** The single view has a **New Codex session** button; each grid
376
- cell's launch form and the Collections browser carry a **Claude / Codex** toggle (your
377
- choice is remembered).
419
+ cell's launch form carries a **Claude / Codex / Antigravity / Shell** toggle, and the
420
+ Collections browser a **Claude / Codex / Antigravity** one (your choice is remembered).
421
+ **Shell** is not an agent: it runs your OS default shell (`$SHELL`, or `/bin/sh`) in the
422
+ chosen directory, with nothing to install and nothing to configure. It starts a launcher
423
+ cell, so it has no model, no MCP registration, and no worktree — those rows disappear
424
+ while it is picked.
378
425
 
379
426
  **Other models.**
380
427
  Claude Code can run against any **Anthropic-compatible** backend (OpenRouter, Moonshot, a
@@ -501,7 +548,14 @@ the `claude` / `codex` sessions themselves.
501
548
  | `CODEX_BIN` | `codex` | The Codex CLI binary to spawn. |
502
549
  | `CODEX_MODEL`| codex default | Model passed to Codex as `--model` (unset = Codex's own default). |
503
550
  | `CODEX_HOME` | `~/.codex` | Codex home — where its session rollouts and MulmoTerminal-mirrored skills live. |
551
+ | `ANTIGRAVITY_BIN` | `agy` | The Antigravity CLI binary to spawn. |
552
+ | `ANTIGRAVITY_MODEL` | agy default | Model passed to Antigravity as `--model` (unset = agy's own default). |
553
+ | `ANTIGRAVITY_HOME` | `~/.gemini/antigravity-cli` | Antigravity home directory containing session brain storage. |
504
554
  | `MULMOTERMINAL_HOME` | `~/.mulmoterminal` | Root for managed **git worktrees**. |
555
+ | `CLAUDE_CONFIG_DIR` | `~` | Claude Code's own config directory. `.claude.json` lives **inside** it, so relocating your Claude Code config moves that file too — MulmoTerminal reads it to tell whether the per-project GUI MCP server is registered (`server/infra/gui-mcp-registration.ts`). Leave it unset and `~/.claude.json` is used. |
556
+ | `MULMOCLAUDE_WORKSPACE_PATH` | `~/mulmoclaude` | Where the managed MulmoClaude workspace lives. MulmoTerminal seeds presets/helps **only** into this directory, so launching in an arbitrary project never writes them there (`server/backends/workspaceSetup.ts`). Set it to the same value MulmoClaude uses. |
557
+ | `MULMOTERMINAL_NO_SKILL_INSTALL` | unset | Set to any value to skip installing the bundled skills (`mulmoterminal-config` and the `-dirs` / `-theme` / `-header` / `-keys` / `-model` / `-notify` / `-bug-report` / `-decisions` family) into `~/.claude/skills/` and the Codex skills root on startup. |
558
+ | `GEMINI_IMAGE_MODEL` | `gemini-3.1-flash-image-preview` | Model used for image generation (needs `GEMINI_API_KEY`). The default is a **preview** model Google schedules for retirement around mid-2026, so pin a stable one here (e.g. `gemini-2.5-flash-image`) rather than waiting for a code change. |
505
559
  | `WAIT_REAP_GRACE_MS` | `1800000` | How long a **waiting** background session is kept before it's auto-reaped (`0` or negative = never). |
506
560
 
507
561
  The Docker-sandbox variables (`MULMOTERMINAL_SANDBOX`, `MULMOTERMINAL_SANDBOX_IMAGE`,
@@ -531,7 +585,7 @@ The Settings modal (⚙) persists per-user UI choices to `~/.mulmoterminal/confi
531
585
  | `soundKinds` | Which moments beep — see [Notification sounds](#notification-sounds). Defaults to `["finished","waiting"]`; the other kinds are opt-in. |
532
586
  | `sounds` | Per-kind sound: `{ "waiting": "preset:coin" }`. A `preset:<id>` reference or an absolute path; a kind with no entry falls back to `soundFile`. |
533
587
  | `prRepos` | `owner/repo` entries whose open PRs/issues the cross-repo **PRs & Issues** view aggregates (via your `gh` login). |
534
- | `launchers` | `{ label, command }` entries offered in a grid cell's launcher besides Claudea plain shell, `codex`, any interactive command. |
588
+ | `launchers` | `{ label, command }` entries offered in a grid cell's launcher besides the agents any interactive command. A plain shell needs no entry: the launch form's **Shell** toggle opens `$SHELL` unconfigured. |
535
589
  | `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. |
536
590
  | `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. |
537
591
  | `buttons` | Header action buttons — see [Header buttons](#header-buttons). Omit to keep the defaults; set to replace them. |
@@ -569,8 +623,8 @@ agent), or `"open"`. An `open` button targets one of `url` / `reveal` (OS file m
569
623
  opened next to the current one) / `pr: true` (open the current branch's PR — the button is hidden when
570
624
  there's no open PR) / `pickFile: true` (OS file dialog → insert the path).
571
625
  `${dir}`, `${branch}`, `${repo}`, … substitute live context, and `when` (e.g. `"isGitRepo"`) gates
572
- visibility. The `/mulmoterminal-config` skill writes a valid config interactively; per-dir buttons
573
- merge over the global ones by `id`.
626
+ visibility. The `/mulmoterminal-header` skill writes a valid config interactively; per-dir buttons
627
+ merge over the global ones by `id`, while `chips` replace the global list wholesale.
574
628
 
575
629
  ### Notification sounds
576
630
 
@@ -666,7 +720,7 @@ malformed file is ignored.
666
720
  "colors": { "background": "#190a23", "cursor": "#ff2e63" }, // per-key palette overrides
667
721
  "fontSize": 16, // terminal font size in px (8–32); overrides Settings
668
722
  "fontFamily": "'Cica', monospace", // terminal font stack; overrides the global config
669
- "orderPriority": 10, // rank in the grid's "priority" ordering (lowest first)
723
+ "orderPriority": 10, // rank in the grid's "priority" order and the launcher chips (lowest first)
670
724
  "sound": "./.mulmoterminal/alert.mp3", // attention sound, RELATIVE to this directory
671
725
  "sounds": { "command-failed": "preset:gong" }, // per-notification-kind override
672
726
  "appendSystemPrompt": false // no closing summary here; omit to follow the global setting
@@ -690,7 +744,7 @@ malformed file is ignored.
690
744
  | `theme` | xterm palette for terminals in this directory (one of the built-in theme ids). |
691
745
  | `colors` | Per-key xterm palette overrides applied on top of `theme` (or the app theme when `theme` is unset). Keys are xterm `ITheme` names (`background`, `foreground`, `cursor`, `selectionBackground`, the 16 ANSI colors, …); values are hex (`#rgb` / `#rrggbb` / `#rrggbbaa`). Unknown keys / bad values are dropped. |
692
746
  | `fontSize` | Terminal font size in px for this directory (8–32), overriding the Settings value. A size outside the range is clamped; a non-number is ignored. Changing it re-fits the terminal, so the PTY learns the new width — unlike browser zoom, which leaves the two disagreeing. |
693
- | `orderPriority` | This directory's rank in the grid's **priority** ordering — the third mode on the toolbar's ordering button, next to auto (attention-first) and manual (the move buttons). Any integer, **lowest first**; negatives are allowed. Directories that set nothing sort last, keeping their existing order, so adding the key to one project doesn't shuffle the rest. Only the priority mode reads it. |
747
+ | `orderPriority` | This directory's rank in the grid's **priority** ordering — the third mode on the toolbar's ordering button, next to auto (attention-first) and manual (the move buttons). Any integer, **lowest first**; negatives are allowed. Directories that set nothing sort last, keeping their existing order, so adding the key to one project doesn't shuffle the rest. The grid reads it in **priority** mode only; the launcher's directory chips always sort by it, so a project sits in the same place on both. |
694
748
  | `fontFamily` | CSS font-family stack for this directory's terminals, overriding the global `fontFamily`. Use the names as your OS lists them (`"'Cica', 'MS Gothic', monospace"`). An unusable stack is ignored whole rather than half-applied; `monospace` is appended if you name no generic family. Prefer fonts whose fullwidth glyphs are exactly twice the Latin width, or box-drawing frames tear. |
695
749
  | `sound` | Attention sound for this directory's sessions, a path **relative to the directory** (served at `GET /api/dir-sound`). The fallback for every kind. |
696
750
  | `sounds` | Per-kind override of `sound`: `{ "command-failed": "preset:gong" }`. Each value is a `preset:<id>` or a directory-relative path, under the same confinement. |
@@ -701,7 +755,7 @@ malformed file is ignored.
701
755
  paths and any `../` that escapes the directory are rejected, and the path is never taken from the
702
756
  HTTP request, so an opened project can't point the player at arbitrary files.
703
757
  **When changes take effect.** A write made *through Claude's tools* — which includes the
704
- `mulmoterminal-config` skill — applies **live**: the tool hook that reports the write doubles
758
+ `mulmoterminal-dirs` skill — applies **live**: the tool hook that reports the write doubles
705
759
  as the reload signal, so colors, palette, font size and grid order update without reopening
706
760
  anything. There is no filesystem watcher, so an edit made **outside** a session (your own
707
761
  editor) is picked up when the terminal is next opened.
@@ -759,8 +813,9 @@ alongside the Claude sessions. Scripts are **per-directory**: the cell reads the
759
813
  different projects' scripts.
760
814
 
761
815
  The same launcher also has an **or launch** row for your configured **launch commands**
762
- a plain interactive shell, `codex`, any command — set in Settings (⚙) → **Launch
763
- commands** as `{ label, command }` (e.g. `Shell` → `$SHELL`, `Codex` → `codex`). Unlike
816
+ any interactive command — set in Settings (⚙) → **Launch commands** as
817
+ `{ label, command }` (e.g. `htop` → `htop`, `Codex` → `codex`). A plain shell needs no
818
+ entry here: the launch form's **Shell** toggle already opens `$SHELL`. Unlike
764
819
  a one-shot script, a launcher runs as a **persistent terminal in the cell's directory**:
765
820
  it survives grid page switches and reconnects, and its dot shows running vs. exited (it
766
821
  has no Claude hooks, so no blocked/done states).
@@ -905,7 +960,7 @@ repo without colliding. Worktrees live under `~/.mulmoterminal/worktrees/` (over
905
960
 
906
961
  ![An empty cell's launch form — choose the agent, working directory, or a worktree](https://raw.githubusercontent.com/receptron/mulmoterminal/main/docs/guide/images/grid-launch-form.png)
907
962
 
908
- *Every empty grid cell shows this launch form: toggle **Claude / Codex**, type a **working directory** (frequent ones autocomplete from your presets), or — in a git repo — name a task under **OR ISOLATE IN A WORKTREE** and hit **+ New worktree** to start the agent on its own isolated branch. **OR LAUNCH** starts a plain shell or any launch command instead.*
963
+ *Every empty grid cell shows this launch form: toggle **Claude / Codex / Antigravity / Shell**, type a **working directory** (frequent ones autocomplete from your presets), or — in a git repo — name a task under **OR ISOLATE IN A WORKTREE** and hit **+ New worktree** to start the agent on its own isolated branch. **Shell** runs your OS default shell there instead of an agent; **OR LAUNCH** runs one of your configured launch commands.*
909
964
 
910
965
  A worktree cell's header carries a **diff badge** (`+<commits> ●<dirty>`); click it for a
911
966
  **Changes vs `<base>`** panel (file list + patch) with actions:
@@ -979,8 +1034,8 @@ your **own HTTP MCP servers** into the single-view session via Settings → `use
979
1034
  **Wiki.** The toolbar **Wiki** button opens a read-only browser over `<workspace>/data/wiki/`
980
1035
  — an **index** (tag-filterable page catalog), rendered **pages** with `[[wiki links]]` and
981
1036
  backlinks, a **graph** view (pages ranked by references), and a **lint** report (orphans /
982
- broken links / tag drift). Read-only endpoints: `GET /api/wiki`, `/api/wiki/graph`,
983
- `/api/wiki/lint`.
1037
+ broken links / tag drift) whose `[[links]]` are clickable too. Read-only endpoints:
1038
+ `GET /api/wiki`, `/api/wiki/graph`, `/api/wiki/lint`.
984
1039
 
985
1040
  **Collections.** The toolbar **Collections** button browses the workspace's collection
986
1041
  "cards" (`@mulmoclaude/collection-plugin`). Running a collection **action** fetches a seed
@@ -1087,13 +1142,32 @@ newest first, including freshly-created sessions that aren't yet written to disk
1087
1142
  - `500 { "error": string }` on an unexpected filesystem error. A missing project
1088
1143
  directory is **not** an error — it yields an empty `sessions` array.
1089
1144
 
1145
+ ### Directories that cannot be used
1146
+
1147
+ Every `?cwd=` — on the terminal sockets and on the read routes alike — names the directory
1148
+ the request is about. When one is named and cannot be used, the server says so instead of
1149
+ quietly answering about the **default workspace** (#1151):
1150
+
1151
+ | Where | What happens |
1152
+ | --- | --- |
1153
+ | `/ws`, `/ws/codex`, `/ws/antigravity`, `/ws/launch`, `/ws/run` | The socket is closed with `{ type: "error", message }`, which the terminal shows as a red banner and does not retry. |
1154
+ | A session that is still running (`?session=` names a live PTY or a surviving tmux session) | **Attaches anyway**, with a warning in the server log. Moving or renaming a directory must not shut you out of an agent that is still working in it — and the cwd reported back comes from the running PTY, not from the request. |
1155
+ | `GET /api/scripts`, `/api/skills`, `/api/dir-config`, `/api/dir-sound`, `/api/git-status`, `/api/pr-phase`, `/api/header`, `/api/sessions`, `/api/codex/sessions`, `/api/session/:id`, `/api/transcript/*`, `/api/cost` | `404 { error, cwd }` — a directory that is not there. |
1156
+ | A `?cwd=` that cannot name a directory at all (relative, or repeated as `?cwd=a&cwd=b`) | `400 { error, cwd }`. |
1157
+
1158
+ A request that names **no** directory is unaffected: `CLAUDE_CWD` is then the answer it
1159
+ asked for. The wording of the refusal is the one a refused spawn already uses, so the same
1160
+ condition reads the same whether it is caught here or by `ptySpawn` itself.
1161
+
1090
1162
  ### HTTP: `GET /api/scripts`
1091
1163
 
1092
1164
  The runnable entries from `<cwd>/script.json` for a cell's chosen directory
1093
- (`?cwd=<dir>`, falling back to `CLAUDE_CWD`); see
1094
- [Scripts (Run menu)](#scripts-run-menu). The resolved `cwd` is echoed back (the
1095
- server may fall back from a bad path), and each entry carries its `index` (the
1096
- position the client sends back to `/ws/run`).
1165
+ (`?cwd=<dir>`, or `CLAUDE_CWD` when none is named); see
1166
+ [Scripts (Run menu)](#scripts-run-menu). The resolved `cwd` is echoed back, and each
1167
+ entry carries its `index` (the position the client sends back to `/ws/run`). A `?cwd=`
1168
+ that names a directory the server cannot enter is answered `404 { error, cwd }` rather
1169
+ than with the default workspace's scripts — see
1170
+ [Directories that cannot be used](#directories-that-cannot-be-used).
1097
1171
 
1098
1172
  ```jsonc
1099
1173
  // GET /api/scripts?cwd=/Users/me/proj
@@ -1111,8 +1185,8 @@ A missing or invalid `script.json` is **not** an error — it yields an empty
1111
1185
 
1112
1186
  ### HTTP: `GET /api/skills`
1113
1187
 
1114
- The Claude skills discoverable for a terminal's chosen directory (`?cwd=<dir>`,
1115
- falling back to `CLAUDE_CWD`) — project scope (`<cwd>/.claude/skills`) plus user scope
1188
+ The Claude skills discoverable for a terminal's chosen directory (`?cwd=<dir>`, or
1189
+ `CLAUDE_CWD` when none is named) — project scope (`<cwd>/.claude/skills`) plus user scope
1116
1190
  (`~/.claude/skills`), deduped by slug (project shadows user), **working-dir skills
1117
1191
  first**; see [Skills (Skill menu)](#skills-skill-menu). A `skills` allowlist in that
1118
1192
  directory's `.mulmoterminal.json` narrows and reorders the result; absent → all. The
@@ -1567,7 +1641,8 @@ server/
1567
1641
  mcp/ per-session MCP broker
1568
1642
  backends/ wiki, collections, feeds, accounting, notifier,
1569
1643
  translation, whisper, remote-host, html, files
1570
- skills/ bundled skills (mulmoterminal-config, mulmoterminal-bug-report, mulmoterminal-decisions)
1644
+ skills/ bundled skills: mulmoterminal-config (entry point + audit), -dirs, -theme,
1645
+ -header, -keys, -model, -notify, -bug-report, -decisions
1571
1646
  fix-pty-perms.js postinstall: fixes node-pty binary permissions
1572
1647
  src/
1573
1648
  App.vue Layout; owns the active session + single/grid view
@@ -1578,13 +1653,19 @@ src/
1578
1653
  Terminal.vue xterm.js terminal; /ws, /ws/codex, /ws/run
1579
1654
  AppToolbar.vue shared header + toolbar buttons
1580
1655
  GridView.vue, TerminalGrid.vue, TerminalCell.vue, CommandCell.vue, LauncherCell.vue
1656
+ CellLaunchForm.vue what an EMPTY cell shows: dir + target + resume /
1657
+ scripts / worktrees / tool groups
1581
1658
  GuiPanel.vue, PluginFrame.vue GUI panel (Canvas) + Shadow-DOM plugin host
1582
1659
  FilesOverlay.vue file browser + CodeMirror editor
1583
1660
  GitBranchChip.vue, ModelContextBadge.vue header chips / badges
1584
1661
  PrsOverlay.vue cross-repo PRs & Issues
1585
1662
  Wiki*View.vue, Collections*.vue, AccountingOverlay.vue workspace views
1586
1663
  TimelineOverlay.vue, ToolsPane.vue, NotificationBell.vue, RemoteHostControl.vue
1587
- SettingsModal.vue ⚙ settings
1664
+ SettingsModal.vue ⚙ settings — the dialog shell + section order
1665
+ settings/ one file per settings section (theme, sounds,
1666
+ web push, google, PR repos, launchers, quick
1667
+ commands, MCP, cost, shortcuts, …), plus the
1668
+ shared SettingsStepper / SettingsListRow
1588
1669
  composables/ useSessions, usePubSub, useGitStatus, useCost,
1589
1670
  useChatLauncher, useFilesView, useWikiBrowse,
1590
1671
  useCollectionBrowse, useNotifications, useVoiceInput, …
@@ -0,0 +1,31 @@
1
+ // The skills MulmoTerminal ships, named once for both sides.
2
+ //
3
+ // Shared rather than mirrored because BOTH sides decide from these: the server COPIES these
4
+ // directories into the user's skills roots at boot, and the client LAUNCHES one by name from the
5
+ // Settings section that owns it. A name that lives in two places is a name that can drift, and it
6
+ // did — the grid view and the single view each spelled the appearance skill out, a rename updated
7
+ // one of them, and the same button then launched a different skill depending on which view you
8
+ // pressed it from. Each half was self-consistent, so nothing failed (Codex review on #1103).
9
+ // `BundledSkillName` is what keeps the Settings buttons honest now: a slug naming no shipped skill
10
+ // is a type error rather than an agent replying that it can't find one.
11
+
12
+ /** Every skill directory under `server/skills/`, in the order the installer walks them. A spec
13
+ * pins this list against what is actually on disk: adding a directory is not what ships it, and
14
+ * one nobody lists is copied nowhere with no error at any point. */
15
+ export const BUNDLED_SKILL_NAMES = [
16
+ "mulmoterminal-config",
17
+ "mulmoterminal-dirs",
18
+ "mulmoterminal-theme",
19
+ "mulmoterminal-header",
20
+ "mulmoterminal-keys",
21
+ "mulmoterminal-model",
22
+ "mulmoterminal-notify",
23
+ "mulmoterminal-bug-report",
24
+ "mulmoterminal-decisions",
25
+ ] as const;
26
+
27
+ export type BundledSkillName = (typeof BUNDLED_SKILL_NAMES)[number];
28
+
29
+ /** The skill that owns `<project>/.mulmoterminal.json` — the one the generated JSON Schema ships
30
+ * beside, so the schema sits with the skill that actually writes that file. */
31
+ export const DIR_CONFIG_SKILL = "mulmoterminal-dirs";
@@ -1,3 +1,5 @@
1
+ import { isRecord } from "./isRecord.js";
2
+
1
3
  // The pub/sub channel carrying "an agent just wrote this file". Both sides decide from it —
2
4
  // the server publishes, the editor listens to know its open file moved under it — so the name
3
5
  // and the payload shape live here rather than as a string literal on each side.
@@ -7,3 +9,7 @@ export const FILE_WRITE_CHANNEL = "file-write";
7
9
  export interface FileWriteEvent {
8
10
  file: string;
9
11
  }
12
+
13
+ /** Both sides go through this rather than re-spelling `{ file: string }`: the publisher pins its
14
+ * payload with `satisfies`, the subscriber narrows an untyped pub/sub frame with it. */
15
+ export const isFileWriteEvent = (data: unknown): data is FileWriteEvent => isRecord(data) && typeof data.file === "string";
@@ -4,7 +4,7 @@
4
4
  // Deliberately NOT the configured `launchers` list: those are arbitrary user commands, and
5
5
  // the phone picks from a fixed set the host can name. "shell" is the OS default shell, the
6
6
  // same thing the header's new-terminal button opens.
7
- export const LAUNCH_AGENTS = ["shell", "claude", "codex"] as const;
7
+ export const LAUNCH_AGENTS = ["shell", "claude", "codex", "antigravity"] as const;
8
8
 
9
9
  export type LaunchAgent = (typeof LAUNCH_AGENTS)[number];
10
10
 
@@ -2,7 +2,7 @@
2
2
  // backend, and how well each one actually drove Claude Code when it was measured.
3
3
  //
4
4
  // Shared across the build boundary (like themeIds / dirChrome): the launch picker reads
5
- // it in the browser, and the mulmoterminal-config skill reads it to know what it may
5
+ // it in the browser, and the mulmoterminal-model skill reads it to know what it may
6
6
  // suggest. One list, one source of truth.
7
7
  //
8
8
  // WHY THE NUMBERS: a model that answers a plain prompt can still be unusable, because the
@@ -0,0 +1,23 @@
1
+ // The image types a pasted screenshot can be saved as. BOTH sides decide from this list, so
2
+ // it lives here rather than in either of them: the client only takes a paste away from xterm
3
+ // when the server will accept it, and the server only writes a type it can name a file for.
4
+ // Were the two to drift, a paste would vanish from the terminal and then fail to upload —
5
+ // the user would see nothing happen at all.
6
+ //
7
+ // SVG is deliberately absent: it is a script-bearing document, not a screenshot, and nothing
8
+ // puts one on the clipboard as an image.
9
+ const EXTENSION_BY_MIME: ReadonlyMap<string, string> = new Map([
10
+ ["image/png", ".png"],
11
+ ["image/jpeg", ".jpg"],
12
+ ["image/gif", ".gif"],
13
+ ["image/webp", ".webp"],
14
+ ]);
15
+
16
+ /** The file extension for a pasteable image type, or null when it isn't one. */
17
+ export function extensionForImageMime(mime: string): string | null {
18
+ return EXTENSION_BY_MIME.get(mime.toLowerCase().trim()) ?? null;
19
+ }
20
+
21
+ export function isPasteableImageMime(mime: string): boolean {
22
+ return extensionForImageMime(mime) !== null;
23
+ }
@@ -1,8 +1,42 @@
1
1
  // What a remote-host session is running. The server decides it (from the PtyEntry, or from
2
2
  // what tmux says is in the pane) and the settings UI offers it as the scope for a quick
3
3
  // command, so the list of kinds is a value both sides read.
4
- export const SESSION_AGENTS = ["claude", "codex", "shell"] as const;
4
+ export const SESSION_AGENTS = ["claude", "codex", "antigravity", "shell"] as const;
5
5
 
6
6
  // Null anywhere this appears means the host cannot tell — a session that outlived a restart
7
7
  // exists only in tmux, and nothing recorded what launched it. That is distinct from "shell".
8
8
  export type SessionAgent = (typeof SESSION_AGENTS)[number];
9
+
10
+ // The agents a terminal can be LAUNCHED as, which is the session kinds minus "shell" (a shell is
11
+ // a launcher, not an agent). Claude is first because it is the default everywhere below.
12
+ export const TERMINAL_AGENTS = ["claude", "codex", "antigravity"] as const;
13
+
14
+ export type TerminalAgent = (typeof TERMINAL_AGENTS)[number];
15
+
16
+ // A remembered agent — a persisted grid cell, a localStorage toggle, a query param — read back.
17
+ // Validated rather than cast: anything unrecognised is Claude, which is also what an older
18
+ // persisted cell (written before the field existed) means.
19
+ export const asTerminalAgent = (value: unknown): TerminalAgent => (TERMINAL_AGENTS.some((agent) => agent === value) ? (value as TerminalAgent) : "claude");
20
+
21
+ export interface AgentBadge {
22
+ /** Where there is room for it — a sidebar row, a header bar. */
23
+ full: string;
24
+ /** For the tab bar, which fits about two characters between the dot and the title. */
25
+ short: string;
26
+ }
27
+
28
+ // Claude has no badge on purpose: it is the default everywhere, so badging it would put one on
29
+ // nearly every row and stop the badge meaning "this one is different". A shell is not an agent
30
+ // and reaches this with no entry, which is the same answer.
31
+ const AGENT_BADGES: Partial<Record<TerminalAgent, AgentBadge>> = {
32
+ codex: { full: "codex", short: "cx" },
33
+ antigravity: { full: "agy", short: "agy" },
34
+ };
35
+
36
+ /** How a session row announces which agent it is running, or null when it wears no badge. The one
37
+ * place the wording lives: the sidebar and the tab bar show the SAME list, so a literal compared
38
+ * in each is how the two came to disagree about a third agent. */
39
+ export function agentBadge(agent: string | null | undefined): AgentBadge | null {
40
+ const known = TERMINAL_AGENTS.find((candidate) => candidate === agent);
41
+ return known ? (AGENT_BADGES[known] ?? null) : null;
42
+ }
@@ -30,6 +30,35 @@ export const newlineSequence = (mode: TerminalSubmitMode): string => (mode === "
30
30
  // where ESC+CR is Alt+Enter, not submit.
31
31
  export const submitSequenceForAgent = (agent: string | undefined, mode: TerminalSubmitMode): string => (agent === "claude" ? submitSequence(mode) : CR);
32
32
 
33
+ // Text we are about to submit ON the user's behalf, ended so Claude Code has no completion menu
34
+ // open when the submit byte(s) arrive.
35
+ //
36
+ // Claude Code holds a completion menu open while the cursor sits at the end of a completion token
37
+ // — a `/command` or an `@path` — and an open menu consumes the key that should have submitted.
38
+ // Measured against Claude Code 2.1.220:
39
+ //
40
+ // `/help` + CR submits (the one case that always worked)
41
+ // `/help` + ESC CR does NOT submit — the menu's own `escape` binding eats the ESC, so the
42
+ // "esc-cr" submit never lands, however often it is sent
43
+ // `@path` + CR does NOT submit — the file picker takes that CR for itself
44
+ // either + a trailing space submits: the space ends the token, closing the menu
45
+ //
46
+ // So this is NOT specific to the reversed submit mapping (#1142 was reported there, but the
47
+ // `@path` half hits the default mapping too), and it is deliberately not a list of Claude Code's
48
+ // trigger characters: a trigger we failed to enumerate would silently restore a dead end that
49
+ // looks like a broken feature.
50
+ //
51
+ // For auto-submitted text only. A draft the user still edits keeps exactly what was handed over —
52
+ // their own Enter is a keystroke they can retry once they see the menu.
53
+ export const submittableLine = (text: string): string => (/\S$/.test(text) ? `${text} ` : text);
54
+
55
+ // The same Claude-only scoping as submitSequenceForAgent, and for a sharper reason: the completion
56
+ // menu is Claude Code's behaviour, while for every other target the trailing space would be REAL
57
+ // input. A shell line ending in `\` escapes the newline and waits for more (`echo foo\` → a
58
+ // continuation prompt); with a space appended it is an escaped space and runs instead. So a
59
+ // shell/codex/command session's bytes stay exactly what the sender wrote.
60
+ export const submittableLineForAgent = (agent: string | undefined, text: string): string => (agent === "claude" ? submittableLine(text) : text);
61
+
33
62
  // The structural shape of a keydown the override needs. A real DOM KeyboardEvent satisfies
34
63
  // it, and so does a plain test object — no DOM dependency, so this stays testable and shared.
35
64
  export interface EnterKeyEvent {
@@ -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-C_pDG6kL.js";import"./chunk-WYO6CB5R-BlzOfotS-BAOXGFjf.js";import"./chunk-VAUOI2AC-CS9QJ4yz-DNlwzQL1.js";import{n as i,r as a,t as o}from"./chunk-MOJQB5TN-CMZRaeqt-Ch-WBypm.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-C7AoyBVj.js";import"./chunk-WYO6CB5R-BlzOfotS-Bx9N57Se.js";import"./chunk-VAUOI2AC-CS9QJ4yz-BQv37MCq.js";import{n as i,r as a,t as o}from"./chunk-MOJQB5TN-CMZRaeqt-BPbi0qBZ.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-C7AoyBVj.js";export{e as createArchitectureServices};