mulmoterminal 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/README.md +382 -42
  2. package/dist/assets/{abnfDiagram-VRR7QNED-6nNByj6v-IGFnNkfa.js → abnfDiagram-VRR7QNED-6nNByj6v-CcrZYOgs.js} +1 -1
  3. package/dist/assets/architecture-TIHT7OUA-CYMWc3UT-QInGApkV.js +1 -0
  4. package/dist/assets/{architectureDiagram-ZJ3FMSHR-Bgnyaj_i-CAxLCofF.js → architectureDiagram-ZJ3FMSHR-Bgnyaj_i-CUuVwmX4.js} +1 -1
  5. package/dist/assets/{blockDiagram-677ZJIJ3-DQ35o5E4-x3_3MCHB.js → blockDiagram-677ZJIJ3-DQ35o5E4-CvxU8aZe.js} +1 -1
  6. package/dist/assets/{c4Diagram-LMCZKHZV-ClWZeiWo-BHgXFEHO.js → c4Diagram-LMCZKHZV-ClWZeiWo-D8f1kYY_.js} +1 -1
  7. package/dist/assets/channel-Di5rtkx0-DHqL817r.js +1 -0
  8. package/dist/assets/{chunk-32BRIVSS-CCt9wtYd-Cevd76M7.js → chunk-32BRIVSS-CCt9wtYd-Db_FZIf1.js} +1 -1
  9. package/dist/assets/{chunk-52WLFC77-FbBbR4uI-BiOqnP1l.js → chunk-52WLFC77-FbBbR4uI-7n2yHJ2K.js} +1 -1
  10. package/dist/assets/{chunk-C7G6YPKG-C87hlS9c-qjNSM7yE.js → chunk-C7G6YPKG-C87hlS9c-DpkxPQKG.js} +1 -1
  11. package/dist/assets/{chunk-EX3LRPZG-BqGqMXLN-DP83K0Kb.js → chunk-EX3LRPZG-BqGqMXLN-BgqbD-Ak.js} +1 -1
  12. package/dist/assets/{chunk-FWX5IMBZ-BkbSAAuW-DDaQ5S2G.js → chunk-FWX5IMBZ-BkbSAAuW-BmYnGiYn.js} +2 -2
  13. package/dist/assets/{chunk-HOUHSVGY-Cxu0eDlh-CmLLHV-D.js → chunk-HOUHSVGY-Cxu0eDlh-TqsiFF7j.js} +1 -1
  14. package/dist/assets/{chunk-ICXQ74PX-hiraF_Xj-xuqMpWo5.js → chunk-ICXQ74PX-hiraF_Xj-Cx0hIueU.js} +1 -1
  15. package/dist/assets/{chunk-MOJQB5TN-CqxshQHA-jUnAGLd5.js → chunk-MOJQB5TN-CqxshQHA-pSbnAJ7U.js} +1 -1
  16. package/dist/assets/{chunk-OGEWGWER-CjCr7ceX-Be5sOa4R.js → chunk-OGEWGWER-CjCr7ceX-C2qBJ2Pg.js} +1 -1
  17. package/dist/assets/{chunk-PUDLZKDR-Dx6M-vz1-B6N9l2sp.js → chunk-PUDLZKDR-Dx6M-vz1-DvP2VeJc.js} +1 -1
  18. package/dist/assets/{chunk-Q4XR5HBZ-CZd-9lTB-CNc2UnbF.js → chunk-Q4XR5HBZ-CZd-9lTB-B9gNYRb9.js} +1 -1
  19. package/dist/assets/{chunk-V7JOEXUC-rI0xlC_O-BKwWIZFY.js → chunk-V7JOEXUC-rI0xlC_O-W1oR2e7R.js} +1 -1
  20. package/dist/assets/{chunk-VAUOI2AC-DrcykVNK-Um-9nuwz.js → chunk-VAUOI2AC-DrcykVNK-UQ8qJq-D.js} +1 -1
  21. package/dist/assets/{chunk-VR4S4FIN-O6iF8Yvf-C55cFzwq.js → chunk-VR4S4FIN-O6iF8Yvf-BEZ-hkFq.js} +1 -1
  22. package/dist/assets/{chunk-WYO6CB5R-B83L_z6I-NmPlbhpt.js → chunk-WYO6CB5R-B83L_z6I-zjc6vart.js} +1 -1
  23. package/dist/assets/{chunk-ZGVPDNZ5-BpFv9JSP-DQuoM4dk.js → chunk-ZGVPDNZ5-BpFv9JSP-WgpTVFz9.js} +1 -1
  24. package/dist/assets/classDiagram-OUVF2IWQ-BgAZMSbT-VLjGJAJO.js +1 -0
  25. package/dist/assets/classDiagram-v2-EOCWNBFH-DxHTyui1-VLjGJAJO.js +1 -0
  26. package/dist/assets/cynefin-VYW2F7L2-DqA3n9nY-DeDS-5by.js +1 -0
  27. package/dist/assets/{cynefinDiagram-TSTJHNR4-x0-0sQ15-BjW2zyQO.js → cynefinDiagram-TSTJHNR4-x0-0sQ15-D6l6qqRO.js} +1 -1
  28. package/dist/assets/{dagre-VKFMJZFB-CQdfl-bx-CiVq2pd3.js → dagre-VKFMJZFB-CQdfl-bx-BVKoii9B.js} +1 -1
  29. package/dist/assets/{diagram-FQU43EPY-BOSB6VUb-BviH94fe.js → diagram-FQU43EPY-BOSB6VUb-OjDjlvVc.js} +1 -1
  30. package/dist/assets/{diagram-G47NLZAW-DLXrcXsN-C9ANoKwM.js → diagram-G47NLZAW-DLXrcXsN-TSzEtHdz.js} +1 -1
  31. package/dist/assets/{diagram-NH7WQ7WH-BMQp1rkF-iRLoGEIF.js → diagram-NH7WQ7WH-BMQp1rkF-6YpHLSkw.js} +1 -1
  32. package/dist/assets/{diagram-OA4YK3LP-D1wQ0vUj-DApMIOOW.js → diagram-OA4YK3LP-D1wQ0vUj-D080UfcD.js} +1 -1
  33. package/dist/assets/{diagram-WEI45ONY-RR0DpF8R-5HyDvuK6.js → diagram-WEI45ONY-RR0DpF8R-DAhnbGro.js} +1 -1
  34. package/dist/assets/{ebnfDiagram-CCIWWBDH-M123uVJ8-2mE0-H0-.js → ebnfDiagram-CCIWWBDH-M123uVJ8-CgYjblvQ.js} +1 -1
  35. package/dist/assets/{erDiagram-Q63AITRT-BWx_-PXG-CY-qvEZP.js → erDiagram-Q63AITRT-BWx_-PXG-B-9YiTfM.js} +1 -1
  36. package/dist/assets/eventmodeling-45OFAUF4-_BVSjAXf-CF7wgGuX.js +1 -0
  37. package/dist/assets/flowDiagram-23GEKE2U-BeOc_anm-CfWH0Tbu.js +1 -0
  38. package/dist/assets/{ganttDiagram-NO4QXBWP-BOoJ1eTw-DJnZnEVq.js → ganttDiagram-NO4QXBWP-BOoJ1eTw-DpUm7W4G.js} +1 -1
  39. package/dist/assets/gitGraph-TEB2WS4Q-CH12KLTN-C2PnVeMX.js +1 -0
  40. package/dist/assets/{gitGraphDiagram-IHSO6WYX-B2CJhk_G-CS2k47ZS.js → gitGraphDiagram-IHSO6WYX-B2CJhk_G-CXhq2YaR.js} +1 -1
  41. package/dist/assets/index-83utxopd.js +357 -0
  42. package/dist/assets/index-BVNLkhp4.css +1 -0
  43. package/dist/assets/info-DKCQHKI2-Cbw3mbiK-CLk5uJ5t.js +1 -0
  44. package/dist/assets/{infoDiagram-FWYZ7A6U-Mp1X3pBP-DA4NgCU8.js → infoDiagram-FWYZ7A6U-Mp1X3pBP-BH6ylmwo.js} +1 -1
  45. package/dist/assets/{ishikawaDiagram-FXEZZL3T-BNG7tkJu-DPK-CeOV.js → ishikawaDiagram-FXEZZL3T-BNG7tkJu-DKlrUTwv.js} +1 -1
  46. package/dist/assets/{journeyDiagram-5HDEW3XC-Dbp_hY9X-C7-vjDGz.js → journeyDiagram-5HDEW3XC-Dbp_hY9X-B0zuSRxn.js} +1 -1
  47. package/dist/assets/{kanban-definition-HUTT4EX6-DSTc5u3q-DDPYyGjM.js → kanban-definition-HUTT4EX6-DSTc5u3q-B2UMHQ_t.js} +1 -1
  48. package/dist/assets/{line-B1wBwzrY-C8LCGp0g.js → line-B1wBwzrY-NlyrzQ8H.js} +1 -1
  49. package/dist/assets/{marp-CXxH4bDL.js → marp-Ak2po4xM.js} +1 -1
  50. package/dist/assets/{mermaid-parser.core-DC7NPJ_M-DpdiJOd9.js → mermaid-parser.core-DC7NPJ_M-uogn_ZQO.js} +2 -2
  51. package/dist/assets/{mermaid.core-DZM3Ha-E-qDLGiF8u.js → mermaid.core-DZM3Ha-E-CTwQuxp4.js} +3 -3
  52. package/dist/assets/{mindmap-definition-LN4V7U3C-DYtgcMsY-X9JS6B27.js → mindmap-definition-LN4V7U3C-DYtgcMsY-CF8BKsai.js} +1 -1
  53. package/dist/assets/packet-7NZHBO7P-lwb58iYx-3tUu63Qj.js +1 -0
  54. package/dist/assets/{pegDiagram-2B236MQR-C43eIpKM-q5euCaBj.js → pegDiagram-2B236MQR-C43eIpKM-Byur3AI9.js} +1 -1
  55. package/dist/assets/pie-RZYD4A2V-B1UWb4Gu-DiT3qAWP.js +1 -0
  56. package/dist/assets/{pieDiagram-ENE6RG2P-BkTqgJyR-ee81vkeN.js → pieDiagram-ENE6RG2P-BkTqgJyR-LmzmCqEK.js} +1 -1
  57. package/dist/assets/{quadrantDiagram-ABIIQ3AL-Bm1Zjm45-CoACCyaD.js → quadrantDiagram-ABIIQ3AL-Bm1Zjm45-CvxrZG72.js} +1 -1
  58. package/dist/assets/radar-I7S5WNFK-7CKcb_l--C_JwXiDu.js +1 -0
  59. package/dist/assets/railroad-3IZDKUUU-gCySKdnW-C30t7Eqd.js +1 -0
  60. package/dist/assets/railroad-abnf-AHOZXSZD-BophH4r--CGjr3zMY.js +1 -0
  61. package/dist/assets/railroad-ebnf-EBAXGLYW-AZNjl_Zu-BLsfAiEh.js +1 -0
  62. package/dist/assets/railroad-peg-LSFZ7HO6-BSiEEyeb-DpWrKO8r.js +1 -0
  63. package/dist/assets/{railroadDiagram-RFXS5EU6-BkfbdeAs-Dgv56gIO.js → railroadDiagram-RFXS5EU6-BkfbdeAs-Dmaz7qwx.js} +1 -1
  64. package/dist/assets/{requirementDiagram-TGXJPOKE-CrGTTjYg-CCYvGt-t.js → requirementDiagram-TGXJPOKE-CrGTTjYg-DLd9Qy31.js} +1 -1
  65. package/dist/assets/{sankeyDiagram-HTMAVEWB-rWXPf03Z-CprB_Z8u.js → sankeyDiagram-HTMAVEWB-rWXPf03Z-DuYt79nd.js} +1 -1
  66. package/dist/assets/{sequenceDiagram-DBY2YBRQ-nkJYWO2m-Dq6hIypb.js → sequenceDiagram-DBY2YBRQ-nkJYWO2m-CN2BTBIe.js} +1 -1
  67. package/dist/assets/{stateDiagram-2N3HPSRC-T4-clK8b-Ch8H-wNS.js → stateDiagram-2N3HPSRC-T4-clK8b-CQkZXPtf.js} +1 -1
  68. package/dist/assets/stateDiagram-v2-6OUMAXLB-DIp7nhRd-TfY_5w1P.js +1 -0
  69. package/dist/assets/{swimlanes-5IMT3BWC-HmQNEntu-D8pbAIlW.js → swimlanes-5IMT3BWC-HmQNEntu-BxSqfUwA.js} +1 -1
  70. package/dist/assets/swimlanesDiagram-G3AALYLV-BeoZhwg7-BXdsLx4g.js +8 -0
  71. package/dist/assets/{timeline-definition-FHXFAJF6-CNc9jSTP--kxf46LG.js → timeline-definition-FHXFAJF6-CNc9jSTP-XW10LDWA.js} +1 -1
  72. package/dist/assets/treeView-QDETBFTQ-nIQcG1h9-BbYxhc6_.js +1 -0
  73. package/dist/assets/treemap-6X3UGDF4-BnsvC8yL-C4IdHouD.js +1 -0
  74. package/dist/assets/{vennDiagram-L72KCM5P-Dr3pTJ_0-BDmffNZb.js → vennDiagram-L72KCM5P-Dr3pTJ_0-BV8NVuqb.js} +1 -1
  75. package/dist/assets/wardley-OPB4EBWU-8Odxkx6V-BlJFib5h.js +1 -0
  76. package/dist/assets/{wardleyDiagram-EHGQE667-DSFc7ZZa-B5tAi_lA.js → wardleyDiagram-EHGQE667-DSFc7ZZa-CPdAgmn_.js} +1 -1
  77. package/dist/assets/{xychartDiagram-FW5EYKEG-BP0Nn4Pp-Du36xrax.js → xychartDiagram-FW5EYKEG-BP0Nn4Pp-CJCoz2E2.js} +1 -1
  78. package/dist/index.html +2 -2
  79. package/package.json +14 -11
  80. package/server/app-config.spec.ts +5 -1
  81. package/server/app-config.ts +20 -27
  82. package/server/backends/workspaceSetup.ts +8 -0
  83. package/server/codex-args.spec.ts +25 -4
  84. package/server/codex-args.ts +13 -0
  85. package/server/codex-sessions.spec.ts +96 -0
  86. package/server/codex-sessions.ts +140 -0
  87. package/server/codex-skills.spec.ts +70 -0
  88. package/server/codex-skills.ts +54 -0
  89. package/server/command-summary.spec.ts +122 -0
  90. package/server/command-summary.ts +157 -0
  91. package/server/config-routes.ts +44 -40
  92. package/server/config-schema.spec.ts +123 -0
  93. package/server/config-schema.ts +226 -0
  94. package/server/cost.spec.ts +107 -0
  95. package/server/cost.ts +215 -0
  96. package/server/cwd-presets.ts +1 -5
  97. package/server/dir-config.spec.ts +106 -5
  98. package/server/dir-config.ts +69 -68
  99. package/server/git-status.spec.ts +101 -0
  100. package/server/git-status.ts +54 -0
  101. package/server/header-config.spec.ts +108 -0
  102. package/server/header-config.ts +175 -0
  103. package/server/header-context.spec.ts +16 -0
  104. package/server/header-context.ts +68 -0
  105. package/server/header-resolve.spec.ts +132 -0
  106. package/server/header-resolve.ts +115 -0
  107. package/server/header-title.spec.ts +130 -0
  108. package/server/header-title.ts +112 -0
  109. package/server/index.ts +368 -72
  110. package/server/install-config-skill.spec.ts +103 -0
  111. package/server/install-config-skill.ts +62 -0
  112. package/server/session-resolve.spec.ts +41 -0
  113. package/server/session-resolve.ts +37 -0
  114. package/server/skills/mulmoterminal-config/SKILL.md +208 -0
  115. package/server/skills/mulmoterminal-config/palettes.json +167 -0
  116. package/server/transcript.spec.ts +140 -0
  117. package/server/transcript.ts +111 -0
  118. package/dist/assets/architecture-TIHT7OUA-CYMWc3UT-bWcDFBmU.js +0 -1
  119. package/dist/assets/channel-Di5rtkx0-GohkERB0.js +0 -1
  120. package/dist/assets/classDiagram-OUVF2IWQ-BgAZMSbT-GlJ3u1Wb.js +0 -1
  121. package/dist/assets/classDiagram-v2-EOCWNBFH-DxHTyui1-GlJ3u1Wb.js +0 -1
  122. package/dist/assets/cynefin-VYW2F7L2-DqA3n9nY-Dnv61JuF.js +0 -1
  123. package/dist/assets/eventmodeling-45OFAUF4-_BVSjAXf-Bbbkffe1.js +0 -1
  124. package/dist/assets/flowDiagram-23GEKE2U-BeOc_anm-DulRGLp4.js +0 -1
  125. package/dist/assets/gitGraph-TEB2WS4Q-CH12KLTN-BtMbgf2j.js +0 -1
  126. package/dist/assets/index-B7dNL7y0.css +0 -1
  127. package/dist/assets/index-a5e0Ms7-.js +0 -355
  128. package/dist/assets/info-DKCQHKI2-Cbw3mbiK-pm3KH1hx.js +0 -1
  129. package/dist/assets/packet-7NZHBO7P-lwb58iYx-CrXwOyyd.js +0 -1
  130. package/dist/assets/pie-RZYD4A2V-B1UWb4Gu-BaiAaew5.js +0 -1
  131. package/dist/assets/radar-I7S5WNFK-7CKcb_l--CFNO3OrR.js +0 -1
  132. package/dist/assets/railroad-3IZDKUUU-gCySKdnW-CdeNVhid.js +0 -1
  133. package/dist/assets/railroad-abnf-AHOZXSZD-BophH4r--Btv0ecNO.js +0 -1
  134. package/dist/assets/railroad-ebnf-EBAXGLYW-AZNjl_Zu-CH9b-tjP.js +0 -1
  135. package/dist/assets/railroad-peg-LSFZ7HO6-BSiEEyeb-C3e6kQJQ.js +0 -1
  136. package/dist/assets/stateDiagram-v2-6OUMAXLB-DIp7nhRd-C9sabJiH.js +0 -1
  137. package/dist/assets/swimlanesDiagram-G3AALYLV-BeoZhwg7-Dgqw4HBg.js +0 -8
  138. package/dist/assets/treeView-QDETBFTQ-nIQcG1h9-pio3kitt.js +0 -1
  139. package/dist/assets/treemap-6X3UGDF4-BnsvC8yL-DprilSCD.js +0 -1
  140. package/dist/assets/wardley-OPB4EBWU-8Odxkx6V-E_XL_tuo.js +0 -1
