mulmoterminal 4.0.0 → 4.1.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 (170) hide show
  1. package/README.md +3 -2
  2. package/common/dirConfigSource.ts +1 -1
  3. package/common/issueStartPlan.ts +58 -0
  4. package/common/keymap.ts +8 -8
  5. package/common/prPhase.ts +1 -1
  6. package/common/repoEntry.ts +77 -0
  7. package/common/sessionAgent.ts +1 -1
  8. package/common/terminalSubmit.ts +1 -1
  9. package/common/themeVars.ts +10 -4
  10. package/common/voiceInputStatus.ts +14 -0
  11. package/dist/assets/{abnfDiagram-VRR7QNED-RjiYivmv-Ds795mMl.js → abnfDiagram-VRR7QNED-RjiYivmv-BCXxUk19.js} +1 -1
  12. package/dist/assets/architecture-TIHT7OUA-0NsD2xW1-vpkNSsXO.js +1 -0
  13. package/dist/assets/{architectureDiagram-ZJ3FMSHR-3nWA91tG-C_pKLVRy.js → architectureDiagram-ZJ3FMSHR-3nWA91tG-Bs3iPPHw.js} +1 -1
  14. package/dist/assets/{blockDiagram-677ZJIJ3-BPuAJQRW-C1XW9rk5.js → blockDiagram-677ZJIJ3-BPuAJQRW-CxIHRfjJ.js} +1 -1
  15. package/dist/assets/{c4Diagram-LMCZKHZV-C0LAqQso-B_DJUxYS.js → c4Diagram-LMCZKHZV-C0LAqQso-D1Zp3ZLf.js} +1 -1
  16. package/dist/assets/channel-7wUqSdoX-BmaXSumw.js +1 -0
  17. package/dist/assets/{chunk-32BRIVSS-BRrYpgtb-muO9l3Wd.js → chunk-32BRIVSS-BRrYpgtb-iqbVrSxA.js} +1 -1
  18. package/dist/assets/{chunk-52WLFC77-BJ-ss3Xr-2G1qxJCe.js → chunk-52WLFC77-BJ-ss3Xr-DsGxy9sK.js} +1 -1
  19. package/dist/assets/{chunk-C7G6YPKG-BZEucKEL-DcP1Ik_Y.js → chunk-C7G6YPKG-BZEucKEL-FHV3qMNI.js} +1 -1
  20. package/dist/assets/{chunk-EX3LRPZG-DLS6FBN1-DzGx10EI.js → chunk-EX3LRPZG-DLS6FBN1-D3JiHJ-E.js} +1 -1
  21. package/dist/assets/{chunk-FWX5IMBZ-DHLSFw1H-C_1-WiA5.js → chunk-FWX5IMBZ-DHLSFw1H-BLB-Jc4x.js} +2 -2
  22. package/dist/assets/{chunk-HOUHSVGY-Bhlt8hXJ-CNQLLKZo.js → chunk-HOUHSVGY-Bhlt8hXJ-B9Sp5Rm4.js} +1 -1
  23. package/dist/assets/{chunk-ICXQ74PX-DwgHBX_g-zUyYvMip.js → chunk-ICXQ74PX-DwgHBX_g-Couvoogt.js} +1 -1
  24. package/dist/assets/{chunk-MOJQB5TN-CMZRaeqt-CuddDdck.js → chunk-MOJQB5TN-CMZRaeqt-pTxGJtb7.js} +1 -1
  25. package/dist/assets/{chunk-OGEWGWER-8Qy4a8b5-OusgHU3N.js → chunk-OGEWGWER-8Qy4a8b5-Bjam2Bd1.js} +1 -1
  26. package/dist/assets/{chunk-PUDLZKDR-DcrWQRYh-CbEu-YVk.js → chunk-PUDLZKDR-DcrWQRYh-z_O9UUQX.js} +1 -1
  27. package/dist/assets/{chunk-Q4XR5HBZ-ZXVGkG8Z-C8R8rf8U.js → chunk-Q4XR5HBZ-ZXVGkG8Z-yi9fnIVw.js} +1 -1
  28. package/dist/assets/{chunk-V7JOEXUC-DmGdheTX-CAvev2P-.js → chunk-V7JOEXUC-DmGdheTX-DTtWrQx1.js} +1 -1
  29. package/dist/assets/{chunk-VAUOI2AC-CS9QJ4yz-6Fqcztnb.js → chunk-VAUOI2AC-CS9QJ4yz-DW0Tgecw.js} +1 -1
  30. package/dist/assets/{chunk-VR4S4FIN-C6a91eNY-Oo6w4umS.js → chunk-VR4S4FIN-C6a91eNY-BSK94OLt.js} +1 -1
  31. package/dist/assets/{chunk-WYO6CB5R-BlzOfotS-CIUpBpsn.js → chunk-WYO6CB5R-BlzOfotS-D5Vl4QYq.js} +1 -1
  32. package/dist/assets/{chunk-ZGVPDNZ5-BYwxNFTK-DJKhMGKs.js → chunk-ZGVPDNZ5-BYwxNFTK-BnFKS269.js} +1 -1
  33. package/dist/assets/classDiagram-OUVF2IWQ-BYlRi6YQ-jw8dvfs2.js +1 -0
  34. package/dist/assets/classDiagram-v2-EOCWNBFH-BCHaYHxj-jw8dvfs2.js +1 -0
  35. package/dist/assets/cynefin-VYW2F7L2-CGYr2F6h-Bb2l8CKT.js +1 -0
  36. package/dist/assets/{cynefinDiagram-TSTJHNR4-DHA9iPo--D510DZwZ.js → cynefinDiagram-TSTJHNR4-DHA9iPo--CaO8WZ9J.js} +1 -1
  37. package/dist/assets/{dagre-VKFMJZFB--oJKqXBZ-BqyoygxX.js → dagre-VKFMJZFB--oJKqXBZ-i_xOpR1t.js} +1 -1
  38. package/dist/assets/{diagram-FQU43EPY-CcJCB9bG-DfnckLd4.js → diagram-FQU43EPY-CcJCB9bG-Dawy0bUD.js} +1 -1
  39. package/dist/assets/{diagram-G47NLZAW-C_o-WGG1-BPt0rgWP.js → diagram-G47NLZAW-C_o-WGG1-DQMAjcpJ.js} +1 -1
  40. package/dist/assets/{diagram-NH7WQ7WH-CXJCYvY--CkJBzBK2.js → diagram-NH7WQ7WH-CXJCYvY--BdH6UDVY.js} +1 -1
  41. package/dist/assets/{diagram-OA4YK3LP-BMzeJ87A-COSn3f12.js → diagram-OA4YK3LP-BMzeJ87A-DdlsuPDl.js} +1 -1
  42. package/dist/assets/{diagram-WEI45ONY-D_93NKqo-Dp8Om4pW.js → diagram-WEI45ONY-D_93NKqo-BYxqHeiT.js} +1 -1
  43. package/dist/assets/{dist-JstXoCzo.js → dist-CCytdxgu.js} +1 -1
  44. package/dist/assets/{dist-DZD6s32q.js → dist-Cl-hw6ph.js} +1 -1
  45. package/dist/assets/{dist-DiEF7DGo.js → dist-D9fZbRmf.js} +1 -1
  46. package/dist/assets/{dist-gls9ajYD.js → dist-SjIG9fJN.js} +1 -1
  47. package/dist/assets/{ebnfDiagram-CCIWWBDH-CVai1Ii9-Di2Rnx6S.js → ebnfDiagram-CCIWWBDH-CVai1Ii9-CqbaCkfN.js} +1 -1
  48. package/dist/assets/{erDiagram-Q63AITRT-CiABnA0s-dgCq9gZ-.js → erDiagram-Q63AITRT-CiABnA0s-CFRhcxJL.js} +1 -1
  49. package/dist/assets/eventmodeling-45OFAUF4-CWmqhm3a-CV0AXWMH.js +1 -0
  50. package/dist/assets/flowDiagram-23GEKE2U-DKSB5AiY-BzXDVc32.js +1 -0
  51. package/dist/assets/{ganttDiagram-NO4QXBWP-DQZvdFo1-Cia0ajA1.js → ganttDiagram-NO4QXBWP-DQZvdFo1-D_cPrHuB.js} +1 -1
  52. package/dist/assets/gitGraph-TEB2WS4Q-BR9qPwdN-DePJCG9u.js +1 -0
  53. package/dist/assets/{gitGraphDiagram-IHSO6WYX-C2ovBouh-Dh1y0VXZ.js → gitGraphDiagram-IHSO6WYX-C2ovBouh-CmpMFHzx.js} +1 -1
  54. package/dist/assets/index-CNOunTbV.js +616 -0
  55. package/dist/assets/index-Cdf9kaYt.css +1 -0
  56. package/dist/assets/info-DKCQHKI2-Dplx5kMp-CmgoBoly.js +1 -0
  57. package/dist/assets/{infoDiagram-FWYZ7A6U-7UnoB5AP-BqYiXSaj.js → infoDiagram-FWYZ7A6U-7UnoB5AP-1qH_6MSO.js} +1 -1
  58. package/dist/assets/{ishikawaDiagram-FXEZZL3T-ByUDM_N2-CcZgH7Ew.js → ishikawaDiagram-FXEZZL3T-ByUDM_N2-C4Y9h3Kc.js} +1 -1
  59. package/dist/assets/{journeyDiagram-5HDEW3XC-c5xIah9o-DdArPeNL.js → journeyDiagram-5HDEW3XC-c5xIah9o-2CHdMO52.js} +1 -1
  60. package/dist/assets/{kanban-definition-HUTT4EX6-Cnt6loYD-DmJ152Sf.js → kanban-definition-HUTT4EX6-Cnt6loYD-C3tvejl6.js} +1 -1
  61. package/dist/assets/{lib-Db3KPEnF.js → lib-l0WFljNN.js} +1 -1
  62. package/dist/assets/{line-D7ziSjKi-B3NE0st9.js → line-D7ziSjKi-BgJrHM2o.js} +1 -1
  63. package/dist/assets/{marp-SCu9aKhI.js → marp-DXxN_4Os.js} +1 -1
  64. package/dist/assets/{mermaid-parser.core-DxEa8E3F-nhwF9Zmw.js → mermaid-parser.core-DxEa8E3F-XDB5rg1G.js} +2 -2
  65. package/dist/assets/{mermaid.core-V0OYwIz3-COnZ7RY6.js → mermaid.core-V0OYwIz3-BE18ndG4.js} +3 -3
  66. package/dist/assets/{mindmap-definition-LN4V7U3C-CexN3O6L-DAFbTZnf.js → mindmap-definition-LN4V7U3C-CexN3O6L-B_Z8XGvs.js} +1 -1
  67. package/dist/assets/packet-7NZHBO7P-CQI3flND-Ckhq3qGx.js +1 -0
  68. package/dist/assets/{pegDiagram-2B236MQR-BiM4G0cW-DVdp8F2g.js → pegDiagram-2B236MQR-BiM4G0cW-CoS_Je3G.js} +1 -1
  69. package/dist/assets/pie-RZYD4A2V-CnmKE2Wa-COGnIXXn.js +1 -0
  70. package/dist/assets/{pieDiagram-ENE6RG2P-B_fBBS-2-dOKbqFlT.js → pieDiagram-ENE6RG2P-B_fBBS-2-q0d7psft.js} +1 -1
  71. package/dist/assets/{quadrantDiagram-ABIIQ3AL-BigGVxCR-B0K7Bi_G.js → quadrantDiagram-ABIIQ3AL-BigGVxCR-DX-o6YRc.js} +1 -1
  72. package/dist/assets/radar-I7S5WNFK-us6x-Z9R-DYVN7hVv.js +1 -0
  73. package/dist/assets/railroad-3IZDKUUU-BKdz6pdN-Ccr316bA.js +1 -0
  74. package/dist/assets/railroad-abnf-AHOZXSZD-m_CSSIfd-1lHKiwjs.js +1 -0
  75. package/dist/assets/railroad-ebnf-EBAXGLYW-Nu3nAG7C-CDsiITOB.js +1 -0
  76. package/dist/assets/railroad-peg-LSFZ7HO6--Y3mD4R_-BVRC2yH2.js +1 -0
  77. package/dist/assets/{railroadDiagram-RFXS5EU6--lNIlG64-IX0DQMzv.js → railroadDiagram-RFXS5EU6--lNIlG64-wRRd9wDB.js} +1 -1
  78. package/dist/assets/{requirementDiagram-TGXJPOKE-D-B0Y5Wu-7ePp-6uY.js → requirementDiagram-TGXJPOKE-D-B0Y5Wu-C29F0qnM.js} +1 -1
  79. package/dist/assets/{sankeyDiagram-HTMAVEWB-DlutaXDv-DcPeO3wM.js → sankeyDiagram-HTMAVEWB-DlutaXDv-BPq3vrC9.js} +1 -1
  80. package/dist/assets/{sequenceDiagram-DBY2YBRQ-BOLgtggH-BPKloVvU.js → sequenceDiagram-DBY2YBRQ-BOLgtggH-Cq_jo_ss.js} +1 -1
  81. package/dist/assets/{stateDiagram-2N3HPSRC-jf_dXUEU-Bd6-ZMXE.js → stateDiagram-2N3HPSRC-jf_dXUEU-DKYpGC0n.js} +1 -1
  82. package/dist/assets/stateDiagram-v2-6OUMAXLB-CuzgCL2v-CC7z1AQz.js +1 -0
  83. package/dist/assets/{swimlanes-5IMT3BWC-CYjtALQH-UC879FAu.js → swimlanes-5IMT3BWC-CYjtALQH-jmF9axmm.js} +1 -1
  84. package/dist/assets/swimlanesDiagram-G3AALYLV-3bmSBtKs-i5S7soVG.js +8 -0
  85. package/dist/assets/{timeline-definition-FHXFAJF6-DYJ4oUm8-ByREbTkR.js → timeline-definition-FHXFAJF6-DYJ4oUm8-B8bL7wcx.js} +1 -1
  86. package/dist/assets/treeView-QDETBFTQ-D27HWG6Q-DnDVKvX3.js +1 -0
  87. package/dist/assets/treemap-6X3UGDF4-CmGIU3H5-Dkb6cQ10.js +1 -0
  88. package/dist/assets/{vennDiagram-L72KCM5P-CdKAHoek-CznsE58R.js → vennDiagram-L72KCM5P-CdKAHoek-D9FWFG6j.js} +1 -1
  89. package/dist/assets/wardley-OPB4EBWU-CbHWsb2k-Za38PV_G.js +1 -0
  90. package/dist/assets/{wardleyDiagram-EHGQE667-BF6c4_CW-MMgJtZmB.js → wardleyDiagram-EHGQE667-BF6c4_CW-BffT3P-h.js} +1 -1
  91. package/dist/assets/{xychartDiagram-FW5EYKEG-CGiKngj7-D9mjkddh.js → xychartDiagram-FW5EYKEG-CGiKngj7-vPRabLJJ.js} +1 -1
  92. package/dist/index.html +2 -2
  93. package/package.json +3 -3
  94. package/server/agents/antigravity-mcp.ts +4 -3
  95. package/server/agents/antigravity-session.ts +3 -3
  96. package/server/agents/antigravity-sessions.ts +104 -0
  97. package/server/agents/codex-sessions.ts +7 -32
  98. package/server/agents/probe-transcript.ts +8 -11
  99. package/server/agents/transcript-head.ts +47 -0
  100. package/server/backends/collectionNotifierAdapter.ts +3 -2
  101. package/server/backends/collections.ts +23 -13
  102. package/server/backends/html.ts +2 -1
  103. package/server/backends/mulmoscript.ts +2 -1
  104. package/server/backends/openPath.ts +7 -0
  105. package/server/backends/presentPathRoot.ts +123 -0
  106. package/server/backends/remoteHost/getFeed.spec.ts +1 -0
  107. package/server/backends/remoteHost/handlers/deps.ts +9 -0
  108. package/server/backends/remoteHost/handlers/index.ts +5 -0
  109. package/server/backends/remoteHost/handlers/issueWork.ts +116 -0
  110. package/server/backends/remoteHost/handlers.spec.ts +3 -0
  111. package/server/backends/remoteHost/index.ts +1 -0
  112. package/server/backends/remoteHost/issueWork.spec.ts +244 -0
  113. package/server/backends/remoteHost/terminalScreen.ts +6 -4
  114. package/server/backends/remoteView.ts +3 -3
  115. package/server/backends/scheduler.ts +6 -3
  116. package/server/backends/shortcuts.ts +13 -9
  117. package/server/backends/translation.ts +12 -7
  118. package/server/backends/viewToken.ts +14 -8
  119. package/server/config/app-config.ts +12 -6
  120. package/server/config/cwd-presets.ts +4 -2
  121. package/server/config/header-context.ts +2 -14
  122. package/server/config/update-status.ts +6 -1
  123. package/server/files/scripts.ts +2 -1
  124. package/server/git/forge-host.ts +80 -0
  125. package/server/git/forge-support.ts +73 -0
  126. package/server/git/gitRemote.ts +31 -25
  127. package/server/git/glab-items.ts +73 -0
  128. package/server/git/glab.ts +29 -0
  129. package/server/git/issue-work.ts +129 -13
  130. package/server/git/issues.ts +17 -5
  131. package/server/git/pr-for-branch.ts +3 -2
  132. package/server/git/prs.ts +17 -6
  133. package/server/git/repo-dirs.ts +2 -3
  134. package/server/git/worktrees.ts +17 -1
  135. package/server/index.ts +26 -7
  136. package/server/infra/gui-mcp-registration.ts +4 -4
  137. package/server/mcp/broker.ts +8 -3
  138. package/server/routes/app-routes.ts +9 -0
  139. package/server/routes/dir-routes.ts +9 -6
  140. package/server/routes/issue-work-routes.ts +20 -17
  141. package/server/routes/session-routes.ts +22 -0
  142. package/server/session/background-chat.ts +2 -1
  143. package/server/session/cleared-transcripts.ts +1 -1
  144. package/server/session/issue-spawn-options.ts +20 -0
  145. package/server/session/registry.ts +12 -0
  146. package/server/session/scheduled-sessions.ts +1 -1
  147. package/server/session/session-cwd.ts +23 -0
  148. package/dist/assets/architecture-TIHT7OUA-0NsD2xW1-_YNv0NMb.js +0 -1
  149. package/dist/assets/channel-7wUqSdoX-6DJ89-Qe.js +0 -1
  150. package/dist/assets/classDiagram-OUVF2IWQ-BYlRi6YQ-B85sB4KG.js +0 -1
  151. package/dist/assets/classDiagram-v2-EOCWNBFH-BCHaYHxj-B85sB4KG.js +0 -1
  152. package/dist/assets/cynefin-VYW2F7L2-CGYr2F6h-CVLC7hXj.js +0 -1
  153. package/dist/assets/eventmodeling-45OFAUF4-CWmqhm3a-D-U4pUhx.js +0 -1
  154. package/dist/assets/flowDiagram-23GEKE2U-DKSB5AiY-CeqmKjiQ.js +0 -1
  155. package/dist/assets/gitGraph-TEB2WS4Q-BR9qPwdN-Dv8G0LNT.js +0 -1
  156. package/dist/assets/index-BBcSnsqR.js +0 -616
  157. package/dist/assets/index-Dal2P4Y6.css +0 -1
  158. package/dist/assets/info-DKCQHKI2-Dplx5kMp-Gtb9rU5N.js +0 -1
  159. package/dist/assets/packet-7NZHBO7P-CQI3flND-BlSkfnzl.js +0 -1
  160. package/dist/assets/pie-RZYD4A2V-CnmKE2Wa-PTCmAnlr.js +0 -1
  161. package/dist/assets/radar-I7S5WNFK-us6x-Z9R-sOU_Qtf_.js +0 -1
  162. package/dist/assets/railroad-3IZDKUUU-BKdz6pdN-CZse0pRF.js +0 -1
  163. package/dist/assets/railroad-abnf-AHOZXSZD-m_CSSIfd-DD141lVl.js +0 -1
  164. package/dist/assets/railroad-ebnf-EBAXGLYW-Nu3nAG7C-6h3qaEy8.js +0 -1
  165. package/dist/assets/railroad-peg-LSFZ7HO6--Y3mD4R_-D3yOgOUU.js +0 -1
  166. package/dist/assets/stateDiagram-v2-6OUMAXLB-CuzgCL2v--iFusbAN.js +0 -1
  167. package/dist/assets/swimlanesDiagram-G3AALYLV-3bmSBtKs-B7GGyqBW.js +0 -8
  168. package/dist/assets/treeView-QDETBFTQ-D27HWG6Q-COPupObu.js +0 -1
  169. package/dist/assets/treemap-6X3UGDF4-CmGIU3H5-DebmT2Ye.js +0 -1
  170. package/dist/assets/wardley-OPB4EBWU-CbHWsb2k-CqYCfIUl.js +0 -1
