mulmoterminal 2.6.0 → 2.8.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.
- package/README.md +175 -29
- package/bin/cli-args.d.ts +5 -0
- package/bin/cli-args.js +20 -0
- package/bin/instances.d.ts +10 -0
- package/bin/instances.js +115 -0
- package/bin/mulmoterminal.js +23 -1
- package/common/bundledSkills.ts +31 -0
- package/common/dirConfigSource.ts +6 -0
- package/common/dropUpload.ts +16 -0
- package/common/finiteNumber.ts +9 -0
- package/common/launchAgent.ts +1 -1
- package/common/modelPresets.ts +1 -1
- package/common/pastedImageTypes.ts +23 -0
- package/common/quickCommands.ts +3 -1
- package/common/rateLimits.ts +2 -3
- package/common/remoteHostHealth.ts +6 -0
- package/common/sessionAgent.ts +22 -1
- package/common/sessionMemo.ts +44 -0
- package/common/toolGroups.ts +14 -0
- package/dist/assets/{abnfDiagram-VRR7QNED-RjiYivmv-2UuooAL1.js → abnfDiagram-VRR7QNED-RjiYivmv-rGHffQn8.js} +1 -1
- package/dist/assets/architecture-TIHT7OUA-0NsD2xW1-Dg-O9e_V.js +1 -0
- package/dist/assets/{architectureDiagram-ZJ3FMSHR-3nWA91tG-Cadi87eN.js → architectureDiagram-ZJ3FMSHR-3nWA91tG-ClBqZt07.js} +1 -1
- package/dist/assets/{blockDiagram-677ZJIJ3-BPuAJQRW-DqOBzK4r.js → blockDiagram-677ZJIJ3-BPuAJQRW-CRpyIN7j.js} +1 -1
- package/dist/assets/{c4Diagram-LMCZKHZV-C0LAqQso-Czc9LUxD.js → c4Diagram-LMCZKHZV-C0LAqQso-CP3Nkj32.js} +1 -1
- package/dist/assets/channel-7wUqSdoX-Cc3ae2uC.js +1 -0
- package/dist/assets/{chunk-32BRIVSS-BRrYpgtb-DPioxLNJ.js → chunk-32BRIVSS-BRrYpgtb-bBx9h5ar.js} +1 -1
- package/dist/assets/{chunk-52WLFC77-BJ-ss3Xr-BGc0pGjo.js → chunk-52WLFC77-BJ-ss3Xr-CJsYFk5u.js} +1 -1
- package/dist/assets/{chunk-C7G6YPKG-BZEucKEL-C4mkPiqB.js → chunk-C7G6YPKG-BZEucKEL-CySmS4KO.js} +1 -1
- package/dist/assets/{chunk-EX3LRPZG-DLS6FBN1-C3Wn7cGn.js → chunk-EX3LRPZG-DLS6FBN1-Bd6UgUyI.js} +1 -1
- package/dist/assets/{chunk-FWX5IMBZ-DHLSFw1H-C8DnOSSj.js → chunk-FWX5IMBZ-DHLSFw1H-NTtO_c6Q.js} +2 -2
- package/dist/assets/{chunk-HOUHSVGY-Bhlt8hXJ-BT_GFYXY.js → chunk-HOUHSVGY-Bhlt8hXJ-CZ6o_X29.js} +1 -1
- package/dist/assets/{chunk-ICXQ74PX-DwgHBX_g-DY-sDZ2-.js → chunk-ICXQ74PX-DwgHBX_g-CLxX8cQ2.js} +1 -1
- package/dist/assets/{chunk-MOJQB5TN-CMZRaeqt-DvOpObYg.js → chunk-MOJQB5TN-CMZRaeqt-LKJvD71K.js} +1 -1
- package/dist/assets/{chunk-OGEWGWER-8Qy4a8b5-BxAWKfDq.js → chunk-OGEWGWER-8Qy4a8b5-DaWsU33j.js} +1 -1
- package/dist/assets/{chunk-PUDLZKDR-DcrWQRYh-HeYWroWP.js → chunk-PUDLZKDR-DcrWQRYh-zTDqq542.js} +1 -1
- package/dist/assets/{chunk-Q4XR5HBZ-ZXVGkG8Z-Cqn2FjJe.js → chunk-Q4XR5HBZ-ZXVGkG8Z-Dtx2iGhH.js} +1 -1
- package/dist/assets/{chunk-V7JOEXUC-DmGdheTX-WySfFBLu.js → chunk-V7JOEXUC-DmGdheTX-BgwCMqp5.js} +1 -1
- package/dist/assets/{chunk-VAUOI2AC-CS9QJ4yz-xhVI0AT_.js → chunk-VAUOI2AC-CS9QJ4yz-FXdeVJH3.js} +1 -1
- package/dist/assets/{chunk-VR4S4FIN-C6a91eNY-Dawtxx1M.js → chunk-VR4S4FIN-C6a91eNY-huBR399y.js} +1 -1
- package/dist/assets/{chunk-WYO6CB5R-BlzOfotS-cPQQuq_p.js → chunk-WYO6CB5R-BlzOfotS-B9k6Do_6.js} +1 -1
- package/dist/assets/{chunk-ZGVPDNZ5-BYwxNFTK-CwEy22EH.js → chunk-ZGVPDNZ5-BYwxNFTK-17x7kiDu.js} +1 -1
- package/dist/assets/classDiagram-OUVF2IWQ-BYlRi6YQ-gmGwQCKi.js +1 -0
- package/dist/assets/classDiagram-v2-EOCWNBFH-BCHaYHxj-gmGwQCKi.js +1 -0
- package/dist/assets/cynefin-VYW2F7L2-CGYr2F6h-C5PuFrCb.js +1 -0
- package/dist/assets/{cynefinDiagram-TSTJHNR4-DHA9iPo--6gP3lGiQ.js → cynefinDiagram-TSTJHNR4-DHA9iPo--BmU0gwz5.js} +1 -1
- package/dist/assets/{dagre-VKFMJZFB--oJKqXBZ-Ogb4ZCJY.js → dagre-VKFMJZFB--oJKqXBZ-Cf0skqxL.js} +1 -1
- package/dist/assets/{diagram-FQU43EPY-CcJCB9bG-En7Wv4VU.js → diagram-FQU43EPY-CcJCB9bG-XUWMFcgP.js} +1 -1
- package/dist/assets/{diagram-G47NLZAW-C_o-WGG1-CvfDKE9B.js → diagram-G47NLZAW-C_o-WGG1-B6HfMZd3.js} +1 -1
- package/dist/assets/{diagram-NH7WQ7WH-CXJCYvY--D_SuUUUL.js → diagram-NH7WQ7WH-CXJCYvY--BACwoprc.js} +1 -1
- package/dist/assets/{diagram-OA4YK3LP-BMzeJ87A-COaQX8ss.js → diagram-OA4YK3LP-BMzeJ87A-zoxV9d9i.js} +1 -1
- package/dist/assets/{diagram-WEI45ONY-D_93NKqo-B5qjqS3m.js → diagram-WEI45ONY-D_93NKqo-e-eIgWvy.js} +1 -1
- package/dist/assets/{dist-IEKl3ifz.js → dist-BHoD_w15.js} +1 -1
- package/dist/assets/{dist-CeY8Kgqb.js → dist-BWiUENTZ.js} +1 -1
- package/dist/assets/{dist-kXEjeiqx.js → dist-CX4c9UBB.js} +1 -1
- package/dist/assets/{dist-CBx3SN5Y.js → dist-Cm6rSKic.js} +1 -1
- package/dist/assets/{ebnfDiagram-CCIWWBDH-CVai1Ii9-Bbl_CbGD.js → ebnfDiagram-CCIWWBDH-CVai1Ii9-DujO8khX.js} +1 -1
- package/dist/assets/{erDiagram-Q63AITRT-CiABnA0s-DtNjq60k.js → erDiagram-Q63AITRT-CiABnA0s-DaHESKPI.js} +1 -1
- package/dist/assets/eventmodeling-45OFAUF4-CWmqhm3a-D1MuSTYF.js +1 -0
- package/dist/assets/flowDiagram-23GEKE2U-DKSB5AiY-DT6n3-Ft.js +1 -0
- package/dist/assets/{ganttDiagram-NO4QXBWP-DQZvdFo1-Dtybxb5k.js → ganttDiagram-NO4QXBWP-DQZvdFo1-De_ebWFp.js} +1 -1
- package/dist/assets/gitGraph-TEB2WS4Q-BR9qPwdN-CNsR02rf.js +1 -0
- package/dist/assets/{gitGraphDiagram-IHSO6WYX-C2ovBouh-CibskzGI.js → gitGraphDiagram-IHSO6WYX-C2ovBouh-C4RETxbL.js} +1 -1
- package/dist/assets/index-BivYtiDo.css +1 -0
- package/dist/assets/index-COmdTh0t.js +616 -0
- package/dist/assets/info-DKCQHKI2-Dplx5kMp-BeUJlQSg.js +1 -0
- package/dist/assets/{infoDiagram-FWYZ7A6U-7UnoB5AP-WyZaP5rz.js → infoDiagram-FWYZ7A6U-7UnoB5AP-DNsURlj_.js} +1 -1
- package/dist/assets/{ishikawaDiagram-FXEZZL3T-ByUDM_N2-C_Q9CmwS.js → ishikawaDiagram-FXEZZL3T-ByUDM_N2-QyeLiJnO.js} +1 -1
- package/dist/assets/{journeyDiagram-5HDEW3XC-c5xIah9o-D-Ru3_wb.js → journeyDiagram-5HDEW3XC-c5xIah9o-CFkMurTe.js} +1 -1
- package/dist/assets/{kanban-definition-HUTT4EX6-Cnt6loYD-eWJL56Xd.js → kanban-definition-HUTT4EX6-Cnt6loYD-DsoZE32R.js} +1 -1
- package/dist/assets/{lib-CTNrhK1N.js → lib-DC86gNgf.js} +1 -1
- package/dist/assets/{line-D7ziSjKi-CEMKL39T.js → line-D7ziSjKi-Dz37a1T7.js} +1 -1
- package/dist/assets/{marp-C4na0vHp.js → marp-B-h6KS_6.js} +1 -1
- package/dist/assets/{mermaid-parser.core-DxEa8E3F-3f1dQMCy.js → mermaid-parser.core-DxEa8E3F-DteVxl8X.js} +2 -2
- package/dist/assets/{mermaid.core-V0OYwIz3-Dg1HWQ_i.js → mermaid.core-V0OYwIz3-DAwdqlFZ.js} +3 -3
- package/dist/assets/{mindmap-definition-LN4V7U3C-CexN3O6L-CyxV57-U.js → mindmap-definition-LN4V7U3C-CexN3O6L-B4GX796F.js} +1 -1
- package/dist/assets/packet-7NZHBO7P-CQI3flND-C_pMfWi_.js +1 -0
- package/dist/assets/{pegDiagram-2B236MQR-BiM4G0cW-B40BLiy-.js → pegDiagram-2B236MQR-BiM4G0cW-BWBzRiaC.js} +1 -1
- package/dist/assets/pie-RZYD4A2V-CnmKE2Wa-bM2FdcNh.js +1 -0
- package/dist/assets/{pieDiagram-ENE6RG2P-B_fBBS-2-j0wsTlPA.js → pieDiagram-ENE6RG2P-B_fBBS-2-CsBnZfAY.js} +1 -1
- package/dist/assets/{quadrantDiagram-ABIIQ3AL-BigGVxCR-BVxlsDNI.js → quadrantDiagram-ABIIQ3AL-BigGVxCR-B1j3aQ5d.js} +1 -1
- package/dist/assets/radar-I7S5WNFK-us6x-Z9R-BB9wirGo.js +1 -0
- package/dist/assets/railroad-3IZDKUUU-BKdz6pdN-CbOoELZg.js +1 -0
- package/dist/assets/railroad-abnf-AHOZXSZD-m_CSSIfd-uE3YbEg2.js +1 -0
- package/dist/assets/railroad-ebnf-EBAXGLYW-Nu3nAG7C-u4DQhs69.js +1 -0
- package/dist/assets/railroad-peg-LSFZ7HO6--Y3mD4R_-B2vytEWW.js +1 -0
- package/dist/assets/{railroadDiagram-RFXS5EU6--lNIlG64-7RnYoafQ.js → railroadDiagram-RFXS5EU6--lNIlG64-BjmRh1tU.js} +1 -1
- package/dist/assets/{requirementDiagram-TGXJPOKE-D-B0Y5Wu-xH07LQI_.js → requirementDiagram-TGXJPOKE-D-B0Y5Wu-UJKUKfTI.js} +1 -1
- package/dist/assets/{sankeyDiagram-HTMAVEWB-DlutaXDv-BUmys5rr.js → sankeyDiagram-HTMAVEWB-DlutaXDv-Bsrgx8aw.js} +1 -1
- package/dist/assets/{sequenceDiagram-DBY2YBRQ-BOLgtggH-BYyf27T3.js → sequenceDiagram-DBY2YBRQ-BOLgtggH-BpxKPxVk.js} +1 -1
- package/dist/assets/{stateDiagram-2N3HPSRC-jf_dXUEU-DGzqzTG5.js → stateDiagram-2N3HPSRC-jf_dXUEU-90vLBL-t.js} +1 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-CuzgCL2v-B-UjCOGA.js +1 -0
- package/dist/assets/{swimlanes-5IMT3BWC-CYjtALQH-BD-WjB9j.js → swimlanes-5IMT3BWC-CYjtALQH-BQj65nKh.js} +1 -1
- package/dist/assets/swimlanesDiagram-G3AALYLV-3bmSBtKs-BMxuxpet.js +8 -0
- package/dist/assets/{timeline-definition-FHXFAJF6-DYJ4oUm8-BL8HXryX.js → timeline-definition-FHXFAJF6-DYJ4oUm8-Brr9I81k.js} +1 -1
- package/dist/assets/treeView-QDETBFTQ-D27HWG6Q-DvTQoVxm.js +1 -0
- package/dist/assets/treemap-6X3UGDF4-CmGIU3H5-DCrIc8uR.js +1 -0
- package/dist/assets/{vennDiagram-L72KCM5P-CdKAHoek-_pUekkpD.js → vennDiagram-L72KCM5P-CdKAHoek-BFYXaKjS.js} +1 -1
- package/dist/assets/wardley-OPB4EBWU-CbHWsb2k--RdJ9IY0.js +1 -0
- package/dist/assets/{wardleyDiagram-EHGQE667-BF6c4_CW-BVNPNGsg.js → wardleyDiagram-EHGQE667-BF6c4_CW-BN4fz_6C.js} +1 -1
- package/dist/assets/{xychartDiagram-FW5EYKEG-CGiKngj7-C5EYDD6T.js → xychartDiagram-FW5EYKEG-CGiKngj7-CJAizw9P.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +17 -11
- package/server/agents/agent-resume.ts +27 -0
- package/server/agents/antigravity-args.ts +39 -0
- package/server/agents/antigravity-mcp.ts +122 -0
- package/server/agents/antigravity-session.ts +68 -0
- package/server/agents/antigravity.ts +7 -0
- package/server/agents/appended-prompt.ts +28 -0
- package/server/agents/claude-args.ts +11 -12
- package/server/agents/claude.ts +1 -0
- package/server/agents/codex-rate-limits.spec.ts +20 -0
- package/server/agents/codex-rate-limits.ts +16 -6
- package/server/agents/codex.ts +1 -0
- package/server/agents/rate-limit-persist.spec.ts +26 -1
- package/server/agents/rate-limit-persist.ts +3 -4
- package/server/agents/registry.ts +2 -0
- package/server/agents/statusline.spec.ts +4 -0
- package/server/agents/statusline.ts +2 -3
- package/server/agents/types.ts +4 -1
- package/server/backends/collections.ts +15 -3
- package/server/backends/feed-summary.ts +1 -1
- package/server/backends/fileChange.ts +19 -6
- package/server/backends/fileOps.ts +5 -1
- package/server/backends/google.ts +3 -1
- package/server/backends/html.ts +67 -9
- package/server/backends/imageResult.ts +2 -2
- package/server/backends/markdown.ts +20 -9
- package/server/backends/mulmoscript.spec.ts +3 -3
- package/server/backends/openPath.ts +67 -0
- package/server/backends/remoteHost/googleCalendar.ts +9 -4
- package/server/backends/remoteHost/handlers.ts +2 -1
- package/server/backends/remoteHost/index.ts +26 -22
- package/server/backends/remoteHost/launchTerminal.ts +2 -2
- package/server/backends/remoteHost/routes.spec.ts +13 -1
- package/server/backends/remoteHost/routes.ts +6 -3
- package/server/backends/remoteHost/sessionActivity.ts +27 -3
- package/server/backends/remoteHost/terminalInput.ts +1 -1
- package/server/backends/remoteHost/terminalScreen.ts +72 -4
- package/server/backends/thumbnailStore.ts +5 -2
- package/server/cli-google.ts +1 -1
- package/server/config/app-config.ts +16 -0
- package/server/config/config-routes.ts +7 -0
- package/server/config/config-schema.ts +8 -0
- package/server/config/dir-config.ts +10 -2
- package/server/config/header-config.ts +1 -1
- package/server/config/header-context.ts +1 -1
- package/server/files/dirRequest.ts +2 -1
- package/server/files/pathContainment.ts +5 -1
- package/server/files/pick-file.ts +2 -1
- package/server/git/worktree-pr.ts +3 -3
- package/server/git/worktree-routes.ts +6 -5
- package/server/index.ts +136 -26
- package/server/infra/collection-tool.ts +1 -1
- package/server/infra/gui-mcp-registration.ts +15 -2
- package/server/infra/has-binary.ts +162 -14
- package/server/infra/install-bundled-skills.ts +7 -8
- package/server/infra/plugins-registry.ts +10 -1
- package/server/infra/resolve-bin.ts +3 -3
- package/server/infra/spawn-cwd.ts +46 -0
- package/server/infra/tmux-routes.ts +3 -2
- package/server/infra/tmux.ts +106 -1
- package/server/infra/web-push.ts +1 -1
- package/server/mcp/bridge.mjs +66 -0
- package/server/mcp/broker.ts +43 -8
- package/server/mcp/gui-call-history.ts +96 -0
- package/server/routes/app-routes.ts +53 -9
- package/server/routes/drop-routes.ts +50 -0
- package/server/routes/gui-mcp-routes.ts +6 -0
- package/server/routes/hook-routes.ts +26 -9
- package/server/routes/mcp-routes.ts +53 -3
- package/server/routes/plugin-routes.ts +24 -8
- package/server/routes/routeParams.ts +4 -2
- package/server/routes/same-origin-guard.ts +17 -3
- package/server/routes/session-routes.ts +47 -2
- package/server/routes/terminal-ws-path.ts +2 -1
- package/server/routes/tool-routes.ts +14 -4
- package/server/routes/ws-routes.ts +155 -42
- package/server/session/activity-transition.ts +9 -4
- package/server/session/background-chat.ts +24 -10
- package/server/session/cleared-transcripts.ts +118 -0
- package/server/session/command-summary.ts +2 -1
- package/server/session/completion-hooks.ts +51 -0
- package/server/session/hook-settings.ts +1 -1
- package/server/session/launch-choice.ts +2 -2
- package/server/session/lifecycle.ts +21 -1
- package/server/session/mcp-config.ts +3 -3
- package/server/session/provider-env.ts +2 -2
- package/server/session/pty-connection.ts +36 -3
- package/server/session/pty-exit-log.ts +92 -0
- package/server/session/pty-relay.ts +35 -0
- package/server/session/pty-spawn.ts +101 -9
- package/server/session/registry.ts +160 -37
- package/server/session/session-detail-view.ts +28 -6
- package/server/session/session-drops.ts +174 -0
- package/server/session/{dev-terminal-sessions.ts → session-id-log.ts} +16 -15
- package/server/session/session-list.ts +15 -4
- package/server/session/session-memos.ts +72 -0
- package/server/session/session-reads.ts +6 -5
- package/server/session/session-settings.ts +27 -4
- package/server/session/session-title.ts +11 -3
- package/server/session/session-tool-groups.ts +2 -2
- package/server/session/sessionListTitle.ts +13 -4
- package/server/session/spawn-antigravity.ts +83 -0
- package/server/session/spawn-claude.ts +41 -16
- package/server/session/spawn-codex.ts +8 -20
- package/server/session/spawn-deps.ts +2 -0
- package/server/session/spawn-shell.ts +10 -5
- package/server/session/spawners.ts +2 -0
- package/server/session/task-push.ts +12 -6
- package/server/session/taskPushRules.ts +5 -4
- package/server/session/terminal-replay.ts +15 -0
- package/server/session/tmux-size-sync.ts +184 -0
- package/server/session/tool-hook.ts +3 -3
- package/server/session/tool-store.ts +28 -24
- package/server/session/types.ts +9 -3
- package/server/skills/mulmoterminal-bug-report/faq.md +6 -0
- package/server/skills/mulmoterminal-config/SKILL.md +113 -490
- package/server/skills/mulmoterminal-dirs/SKILL.md +227 -0
- package/server/skills/{mulmoterminal-config → mulmoterminal-dirs}/palettes.json +1 -1
- package/server/skills/mulmoterminal-header/SKILL.md +165 -0
- package/server/skills/mulmoterminal-keys/SKILL.md +153 -0
- package/server/skills/mulmoterminal-model/SKILL.md +102 -0
- package/server/skills/mulmoterminal-notify/SKILL.md +111 -0
- package/server/skills/mulmoterminal-theme/SKILL.md +128 -0
- package/dist/assets/architecture-TIHT7OUA-0NsD2xW1-BgkdTJQE.js +0 -1
- package/dist/assets/channel-7wUqSdoX-C1cie6OA.js +0 -1
- package/dist/assets/classDiagram-OUVF2IWQ-BYlRi6YQ-Do5m3IoG.js +0 -1
- package/dist/assets/classDiagram-v2-EOCWNBFH-BCHaYHxj-Do5m3IoG.js +0 -1
- package/dist/assets/cynefin-VYW2F7L2-CGYr2F6h-DwN-y4W_.js +0 -1
- package/dist/assets/eventmodeling-45OFAUF4-CWmqhm3a-EIcAkJu9.js +0 -1
- package/dist/assets/flowDiagram-23GEKE2U-DKSB5AiY-BLvmawCu.js +0 -1
- package/dist/assets/gitGraph-TEB2WS4Q-BR9qPwdN-CWvVulXn.js +0 -1
- package/dist/assets/index-B1LdU5cE.js +0 -610
- package/dist/assets/index-Dwz79YwY.css +0 -1
- package/dist/assets/info-DKCQHKI2-Dplx5kMp-CBgSXevA.js +0 -1
- package/dist/assets/packet-7NZHBO7P-CQI3flND-BlXZ8rUp.js +0 -1
- package/dist/assets/pie-RZYD4A2V-CnmKE2Wa-elRgXCv3.js +0 -1
- package/dist/assets/radar-I7S5WNFK-us6x-Z9R-BZqQUr3N.js +0 -1
- package/dist/assets/railroad-3IZDKUUU-BKdz6pdN-Cb5GXZCb.js +0 -1
- package/dist/assets/railroad-abnf-AHOZXSZD-m_CSSIfd-BddD0JXg.js +0 -1
- package/dist/assets/railroad-ebnf-EBAXGLYW-Nu3nAG7C-7VkImbqm.js +0 -1
- package/dist/assets/railroad-peg-LSFZ7HO6--Y3mD4R_-DHko1-Zq.js +0 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-CuzgCL2v-DDeTaew1.js +0 -1
- package/dist/assets/swimlanesDiagram-G3AALYLV-3bmSBtKs-DZLi_c2t.js +0 -8
- package/dist/assets/treeView-QDETBFTQ-D27HWG6Q-C9kkOUqZ.js +0 -1
- package/dist/assets/treemap-6X3UGDF4-CmGIU3H5-zjv1syce.js +0 -1
- package/dist/assets/wardley-OPB4EBWU-CbHWsb2k-DE_WhSD_.js +0 -1
- package/server/agents/codex-resume.ts +0 -26
- package/server/backends/remoteHost/firestoreSafeResult.ts +0 -72
- package/server/backends/remoteHost/presenceProbe.ts +0 -67
- package/server/backends/remoteHost/resilientRunner.ts +0 -231
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# mulmoterminal
|
|
2
2
|
|
|
3
|
-
**Run
|
|
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
|

|
|
19
22
|
|
|
@@ -86,11 +89,31 @@ output you haven't seen) — driven by Claude/Codex activity hooks the server in
|
|
|
86
89
|
*Besides the grid there's a **single view** for focusing on one agent: the conversation/terminal on the left, and a **GUI panel** ("Canvas") on the right where the agent's tool calls render as documents, forms, charts, images, and HTML — not just printed text. Switch between the two with the chat / grid icons in the toolbar. **The app opens on the grid** (`/`); the single view has its own URL, `/chat`, so you can bookmark either.*
|
|
87
90
|
|
|
88
91
|
**Inserting a file path** — like a native terminal, you can put a file's absolute path into
|
|
89
|
-
the prompt: **drag a file** onto the terminal
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
the prompt: **drag a file** onto the terminal, or click the **file button** in the terminal
|
|
93
|
+
header, which asks the local server to open the OS file dialog and inserts the chosen path. The
|
|
94
|
+
path is inserted at the cursor — it is not submitted, so you can review it first.
|
|
95
|
+
|
|
96
|
+
A drag inserts the file's **own** path where the browser exposes one via `file://`
|
|
97
|
+
(Firefox/Safari), so editing it afterwards edits the file you dropped. Where the browser
|
|
98
|
+
withholds it — **Chrome**, and every browser when MulmoTerminal is open **from another
|
|
99
|
+
machine**, where a local path would name nothing on the host — the file's bytes are sent
|
|
100
|
+
instead, saved to a private per-session directory under the OS temp dir, and *that* path is
|
|
101
|
+
inserted. The session is granted that directory at launch (Claude Code's `--add-dir`, bind-mounted
|
|
102
|
+
in the sandbox too), so the agent reads it without a permission prompt; the copies are removed
|
|
103
|
+
when the session ends, and any left by a crash are swept at the next start. Up to 110 MiB per
|
|
104
|
+
file — the same ceiling as a phone attachment. **A session already running when you upgrade
|
|
105
|
+
was launched without that grant**, so drops into it still prompt; new sessions don't.
|
|
106
|
+
|
|
107
|
+
**Pasting a screenshot** — take a screenshot and paste it straight into the terminal
|
|
108
|
+
(`Cmd`/`Ctrl`+`V`). The image is saved to the session's own drop directory — the same place a
|
|
109
|
+
dropped file goes, with the same grant, the same 110 MiB ceiling and the same cleanup when the
|
|
110
|
+
session ends — and its **absolute path** is inserted at the cursor, so the agent can read it.
|
|
111
|
+
Unlike a drop, this does not need the browser to expose a path — the bytes are on the
|
|
112
|
+
clipboard — so it also covers Chrome, where dropping a file cannot insert a path. It works
|
|
113
|
+
wherever the browser puts the image on the clipboard as `image/png`, `image/jpeg`,
|
|
114
|
+
`image/gif`, or `image/webp`. Anything else is left to the terminal's own paste handling,
|
|
115
|
+
exactly as before — including a paste that carries **plain text** next to the image, which
|
|
116
|
+
copying from a web page usually does, so that pasting text keeps working.
|
|
94
117
|
|
|
95
118
|
<a id="clicking-a-file-path"></a>
|
|
96
119
|
|
|
@@ -129,6 +152,51 @@ dotfiles are server-only. The 45 extensions both sides agree on live in
|
|
|
129
152
|
|
|
130
153
|
---
|
|
131
154
|
|
|
155
|
+
## What people say after switching
|
|
156
|
+
|
|
157
|
+
> These are experiences reported by users who moved over from an IDE or a split terminal —
|
|
158
|
+
> not benchmarks, and not claims we measured. Your setup may differ.
|
|
159
|
+
|
|
160
|
+
### "It stopped eating my memory"
|
|
161
|
+
|
|
162
|
+
Keeping several agents apart by opening several IDE windows is expensive: each one brings its own
|
|
163
|
+
editor, language server, extensions and file watchers. One user reported a **64 GB machine
|
|
164
|
+
stuttering** under that load, and running smoothly after moving over — here the agents are PTYs on
|
|
165
|
+
a server and the UI is browser tabs.
|
|
166
|
+
|
|
167
|
+
### "I stopped answering the wrong agent"
|
|
168
|
+
|
|
169
|
+
Six panes of scrolling text look identical. Users have described **typing a reply into another
|
|
170
|
+
agent's terminal**, and losing track of what they had asked in the first place. As one put it, the
|
|
171
|
+
windows all look the same, so switching between them costs time just to work out what you are
|
|
172
|
+
looking at.
|
|
173
|
+
|
|
174
|
+
The problem isn't attention — it's that N identical panes means holding N contexts in your head.
|
|
175
|
+
Colour-coded state, a name badge and a per-directory colour move that onto the screen instead.
|
|
176
|
+
|
|
177
|
+
### "Watching many and reading one stopped being a trade-off"
|
|
178
|
+
|
|
179
|
+
Splitting a terminal six ways leaves every pane too small to read a long answer without constant
|
|
180
|
+
scrolling and resizing — one user described exactly that with a 4,000-character reply. So you
|
|
181
|
+
quietly accept worse reading every time you add an agent.
|
|
182
|
+
|
|
183
|
+
**Grid ↔ enlarge removes that.** Watch all of them, then blow one up and read it properly — the
|
|
184
|
+
cockpit roster keeps the rest in view as text while you do.
|
|
185
|
+
|
|
186
|
+
### "My existing sessions came with me"
|
|
187
|
+
|
|
188
|
+
Sessions resume as-is — same `claude --resume`, same transcripts. Point it at a directory you
|
|
189
|
+
already work in and your history is there. Nothing to migrate, nothing to redo. One user said this
|
|
190
|
+
alone made the switch worth it, having previously lost context to killed sessions.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
**You don't need ten agents for this to pay off.** Users have reported the switch being worth it at
|
|
195
|
+
**one to three** parallel sessions. The wins above are about not losing track, not about running
|
|
196
|
+
more.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
132
200
|
## Install & run
|
|
133
201
|
|
|
134
202
|
Needs **Node ≥ 22.9**, plus these CLIs on your `PATH`:
|
|
@@ -161,7 +229,10 @@ and every CLI in the table above), seeds the launcher's **directory
|
|
|
161
229
|
presets** from the projects in your Claude Code history, and writes `~/.mulmoterminal/config.json`.
|
|
162
230
|
It's **idempotent** — re-run it any time to refresh the presets; it overwrites the managed parts
|
|
163
231
|
and keeps your other settings. When `claude` is installed it can hand off to the
|
|
164
|
-
`/mulmoterminal-config` skill for interactive tweaks
|
|
232
|
+
`/mulmoterminal-config` skill for interactive tweaks — it routes to the one that owns what you
|
|
233
|
+
want to change. Once the app is up you can also reach them from **Settings**: each section that a
|
|
234
|
+
skill can write ends in a button that starts that skill in a new session, which is how the settings
|
|
235
|
+
with no UI (a theme of your own, `keymap`) get written without hand-editing JSON.
|
|
165
236
|
|
|
166
237
|
**Google account (optional).** Link a Google account to enable the chat's `google` tool and the
|
|
167
238
|
phone's `google.calendar.*` commands: read/create events on any calendar (not just your primary),
|
|
@@ -298,12 +369,12 @@ SDK; we drive the real interactive CLI and relay its TTY over the WebSocket.
|
|
|
298
369
|
|
|
299
370
|
---
|
|
300
371
|
|
|
301
|
-
## Agents: Claude &
|
|
372
|
+
## Agents: Claude, Codex & Antigravity
|
|
302
373
|
|
|
303
374
|
MulmoTerminal drives **interactive coding-agent CLIs**, not just Claude. An
|
|
304
375
|
`AgentAdapter` seam abstracts the per-agent bits (which binary to spawn, how it resumes)
|
|
305
|
-
so the PTY, grid, persistence, and GUI-panel plumbing stay shared.
|
|
306
|
-
today — **Claude Code** (the default) and **
|
|
376
|
+
so the PTY, grid, persistence, and GUI-panel plumbing stay shared. Three adapters ship
|
|
377
|
+
today — **Claude Code** (the default), **Codex**, and **Antigravity** (`agy`).
|
|
307
378
|
|
|
308
379
|
- **Claude** — spawned as `claude` (override with `CLAUDE_BIN`). The server passes
|
|
309
380
|
`--session-id <uuid>`, so it knows the live session's id even before its transcript
|
|
@@ -317,10 +388,34 @@ today — **Claude Code** (the default) and **Codex**.
|
|
|
317
388
|
`CODEX_HOME`) and maps the new rollout to the session — attributed only when it's
|
|
318
389
|
unambiguous, never by "newest wins". Resume reattaches a live PTY, adopts a surviving
|
|
319
390
|
tmux session, or cold-resumes the rollout id.
|
|
391
|
+
- **Antigravity** — spawned as `agy` (override with `ANTIGRAVITY_BIN`; `ANTIGRAVITY_MODEL` sets
|
|
392
|
+
`--model`). Antigravity runs on its own WebSocket (`/ws/antigravity`). Like Codex it mints its
|
|
393
|
+
own conversation id, so the server watches `~/.gemini/antigravity-cli/brain/` (home overridable
|
|
394
|
+
via `ANTIGRAVITY_HOME`) for the directory the new conversation creates — attributed only when
|
|
395
|
+
unambiguous — and cold-resumes it with `--conversation <id>`.
|
|
396
|
+
|
|
397
|
+
Its **GUI tools work differently**, because `agy` takes no MCP flag: it reads its servers from
|
|
398
|
+
`.agents/mcp_config.json` in the working directory. MulmoTerminal writes that file from the
|
|
399
|
+
directory's [Canvas switches](#wiki-collections--the-gui-panel) — the same switches Claude's cells read — so
|
|
400
|
+
one switch serves every agent, and rewrites it whenever a switch flips or an agy session starts.
|
|
401
|
+
Servers in it that MulmoTerminal did not write are left alone, the file is removed once no group
|
|
402
|
+
is on, and it is kept out of your `git status` through `.git/info/exclude` — a local switch on a
|
|
403
|
+
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
|
|
404
|
+
GUI MCP server the other agents call. The **session id is never written into that file** — it is
|
|
405
|
+
per directory and shared by every session running there — and reaches the bridge through the agy
|
|
406
|
+
process's own environment instead.
|
|
407
|
+
|
|
408
|
+
The Docker sandbox does NOT cover agy: it stays claude-only until `buildDockerRunArgs` is
|
|
409
|
+
generalized (see `plans/feat-multi-agent-support.md`, PR#5). `agy` also ships as a standalone
|
|
410
|
+
binary rather than an npm package, so the sandbox image has nothing to install.
|
|
320
411
|
|
|
321
412
|
**Choosing an agent.** The single view has a **New Codex session** button; each grid
|
|
322
|
-
cell's launch form
|
|
323
|
-
choice is remembered).
|
|
413
|
+
cell's launch form carries a **Claude / Codex / Antigravity / Shell** toggle, and the
|
|
414
|
+
Collections browser a **Claude / Codex / Antigravity** one (your choice is remembered).
|
|
415
|
+
**Shell** is not an agent: it runs your OS default shell (`$SHELL`, or `/bin/sh`) in the
|
|
416
|
+
chosen directory, with nothing to install and nothing to configure. It starts a launcher
|
|
417
|
+
cell, so it has no model, no MCP registration, and no worktree — those rows disappear
|
|
418
|
+
while it is picked.
|
|
324
419
|
|
|
325
420
|
**Other models.**
|
|
326
421
|
Claude Code can run against any **Anthropic-compatible** backend (OpenRouter, Moonshot, a
|
|
@@ -447,7 +542,14 @@ the `claude` / `codex` sessions themselves.
|
|
|
447
542
|
| `CODEX_BIN` | `codex` | The Codex CLI binary to spawn. |
|
|
448
543
|
| `CODEX_MODEL`| codex default | Model passed to Codex as `--model` (unset = Codex's own default). |
|
|
449
544
|
| `CODEX_HOME` | `~/.codex` | Codex home — where its session rollouts and MulmoTerminal-mirrored skills live. |
|
|
545
|
+
| `ANTIGRAVITY_BIN` | `agy` | The Antigravity CLI binary to spawn. |
|
|
546
|
+
| `ANTIGRAVITY_MODEL` | agy default | Model passed to Antigravity as `--model` (unset = agy's own default). |
|
|
547
|
+
| `ANTIGRAVITY_HOME` | `~/.gemini/antigravity-cli` | Antigravity home directory containing session brain storage. |
|
|
450
548
|
| `MULMOTERMINAL_HOME` | `~/.mulmoterminal` | Root for managed **git worktrees**. |
|
|
549
|
+
| `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. |
|
|
550
|
+
| `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. |
|
|
551
|
+
| `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. |
|
|
552
|
+
| `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. |
|
|
451
553
|
| `WAIT_REAP_GRACE_MS` | `1800000` | How long a **waiting** background session is kept before it's auto-reaped (`0` or negative = never). |
|
|
452
554
|
|
|
453
555
|
The Docker-sandbox variables (`MULMOTERMINAL_SANDBOX`, `MULMOTERMINAL_SANDBOX_IMAGE`,
|
|
@@ -477,7 +579,7 @@ The Settings modal (⚙) persists per-user UI choices to `~/.mulmoterminal/confi
|
|
|
477
579
|
| `soundKinds` | Which moments beep — see [Notification sounds](#notification-sounds). Defaults to `["finished","waiting"]`; the other kinds are opt-in. |
|
|
478
580
|
| `sounds` | Per-kind sound: `{ "waiting": "preset:coin" }`. A `preset:<id>` reference or an absolute path; a kind with no entry falls back to `soundFile`. |
|
|
479
581
|
| `prRepos` | `owner/repo` entries whose open PRs/issues the cross-repo **PRs & Issues** view aggregates (via your `gh` login). |
|
|
480
|
-
| `launchers` | `{ label, command }` entries offered in a grid cell's launcher besides
|
|
582
|
+
| `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. |
|
|
481
583
|
| `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. |
|
|
482
584
|
| `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. |
|
|
483
585
|
| `buttons` | Header action buttons — see [Header buttons](#header-buttons). Omit to keep the defaults; set to replace them. |
|
|
@@ -491,6 +593,7 @@ The Settings modal (⚙) persists per-user UI choices to `~/.mulmoterminal/confi
|
|
|
491
593
|
| `decisionDigest` | Keep a **Markdown digest of the decisions this project's sessions asked for**, refreshed at startup and every few hours, so an agent can read what has already been decided before asking something similar. Written to `~/.mulmoterminal/decisions/<project>.md` (never into your repository) and served to agents by the bundled `mulmoterminal-decisions` skill. **Off by default** — it is a vision-stage idea, and it writes a file that would otherwise not exist. The digest holds dated facts, never inferred rules. |
|
|
492
594
|
| `issueWorkComments` | Let a cell **comment on the issue it is working on**: once when it starts, and again when its PR merges (closing the issue if GitHub has not already). The comment names the working **directory** it happened in — the folder name only, never the path — so a reader can tell which clone. **Off by default**; it writes to GitHub, often on somebody else's issue. Needs `gh` logged in. See the [Configuration guide](https://receptron.github.io/mulmoterminal/guide/en/config.html#issue-work-comments). |
|
|
493
595
|
| `prWorkdirFooter` | Ends a PR body with `work in <clone>` — the directory name of the clone the work happened in, so a PR says which of several side-by-side checkouts produced it. Applies to **both** paths that open PRs here: **⧉ Open PR** appends it to the PR it creates, and every Claude session is told to end the bodies it writes with the same line (the name is resolved by the server, so a session inside a managed worktree still names the main checkout). **On by default**; set `false` to opt out — read per PR and per session spawn, so no restart is needed (there is no Settings control for it). Appending is idempotent: an existing PR never gets a second copy. |
|
|
596
|
+
| `appendSystemPrompt` | Whether a spawned Claude session is asked to end a reply with a **closing summary** — what was asked, what was achieved, what was not (see [Closing summary](#closing-summary)). **On by default**; set `false` to opt out, and a directory's `.mulmoterminal.json` outranks this. Read per spawn, so no restart is needed (there is no Settings control for it), though a session already running keeps what it was launched with. `true` / `false` only. |
|
|
494
597
|
| `fontFamily` | The **terminal font** every session renders in — a CSS font-family stack, e.g. `"'Cica', 'MS Gothic', monospace"`. No Settings UI: edit the file, then **restart** (this config is read once at startup). Unset uses the built-in stack (JetBrains Mono / Fira Code / Menlo / Consolas, then CJK faces for Japanese, Korean and Chinese). Unlike the per-browser font **size**, this is one value for the whole host — it names fonts, and which fonts exist is a property of the machine. A directory can override it. See the [Configuration guide](https://receptron.github.io/mulmoterminal/guide/en/config.html#font-family). |
|
|
495
598
|
|
|
496
599
|
Every MulmoTerminal on the machine shares this one file, so an older build could save over a key a
|
|
@@ -514,8 +617,8 @@ agent), or `"open"`. An `open` button targets one of `url` / `reveal` (OS file m
|
|
|
514
617
|
opened next to the current one) / `pr: true` (open the current branch's PR — the button is hidden when
|
|
515
618
|
there's no open PR) / `pickFile: true` (OS file dialog → insert the path).
|
|
516
619
|
`${dir}`, `${branch}`, `${repo}`, … substitute live context, and `when` (e.g. `"isGitRepo"`) gates
|
|
517
|
-
visibility. The `/mulmoterminal-
|
|
518
|
-
merge over the global ones by `id
|
|
620
|
+
visibility. The `/mulmoterminal-header` skill writes a valid config interactively; per-dir buttons
|
|
621
|
+
merge over the global ones by `id`, while `chips` replace the global list wholesale.
|
|
519
622
|
|
|
520
623
|
### Notification sounds
|
|
521
624
|
|
|
@@ -611,9 +714,10 @@ malformed file is ignored.
|
|
|
611
714
|
"colors": { "background": "#190a23", "cursor": "#ff2e63" }, // per-key palette overrides
|
|
612
715
|
"fontSize": 16, // terminal font size in px (8–32); overrides Settings
|
|
613
716
|
"fontFamily": "'Cica', monospace", // terminal font stack; overrides the global config
|
|
614
|
-
"orderPriority": 10, // rank in the grid's "priority"
|
|
717
|
+
"orderPriority": 10, // rank in the grid's "priority" order and the launcher chips (lowest first)
|
|
615
718
|
"sound": "./.mulmoterminal/alert.mp3", // attention sound, RELATIVE to this directory
|
|
616
|
-
"sounds": { "command-failed": "preset:gong" } // per-notification-kind override
|
|
719
|
+
"sounds": { "command-failed": "preset:gong" }, // per-notification-kind override
|
|
720
|
+
"appendSystemPrompt": false // no closing summary here; omit to follow the global setting
|
|
617
721
|
}
|
|
618
722
|
```
|
|
619
723
|
|
|
@@ -634,17 +738,18 @@ malformed file is ignored.
|
|
|
634
738
|
| `theme` | xterm palette for terminals in this directory (one of the built-in theme ids). |
|
|
635
739
|
| `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. |
|
|
636
740
|
| `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. |
|
|
637
|
-
| `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.
|
|
741
|
+
| `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. |
|
|
638
742
|
| `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. |
|
|
639
743
|
| `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. |
|
|
640
744
|
| `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. |
|
|
745
|
+
| `appendSystemPrompt` | Whether this directory's Claude sessions are asked to end a reply with a **closing summary** (see [Closing summary](#closing-summary)). Omit to follow the global `appendSystemPrompt`, which is on; `true` / `false` here outranks it. Read per spawn, so a new session in this directory picks up an edit without a restart. |
|
|
641
746
|
| `addDirs` | Extra directories this project's Claude sessions may read and edit — the terminal-side equivalent of opening several folders in one VS Code workspace, via Claude Code's `--add-dir`. Relative entries resolve against **this file's directory** (`"../shared-lib"`), a path that doesn't exist is dropped, max 16. In the Docker sandbox each one is bind-mounted too, so the grant is real inside the container — which widens the sandbox on purpose. Claude only: codex has no equivalent flag and ignores the key. |
|
|
642
747
|
|
|
643
748
|
**Security.** `sound` and every `sounds` entry are directory-relative paths only — absolute
|
|
644
749
|
paths and any `../` that escapes the directory are rejected, and the path is never taken from the
|
|
645
750
|
HTTP request, so an opened project can't point the player at arbitrary files.
|
|
646
751
|
**When changes take effect.** A write made *through Claude's tools* — which includes the
|
|
647
|
-
`mulmoterminal-
|
|
752
|
+
`mulmoterminal-dirs` skill — applies **live**: the tool hook that reports the write doubles
|
|
648
753
|
as the reload signal, so colors, palette, font size and grid order update without reopening
|
|
649
754
|
anything. There is no filesystem watcher, so an edit made **outside** a session (your own
|
|
650
755
|
editor) is picked up when the terminal is next opened.
|
|
@@ -702,8 +807,9 @@ alongside the Claude sessions. Scripts are **per-directory**: the cell reads the
|
|
|
702
807
|
different projects' scripts.
|
|
703
808
|
|
|
704
809
|
The same launcher also has an **or launch** row for your configured **launch commands**
|
|
705
|
-
—
|
|
706
|
-
|
|
810
|
+
— any interactive command — set in Settings (⚙) → **Launch commands** as
|
|
811
|
+
`{ label, command }` (e.g. `htop` → `htop`, `Codex` → `codex`). A plain shell needs no
|
|
812
|
+
entry here: the launch form's **Shell** toggle already opens `$SHELL`. Unlike
|
|
707
813
|
a one-shot script, a launcher runs as a **persistent terminal in the cell's directory**:
|
|
708
814
|
it survives grid page switches and reconnects, and its dot shows running vs. exited (it
|
|
709
815
|
has no Claude hooks, so no blocked/done states).
|
|
@@ -848,7 +954,7 @@ repo without colliding. Worktrees live under `~/.mulmoterminal/worktrees/` (over
|
|
|
848
954
|
|
|
849
955
|

|
|
850
956
|
|
|
851
|
-
*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**
|
|
957
|
+
*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.*
|
|
852
958
|
|
|
853
959
|
A worktree cell's header carries a **diff badge** (`+<commits> ●<dirty>`); click it for a
|
|
854
960
|
**Changes vs `<base>`** panel (file list + patch) with actions:
|
|
@@ -1191,6 +1297,7 @@ same-origin-guarded.
|
|
|
1191
1297
|
| `POST /api/translation` | Runtime UI-string translation. |
|
|
1192
1298
|
| `GET /api/remote-host/status` · `POST /api/remote-host/{connect,disconnect}` | Companion phone-client link. Each response carries the command channel's `health` (`online` / `reconnecting` / `offline`, plus the last listener error), so the toolbar shows a dropped channel instead of the last state it happened to fetch. |
|
|
1193
1299
|
| `POST /api/open-dir` · `POST /api/pick-file` | Reveal a dir in Finder/Explorer; OS file-picker → path (`{ directory: true }` opens the folder picker — used by the launcher's Working-directory 📁 button). |
|
|
1300
|
+
| `POST /api/session/:id/drop` | A dropped file whose path the browser withheld. **Raw bytes**, not JSON, under the file's own content type (base64 in JSON would cap real files near 18 MB, and a dropped `.json` would be parsed as a document); the original name rides percent-encoded in `x-drop-filename` and is used for its **suffix only**. Answers `{ path }` — absolute, inside the private per-session directory the session was granted at launch. 110 MiB cap; 404 for a session this server isn't running. |
|
|
1194
1301
|
|
|
1195
1302
|
The phone itself uses **none** of these routes — it reaches the host over Firestore command
|
|
1196
1303
|
docs, not HTTP. Every command it can send, and the shapes it gets back, are in
|
|
@@ -1359,6 +1466,16 @@ Key rules:
|
|
|
1359
1466
|
- Brand-new sessions appear in the sidebar **immediately** (before their `.jsonl`
|
|
1360
1467
|
exists) via the in-memory `knownSessions` registry + a `created` push; an
|
|
1361
1468
|
unused one disappears when its PTY is reaped.
|
|
1469
|
+
- **Background workers get their own filter.** A session nobody started by hand —
|
|
1470
|
+
a collection's scheduled refresh, or a plugin's `spawnBackgroundChat`
|
|
1471
|
+
`hidden: true` — is listed under the **Background** chip instead of among the
|
|
1472
|
+
chats, so a refresh schedule doesn't fill the history. It stays openable (a
|
|
1473
|
+
MulmoTerminal session is a live terminal, so a row you can't reach is a process
|
|
1474
|
+
you can't stop), and it is put on the same count+age retention as the
|
|
1475
|
+
scheduler's own sessions. The chip appears only when there is one to show. A
|
|
1476
|
+
**manual** collection Refresh is a normal visible session — unchanged. The
|
|
1477
|
+
marking is persisted (`~/.mulmoterminal/background-sessions.json`), so a worker
|
|
1478
|
+
stays out of the chat list after it finishes and after a restart.
|
|
1362
1479
|
|
|
1363
1480
|
---
|
|
1364
1481
|
|
|
@@ -1400,7 +1517,17 @@ placed last with nothing after it.
|
|
|
1400
1517
|
|
|
1401
1518
|
It is deliberately **not** written on every turn: mid-work replies and short factual answers
|
|
1402
1519
|
carry no standing request, and a summary that always appears stops being read. The wording
|
|
1403
|
-
lives in `server/agents/session-summary-prompt.ts
|
|
1520
|
+
lives in `server/agents/session-summary-prompt.ts`.
|
|
1521
|
+
|
|
1522
|
+
**On by default, and switchable off** with `appendSystemPrompt: false` — in
|
|
1523
|
+
`~/.mulmoterminal/config.json`, or in a directory's `.mulmoterminal.json`, which outranks the
|
|
1524
|
+
global value. Read per spawn, so no restart is needed; a session already running keeps what it
|
|
1525
|
+
was launched with. Nothing in the app parses what the summary says, so turning it off costs no
|
|
1526
|
+
feature — the roster and push notifications simply show the raw tail of the reply.
|
|
1527
|
+
|
|
1528
|
+
Which sections `--append-system-prompt` ends up carrying is decided in
|
|
1529
|
+
`server/agents/appended-prompt.ts`: this one and the `prWorkdirFooter` clone line are separate
|
|
1530
|
+
settings on the same flag, and with both off the flag is not passed at all.
|
|
1404
1531
|
|
|
1405
1532
|
Passed inline rather than as `--append-system-prompt-file` for the same reason `--settings`
|
|
1406
1533
|
is: the sandbox spawn runs in a container that cannot read a host path.
|
|
@@ -1419,12 +1546,13 @@ has its `/` and `.` characters replaced with `-` (e.g.
|
|
|
1419
1546
|
A session's display **title** is derived by scanning its JSONL for, in order of
|
|
1420
1547
|
preference:
|
|
1421
1548
|
|
|
1422
|
-
1.
|
|
1423
|
-
2. else
|
|
1424
|
-
3. else the latest `
|
|
1425
|
-
4. else the
|
|
1549
|
+
1. the **session note** the user wrote (see below),
|
|
1550
|
+
2. else a live **AI title** the server generated for the session this run (see below),
|
|
1551
|
+
3. else the latest `ai-title` record's `aiTitle` (e.g. written by MulmoClaude),
|
|
1552
|
+
4. else the latest `last-prompt` record's `lastPrompt`,
|
|
1553
|
+
5. else the first real user message (slash/local-command wrappers like
|
|
1426
1554
|
`<local-command-…>` are skipped),
|
|
1427
|
-
|
|
1555
|
+
6. else `"(untitled session)"`.
|
|
1428
1556
|
|
|
1429
1557
|
In-memory sessions not yet persisted show as `"New session"` until their file
|
|
1430
1558
|
appears, at which point the on-disk title takes over.
|
|
@@ -1444,6 +1572,23 @@ ack (so the raw last prompt would be stale), or every few turns to keep a long s
|
|
|
1444
1572
|
title current. The title lives in memory (never written into Claude's own transcript); a
|
|
1445
1573
|
resumed session falls back to any on-disk `ai-title`.
|
|
1446
1574
|
|
|
1575
|
+
### Session note
|
|
1576
|
+
|
|
1577
|
+
Every tier above says what the **agent** said, which stops answering "which cell is this?"
|
|
1578
|
+
once several sessions are open. So a cell header also takes a **note you write yourself**: the
|
|
1579
|
+
pencil button beside the header text opens a one-line box (Enter saves, Esc cancels, clicking
|
|
1580
|
+
away saves). While a note is set it *replaces* the header line — the title it displaced stays in
|
|
1581
|
+
the tooltip — and it becomes the session's title in the sidebar list and on the phone's roster
|
|
1582
|
+
too, so one session goes by one name everywhere.
|
|
1583
|
+
|
|
1584
|
+
Notes are capped at 200 characters and folded to a single line. They are stored per **session
|
|
1585
|
+
id** in `~/.mulmoterminal/session-memos.jsonl` and survive both the session being reaped and a
|
|
1586
|
+
server restart: resume the session and the note comes back. Saving one publishes it on the
|
|
1587
|
+
`sessions` channel, so every other open tab and the phone update without asking.
|
|
1588
|
+
|
|
1589
|
+
`POST /api/session/:id/memo` with `{ "text": "…" }` writes one; an empty `text` erases it. The
|
|
1590
|
+
route answers with the **stored** text, which is what a reload will show.
|
|
1591
|
+
|
|
1447
1592
|
---
|
|
1448
1593
|
|
|
1449
1594
|
## Project structure
|
|
@@ -1471,7 +1616,8 @@ server/
|
|
|
1471
1616
|
mcp/ per-session MCP broker
|
|
1472
1617
|
backends/ wiki, collections, feeds, accounting, notifier,
|
|
1473
1618
|
translation, whisper, remote-host, html, files
|
|
1474
|
-
skills/ bundled skills
|
|
1619
|
+
skills/ bundled skills: mulmoterminal-config (entry point + audit), -dirs, -theme,
|
|
1620
|
+
-header, -keys, -model, -notify, -bug-report, -decisions
|
|
1475
1621
|
fix-pty-perms.js postinstall: fixes node-pty binary permissions
|
|
1476
1622
|
src/
|
|
1477
1623
|
App.vue Layout; owns the active session + single/grid view
|
package/bin/cli-args.d.ts
CHANGED
|
@@ -11,3 +11,8 @@ export declare const MIN_NODE_LABEL: string;
|
|
|
11
11
|
export declare function nodeMeetsMinimum(version: string): boolean;
|
|
12
12
|
export declare function serverNodeArgs(serverEntry: string, launchDir: string): string[];
|
|
13
13
|
export declare function serverSpawnEnv(env: Record<string, string | undefined>, port: number, cwd: string): Record<string, string | undefined>;
|
|
14
|
+
export interface RunningInstance {
|
|
15
|
+
pid: number;
|
|
16
|
+
port: number | null;
|
|
17
|
+
}
|
|
18
|
+
export declare function runningInstancesPrompt(instances: readonly RunningInstance[]): string;
|
package/bin/cli-args.js
CHANGED
|
@@ -108,6 +108,26 @@ export function saysYes(answer) {
|
|
|
108
108
|
* updates never cross — so the instance that did not start a session shows a frozen history
|
|
109
109
|
* for it (#611).
|
|
110
110
|
*/
|
|
111
|
+
/**
|
|
112
|
+
* The question asked when another server is ALREADY RUNNING, whatever port this one was told to
|
|
113
|
+
* use (#1061).
|
|
114
|
+
*
|
|
115
|
+
* `secondInstancePrompt` below only fires when the wanted port is taken, so `--port <free>`
|
|
116
|
+
* started a second instance in silence — which is how eight live sessions had their settings
|
|
117
|
+
* deleted by a peer's boot. The clash is a symptom; the shared `~/.mulmoterminal` is the thing
|
|
118
|
+
* that is not supported, and that is true at any port.
|
|
119
|
+
*/
|
|
120
|
+
export function runningInstancesPrompt(instances) {
|
|
121
|
+
const where = instances.map((i) => (i.port === null ? `pid ${i.pid}` : `http://localhost:${i.port}`)).join(", ");
|
|
122
|
+
const subject = instances.length === 1 ? "MulmoTerminal is already running" : `${instances.length} MulmoTerminal servers are already running`;
|
|
123
|
+
return [
|
|
124
|
+
`${subject} (${where}).`,
|
|
125
|
+
" Running more than one is NOT a supported setup: they share ~/.mulmoterminal,",
|
|
126
|
+
" so they can overwrite each other's session state.",
|
|
127
|
+
"Start another one anyway? [y/N] ",
|
|
128
|
+
].join("\n");
|
|
129
|
+
}
|
|
130
|
+
|
|
111
131
|
export const SECOND_INSTANCE_NOTE = [
|
|
112
132
|
" Note: both share ~/.mulmoterminal. A session's tool history does not live-update",
|
|
113
133
|
" in the instance that did not start it.",
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface InstanceEntry {
|
|
2
|
+
pid: number;
|
|
3
|
+
port: number | null;
|
|
4
|
+
startedAt: number | null;
|
|
5
|
+
}
|
|
6
|
+
export declare function instancesDir(): string;
|
|
7
|
+
export declare function isProcessAlive(pid: number): boolean;
|
|
8
|
+
export declare function registerInstance(port: number, pid?: number, startedAt?: number): () => void;
|
|
9
|
+
export declare function liveInstances(excludePid?: number): InstanceEntry[];
|
|
10
|
+
export declare function earliestStartedAt(instances: readonly InstanceEntry[]): number | null;
|
package/bin/instances.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// Which MulmoTerminal servers are running right now (#1061).
|
|
2
|
+
//
|
|
3
|
+
// Two things need this answer and neither could get it before. The launcher asks so it can say
|
|
4
|
+
// "one is already running" no matter which port the new one was told to use — the port probe only
|
|
5
|
+
// notices a clash on the SAME port, so `--port <free>` started a second instance in silence. And
|
|
6
|
+
// the server asks before deleting orphaned session settings: without tmux, "nothing survived the
|
|
7
|
+
// last lifetime" is only true for the process that OWNED that lifetime, so a second instance was
|
|
8
|
+
// reading a peer's live files as leftovers and removing them.
|
|
9
|
+
//
|
|
10
|
+
// Shared as plain JS rather than through common/ because the launcher runs on bare node before
|
|
11
|
+
// any TypeScript exists — the same reason bin/update-check.js is shaped this way, and the server
|
|
12
|
+
// imports it the same way.
|
|
13
|
+
import { mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { homedir } from "node:os";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
|
|
17
|
+
export const instancesDir = () => path.join(homedir(), ".mulmoterminal", "instances");
|
|
18
|
+
|
|
19
|
+
const entryFile = (pid) => path.join(instancesDir(), `${pid}.json`);
|
|
20
|
+
|
|
21
|
+
/** Whether a pid belongs to a process that still exists. Signal 0 performs the permission and
|
|
22
|
+
* existence checks without delivering anything; EPERM means it IS there, just not ours. */
|
|
23
|
+
export function isProcessAlive(pid) {
|
|
24
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
25
|
+
try {
|
|
26
|
+
process.kill(pid, 0);
|
|
27
|
+
return true;
|
|
28
|
+
} catch (err) {
|
|
29
|
+
return err?.code === "EPERM";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Announce this process, and hand back the removal. Best-effort throughout: not being able to
|
|
34
|
+
* say hello is not a reason to fail a boot, and a crash simply leaves an entry whose pid no
|
|
35
|
+
* longer resolves — which every reader already has to handle. */
|
|
36
|
+
export function registerInstance(port, pid = process.pid, startedAt = Date.now()) {
|
|
37
|
+
const file = entryFile(pid);
|
|
38
|
+
try {
|
|
39
|
+
mkdirSync(instancesDir(), { recursive: true, mode: 0o700 });
|
|
40
|
+
// Written elsewhere and renamed into place: a reader that catches us mid-write would see a
|
|
41
|
+
// truncated entry, and "I could not parse it" must never be how a LIVE peer disappears from
|
|
42
|
+
// the registry (Codex review). rename is atomic for a reader on the same directory.
|
|
43
|
+
const tmp = `${file}.${startedAt}.tmp`;
|
|
44
|
+
writeFileSync(tmp, JSON.stringify({ pid, port, startedAt }), { encoding: "utf8", mode: 0o600 });
|
|
45
|
+
renameSync(tmp, file);
|
|
46
|
+
} catch {
|
|
47
|
+
return () => {};
|
|
48
|
+
}
|
|
49
|
+
return () => {
|
|
50
|
+
try {
|
|
51
|
+
rmSync(file, { force: true });
|
|
52
|
+
} catch {
|
|
53
|
+
// Left behind; the next reader drops it as stale.
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const parseEntry = (raw) => {
|
|
59
|
+
try {
|
|
60
|
+
const entry = JSON.parse(raw);
|
|
61
|
+
if (!entry || typeof entry !== "object") return null;
|
|
62
|
+
const { pid, port, startedAt } = entry;
|
|
63
|
+
if (!Number.isInteger(pid) || pid <= 0) return null;
|
|
64
|
+
return { pid, port: Number.isInteger(port) ? port : null, startedAt: Number.isFinite(startedAt) ? startedAt : null };
|
|
65
|
+
} catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/** Every OTHER running server, newest entry first. Entries whose process is gone are deleted as
|
|
71
|
+
* they are found: a crash cannot clean up after itself, and leaving them would make a lone
|
|
72
|
+
* instance believe it has company forever. */
|
|
73
|
+
export function liveInstances(excludePid = process.pid) {
|
|
74
|
+
let names;
|
|
75
|
+
try {
|
|
76
|
+
names = readdirSync(instancesDir());
|
|
77
|
+
} catch {
|
|
78
|
+
return []; // never registered on this machine
|
|
79
|
+
}
|
|
80
|
+
const live = [];
|
|
81
|
+
for (const name of names) {
|
|
82
|
+
if (!name.endsWith(".json")) continue;
|
|
83
|
+
const file = path.join(instancesDir(), name);
|
|
84
|
+
let entry;
|
|
85
|
+
try {
|
|
86
|
+
entry = parseEntry(readFileSync(file, "utf8"));
|
|
87
|
+
} catch {
|
|
88
|
+
continue; // being written right now, or unreadable — say nothing about it
|
|
89
|
+
}
|
|
90
|
+
// Only a positively-identified dead owner earns removal. An entry we could not parse is one
|
|
91
|
+
// we know nothing about — deleting it on that basis is how a live peer gets erased, which is
|
|
92
|
+
// the very failure this registry exists to prevent (Codex review).
|
|
93
|
+
if (entry === null) continue;
|
|
94
|
+
if (!isProcessAlive(entry.pid)) {
|
|
95
|
+
try {
|
|
96
|
+
rmSync(file, { force: true });
|
|
97
|
+
} catch {
|
|
98
|
+
// not ours to remove; harmless
|
|
99
|
+
}
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (entry.pid !== excludePid) live.push(entry);
|
|
103
|
+
}
|
|
104
|
+
return live.sort((a, b) => (b.startedAt ?? 0) - (a.startedAt ?? 0));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** When the EARLIEST of the given instances started, or null when there are none.
|
|
108
|
+
*
|
|
109
|
+
* This is the cutoff the settings prune needs: a file written before every live peer began
|
|
110
|
+
* cannot belong to one of them, so it is safe to treat as a leftover. A file written after that
|
|
111
|
+
* moment might be a peer's, and a maybe is not good enough to delete somebody's live state. */
|
|
112
|
+
export function earliestStartedAt(instances) {
|
|
113
|
+
const times = instances.map((i) => i.startedAt).filter((t) => typeof t === "number");
|
|
114
|
+
return times.length ? Math.min(...times) : null;
|
|
115
|
+
}
|
package/bin/mulmoterminal.js
CHANGED
|
@@ -22,12 +22,14 @@ import {
|
|
|
22
22
|
portInUseMessage,
|
|
23
23
|
saysYes,
|
|
24
24
|
secondInstancePrompt,
|
|
25
|
+
runningInstancesPrompt,
|
|
25
26
|
SECOND_INSTANCE_NOTE,
|
|
26
27
|
nodeMeetsMinimum,
|
|
27
28
|
MIN_NODE_LABEL,
|
|
28
29
|
serverNodeArgs,
|
|
29
30
|
serverSpawnEnv,
|
|
30
31
|
} from "./cli-args.js";
|
|
32
|
+
import { liveInstances } from "./instances.js";
|
|
31
33
|
|
|
32
34
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
33
35
|
const PKG_DIR = join(__dirname, "..");
|
|
@@ -243,6 +245,20 @@ function findEphemeralPort() {
|
|
|
243
245
|
});
|
|
244
246
|
}
|
|
245
247
|
|
|
248
|
+
// Ask about an ALREADY-RUNNING server, whatever port this one will use. Declining exits 0: the
|
|
249
|
+
// user answered the question that was asked, which is not a failure.
|
|
250
|
+
async function confirmNoRunningInstance() {
|
|
251
|
+
const running = liveInstances();
|
|
252
|
+
if (running.length === 0) return;
|
|
253
|
+
if (!process.stdin.isTTY) {
|
|
254
|
+
log(runningInstancesPrompt(running).replace(/\nStart another one anyway\? \[y\/N\] $/, ""));
|
|
255
|
+
log(SECOND_INSTANCE_NOTE);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (!(await promptYesNo(runningInstancesPrompt(running)))) process.exit(0);
|
|
259
|
+
log(SECOND_INSTANCE_NOTE);
|
|
260
|
+
}
|
|
261
|
+
|
|
246
262
|
async function choosePort(requested, explicit) {
|
|
247
263
|
if (await isPortFree(requested)) return requested;
|
|
248
264
|
// No SILENT fallback: starting a second server on another port without saying so is how
|
|
@@ -322,7 +338,7 @@ function runServer(port, noOpen, cwd, onChild) {
|
|
|
322
338
|
|
|
323
339
|
function printHelp() {
|
|
324
340
|
console.log(`
|
|
325
|
-
Usage: npx mulmoterminal [command] [options]
|
|
341
|
+
Usage: npx mulmoterminal@latest [command] [options]
|
|
326
342
|
|
|
327
343
|
Commands:
|
|
328
344
|
(none) Start the server (default)
|
|
@@ -396,6 +412,12 @@ async function main() {
|
|
|
396
412
|
// The probe above can still lose to something binding the port in the same instant, in
|
|
397
413
|
// which case the server exits 75 and runServer returns. Same answer as the probe: say who
|
|
398
414
|
// has it rather than moving to a port nobody asked for.
|
|
415
|
+
// Before anything about PORTS: is one already running at all? A clash on the same port is only
|
|
416
|
+
// the visible half — `--port <free>` used to start a second instance in silence, and the two
|
|
417
|
+
// then share ~/.mulmoterminal (#1061). Non-TTY says it and carries on rather than hanging on a
|
|
418
|
+
// prompt nobody can answer: a script that asked for a server should still get one.
|
|
419
|
+
await confirmNoRunningInstance();
|
|
420
|
+
|
|
399
421
|
const port = await choosePort(requestedPort, portExplicit);
|
|
400
422
|
await runServer(port, noOpen, cwd, (c) => {
|
|
401
423
|
child = c;
|
|
@@ -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";
|