package/README.md CHANGED
@@ -1,14 +1,21 @@
1
1
  # mulmoterminal
2
2
 
3
- A browser-based terminal for running [Claude Code](https://claude.com/claude-code)
4
- sessions, with a sidebar that lists the current project's chat sessions and shows
5
- live, hook-driven activity for each one.
3
+ A browser-based terminal + GUI workspace for coding agents — [Claude Code](https://claude.com/claude-code)
4
+ and OpenAI's **Codex** — with a sidebar that lists the current project's chat sessions
5
+ and shows live, hook-driven activity for each one.
6
6
 
7
- Each session runs as a real PTY on the server (`claude` in a pseudo-terminal) and
7
+ Each session runs as a real PTY on the server (the agent CLI in a pseudo-terminal) and
8
8
  is streamed to an [xterm.js](https://xtermjs.org/) terminal in the browser over a
9
- WebSocket. A sidebar lists every Claude session for the project and reflects, in
10
- real time, which sessions are **working** (Claude is thinking) and which **need
11
- attention** (waiting for input, or finished with output you haven't seen).
9
+ WebSocket. A sidebar lists every session for the project and reflects, in real time,
10
+ which sessions are **working** (the agent is thinking) and which **need attention**
11
+ (waiting for input, or finished with output you haven't seen).
12
+
13
+ Around the terminal sit the things a coding session needs: a **grid** of parallel
14
+ sessions, an optional **GUI panel** for rich tool output (documents, forms, images,
15
+ charts), **git worktree** isolation with one-click **push / open PR**, a full-screen
16
+ **file browser + editor**, per-session **cost & token** readouts, a cross-repo **PRs &
17
+ Issues** view, and read-only **Wiki** and **Collections** browsers over the shared
18
+ workspace.
12
19
 
13
20
  **Inserting a file path** — like a native terminal, you can put a file's absolute
14
21
  path into the prompt: **drag a file** onto the terminal (works where the browser
@@ -22,7 +29,10 @@ inserted at the cursor — it is not submitted, so you can review it first.
22
29
  ## Install & run
23
30
 
24
31
  Requires the [`claude`](https://claude.com/claude-code) CLI on your `PATH` and
25
- **Node ≥ 22.9**.
32
+ **Node ≥ 22.9**. Optional but recommended: **`tmux`** so terminals survive a server
33
+ restart (see [Session persistence (tmux)](#session-persistence-tmux)), the **`gh`** CLI
34
+ logged in for the PRs/Issues view and one-click PR creation, and — for Codex sessions —
35
+ the **`codex`** CLI on your `PATH`.
26
36
 
27
37
  ```bash
28
38
  npx mulmoterminal # start on http://localhost:34567 and open the browser
@@ -54,15 +64,26 @@ server, and opens the browser. For local development from a clone, see
54
64
 
55
65
  - [Architecture](#architecture)
56
66
  - [Why a PTY?](#why-a-pty)
67
+ - [Agents: Claude & Codex](#agents-claude--codex)
68
+ - [Session persistence (tmux)](#session-persistence-tmux)
69
+ - [Docker sandbox (experimental, single view)](#docker-sandbox-experimental-single-view)
57
70
  - [Tech stack](#tech-stack)
58
71
  - [Configuration](#configuration)
59
72
  - [Running](#running)
60
73
  - [Scripts (Run menu)](#scripts-run-menu)
74
+ - [Files view (browse & edit)](#files-view-browse--edit)
75
+ - [Git worktrees & pull requests](#git-worktrees--pull-requests)
76
+ - [Cost & token usage](#cost--token-usage)
77
+ - [Wiki, Collections & the GUI panel](#wiki-collections--the-gui-panel)
78
+ - [More features](#more-features)
61
79
  - [Server API specification](#server-api-specification)
62
80
  - [HTTP: `GET /api/sessions`](#http-get-apisessions)
63
81
  - [HTTP: `GET /api/scripts`](#http-get-apiscripts)
82
+ - [HTTP: `POST /api/command/summarize`](#http-post-apicommandsummarize)
64
83
  - [HTTP: `POST /api/hook`](#http-post-apihook)
84
+ - [More HTTP endpoints](#more-http-endpoints)
65
85
  - [WebSocket: `/ws` (terminal)](#websocket-ws-terminal)
86
+ - [More WebSocket endpoints](#more-websocket-endpoints)
66
87
  - [WebSocket: `/ws/run` (command terminal)](#websocket-wsrun-command-terminal)
67
88
  - [Socket.IO: `/ws/pubsub` (activity pub/sub)](#socketio-wspubsub-activity-pubsub)
68
89
  - [Session model](#session-model)
@@ -93,14 +114,15 @@ server, and opens the browser. For local development from a clone, see
93
114
  - **Session list** is fetched over HTTP (`/api/sessions`).
94
115
  - **Live activity** is pushed over a Socket.IO pub/sub channel (`/ws/pubsub`);
95
116
  the server learns of activity from **Claude hooks** that POST to `/api/hook`.
96
- - **Script commands** (`yarn dev`, tests, …), launched from a cell's directory
97
- picker, run in their own ephemeral PTY over a separate WebSocket (`/ws/run`);
98
- they are not Claude sessions. See [Scripts (Run menu)](#scripts-run-menu).
117
+ - **Other terminals** run on their own raw WebSockets: **Codex** sessions on `/ws/codex`,
118
+ persistent **launch commands** on `/ws/launch`, and one-off **script commands**
119
+ (`yarn dev`, tests, …) on `/ws/run`. Only Claude/Codex are agent sessions with hooks;
120
+ see [Agents: Claude & Codex](#agents-claude--codex) and [Scripts (Run menu)](#scripts-run-menu).
99
121
  - In dev (`yarn dev`) the Vite dev server runs on its own port (`CLIENT_PORT`,
100
- default `6856`) and proxies `/ws` (covers `/ws/run`), `/ws/pubsub`, and `/api` to
101
- the backend (`PORT`, default `34567`) so you open the Vite port (e.g.
102
- `http://localhost:6856`). In production the backend serves the built client from
103
- `dist/` on `PORT`, and you open that.
122
+ default `6856`) and proxies `/ws` (a prefix covering `/ws/codex`, `/ws/launch`, and
123
+ `/ws/run`), `/ws/pubsub`, `/api`, and `/artifacts` to the backend (`PORT`, default
124
+ `34567`) — so you open the Vite port (e.g. `http://localhost:6856`). In production the
125
+ backend serves the built client from `dist/` on `PORT`, and you open that.
104
126
 
105
127
  ---
106
128
 
@@ -121,6 +143,38 @@ SDK; we drive the real interactive CLI and relay its TTY over the WebSocket.
121
143
 
122
144
  ---
123
145
 
146
+ ## Agents: Claude & Codex
147
+
148
+ MulmoTerminal drives **interactive coding-agent CLIs**, not just Claude. An
149
+ `AgentAdapter` seam abstracts the per-agent bits (which binary to spawn, how it resumes)
150
+ so the PTY, grid, persistence, and GUI-panel plumbing stay shared. Two adapters ship
151
+ today — **Claude Code** (the default) and **Codex**.
152
+
153
+ - **Claude** — spawned as `claude` (override with `CLAUDE_BIN`). The server passes
154
+ `--session-id <uuid>`, so it knows the live session's id even before its transcript
155
+ file exists, and injects activity hooks + the GUI MCP per spawn (see
156
+ [Claude hook injection](#claude-hook-injection)).
157
+ - **Codex** — spawned as `codex` (override with `CODEX_BIN`; `CODEX_MODEL` sets
158
+ `--model`). Codex runs on its own WebSocket (`/ws/codex`) and its sessions appear in the
159
+ sidebar next to Claude's. Because Codex only mints its rollout id **after** the first
160
+ turn, the server watches `~/.codex/sessions/**/rollout-*.jsonl` (home overridable via
161
+ `CODEX_HOME`) and maps the new rollout to the session — attributed only when it's
162
+ unambiguous, never by "newest wins". Resume reattaches a live PTY, adopts a surviving
163
+ tmux session, or cold-resumes the rollout id.
164
+
165
+ **Choosing an agent.** The single view has a **New Codex session** button; each grid
166
+ cell's launch form and the Collections browser carry a **Claude / Codex** toggle (your
167
+ choice is remembered).
168
+
169
+ **Skills for Codex.** Codex has no `/<slug>` slash commands, so on session setup
170
+ MulmoTerminal **mirrors the workspace's `.claude/skills` into `~/.codex/skills`** (each
171
+ mirrored directory carries a `.mt-mirror` marker so a re-sync overwrites what MulmoTerminal
172
+ owns and never clobbers Codex's own skills), and rewrites a collection's `/<slug> …` seed
173
+ into a plain `Use the "<slug>" skill.` instruction. The same skills Claude uses then show
174
+ up for Codex, loaded by description.
175
+
176
+ ---
177
+
124
178
  ## Session persistence (tmux)
125
179
 
126
180
  If **`tmux` is installed**, MulmoTerminal runs each Claude session and launcher inside
@@ -134,6 +188,18 @@ it never touches your personal tmux sessions or keybindings.
134
188
  before. An explicit close (a cell's ✕) ends the tmux session; a machine reboot does not
135
189
  survive (tmux itself is gone). Command-cell scripts are ephemeral and not persisted.
136
190
 
191
+ **Installing tmux** (optional):
192
+
193
+ ```bash
194
+ brew install tmux # macOS (Homebrew)
195
+ sudo apt install tmux # Debian / Ubuntu
196
+ sudo dnf install tmux # Fedora
197
+ ```
198
+
199
+ On Windows there's no native tmux, so sessions use the non-persistent fallback — run the
200
+ server under **WSL** if you want persistence. Nothing else is required: MulmoTerminal
201
+ detects `tmux` on `PATH` at startup and uses it automatically when present.
202
+
137
203
  ---
138
204
 
139
205
  ## Docker sandbox (experimental, single view)
@@ -181,8 +247,9 @@ building the sandbox spawn, so they have no effect unless `MULMOTERMINAL_SANDBOX
181
247
 
182
248
  | Layer | Technology |
183
249
  | -------- | ---------- |
184
- | Frontend | Vue 3 (`<script setup>` + TypeScript), Vite, xterm.js (`@xterm/*`), socket.io-client |
185
- | Backend | Node (ESM, TypeScript run via `tsx`), Express 5, `ws` (terminal WebSocket), `node-pty`, socket.io |
250
+ | Frontend | Vue 3 (`<script setup>` + TypeScript), Vue Router, Vite, xterm.js (`@xterm/*`), CodeMirror 6, socket.io-client |
251
+ | Backend | Node (ESM, TypeScript run via `tsx`), Express 5, `ws` (terminal WebSocket), `node-pty`, socket.io, `@modelcontextprotocol/sdk` (in-process GUI MCP) |
252
+ | Plugins | GUI-protocol Vue plugins (`@mulmoclaude/*`, `@mulmochat-plugin/*`): markdown, form, image, chart, HTML, collection, accounting |
186
253
  | Tests | Vitest + @vue/test-utils + jsdom |
187
254
 
188
255
  Requires **Node ≥ 22.9** (uses `node --env-file-if-exists`) and the `claude` CLI on `PATH`.
@@ -202,6 +269,18 @@ the server runs without one.
202
269
  | `CLIENT_PORT` | `6856` | Vite dev-server port (dev only: the URL you open with `yarn dev`). |
203
270
  | `CLAUDE_BIN` | `claude` | The Claude Code binary to spawn. |
204
271
  | `CLAUDE_CWD` | current dir | Working directory each `claude` PTY runs in; determines which project's sessions the sidebar lists. Via `npx mulmoterminal` it defaults to the directory you ran the command from (override with `--cwd <dir>`, relative allowed); when the server is run directly it falls back to `~/mulmoclaude`. A value read from `.env` must be an absolute path (`~` is not expanded). |
272
+ | `CLAUDE_PERMISSION_MODE` | `auto` | Permission mode passed to each `claude` spawn. |
273
+ | `MT_TITLE_MODEL` | `haiku` | Model used for the cell header's AI title (a cheap/fast model summarizing the recent turns). Accepts a `--model` alias or a full model id. |
274
+ | `CODEX_BIN` | `codex` | The Codex CLI binary to spawn. |
275
+ | `CODEX_MODEL`| codex default | Model passed to Codex as `--model` (unset = Codex's own default). |
276
+ | `CODEX_HOME` | `~/.codex` | Codex home — where its session rollouts and MulmoTerminal-mirrored skills live. |
277
+ | `MULMOTERMINAL_HOME` | `~/.mulmoterminal` | Root for managed **git worktrees**. |
278
+ | `WAIT_REAP_GRACE_MS` | `1800000` | How long a **waiting** background session is kept before it's auto-reaped (`0` or negative = never). |
279
+
280
+ The Docker-sandbox variables (`MULMOTERMINAL_SANDBOX`, `MULMOTERMINAL_SANDBOX_IMAGE`,
281
+ `SANDBOX_MOUNT_CONFIGS`, `SANDBOX_SSH_AGENT_FORWARD`) and the update-check opt-outs
282
+ (`MULMOTERMINAL_NO_UPDATE_CHECK`, `NO_UPDATE_NOTIFIER`) are covered in
283
+ [Docker sandbox](#docker-sandbox-experimental-single-view) and [Install & run](#install--run).
205
284
 
206
285
  Example `.env` (gitignored):
207
286
 
@@ -241,6 +320,12 @@ malformed file is ignored.
241
320
  {
242
321
  "name": "PROD · payments", // badge shown on this directory's terminals
243
322
  "badgeColor": "#cf222e", // badge color (hex #rrggbb)
323
+ "headerColor": "#190a23", // cell header background (hex #rrggbb)
324
+ "headerTextColor": "#ffffff", // cell header text color (hex #rrggbb)
325
+ "cellColor": "#101014", // cell body background (hex #rrggbb)
326
+ "cellBorderColor": "#2a2a4e", // cell border color (hex #rrggbb)
327
+ "dotColor": "#00e676", // idle status dot (hex #rrggbb)
328
+ "buttonColor": "#c7cdf0", // header icon buttons (hex #rrggbb)
244
329
  "theme": "nord", // terminal palette: midnight | nord | daylight | solarized
245
330
  "colors": { "background": "#190a23", "cursor": "#ff2e63" }, // per-key palette overrides
246
331
  "sound": "./.mulmoterminal/alert.mp3" // attention sound, RELATIVE to this directory
@@ -251,6 +336,12 @@ malformed file is ignored.
251
336
  | ------------ | ------- |
252
337
  | `name` | Label shown as a badge in the terminal/cell header. |
253
338
  | `badgeColor` | Badge background color (`#rrggbb`); text auto-contrasts. |
339
+ | `headerColor` | Header **background** color (`#rrggbb`) — the grid cell's header row and the terminal's own header row (grid row 2 + single view). While a terminal is working/blocked the status tint still shows; the custom color applies when idle. |
340
+ | `headerTextColor` | Header **text** color (`#rrggbb`) — the dir path, title, and prompt. |
341
+ | `cellColor` | Cell **body background** color (`#rrggbb`) — the frame around the terminal. |
342
+ | `cellBorderColor` | Cell **border** color (`#rrggbb`). The status frame (working/blocked) still overrides it while active. |
343
+ | `dotColor` | **Idle** status-dot color (`#rrggbb`). The working/waiting colors are unchanged so the activity signal stays intact. |
344
+ | `buttonColor` | Header **icon button** color (`#rrggbb`) — expand / close / attach / folder / etc., across both header rows. |
254
345
  | `theme` | xterm palette for terminals in this directory (one of the built-in theme ids). |
255
346
  | `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. |
256
347
  | `sound` | Attention sound for this directory's sessions, a path **relative to the directory** (served at `GET /api/dir-sound`). |
@@ -342,6 +433,13 @@ offers a **↻ re-run**. The browser only ever sends the script's **index** + it
342
433
  directory; the server reads that directory's `script.json` and resolves the
343
434
  command, so the file is the allowlist of what can run.
344
435
 
436
+ Each command cell also has a **✦ Summarize** button: click it to send the cell's
437
+ captured output to `claude -p` (headless) and get a short **Errors / Warnings /
438
+ likely cause / suggested fix** note in a panel — handy when a build or install
439
+ buries the one failing line in thousands. It's manual (never auto-runs) and analyzes
440
+ the last 32 KB of output. See
441
+ [`POST /api/command/summarize`](#http-post-apicommandsummarize).
442
+
345
443
  ---
346
444
 
347
445
  ## Files view (browse & edit)
@@ -360,6 +458,119 @@ the terminal is pointed at.
360
458
 
361
459
  ---
362
460
 
461
+ ## Git worktrees & pull requests
462
+
463
+ When a terminal's directory is a git repo, its header shows a **branch chip**
464
+ (`⎇ <branch>` with dirty / ahead / behind counts), fed by `GET /api/git-status` (polled
465
+ while the view is visible). A **GitHub** menu links straight to the repo, its issues, and
466
+ its pull requests.
467
+
468
+ **Worktree isolation.** A grid cell's launch form offers **+ New worktree**: name a task
469
+ and the cell launches its agent inside a fresh
470
+ [git worktree](https://git-scm.com/docs/git-worktree) on a new `agent/<slug>` branch — a
471
+ separate working tree that shares the repo's `.git`, so several agents can work the same
472
+ repo without colliding. Worktrees live under `~/.mulmoterminal/worktrees/` (override with
473
+ `MULMOTERMINAL_HOME`), and existing ones are listed for reuse.
474
+
475
+ A worktree cell's header carries a **diff badge** (`+<commits> ●<dirty>`); click it for a
476
+ **Changes vs `<base>`** panel (file list + patch) with actions:
477
+
478
+ - **✓ Commit** — hands the cell's own session a canned commit prompt.
479
+ - **⬆ Push** — `git push -u origin <branch>` (`POST /api/worktrees/push`).
480
+ - **⧉ Open PR** — pushes, then `gh pr create … --fill`; if `gh` is missing or unauthed it
481
+ falls back to opening the GitHub **compare** URL (`POST /api/worktrees/pr`).
482
+
483
+ Closing a worktree cell asks whether to **keep** the worktree or **discard & remove** it
484
+ (a dirty worktree is never removed unless you confirm).
485
+
486
+ **PRs & Issues (cross-repo).** The toolbar's **Pull requests** button opens a full-screen
487
+ view that aggregates open PRs **and** issues across the repos listed in Settings →
488
+ **Pull request repos** (`prRepos`, `owner/repo` entries) via your server-side `gh` login.
489
+ PRs show a CI-rollup / review-decision / draft badge; each repo lists its latest open
490
+ issues. Rows are real links, per-repo errors don't sink the view, and the two lists load
491
+ independently. Backed by `GET /api/prs` and `GET /api/issues`.
492
+
493
+ ---
494
+
495
+ ## Cost & token usage
496
+
497
+ Each grid cell's header shows two badges for its session, refreshed when a turn finishes
498
+ (from `GET /api/session/:id`):
499
+
500
+ - **Context badge** — e.g. `Opus · ctx 35%`: the model family plus how full its context
501
+ window is (the *last* turn's input + cache tokens ÷ the model's window — **1M** for
502
+ current-gen Opus / Sonnet / Fable, **200k** otherwise; an unknown model shows the label
503
+ with no %).
504
+ - **Token badge** — `⇡<in> ⇣<out>`: cumulative input (fresh + cache-read + cache-creation)
505
+ and output tokens for the session, k/M-formatted, with a full breakdown in the tooltip.
506
+
507
+ The **Settings** modal (⚙) shows an **estimated $ cost** — Session / Today / Month — from
508
+ `GET /api/cost`, using a built-in public per-model price table (cache reads billed at
509
+ 0.1×, cache writes at 1.25× input). It's an estimate: real billing differs, **flat-plan
510
+ (Max) usage isn't reflected**, and turns on unpriced models are flagged and excluded.
511
+
512
+ A separate, full **double-entry accounting** book (the `account_balance` toolbar button →
513
+ `/accounting`) is provided by the bundled `@mulmoclaude/accounting-plugin` and stores its
514
+ books under `<workspace>/data/accounting`. It's a bookkeeping app — unrelated to the LLM
515
+ cost estimate above — and is also exposed to Claude as the `manageAccounting` GUI tool.
516
+
517
+ ---
518
+
519
+ ## Wiki, Collections & the GUI panel
520
+
521
+ MulmoTerminal is also a **live view over the shared workspace** (`CLAUDE_CWD`, default
522
+ `~/mulmoclaude`) that agents author into — never a snapshot, so it re-reads on entry.
523
+
524
+ **GUI panel.** Beside the terminal, a **GUI panel** ("Canvas") renders the rich results of
525
+ GUI-protocol tools the agent calls — documents (`presentDocument`), forms (`presentForm`),
526
+ generated images, charts, HTML, and collection cards. Each result is drawn by its plugin's
527
+ own Vue view inside a Shadow-DOM `PluginFrame` (so a plugin's bundled CSS can't leak),
528
+ mirrors the active session, and replays history on re-select. Plugins reach the agent over
529
+ an **in-process MCP server** served per session at `POST /api/mcp/:sessionId` (server name
530
+ `mulmoterminal-gui`) — which works from the host *or* the Docker sandbox (over
531
+ `host.docker.internal`). Which plugins load is gated by `plugins/plugins.json`; the shipped
532
+ set includes markdown, form, image generation (needs `GEMINI_API_KEY`), chart, HTML, and
533
+ collection views. You can also merge your **own HTTP MCP servers** into the single-view
534
+ session via Settings → `userMcpServers`.
535
+
536
+ **Wiki.** The toolbar **Wiki** button opens a read-only browser over `<workspace>/data/wiki/`
537
+ — an **index** (tag-filterable page catalog), rendered **pages** with `[[wiki links]]` and
538
+ backlinks, a **graph** view (pages ranked by references), and a **lint** report (orphans /
539
+ broken links / tag drift). Read-only endpoints: `GET /api/wiki`, `/api/wiki/graph`,
540
+ `/api/wiki/lint`.
541
+
542
+ **Collections.** The toolbar **Collections** button browses the workspace's collection
543
+ "cards" (`@mulmoclaude/collection-plugin`). Running a collection **action** fetches a seed
544
+ prompt and spawns a fresh agent session for it — the **Launch with Claude / Codex** toggle
545
+ decides which agent (and whether the seed auto-runs or drops in as an editable draft).
546
+ Favorited collections get their own toolbar buttons.
547
+
548
+ ---
549
+
550
+ ## More features
551
+
552
+ - **Grid of parallel sessions** — the + Terminal / grid view runs many sessions at once,
553
+ auto-sizing by count across pages. Cell borders signal state at a glance — **working**
554
+ (pulsing blue), **blocked** (amber — needs a permission / answer), **done** (blue —
555
+ finished, output unreviewed), and **idle** — and the toolbar shows a tally across all
556
+ pages so you notice an off-screen cell that needs you.
557
+ - **Timeline** (🕘) — a read-only per-session activity timeline (tools run, newest first),
558
+ from `GET /api/transcript/timeline`.
559
+ - **Tools pane** — the available GUI tools plus a live tool-call history for the active
560
+ session.
561
+ - **Notifications** (🔔) — a toolbar bell with an unread badge and a dropdown of active
562
+ notifications; click a row to jump to its session.
563
+ - **Voice input** — dictate a prompt via on-device Whisper (`POST /api/transcribe`, macOS
564
+ only; the model downloads on first use).
565
+ - **Remote host** — link MulmoTerminal to the companion phone client (Google sign-in) to
566
+ watch and start sessions from your phone.
567
+ - **Themes** — four terminal palettes (midnight / nord / daylight / solarized), your pick
568
+ remembered; a project's `.mulmoterminal.json` can override per directory.
569
+ - **Editing niceties** — **Shift+Enter** inserts a newline in the prompt, and on macOS
570
+ **Option** is treated as Meta so Claude's Alt-key bindings work.
571
+
572
+ ---
573
+
363
574
  ## Server API specification
364
575
 
365
576
  Base URL: `http://localhost:$PORT` (default `http://localhost:34567`).
@@ -418,6 +629,33 @@ position the client sends back to `/ws/run`).
418
629
  A missing or invalid `script.json` is **not** an error — it yields an empty
419
630
  `scripts` array.
420
631
 
632
+ ### HTTP: `POST /api/command/summarize`
633
+
634
+ Runs `claude -p` **headless** over a command cell's captured terminal output and
635
+ returns a short summary (Errors / Warnings / likely cause / suggested fix). Backs the
636
+ **✦ Summarize** button on a Run cell (see [Scripts (Run menu)](#scripts-run-menu)).
637
+ The browser sends the cell's xterm buffer as `log`; the server truncates it to the
638
+ last **32 KB** (the tail, where errors + the exit line live), runs the CLI with the
639
+ log piped on stdin (argv — no shell), and returns its answer. Same-origin guarded.
640
+
641
+ **Request `application/json`**:
642
+
643
+ ```jsonc
644
+ { "log": "npm ERR! cannot find module 'foo'\n..." }
645
+ ```
646
+
647
+ **Response `200 application/json`**:
648
+
649
+ ```jsonc
650
+ {
651
+ "summary": "Errors: cannot find module 'foo'\nSuggested fix: run `yarn add foo`",
652
+ "truncated": false // true when the log exceeded 32 KB and only the tail was analyzed
653
+ }
654
+ ```
655
+
656
+ Empty output returns a `{ summary }` note rather than calling the CLI. Errors:
657
+ `400` (missing `log`), `403` (disallowed origin), `502` (the `claude` run failed).
658
+
421
659
  ### HTTP: `POST /api/hook`
422
660
 
423
661
  **Internal endpoint.** Claude hooks (injected per session — see
@@ -445,6 +683,62 @@ Any resulting state change is published on the `sessions` pub/sub channel.
445
683
 
446
684
  **Response `200 application/json`**: `{ "ok": true }` (always, even for unknown events).
447
685
 
686
+ ### More HTTP endpoints
687
+
688
+ The endpoints above are the core; the server exposes many more (all under
689
+ `http://localhost:$PORT`; query params shown where relevant). Mutating endpoints are
690
+ same-origin-guarded.
691
+
692
+ **Sessions & agents**
693
+
694
+ | Endpoint | Purpose |
695
+ | -------- | ------- |
696
+ | `GET /api/session/:id?cwd=` | One session's summary — cumulative `usage` and `context` (model + last-turn context tokens). Backs the cell token & ctx% badges. |
697
+ | `GET /api/codex/sessions?cwd=` | Codex sessions for the project (from `~/.codex` rollouts), newest first. |
698
+ | `GET /api/cost?cwd=&session=` | Estimated $ cost — session / today / month. |
699
+ | `GET /api/transcript/timeline?session=&cwd=` | Per-session activity timeline (tools run). |
700
+
701
+ **Git & worktrees**
702
+
703
+ | Endpoint | Purpose |
704
+ | -------- | ------- |
705
+ | `GET /api/git-status?cwd=` | `{ repo, branch, detached, dirty, ahead, behind, upstream }`. |
706
+ | `POST /api/git-remote` | The dir's GitHub repo URL (for the header GitHub menu). |
707
+ | `GET /api/worktrees?cwd=` · `GET /api/worktrees/diff?cwd=` | List managed worktrees / diff one vs its base. |
708
+ | `POST /api/worktrees/create` · `/remove` · `/push` · `/pr` | Create on `agent/<slug>`, remove (managed root only), push, open a PR (`gh`, else compare URL). |
709
+ | `GET /api/prs` · `GET /api/issues` | Open PRs / issues across the configured `prRepos` (via `gh`). |
710
+
711
+ **Workspace views**
712
+
713
+ | Endpoint | Purpose |
714
+ | -------- | ------- |
715
+ | `GET /api/wiki` (`?slug=`) · `/api/wiki/graph` · `/api/wiki/lint` | Read-only wiki index / page / graph / lint. |
716
+ | `GET /api/collections/…` · `/api/feeds` · `GET\|PUT /api/shortcuts` | Collections browser, feeds, favorites (see `docs/collection-plugin-integration.md`). |
717
+ | `GET /api/files/browse/{list,text,md}` · `PUT /api/files/browse/write` | File tree / read / Markdown-render / write (contained within the project root). |
718
+ | `GET /api/files/raw?path=` | Raw asset bytes (workspace-rooted). |
719
+
720
+ **GUI panel / plugins / MCP**
721
+
722
+ | Endpoint | Purpose |
723
+ | -------- | ------- |
724
+ | `POST /api/mcp/:sessionId` | Per-session GUI MCP server (Streamable HTTP; `GET`/`DELETE` → 405). |
725
+ | `POST /api/plugin/:toolName` | GUI-plugin dispatch (incl. `spawnBackgroundChat`, `manageAccounting`, `presentHtml`). |
726
+ | `GET /api/agent/toolResults/:id` · `POST /api/agent/toolResult` | GUI-panel result history / persist. |
727
+ | `GET /api/tools` · `GET /api/tool-calls/:id` | Available tools / tool-call history. |
728
+ | `POST /api/accounting` | Double-entry accounting (bundled plugin). |
729
+
730
+ **Config, sound & misc**
731
+
732
+ | Endpoint | Purpose |
733
+ | -------- | ------- |
734
+ | `GET\|POST /api/config` | User UI config (`cwdPresets`, `soundFile`, `prRepos`, `launchers`, `userMcpServers`). |
735
+ | `GET /api/sound` · `/api/dir-sound?cwd=` · `/api/dir-config?cwd=` | Custom / per-directory attention sound + per-dir config. |
736
+ | `GET /api/notifications`(`/history`) · `POST /api/notifications/:id/clear` | Notification feed. |
737
+ | `POST /api/transcribe`(`/model`…) | Voice-input transcription (Whisper, macOS). |
738
+ | `POST /api/translation` | Runtime UI-string translation. |
739
+ | `GET /api/remote-host/status` · `POST /api/remote-host/{connect,disconnect}` | Companion phone-client link. |
740
+ | `POST /api/open-dir` · `POST /api/pick-file` | Reveal a dir in Finder/Explorer; OS file-picker → path. |
741
+
448
742
  ### WebSocket: `/ws` (terminal)
449
743
 
450
744
  A raw WebSocket carrying the terminal stream for one session. One PTY per
@@ -480,6 +774,20 @@ A non-JSON frame is written to the PTY verbatim (fallback).
480
774
  **kept alive** in the background; otherwise it's killed. See
481
775
  [Session lifecycle](#session-lifecycle).
482
776
 
777
+ ### More WebSocket endpoints
778
+
779
+ Two more raw WebSockets share the `/ws` frame format (`output` / `input` / `resize` /
780
+ `exit`):
781
+
782
+ - **`/ws/codex?session=<id>&cwd=<dir>&gui=<0|1>`** — a **Codex** agent PTY (see
783
+ [Agents: Claude & Codex](#agents-claude--codex)). Like `/ws` it sends a `session` frame
784
+ with the id and reattaches to a live or tmux-backed session on resume. `gui=0` (grid
785
+ cells) omits the GUI MCP and keeps the session out of the sidebar.
786
+ - **`/ws/launch?session=<id>&cwd=<dir>&launcher=<index>`** — a **launch command** PTY (a
787
+ plain shell, `codex`, or any command configured in Settings → Launch commands). Unlike a
788
+ Run-menu script it's **persistent and reattachable** (survives page switches /
789
+ reconnects), but it has no Claude hooks, so its dot only shows running vs. exited.
790
+
483
791
  ### WebSocket: `/ws/run` (command terminal)
484
792
 
485
793
  A raw WebSocket carrying a one-off **Run-menu command** (see
@@ -630,44 +938,76 @@ has its `/` and `.` characters replaced with `-` (e.g.
630
938
  A session's display **title** is derived by scanning its JSONL for, in order of
631
939
  preference:
632
940
 
633
- 1. the latest `ai-title` record's `aiTitle`,
634
- 2. else the latest `last-prompt` record's `lastPrompt`,
635
- 3. else the first real user message (slash/local-command wrappers like
941
+ 1. a live **AI title** the server generated for the session this run (see below),
942
+ 2. else the latest `ai-title` record's `aiTitle` (e.g. written by MulmoClaude),
943
+ 3. else the latest `last-prompt` record's `lastPrompt`,
944
+ 4. else the first real user message (slash/local-command wrappers like
636
945
  `<local-command-…>` are skipped),
637
- 4. else `"(untitled session)"`.
946
+ 5. else `"(untitled session)"`.
638
947
 
639
948
  In-memory sessions not yet persisted show as `"New session"` until their file
640
949
  appears, at which point the on-disk title takes over.
641
950
 
951
+ ### AI header title
952
+
953
+ The raw last prompt is a poor cell-header label once a session becomes a
954
+ back-and-forth: a follow-up is either a trivial ack (`ok`, `はい` — skipped, so the
955
+ header keeps showing the now-stale opening task) or context-dependent (`2番目にして`
956
+ — meaningless on its own). So the server summarizes the **recent turns** with a cheap
957
+ model (`MT_TITLE_MODEL`, default `haiku`) into a short title and shows it in the cell
958
+ header (falling back to the last prompt when there's no title yet).
959
+
960
+ Generation is kept low-cost — it runs at a turn's `Stop` (when the reply is on disk)
961
+ only when a title is **due**: none yet, the newest prompt was a trivial/context-dependent
962
+ ack (so the raw last prompt would be stale), or every few turns to keep a long session's
963
+ title current. The title lives in memory (never written into Claude's own transcript); a
964
+ resumed session falls back to any on-disk `ai-title`.
965
+
642
966
  ---
643
967
 
644
968
  ## Project structure
645
969
 
646
970
  ```
647
971
  server/
648
- index.js Express app, /api routes, terminal WebSocket, PTY lifecycle,
649
- session state, hook injection, session discovery; also
650
- /api/scripts + the /ws/run command-terminal relay
651
- scripts.ts Loads/validates script.json; resolves a Run-menu command by index
652
- pubsub.js createPubSub(server) socket.io pub/sub at /ws/pubsub
653
- fix-pty-perms.js postinstall: fixes node-pty prebuilt binary permissions
972
+ index.ts Express app, /api routes, upgrade routing, PTY lifecycle,
973
+ session state, hook injection, session discovery, GUI-MCP mount
974
+ agents/ AgentAdapter seam claude.ts, codex.ts, registry.ts
975
+ codex-*.ts Codex sessions, args, rollout discovery, skill mirroring
976
+ tmux.ts tmux-backed session persistence (own -L mulmoterminal server)
977
+ sandbox.ts Docker sandbox spawn for the single-view Claude session
978
+ worktrees.ts, worktree-*.ts git worktree create/list/diff/push/PR + routes
979
+ git-status.ts, gitRemote.ts, gh.ts, prs.ts, issues.ts git & GitHub (via gh)
980
+ cost.ts, transcript.ts cost estimate; usage/context from transcripts
981
+ files-browse.ts file tree + read/write (contained to project root)
982
+ scripts.ts Run-menu script.json loader
983
+ command-summary.ts POST /api/command/summarize (claude -p headless)
984
+ header-title.ts cheap-model AI title for the cell header (recent turns)
985
+ app-config.ts, config-routes.ts, dir-config.ts user + per-directory config
986
+ plugins-registry.ts, mcp/ GUI plugin registry + per-session MCP broker
987
+ backends/ wiki, collections, feeds, accounting, notifier,
988
+ translation, whisper, remote-host, html, files
989
+ pubsub.ts socket.io pub/sub at /ws/pubsub
990
+ fix-pty-perms.js postinstall: fixes node-pty binary permissions
654
991
  src/
655
- App.vue Layout (sidebar + terminal); owns the active session id
992
+ App.vue Layout; owns the active session + single/grid view
993
+ router/ Vue Router routes (/, /terminals, /collections,
994
+ /accounting, /prs, /files, /wiki, …)
656
995
  components/
657
- Sidebar.vue Session list; working dot + waiting bold; pub/sub driven
658
- Sidebar.spec.ts Vitest component tests
659
- Terminal.vue xterm.js terminal; /ws (or /ws/run); single-view ▶ Run menu
660
- AppToolbar.vue Shared header (single + grid); grid-only + Terminal + ⇅/↔ cell-order toggle
661
- GridView.vue Grid view: auto-layout, pages, manual/auto cell order; runs handed-off scripts
662
- RunMenu.vue Run dropdown: lists a dir's script.json, emits the pick
663
- TerminalCell.vue A cell: Claude launcher (dir picker + resume + run-a-script); ◀▶ to reorder
664
- TerminalGrid.vue Grid of cells; auto-sizes by count; zoom lines up every tab
665
- CommandCell.vue A grid cell that runs a script.json command (ephemeral)
666
- composables/
667
- usePubSub.ts socket.io-client pub/sub composable (subscribe/unsubscribe)
668
- usePendingScript.ts Hands a header-picked script to the grid to run
669
- useUnloadGuard.ts Confirm before closing/reloading the tab while a terminal is live
670
- vite.config.ts Dev proxy for /ws (covers /ws/run), /ws/pubsub, /api
996
+ Sidebar.vue, SessionTabBar.vue session list + tab bar (pub/sub driven)
997
+ Terminal.vue xterm.js terminal; /ws, /ws/codex, /ws/run
998
+ AppToolbar.vue shared header + toolbar buttons
999
+ GridView.vue, TerminalGrid.vue, TerminalCell.vue, CommandCell.vue, LauncherCell.vue
1000
+ GuiPanel.vue, PluginFrame.vue GUI panel (Canvas) + Shadow-DOM plugin host
1001
+ FilesOverlay.vue file browser + CodeMirror editor
1002
+ GitBranchChip.vue, ModelContextBadge.vue header chips / badges
1003
+ PrsOverlay.vue cross-repo PRs & Issues
1004
+ Wiki*View.vue, Collections*.vue, AccountingOverlay.vue workspace views
1005
+ TimelineOverlay.vue, ToolsPane.vue, NotificationBell.vue, RemoteHostControl.vue
1006
+ SettingsModal.vue ⚙ settings
1007
+ composables/ useSessions, usePubSub, useGitStatus, useCost,
1008
+ useChatLauncher, useFilesView, useWikiBrowse,
1009
+ useCollectionBrowse, useNotifications, useVoiceInput,
1010
+ vite.config.ts Dev proxy for /ws (+ /ws/codex, /ws/launch, /ws/run), /ws/pubsub, /api, /artifacts
671
1011
  vitest.config.ts jsdom test environment
672
1012
  ```
673
1013
 
@@ -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-DC7NPJ_M-DpdiJOd9.js";import"./chunk-WYO6CB5R-B83L_z6I-NmPlbhpt.js";import"./chunk-VAUOI2AC-DrcykVNK-Um-9nuwz.js";import{n as i,r as a,t as o}from"./chunk-MOJQB5TN-CqxshQHA-jUnAGLd5.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-DC7NPJ_M-uogn_ZQO.js";import"./chunk-WYO6CB5R-B83L_z6I-zjc6vart.js";import"./chunk-VAUOI2AC-DrcykVNK-UQ8qJq-D.js";import{n as i,r as a,t as o}from"./chunk-MOJQB5TN-CqxshQHA-pSbnAJ7U.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-DC7NPJ_M-uogn_ZQO.js";export{e as createArchitectureServices};