package/README.md CHANGED
@@ -536,7 +536,7 @@ The Settings modal (⚙) persists per-user UI choices to `~/.mulmoterminal/confi
536
536
  | `soundFile` | Absolute path to a custom **attention sound**, the fallback for every kind. Empty/unset uses the built-in synthesized chime. |
537
537
  | `soundKinds` | Which moments beep — see [Notification sounds](#notification-sounds). Defaults to `["finished","waiting"]`; the other kinds are opt-in. |
538
538
  | `sounds` | Per-kind sound: `{ "waiting": "preset:coin" }`. A `preset:<id>` reference or an absolute path; a kind with no entry falls back to `soundFile`. |
539
- | `prRepos` | `owner/repo` entries whose open PRs/issues the cross-repo **PRs & Issues** view aggregates (via your `gh` login). |
539
+ | `prRepos` | `owner/repo` entries whose open PRs/issues the cross-repo **PRs & Issues** view aggregates (via your `gh` login). An entry may name its host (`gitlab.com/group/project`); only github.com is read today, and other hosts show a row saying so. |
540
540
  | `repoDirs` | `{ "owner/repo": "/abs/path" }` — which local clone work on a repo starts in, when you keep several side by side. Only the *choice* is stored; which clones exist is re-derived from `cwdPresets` on every read, and an entry that no longer names a clone of that repo is ignored. |
541
541
  | `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. |
