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.
- package/README.md +382 -42
- package/dist/assets/{abnfDiagram-VRR7QNED-6nNByj6v-IGFnNkfa.js → abnfDiagram-VRR7QNED-6nNByj6v-CcrZYOgs.js} +1 -1
- package/dist/assets/architecture-TIHT7OUA-CYMWc3UT-QInGApkV.js +1 -0
- package/dist/assets/{architectureDiagram-ZJ3FMSHR-Bgnyaj_i-CAxLCofF.js → architectureDiagram-ZJ3FMSHR-Bgnyaj_i-CUuVwmX4.js} +1 -1
- package/dist/assets/{blockDiagram-677ZJIJ3-DQ35o5E4-x3_3MCHB.js → blockDiagram-677ZJIJ3-DQ35o5E4-CvxU8aZe.js} +1 -1
- package/dist/assets/{c4Diagram-LMCZKHZV-ClWZeiWo-BHgXFEHO.js → c4Diagram-LMCZKHZV-ClWZeiWo-D8f1kYY_.js} +1 -1
- package/dist/assets/channel-Di5rtkx0-DHqL817r.js +1 -0
- package/dist/assets/{chunk-32BRIVSS-CCt9wtYd-Cevd76M7.js → chunk-32BRIVSS-CCt9wtYd-Db_FZIf1.js} +1 -1
- package/dist/assets/{chunk-52WLFC77-FbBbR4uI-BiOqnP1l.js → chunk-52WLFC77-FbBbR4uI-7n2yHJ2K.js} +1 -1
- package/dist/assets/{chunk-C7G6YPKG-C87hlS9c-qjNSM7yE.js → chunk-C7G6YPKG-C87hlS9c-DpkxPQKG.js} +1 -1
- package/dist/assets/{chunk-EX3LRPZG-BqGqMXLN-DP83K0Kb.js → chunk-EX3LRPZG-BqGqMXLN-BgqbD-Ak.js} +1 -1
- package/dist/assets/{chunk-FWX5IMBZ-BkbSAAuW-DDaQ5S2G.js → chunk-FWX5IMBZ-BkbSAAuW-BmYnGiYn.js} +2 -2
- package/dist/assets/{chunk-HOUHSVGY-Cxu0eDlh-CmLLHV-D.js → chunk-HOUHSVGY-Cxu0eDlh-TqsiFF7j.js} +1 -1
- package/dist/assets/{chunk-ICXQ74PX-hiraF_Xj-xuqMpWo5.js → chunk-ICXQ74PX-hiraF_Xj-Cx0hIueU.js} +1 -1
- package/dist/assets/{chunk-MOJQB5TN-CqxshQHA-jUnAGLd5.js → chunk-MOJQB5TN-CqxshQHA-pSbnAJ7U.js} +1 -1
- package/dist/assets/{chunk-OGEWGWER-CjCr7ceX-Be5sOa4R.js → chunk-OGEWGWER-CjCr7ceX-C2qBJ2Pg.js} +1 -1
- package/dist/assets/{chunk-PUDLZKDR-Dx6M-vz1-B6N9l2sp.js → chunk-PUDLZKDR-Dx6M-vz1-DvP2VeJc.js} +1 -1
- package/dist/assets/{chunk-Q4XR5HBZ-CZd-9lTB-CNc2UnbF.js → chunk-Q4XR5HBZ-CZd-9lTB-B9gNYRb9.js} +1 -1
- package/dist/assets/{chunk-V7JOEXUC-rI0xlC_O-BKwWIZFY.js → chunk-V7JOEXUC-rI0xlC_O-W1oR2e7R.js} +1 -1
- package/dist/assets/{chunk-VAUOI2AC-DrcykVNK-Um-9nuwz.js → chunk-VAUOI2AC-DrcykVNK-UQ8qJq-D.js} +1 -1
- package/dist/assets/{chunk-VR4S4FIN-O6iF8Yvf-C55cFzwq.js → chunk-VR4S4FIN-O6iF8Yvf-BEZ-hkFq.js} +1 -1
- package/dist/assets/{chunk-WYO6CB5R-B83L_z6I-NmPlbhpt.js → chunk-WYO6CB5R-B83L_z6I-zjc6vart.js} +1 -1
- package/dist/assets/{chunk-ZGVPDNZ5-BpFv9JSP-DQuoM4dk.js → chunk-ZGVPDNZ5-BpFv9JSP-WgpTVFz9.js} +1 -1
- package/dist/assets/classDiagram-OUVF2IWQ-BgAZMSbT-VLjGJAJO.js +1 -0
- package/dist/assets/classDiagram-v2-EOCWNBFH-DxHTyui1-VLjGJAJO.js +1 -0
- package/dist/assets/cynefin-VYW2F7L2-DqA3n9nY-DeDS-5by.js +1 -0
- package/dist/assets/{cynefinDiagram-TSTJHNR4-x0-0sQ15-BjW2zyQO.js → cynefinDiagram-TSTJHNR4-x0-0sQ15-D6l6qqRO.js} +1 -1
- package/dist/assets/{dagre-VKFMJZFB-CQdfl-bx-CiVq2pd3.js → dagre-VKFMJZFB-CQdfl-bx-BVKoii9B.js} +1 -1
- package/dist/assets/{diagram-FQU43EPY-BOSB6VUb-BviH94fe.js → diagram-FQU43EPY-BOSB6VUb-OjDjlvVc.js} +1 -1
- package/dist/assets/{diagram-G47NLZAW-DLXrcXsN-C9ANoKwM.js → diagram-G47NLZAW-DLXrcXsN-TSzEtHdz.js} +1 -1
- package/dist/assets/{diagram-NH7WQ7WH-BMQp1rkF-iRLoGEIF.js → diagram-NH7WQ7WH-BMQp1rkF-6YpHLSkw.js} +1 -1
- package/dist/assets/{diagram-OA4YK3LP-D1wQ0vUj-DApMIOOW.js → diagram-OA4YK3LP-D1wQ0vUj-D080UfcD.js} +1 -1
- package/dist/assets/{diagram-WEI45ONY-RR0DpF8R-5HyDvuK6.js → diagram-WEI45ONY-RR0DpF8R-DAhnbGro.js} +1 -1
- package/dist/assets/{ebnfDiagram-CCIWWBDH-M123uVJ8-2mE0-H0-.js → ebnfDiagram-CCIWWBDH-M123uVJ8-CgYjblvQ.js} +1 -1
- package/dist/assets/{erDiagram-Q63AITRT-BWx_-PXG-CY-qvEZP.js → erDiagram-Q63AITRT-BWx_-PXG-B-9YiTfM.js} +1 -1
- package/dist/assets/eventmodeling-45OFAUF4-_BVSjAXf-CF7wgGuX.js +1 -0
- package/dist/assets/flowDiagram-23GEKE2U-BeOc_anm-CfWH0Tbu.js +1 -0
- package/dist/assets/{ganttDiagram-NO4QXBWP-BOoJ1eTw-DJnZnEVq.js → ganttDiagram-NO4QXBWP-BOoJ1eTw-DpUm7W4G.js} +1 -1
- package/dist/assets/gitGraph-TEB2WS4Q-CH12KLTN-C2PnVeMX.js +1 -0
- package/dist/assets/{gitGraphDiagram-IHSO6WYX-B2CJhk_G-CS2k47ZS.js → gitGraphDiagram-IHSO6WYX-B2CJhk_G-CXhq2YaR.js} +1 -1
- package/dist/assets/index-83utxopd.js +357 -0
- package/dist/assets/index-BVNLkhp4.css +1 -0
- package/dist/assets/info-DKCQHKI2-Cbw3mbiK-CLk5uJ5t.js +1 -0
- package/dist/assets/{infoDiagram-FWYZ7A6U-Mp1X3pBP-DA4NgCU8.js → infoDiagram-FWYZ7A6U-Mp1X3pBP-BH6ylmwo.js} +1 -1
- package/dist/assets/{ishikawaDiagram-FXEZZL3T-BNG7tkJu-DPK-CeOV.js → ishikawaDiagram-FXEZZL3T-BNG7tkJu-DKlrUTwv.js} +1 -1
- package/dist/assets/{journeyDiagram-5HDEW3XC-Dbp_hY9X-C7-vjDGz.js → journeyDiagram-5HDEW3XC-Dbp_hY9X-B0zuSRxn.js} +1 -1
- package/dist/assets/{kanban-definition-HUTT4EX6-DSTc5u3q-DDPYyGjM.js → kanban-definition-HUTT4EX6-DSTc5u3q-B2UMHQ_t.js} +1 -1
- package/dist/assets/{line-B1wBwzrY-C8LCGp0g.js → line-B1wBwzrY-NlyrzQ8H.js} +1 -1
- package/dist/assets/{marp-CXxH4bDL.js → marp-Ak2po4xM.js} +1 -1
- package/dist/assets/{mermaid-parser.core-DC7NPJ_M-DpdiJOd9.js → mermaid-parser.core-DC7NPJ_M-uogn_ZQO.js} +2 -2
- package/dist/assets/{mermaid.core-DZM3Ha-E-qDLGiF8u.js → mermaid.core-DZM3Ha-E-CTwQuxp4.js} +3 -3
- package/dist/assets/{mindmap-definition-LN4V7U3C-DYtgcMsY-X9JS6B27.js → mindmap-definition-LN4V7U3C-DYtgcMsY-CF8BKsai.js} +1 -1
- package/dist/assets/packet-7NZHBO7P-lwb58iYx-3tUu63Qj.js +1 -0
- package/dist/assets/{pegDiagram-2B236MQR-C43eIpKM-q5euCaBj.js → pegDiagram-2B236MQR-C43eIpKM-Byur3AI9.js} +1 -1
- package/dist/assets/pie-RZYD4A2V-B1UWb4Gu-DiT3qAWP.js +1 -0
- package/dist/assets/{pieDiagram-ENE6RG2P-BkTqgJyR-ee81vkeN.js → pieDiagram-ENE6RG2P-BkTqgJyR-LmzmCqEK.js} +1 -1
- package/dist/assets/{quadrantDiagram-ABIIQ3AL-Bm1Zjm45-CoACCyaD.js → quadrantDiagram-ABIIQ3AL-Bm1Zjm45-CvxrZG72.js} +1 -1
- package/dist/assets/radar-I7S5WNFK-7CKcb_l--C_JwXiDu.js +1 -0
- package/dist/assets/railroad-3IZDKUUU-gCySKdnW-C30t7Eqd.js +1 -0
- package/dist/assets/railroad-abnf-AHOZXSZD-BophH4r--CGjr3zMY.js +1 -0
- package/dist/assets/railroad-ebnf-EBAXGLYW-AZNjl_Zu-BLsfAiEh.js +1 -0
- package/dist/assets/railroad-peg-LSFZ7HO6-BSiEEyeb-DpWrKO8r.js +1 -0
- package/dist/assets/{railroadDiagram-RFXS5EU6-BkfbdeAs-Dgv56gIO.js → railroadDiagram-RFXS5EU6-BkfbdeAs-Dmaz7qwx.js} +1 -1
- package/dist/assets/{requirementDiagram-TGXJPOKE-CrGTTjYg-CCYvGt-t.js → requirementDiagram-TGXJPOKE-CrGTTjYg-DLd9Qy31.js} +1 -1
- package/dist/assets/{sankeyDiagram-HTMAVEWB-rWXPf03Z-CprB_Z8u.js → sankeyDiagram-HTMAVEWB-rWXPf03Z-DuYt79nd.js} +1 -1
- package/dist/assets/{sequenceDiagram-DBY2YBRQ-nkJYWO2m-Dq6hIypb.js → sequenceDiagram-DBY2YBRQ-nkJYWO2m-CN2BTBIe.js} +1 -1
- package/dist/assets/{stateDiagram-2N3HPSRC-T4-clK8b-Ch8H-wNS.js → stateDiagram-2N3HPSRC-T4-clK8b-CQkZXPtf.js} +1 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-DIp7nhRd-TfY_5w1P.js +1 -0
- package/dist/assets/{swimlanes-5IMT3BWC-HmQNEntu-D8pbAIlW.js → swimlanes-5IMT3BWC-HmQNEntu-BxSqfUwA.js} +1 -1
- package/dist/assets/swimlanesDiagram-G3AALYLV-BeoZhwg7-BXdsLx4g.js +8 -0
- package/dist/assets/{timeline-definition-FHXFAJF6-CNc9jSTP--kxf46LG.js → timeline-definition-FHXFAJF6-CNc9jSTP-XW10LDWA.js} +1 -1
- package/dist/assets/treeView-QDETBFTQ-nIQcG1h9-BbYxhc6_.js +1 -0
- package/dist/assets/treemap-6X3UGDF4-BnsvC8yL-C4IdHouD.js +1 -0
- package/dist/assets/{vennDiagram-L72KCM5P-Dr3pTJ_0-BDmffNZb.js → vennDiagram-L72KCM5P-Dr3pTJ_0-BV8NVuqb.js} +1 -1
- package/dist/assets/wardley-OPB4EBWU-8Odxkx6V-BlJFib5h.js +1 -0
- package/dist/assets/{wardleyDiagram-EHGQE667-DSFc7ZZa-B5tAi_lA.js → wardleyDiagram-EHGQE667-DSFc7ZZa-CPdAgmn_.js} +1 -1
- package/dist/assets/{xychartDiagram-FW5EYKEG-BP0Nn4Pp-Du36xrax.js → xychartDiagram-FW5EYKEG-BP0Nn4Pp-CJCoz2E2.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +14 -11
- package/server/app-config.spec.ts +5 -1
- package/server/app-config.ts +20 -27
- package/server/backends/workspaceSetup.ts +8 -0
- package/server/codex-args.spec.ts +25 -4
- package/server/codex-args.ts +13 -0
- package/server/codex-sessions.spec.ts +96 -0
- package/server/codex-sessions.ts +140 -0
- package/server/codex-skills.spec.ts +70 -0
- package/server/codex-skills.ts +54 -0
- package/server/command-summary.spec.ts +122 -0
- package/server/command-summary.ts +157 -0
- package/server/config-routes.ts +44 -40
- package/server/config-schema.spec.ts +123 -0
- package/server/config-schema.ts +226 -0
- package/server/cost.spec.ts +107 -0
- package/server/cost.ts +215 -0
- package/server/cwd-presets.ts +1 -5
- package/server/dir-config.spec.ts +106 -5
- package/server/dir-config.ts +69 -68
- package/server/git-status.spec.ts +101 -0
- package/server/git-status.ts +54 -0
- package/server/header-config.spec.ts +108 -0
- package/server/header-config.ts +175 -0
- package/server/header-context.spec.ts +16 -0
- package/server/header-context.ts +68 -0
- package/server/header-resolve.spec.ts +132 -0
- package/server/header-resolve.ts +115 -0
- package/server/header-title.spec.ts +130 -0
- package/server/header-title.ts +112 -0
- package/server/index.ts +368 -72
- package/server/install-config-skill.spec.ts +103 -0
- package/server/install-config-skill.ts +62 -0
- package/server/session-resolve.spec.ts +41 -0
- package/server/session-resolve.ts +37 -0
- package/server/skills/mulmoterminal-config/SKILL.md +208 -0
- package/server/skills/mulmoterminal-config/palettes.json +167 -0
- package/server/transcript.spec.ts +140 -0
- package/server/transcript.ts +111 -0
- package/dist/assets/architecture-TIHT7OUA-CYMWc3UT-bWcDFBmU.js +0 -1
- package/dist/assets/channel-Di5rtkx0-GohkERB0.js +0 -1
- package/dist/assets/classDiagram-OUVF2IWQ-BgAZMSbT-GlJ3u1Wb.js +0 -1
- package/dist/assets/classDiagram-v2-EOCWNBFH-DxHTyui1-GlJ3u1Wb.js +0 -1
- package/dist/assets/cynefin-VYW2F7L2-DqA3n9nY-Dnv61JuF.js +0 -1
- package/dist/assets/eventmodeling-45OFAUF4-_BVSjAXf-Bbbkffe1.js +0 -1
- package/dist/assets/flowDiagram-23GEKE2U-BeOc_anm-DulRGLp4.js +0 -1
- package/dist/assets/gitGraph-TEB2WS4Q-CH12KLTN-BtMbgf2j.js +0 -1
- package/dist/assets/index-B7dNL7y0.css +0 -1
- package/dist/assets/index-a5e0Ms7-.js +0 -355
- package/dist/assets/info-DKCQHKI2-Cbw3mbiK-pm3KH1hx.js +0 -1
- package/dist/assets/packet-7NZHBO7P-lwb58iYx-CrXwOyyd.js +0 -1
- package/dist/assets/pie-RZYD4A2V-B1UWb4Gu-BaiAaew5.js +0 -1
- package/dist/assets/radar-I7S5WNFK-7CKcb_l--CFNO3OrR.js +0 -1
- package/dist/assets/railroad-3IZDKUUU-gCySKdnW-CdeNVhid.js +0 -1
- package/dist/assets/railroad-abnf-AHOZXSZD-BophH4r--Btv0ecNO.js +0 -1
- package/dist/assets/railroad-ebnf-EBAXGLYW-AZNjl_Zu-CH9b-tjP.js +0 -1
- package/dist/assets/railroad-peg-LSFZ7HO6-BSiEEyeb-C3e6kQJQ.js +0 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-DIp7nhRd-C9sabJiH.js +0 -1
- package/dist/assets/swimlanesDiagram-G3AALYLV-BeoZhwg7-Dgqw4HBg.js +0 -8
- package/dist/assets/treeView-QDETBFTQ-nIQcG1h9-pio3kitt.js +0 -1
- package/dist/assets/treemap-6X3UGDF4-BnsvC8yL-DprilSCD.js +0 -1
- 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
|
|
4
|
-
|
|
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 (
|
|
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
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
- **
|
|
97
|
-
|
|
98
|
-
|
|
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` (
|
|
101
|
-
|
|
102
|
-
`http://localhost:6856`). In production the
|
|
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.
|
|
634
|
-
2. else the latest `
|
|
635
|
-
3. else the
|
|
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
|
-
|
|
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.
|
|
649
|
-
session state, hook injection, session discovery
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
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
|
|
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
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
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-
|
|
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};
|