542
542
  | `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. |
@@ -1167,7 +1167,7 @@ quietly answering about the **default workspace** (#1151):
1167
1167
  | --- | --- |
1168
1168
  | `/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. |
1169
1169
  | 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. |
1170
- | `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. |
1170
+ | `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/antigravity/sessions`, `/api/session/:id`, `/api/transcript/*`, `/api/cost` | `404 { error, cwd }` — a directory that is not there. |
1171
1171
  | A `?cwd=` that cannot name a directory at all (relative, or repeated as `?cwd=a&cwd=b`) | `400 { error, cwd }`. |
1172
1172
 
1173
1173
  A request that names **no** directory is unaffected: `CLAUDE_CWD` is then the answer it
@@ -1288,6 +1288,7 @@ same-origin-guarded.
1288
1288
  | -------- | ------- |
1289
1289
  | `GET /api/session/:id?cwd=` | One session's summary — cumulative `usage` and `context` (model + last-turn context tokens). Backs the cell token & ctx% badges. |
1290
1290
  | `GET /api/codex/sessions?cwd=` | Codex sessions for the project (from `~/.codex` rollouts), newest first. |
1291
+ | `GET /api/antigravity/sessions?cwd=` | Antigravity conversations for the project, newest first. agy does record a workspace, but never as a complete conversation-to-workspace map (`cache/last_conversations.json` keeps one conversation per directory and is written at exit; `history.jsonl` carries no conversation id), so the project comes from MulmoTerminal's own `~/.mulmoterminal/antigravity-conversations.jsonl`; agy's transcript supplies the title. |
1291
1292
  | `GET /api/cost?cwd=&session=` | Estimated $ cost — session / today / month. |
1292
1293
  | `GET /api/transcript/timeline?session=&cwd=` | Per-session activity timeline (tools run). |
1293
1294
  | `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. |
@@ -82,7 +82,7 @@ export const EMPTY_DIR_CONFIG_EXTRAS: DirConfigExtras = {
82
82
  function isEmptyValue(value: unknown): boolean {
83
83
  if (value === null || value === undefined) return true;
84
84
  if (Array.isArray(value)) return value.length === 0;
85
- if (typeof value === "object") return Object.keys(value as Record<string, unknown>).length === 0;
85
+ if (typeof value === "object") return Object.keys(value).length === 0;
86
86
  return false;
87
87
  }
88
88
 
@@ -0,0 +1,58 @@
1
+ // Whether work on one repo can start here, decided from the repo -> clone answer alone (#1173).
2
+ // Pure, because the three outcomes are the whole behaviour: on the desktop they are a button, a
3
+ // menu, or a disabled button that says why (IssueStartButton.vue).
4
+ //
5
+ // In `common/` because BOTH hosts decide from it: the phone asks the same question over the remote
6
+ // command channel (#1184) and must reach the same answer — with one difference it makes itself,
7
+ // not one encoded here. It cannot offer the menu (the phone never picks a directory, see
8
+ // docs/remote-host-protocol.md), so it refuses `choose` where the desktop opens it.
9
+ import type { RepoDirs } from "./repoDirs";
10
+ import { GITHUB_HOST, GITLAB_HOST, parseRepoEntry } from "./repoEntry";
11
+
12
+ // Where work can be STARTED — the issue is read and a worktree cut. A host that is merely listable
13
+ // still refuses, and says which host it is rather than blaming a missing clone (#981).
14
+ const STARTABLE_HOSTS: ReadonlySet<string> = new Set([GITHUB_HOST, GITLAB_HOST]);
15
+
16
+ /** The hosts named in a refusal, read off the set rather than written out beside it — a hardcoded
17
+ * "github.com only" survived GitLab becoming startable and told users the opposite of the truth
18
+ * (Codex review). Exported so the phone's refusal says the same thing. */
19
+ export const startableHosts = (): string => [...STARTABLE_HOSTS].join(" and ");
20
+
21
+ export type IssueStartPlan =
22
+ /** No clone of this repo on this machine, so there is nowhere for the work to happen. */
23
+ | { kind: "no-clone" }
24
+ /** The repo is on a forge whose issues can be LISTED but not started from (#981). Distinct from
25
+ * `no-clone`, which would send the reader off to add a clone that would not help. */
26
+ | { kind: "unsupported-forge"; host: string }
27
+ /** Exactly one answer — either recorded, or the only candidate. One click starts. */
28
+ | { kind: "ready"; dir: string }
29
+ /** Several clones and nothing recorded yet: the user picks, and the pick is remembered. */
30
+ | { kind: "choose"; dirs: RepoDirs["dirs"] };
31
+
32
+ /** A plan that cannot start work as it stands. Named so a caller that has already narrowed to it
33
+ * can be given a sentence rather than a nullable one — see the phone's refusal (#1184). */
34
+ export type BlockedIssueStartPlan = Exclude<IssueStartPlan, { kind: "ready" }>;
35
+
36
+ // `repo` is REQUIRED, not optional, even though only one branch reads it: a caller that omitted it
37
+ // would get `no-clone` for a GitLab repo, which is the exact wrong answer this parameter was added
38
+ // to fix. Making the compiler ask for it is what stops that coming back.
39
+ export function issueStartPlan(entry: RepoDirs | undefined, repo: string): IssueStartPlan {
40
+ // Checked before the clone list, because a GitLab repo has no entry there and would otherwise
41
+ // read as "no clone" — a reason that is not true and points at the wrong fix.
42
+ const parsed = parseRepoEntry(repo);
43
+ if (parsed && !STARTABLE_HOSTS.has(parsed.host)) return { kind: "unsupported-forge", host: parsed.host };
44
+ const dirs = entry?.dirs ?? [];
45
+ if (dirs.length === 0) return { kind: "no-clone" };
46
+ // A recording wins even when the repo has one clone: it is still the same answer, and treating
47
+ // the two cases the same keeps "recorded" from being a state the UI has to distinguish.
48
+ const recorded = entry?.primary;
49
+ if (recorded) return { kind: "ready", dir: recorded };
50
+ if (dirs.length === 1) return { kind: "ready", dir: dirs[0].path };
51
+ return { kind: "choose", dirs };
52
+ }
53
+
54
+ /** Why the control is disabled, for the row's title text. Null when it is not disabled. */
55
+ export function issueStartBlockedReason(plan: IssueStartPlan, repo: string): string | null {
56
+ if (plan.kind === "unsupported-forge") return `Starting work supports ${startableHosts()} — ${plan.host} issues are listed here, not started from`;
57
+ return plan.kind === "no-clone" ? `No local clone of ${repo} — add one to your directory presets to start work here` : null;
58
+ }
package/common/keymap.ts CHANGED
@@ -10,6 +10,8 @@
10
10
  // "keymap": { "zoom-next": "PageDown", "zoom-prev": "Shift+PageUp" }
11
11
 
12
12
  // Actions a key can be bound to. Adding one here is all it takes for the config to accept it.
13
+ import { isRecord } from "./isRecord.js";
14
+
13
15
  export const KEYMAP_ACTIONS = [
14
16
  "zoom-toggle",
15
17
  "zoom-next",
@@ -23,7 +25,7 @@ export const KEYMAP_ACTIONS = [
23
25
  ] as const;
24
26
  export type KeymapAction = (typeof KEYMAP_ACTIONS)[number];
25
27
 
26
- export const isKeymapAction = (value: unknown): value is KeymapAction => typeof value === "string" && (KEYMAP_ACTIONS as readonly string[]).includes(value);
28
+ export const isKeymapAction = (value: unknown): value is KeymapAction => typeof value === "string" && KEYMAP_ACTIONS.some((action) => action === value);
27
29
 
28
30
  // action -> binding string. Absent action = unbound = that shortcut does nothing.
29
31
  // Actions the GRID's key handler must never claim, because they are decided inside the terminal
@@ -122,8 +124,7 @@ export function actionForKey(keymap: Keymap, e: KeymapKeyEvent): KeymapAction |
122
124
  return null;
123
125
  }
124
126
 
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
+ const isSendBinding = (value: unknown): value is SendBinding => isRecord(value) && typeof value.key === "string" && typeof value.bytes === "string";
127
128
 
128
129
  // The bytes this keydown should put into the terminal, or null when it is not a send binding.
129
130
  //
@@ -160,7 +161,7 @@ export function validateKeymap(input: unknown): KeymapProblem[] {
160
161
  if (typeof input !== "object" || Array.isArray(input)) {
161
162
  return [{ action: "keymap", binding: input, reason: "`keymap` must be an object of action -> key binding", fatal: true }];
162
163
  }
163
- const entries = Object.entries(input as Record<string, unknown>);
164
+ const entries = Object.entries(input);
164
165
  // A claim on one keystroke. `rank` is DISPATCH order, so the winner can be named: every action
165
166
  // outranks every send binding, because the grid's handler runs in the capture phase and stops
166
167
  // the event before the terminal — see sendBytesFor.
@@ -241,12 +242,11 @@ const canonicalBinding = (b: KeyBinding): string => `${b.shift ? "S" : ""}${b.al
241
242
  // malformed bindings are dropped rather than rejecting the whole map, matching how the
242
243
  // rest of the config treats one bad entry.
243
244
  export function sanitizeKeymap(input: unknown): Keymap {
244
- if (typeof input !== "object" || input === null || Array.isArray(input)) return {};
245
- const raw = input as Record<string, unknown>;
246
- const entries = Object.entries(raw).filter(
245
+ if (!isRecord(input)) return {};
246
+ const entries = Object.entries(input).filter(
247
247
  (entry): entry is [KeymapAction, string] => isKeymapAction(entry[0]) && typeof entry[1] === "string" && parseKeyBinding(entry[1]) !== null,
248
248
  );
249
- const send = sanitizeSendBindings(raw.send);
249
+ const send = sanitizeSendBindings(input.send);
250
250
  return { ...Object.fromEntries(entries), ...(send.length ? { send } : {}) };
251
251
  }
252
252
 
package/common/prPhase.ts CHANGED
@@ -10,7 +10,7 @@ export type PrPhase = "none" | "draft" | "ci-failing" | "changes-requested" | "c
10
10
 
11
11
  export const PR_PHASES: readonly PrPhase[] = ["none", "draft", "ci-failing", "changes-requested", "ci-running", "ready", "merged", "closed"];
12
12
 
13
- export const isPrPhase = (v: unknown): v is PrPhase => typeof v === "string" && (PR_PHASES as readonly string[]).includes(v);
13
+ export const isPrPhase = (v: unknown): v is PrPhase => typeof v === "string" && PR_PHASES.some((phase) => phase === v);
14
14
 
15
15
  // The /api/pr-phase response. `phase` and `prUrl` predate the rest and the roster reads them;
16
16
  // the numbers are what lets a cell say WHICH work it is on rather than just how far along it is.
@@ -0,0 +1,77 @@
1
+ // Reading a configured repository entry — `owner/repo`, or `host/owner/repo` for one that is not
2
+ // on GitHub (#981).
3
+ //
4
+ // In `common/` because BOTH sides decide from it: the server turns an entry into a forge to know
5
+ // which CLI to run, and the browser needs the same answer to say why a control is off. Written
6
+ // twice it would drift, and the two answers disagreeing is precisely the failure this abstraction
7
+ // is undoing.
8
+
9
+ export const GITHUB_HOST = "github.com";
10
+ export const GITLAB_HOST = "gitlab.com";
11
+
12
+ // A leading segment containing a DOT is the host. GitHub user and organisation names may hold only
13
+ // alphanumerics and hyphens, so `owner/repo` and `host/owner/repo` cannot be confused.
14
+ const HOST_SEGMENT = /\./;
15
+
16
+ export interface RepoEntry {
17
+ host: string;
18
+ /** The segments after the host — how many of them name a project is the HOST's rule, not this. */
19
+ path: string[];
20
+ /** True when the entry spelled the host out. `owner/repo` implies github.com without saying it. */
21
+ declared: boolean;
22
+ }
23
+
24
+ /** Split an entry into host and path, or null when it is not one.
25
+ *
26
+ * Null for an empty segment — a doubled, leading or trailing slash. Forgiving those would let
27
+ * `owner//repo` parse as `owner/repo` while the entry is STORED verbatim and handed to a CLI with
28
+ * the extra slash still in it, which is a parse error there.
29
+ */
30
+ export function parseRepoEntry(entry: string): RepoEntry | null {
31
+ const segments = entry.trim().split("/");
32
+ if (segments.some((segment) => segment === "")) return null;
33
+ const declared = segments.length > 1 && HOST_SEGMENT.test(segments[0]);
34
+ return declared ? { host: segments[0].toLowerCase(), path: segments.slice(1), declared } : { host: GITHUB_HOST, path: segments, declared };
35
+ }
36
+
37
+ /** The identity a repository is known by ON ITS OWN HOST — `owner/repo` for GitHub, the group path
38
+ * for GitLab — with any declared host stripped.
39
+ *
40
+ * `/api/repo-dirs` keys by this, because it derives the name from a clone's remote URL where the
41
+ * host is not part of the path. A configured entry may spell the host out (`github.com/owner/repo`
42
+ * became storable in #981 step 2a), so comparing the raw entry against those keys finds nothing
43
+ * and reports "no local clone" for a repo that has one (Codex review).
44
+ */
45
+ export const canonicalRepo = (entry: string): string => parseRepoEntry(entry)?.path.join("/") ?? entry.trim();
46
+
47
+ /** The identity used to MATCH a configured entry against a resolved clone: always host-qualified.
48
+ *
49
+ * Distinct from `canonicalRepo`, which strips the host because that is what a CLI's `--repo`
50
+ * wants. Matching needs the opposite: without the host, `github.com/a/b` and `gitlab.com/a/b`
51
+ * collapse to the same key, and a clone of one would answer for the other (#981).
52
+ */
53
+ export const repoIdentity = (entry: string): string => {
54
+ const parsed = parseRepoEntry(entry);
55
+ return parsed ? `${parsed.host}/${parsed.path.join("/")}`.toLowerCase() : entry.trim().toLowerCase();
56
+ };
57
+
58
+ // The value reaches a CLI's `--repo`, so no spaces and nothing readable as a flag.
59
+ const REPO_CHARS_RE = /^[A-Za-z0-9._/-]+$/;
60
+ // Every forge names a project as namespace + name, so one segment is never a repository.
61
+ const NAMESPACED = 2;
62
+
63
+ /** Whether a string may be stored and used as a repository entry.
64
+ *
65
+ * The ONE rule. It was written out four times — the config sanitizer, both issue-start endpoints
66
+ * and the Settings field — and widening only the first meant an entry the user could save was
67
+ * rejected by everything downstream, including the form that was supposed to accept it (#981).
68
+ *
69
+ * A hostless entry must be exactly `owner/repo`: `gh --repo` reads `a/b/c` as host `a`, so
70
+ * accepting it would have this side and the CLI aiming at different servers. A declared host may
71
+ * be followed by a nested GitLab group.
72
+ */
73
+ export function isRepoEntry(entry: string): boolean {
74
+ const parsed = REPO_CHARS_RE.test(entry.trim()) ? parseRepoEntry(entry) : null;
75
+ if (!parsed) return false;
76
+ return parsed.declared ? parsed.path.length >= NAMESPACED : parsed.path.length === NAMESPACED;
77
+ }
@@ -16,7 +16,7 @@ export type TerminalAgent = (typeof TERMINAL_AGENTS)[number];
16
16
  // A remembered agent — a persisted grid cell, a localStorage toggle, a query param — read back.
17
17
  // Validated rather than cast: anything unrecognised is Claude, which is also what an older
18
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");
19
+ export const asTerminalAgent = (value: unknown): TerminalAgent => (typeof value === "string" && isTerminalAgent(value) ? value : "claude");
20
20
 
21
21
  // Narrowing rather than coercing, for the callers that must be able to say "this is not an agent
22
22
  // at all": a shell is a session kind but not something a cell can be relaunched AS, and reading
@@ -13,7 +13,7 @@ export type TerminalSubmitMode = (typeof TERMINAL_SUBMIT_MODES)[number];
13
13
  export const DEFAULT_TERMINAL_SUBMIT_MODE: TerminalSubmitMode = "cr";
14
14
 
15
15
  export const isTerminalSubmitMode = (value: unknown): value is TerminalSubmitMode =>
16
- typeof value === "string" && (TERMINAL_SUBMIT_MODES as readonly string[]).includes(value);
16
+ typeof value === "string" && TERMINAL_SUBMIT_MODES.some((mode) => mode === value);
17
17
 
18
18
  // CR on its own. ESC+CR is what a terminal emits for Alt/Meta+Enter.
19
19
  const CR = "\r";
@@ -50,7 +50,7 @@ export function isThemeIdLike(value: unknown): value is string {
50
50
  }
51
51
 
52
52
  export function isBuiltinThemeId(id: string): id is ThemeId {
53
- return (THEME_IDS as readonly string[]).includes(id);
53
+ return THEME_IDS.some((builtin) => builtin === id);
54
54
  }
55
55
 
56
56
  /** Whether an id may be used for a CUSTOM theme. Built-in ids are refused rather than merged
@@ -70,13 +70,19 @@ export interface CustomThemeInput {
70
70
  /** The full variable set for a theme: the base it extends, with its own colours on top.
71
71
  * `builtins` supplies the base sets so this stays pure — the client reads them from the
72
72
  * stylesheet's source of truth, the specs from a fixture. */
73
+ // Every variable present, which is what separates a set we can paint with from one we cannot.
74
+ // A guard rather than a length check on the missing keys: both ask the same question, but only
75
+ // this one hands the answer to the compiler.
76
+ function isCompleteThemeVars(vars: Partial<ThemeVars>): vars is ThemeVars {
77
+ return THEME_VAR_KEYS.every((key) => !!vars[key]);
78
+ }
79
+
73
80
  export function resolveThemeVars(theme: CustomThemeInput, builtins: Record<ThemeId, ThemeVars>): ThemeVars | null {
74
81
  const base = theme.extends ? builtins[theme.extends] : null;
75
- const merged = { ...(base ?? {}), ...theme.colors } as Partial<ThemeVars>;
76
- const missing = THEME_VAR_KEYS.filter((key) => !merged[key]);
82
+ const merged: Partial<ThemeVars> = { ...(base ?? {}), ...theme.colors };
77
83
  // No base and an incomplete list is the one case we cannot paint: report it rather than
78
84
  // leaving the gaps to whatever the previously applied theme put on the element.
79
- return missing.length ? null : (merged as ThemeVars);
85
+ return isCompleteThemeVars(merged) ? merged : null;
80
86
  }
81
87
 
82
88
  function channel(hex: string, at: number): number {
@@ -6,6 +6,8 @@
6
6
  // package implements transcription behind it is not the UI's business. The server's
7
7
  // `getVoiceInputStatus(): VoiceInputStatus` annotation is what keeps the two in step.
8
8
 
9
+ import { isRecord } from "./isRecord.js";
10
+
9
11
  export type VoiceModelState = "idle" | "downloading" | "ready" | "error";
10
12
 
11
13
  export interface VoiceInputStatus {
@@ -19,3 +21,15 @@ export interface VoiceInputStatus {
19
21
  error?: string;
20
22
  };
21
23
  }
24
+
25
+ export const VOICE_MODEL_STATES: readonly VoiceModelState[] = ["idle", "downloading", "ready", "error"];
26
+
27
+ /** The wire response as it arrives from `GET /api/transcribe/model`. Lives beside the type so the
28
+ * guard and the shape it checks cannot drift; the UI treats anything else as "no status". */
29
+ export function isVoiceInputStatus(value: unknown): value is VoiceInputStatus {
30
+ if (!isRecord(value) || typeof value.capable !== "boolean" || !isRecord(value.model)) return false;
31
+ const { name, state, progress, error } = value.model;
32
+ if (typeof name !== "string" || !VOICE_MODEL_STATES.some((known) => known === state)) return false;
33
+ if (progress !== undefined && typeof progress !== "number") return false;
34
+ return error === undefined || typeof error === "string";
35
+ }
@@ -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-nhwF9Zmw.js";import"./chunk-WYO6CB5R-BlzOfotS-CIUpBpsn.js";import"./chunk-VAUOI2AC-CS9QJ4yz-6Fqcztnb.js";import{n as i,r as a,t as o}from"./chunk-MOJQB5TN-CMZRaeqt-CuddDdck.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-XDB5rg1G.js";import"./chunk-WYO6CB5R-BlzOfotS-D5Vl4QYq.js";import"./chunk-VAUOI2AC-CS9QJ4yz-DW0Tgecw.js";import{n as i,r as a,t as o}from"./chunk-MOJQB5TN-CMZRaeqt-pTxGJtb7.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-XDB5rg1G.js";export{e as createArchitectureServices};