mulmoterminal 1.12.0 → 2.0.1
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 +49 -9
- package/common/isRecord.ts +13 -0
- package/common/keymap.ts +158 -0
- package/common/launchAgent.ts +15 -0
- package/common/pushKinds.ts +21 -0
- package/common/quickCommands.ts +13 -0
- package/common/remoteHostHealth.ts +2 -2
- package/common/sessionAgent.ts +8 -0
- package/common/sourceExtensions.ts +3 -0
- package/dist/assets/{abnfDiagram-VRR7QNED-RjiYivmv-pSAUl_Ff.js → abnfDiagram-VRR7QNED-RjiYivmv-CMyzg2zY.js} +1 -1
- package/dist/assets/architecture-TIHT7OUA-0NsD2xW1-3kpQCFcP.js +1 -0
- package/dist/assets/{architectureDiagram-ZJ3FMSHR-3nWA91tG--2OsB-_X.js → architectureDiagram-ZJ3FMSHR-3nWA91tG-CSIT7RtO.js} +1 -1
- package/dist/assets/{blockDiagram-677ZJIJ3-BPuAJQRW-B4pUWSnv.js → blockDiagram-677ZJIJ3-BPuAJQRW-CnVQRl8Q.js} +1 -1
- package/dist/assets/{c4Diagram-LMCZKHZV-C0LAqQso-acI3CCdO.js → c4Diagram-LMCZKHZV-C0LAqQso-CrpIWU_7.js} +1 -1
- package/dist/assets/channel-7wUqSdoX-D8_GjCXH.js +1 -0
- package/dist/assets/{chunk-32BRIVSS-BRrYpgtb-D5a7WKYo.js → chunk-32BRIVSS-BRrYpgtb-D_PiqrVh.js} +1 -1
- package/dist/assets/{chunk-52WLFC77-BJ-ss3Xr-DMxnZwtN.js → chunk-52WLFC77-BJ-ss3Xr-BCpG-w5L.js} +1 -1
- package/dist/assets/{chunk-C7G6YPKG-BZEucKEL-CgdrEHA8.js → chunk-C7G6YPKG-BZEucKEL-CP9wac26.js} +1 -1
- package/dist/assets/{chunk-EX3LRPZG-DLS6FBN1-BUQK-28c.js → chunk-EX3LRPZG-DLS6FBN1-BJZF7tEb.js} +1 -1
- package/dist/assets/{chunk-FWX5IMBZ-DHLSFw1H-B47TnGY7.js → chunk-FWX5IMBZ-DHLSFw1H-Cnk0SpZp.js} +2 -2
- package/dist/assets/{chunk-HOUHSVGY-Bhlt8hXJ-BWp5Si0X.js → chunk-HOUHSVGY-Bhlt8hXJ-CRGS9q8z.js} +1 -1
- package/dist/assets/{chunk-ICXQ74PX-DwgHBX_g-wQu6dWYL.js → chunk-ICXQ74PX-DwgHBX_g-DpPUV9Tl.js} +1 -1
- package/dist/assets/{chunk-MOJQB5TN-CMZRaeqt-C9iwMm9l.js → chunk-MOJQB5TN-CMZRaeqt-CG59XkeN.js} +1 -1
- package/dist/assets/{chunk-OGEWGWER-8Qy4a8b5-Deimyoz1.js → chunk-OGEWGWER-8Qy4a8b5-B59YGczL.js} +1 -1
- package/dist/assets/{chunk-PUDLZKDR-DcrWQRYh-DO73cW2r.js → chunk-PUDLZKDR-DcrWQRYh-CXGtfuoa.js} +1 -1
- package/dist/assets/{chunk-Q4XR5HBZ-ZXVGkG8Z-BcnvBQ5f.js → chunk-Q4XR5HBZ-ZXVGkG8Z-eHuKxxBN.js} +1 -1
- package/dist/assets/{chunk-V7JOEXUC-DmGdheTX-3Q8KKE_I.js → chunk-V7JOEXUC-DmGdheTX-BJZa8auK.js} +1 -1
- package/dist/assets/{chunk-VAUOI2AC-CS9QJ4yz-D7-u3P-6.js → chunk-VAUOI2AC-CS9QJ4yz-DviHNtmN.js} +1 -1
- package/dist/assets/{chunk-VR4S4FIN-C6a91eNY-CbAUL0hF.js → chunk-VR4S4FIN-C6a91eNY-DsA9hbD5.js} +1 -1
- package/dist/assets/{chunk-WYO6CB5R-BlzOfotS-D3NwDLFu.js → chunk-WYO6CB5R-BlzOfotS-qckQG4MR.js} +1 -1
- package/dist/assets/{chunk-ZGVPDNZ5-BYwxNFTK-DJT0juMI.js → chunk-ZGVPDNZ5-BYwxNFTK-Cr8j6FMw.js} +1 -1
- package/dist/assets/classDiagram-OUVF2IWQ-BYlRi6YQ-axLhYHqO.js +1 -0
- package/dist/assets/classDiagram-v2-EOCWNBFH-BCHaYHxj-axLhYHqO.js +1 -0
- package/dist/assets/cynefin-VYW2F7L2-CGYr2F6h-C9DtePCD.js +1 -0
- package/dist/assets/{cynefinDiagram-TSTJHNR4-DHA9iPo--BgLHMWdu.js → cynefinDiagram-TSTJHNR4-DHA9iPo--BuPVUHaQ.js} +1 -1
- package/dist/assets/{dagre-VKFMJZFB--oJKqXBZ-DCgFXH4V.js → dagre-VKFMJZFB--oJKqXBZ-l8CP-U85.js} +1 -1
- package/dist/assets/{diagram-FQU43EPY-CcJCB9bG-D0zgnSk-.js → diagram-FQU43EPY-CcJCB9bG-CV6yO6d6.js} +1 -1
- package/dist/assets/{diagram-G47NLZAW-C_o-WGG1-DdeMXEON.js → diagram-G47NLZAW-C_o-WGG1-DmUMCyqP.js} +1 -1
- package/dist/assets/{diagram-NH7WQ7WH-CXJCYvY--nuKbS8-o.js → diagram-NH7WQ7WH-CXJCYvY--D1Zcc9sH.js} +1 -1
- package/dist/assets/{diagram-OA4YK3LP-BMzeJ87A-CuwECnWV.js → diagram-OA4YK3LP-BMzeJ87A-CsC2S-ug.js} +1 -1
- package/dist/assets/{diagram-WEI45ONY-D_93NKqo-DraZEBjb.js → diagram-WEI45ONY-D_93NKqo-C3Xq1urQ.js} +1 -1
- package/dist/assets/{ebnfDiagram-CCIWWBDH-CVai1Ii9-D4MLnQrr.js → ebnfDiagram-CCIWWBDH-CVai1Ii9-BtjcwSW_.js} +1 -1
- package/dist/assets/{erDiagram-Q63AITRT-CiABnA0s-lsZR7DdZ.js → erDiagram-Q63AITRT-CiABnA0s-DhQMPhj9.js} +1 -1
- package/dist/assets/eventmodeling-45OFAUF4-CWmqhm3a-BCX3cvpw.js +1 -0
- package/dist/assets/flowDiagram-23GEKE2U-DKSB5AiY-DjZRfq7e.js +1 -0
- package/dist/assets/{ganttDiagram-NO4QXBWP-DQZvdFo1-FQqd4Iwx.js → ganttDiagram-NO4QXBWP-DQZvdFo1-Bw2aYbCJ.js} +1 -1
- package/dist/assets/gitGraph-TEB2WS4Q-BR9qPwdN-pIAqsAx0.js +1 -0
- package/dist/assets/{gitGraphDiagram-IHSO6WYX-C2ovBouh-BS8dotB3.js → gitGraphDiagram-IHSO6WYX-C2ovBouh-C1Dlsswx.js} +1 -1
- package/dist/assets/index-DVTKApMs.css +1 -0
- package/dist/assets/index-Dwc5wfJg.js +618 -0
- package/dist/assets/info-DKCQHKI2-Dplx5kMp-S1We2Nw2.js +1 -0
- package/dist/assets/{infoDiagram-FWYZ7A6U-7UnoB5AP-CTkU0aJP.js → infoDiagram-FWYZ7A6U-7UnoB5AP-CjB00vD7.js} +1 -1
- package/dist/assets/{ishikawaDiagram-FXEZZL3T-ByUDM_N2-ep-r9EK9.js → ishikawaDiagram-FXEZZL3T-ByUDM_N2-BxVGf9t3.js} +1 -1
- package/dist/assets/{journeyDiagram-5HDEW3XC-c5xIah9o-Dpxig4Bz.js → journeyDiagram-5HDEW3XC-c5xIah9o-BPLsgkBp.js} +1 -1
- package/dist/assets/{kanban-definition-HUTT4EX6-Cnt6loYD-DsTIZE6V.js → kanban-definition-HUTT4EX6-Cnt6loYD-C0UF5ruR.js} +1 -1
- package/dist/assets/{lib-flanDh0L.js → lib-Ca9R1hlX.js} +1 -1
- package/dist/assets/{line-D7ziSjKi-CGxCkapk.js → line-D7ziSjKi-B4ejYAqw.js} +1 -1
- package/dist/assets/{marp-DLdaYmh3.js → marp-J4wSkl3Q.js} +1 -1
- package/dist/assets/material-symbols-outlined-D4PiVfdc.woff2 +0 -0
- package/dist/assets/{mermaid-parser.core-DxEa8E3F-D0mX1O6U.js → mermaid-parser.core-DxEa8E3F-D1ef8DoR.js} +2 -2
- package/dist/assets/{mermaid.core-V0OYwIz3-CcTjQkVc.js → mermaid.core-V0OYwIz3-BSn9HyWz.js} +3 -3
- package/dist/assets/{mindmap-definition-LN4V7U3C-CexN3O6L-Bkhtz3Bc.js → mindmap-definition-LN4V7U3C-CexN3O6L-BLTr4gMi.js} +1 -1
- package/dist/assets/packet-7NZHBO7P-CQI3flND-DaLVcMu_.js +1 -0
- package/dist/assets/{pegDiagram-2B236MQR-BiM4G0cW-DMRxa-tg.js → pegDiagram-2B236MQR-BiM4G0cW-E_eG9LVV.js} +1 -1
- package/dist/assets/pie-RZYD4A2V-CnmKE2Wa-BKDCOtxh.js +1 -0
- package/dist/assets/{pieDiagram-ENE6RG2P-B_fBBS-2-CUnvC4AR.js → pieDiagram-ENE6RG2P-B_fBBS-2-f4ZSZ6GC.js} +1 -1
- package/dist/assets/{quadrantDiagram-ABIIQ3AL-BigGVxCR-DR663h1w.js → quadrantDiagram-ABIIQ3AL-BigGVxCR-DnwDJxmc.js} +1 -1
- package/dist/assets/radar-I7S5WNFK-us6x-Z9R-Cw4aJNVl.js +1 -0
- package/dist/assets/railroad-3IZDKUUU-BKdz6pdN-DJZYlsdK.js +1 -0
- package/dist/assets/railroad-abnf-AHOZXSZD-m_CSSIfd-D9wfK70u.js +1 -0
- package/dist/assets/railroad-ebnf-EBAXGLYW-Nu3nAG7C-C2rW9Qx8.js +1 -0
- package/dist/assets/railroad-peg-LSFZ7HO6--Y3mD4R_-CCUOq6go.js +1 -0
- package/dist/assets/{railroadDiagram-RFXS5EU6--lNIlG64-B5-tZFnu.js → railroadDiagram-RFXS5EU6--lNIlG64-BUH_ocB5.js} +1 -1
- package/dist/assets/{requirementDiagram-TGXJPOKE-D-B0Y5Wu-BMDB2YxN.js → requirementDiagram-TGXJPOKE-D-B0Y5Wu-CT5zmLHk.js} +1 -1
- package/dist/assets/{sankeyDiagram-HTMAVEWB-DlutaXDv-hqbzCnoU.js → sankeyDiagram-HTMAVEWB-DlutaXDv-9sorx_WA.js} +1 -1
- package/dist/assets/{sequenceDiagram-DBY2YBRQ-BOLgtggH-ClLNrm1_.js → sequenceDiagram-DBY2YBRQ-BOLgtggH-CnjR9ja7.js} +1 -1
- package/dist/assets/{stateDiagram-2N3HPSRC-jf_dXUEU-keoVjJu9.js → stateDiagram-2N3HPSRC-jf_dXUEU-DboFu0BL.js} +1 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-CuzgCL2v-FGJ3mpM7.js +1 -0
- package/dist/assets/{swimlanes-5IMT3BWC-CYjtALQH-DxGnhGXl.js → swimlanes-5IMT3BWC-CYjtALQH-Cqxsp00w.js} +1 -1
- package/dist/assets/swimlanesDiagram-G3AALYLV-3bmSBtKs-B3qFCxjl.js +8 -0
- package/dist/assets/{timeline-definition-FHXFAJF6-DYJ4oUm8-CGOvA6DA.js → timeline-definition-FHXFAJF6-DYJ4oUm8-Du08XOrV.js} +1 -1
- package/dist/assets/treeView-QDETBFTQ-D27HWG6Q-dIhNDmqB.js +1 -0
- package/dist/assets/treemap-6X3UGDF4-CmGIU3H5-C8SEy1d3.js +1 -0
- package/dist/assets/{vennDiagram-L72KCM5P-CdKAHoek-Dwd681E8.js → vennDiagram-L72KCM5P-CdKAHoek-DtBtukcO.js} +1 -1
- package/dist/assets/wardley-OPB4EBWU-CbHWsb2k-SYQlwZRo.js +1 -0
- package/dist/assets/{wardleyDiagram-EHGQE667-BF6c4_CW-DnznbMR1.js → wardleyDiagram-EHGQE667-BF6c4_CW-CW4nfoJ3.js} +1 -1
- package/dist/assets/{xychartDiagram-FW5EYKEG-CGiKngj7-DVv7yX_F.js → xychartDiagram-FW5EYKEG-CGiKngj7-Ruux6STV.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +3 -3
- package/server/agents/codex-activity.ts +3 -2
- package/server/agents/codex-session.ts +1 -2
- package/server/agents/codex-sessions.ts +1 -2
- package/server/backends/fileChange.ts +2 -2
- package/server/backends/google.ts +2 -2
- package/server/backends/notifier.ts +2 -2
- package/server/backends/remoteHost/getFeed.spec.ts +2 -1
- package/server/backends/remoteHost/handlers.spec.ts +15 -5
- package/server/backends/remoteHost/handlers.ts +30 -1
- package/server/backends/remoteHost/index.ts +2 -1
- package/server/backends/remoteHost/launchTerminal.ts +38 -0
- package/server/backends/remoteHost/quickCommands.ts +22 -0
- package/server/backends/remoteHost/routes.ts +3 -4
- package/server/backends/remoteHost/terminalInput.ts +1 -1
- package/server/backends/remoteHost/terminalScreen.ts +32 -7
- package/server/backends/scheduler.ts +1 -4
- package/server/config/app-config.ts +65 -2
- package/server/config/config-body.ts +1 -1
- package/server/config/config-routes.ts +16 -1
- package/server/config/config-schema.ts +11 -0
- package/server/config/cwd-presets.ts +1 -2
- package/server/config/dir-config.ts +1 -2
- package/server/config/env.ts +13 -0
- package/server/config/header-config.ts +2 -2
- package/server/config/keymap-check.ts +49 -0
- package/server/files/dirRequest.ts +7 -3
- package/server/files/open-dir.ts +1 -1
- package/server/files/pick-file.ts +3 -4
- package/server/files/scripts.ts +1 -2
- package/server/git/ghItem.ts +1 -2
- package/server/git/gitRemote.ts +1 -1
- package/server/git/prPhase.ts +1 -2
- package/server/git/prs.ts +2 -1
- package/server/git/worktree-routes.ts +5 -5
- package/server/index.ts +75 -8
- package/server/infra/allowed-origin.ts +26 -5
- package/server/infra/loopback.ts +40 -0
- package/server/infra/pubsub.ts +33 -3
- package/server/infra/sandbox.ts +1 -2
- package/server/infra/tmux-routes.ts +3 -3
- package/server/mcp/broker.ts +1 -1
- package/server/routes/app-routes.ts +7 -1
- package/server/routes/plugin-narration.ts +1 -1
- package/server/routes/plugin-routes.ts +1 -1
- package/server/routes/same-origin-guard.ts +42 -0
- package/server/routes/toolResultPlan.ts +1 -1
- package/server/routes/ws-routes.ts +2 -2
- package/server/session/activity-hook.ts +2 -1
- package/server/session/activity-state.ts +2 -2
- package/server/session/command-summary.ts +3 -4
- package/server/session/cost.ts +2 -1
- package/server/session/last-turn.ts +2 -1
- package/server/session/scheduled-sessions.ts +1 -2
- package/server/session/session-reads.ts +1 -1
- package/server/session/task-push.ts +5 -4
- package/server/session/taskPushRules.ts +8 -0
- package/server/session/transcript.ts +1 -1
- package/server/session/translation-submit.ts +1 -1
- package/server/session/types.ts +1 -1
- package/server/skills/mulmoterminal-config/SKILL.md +112 -2
- package/dist/assets/architecture-TIHT7OUA-0NsD2xW1-D6-iafxP.js +0 -1
- package/dist/assets/channel-7wUqSdoX-C5fBtPIO.js +0 -1
- package/dist/assets/classDiagram-OUVF2IWQ-BYlRi6YQ-BG0fUcm0.js +0 -1
- package/dist/assets/classDiagram-v2-EOCWNBFH-BCHaYHxj-BG0fUcm0.js +0 -1
- package/dist/assets/cynefin-VYW2F7L2-CGYr2F6h-BXKEhwdq.js +0 -1
- package/dist/assets/eventmodeling-45OFAUF4-CWmqhm3a-DR77--Xz.js +0 -1
- package/dist/assets/flowDiagram-23GEKE2U-DKSB5AiY-4FjklgCS.js +0 -1
- package/dist/assets/gitGraph-TEB2WS4Q-BR9qPwdN-B1NWuZZc.js +0 -1
- package/dist/assets/index-BanknHa5.css +0 -1
- package/dist/assets/index-C2dG1Vgz.js +0 -618
- package/dist/assets/info-DKCQHKI2-Dplx5kMp-DWaf4lVA.js +0 -1
- package/dist/assets/material-symbols-outlined-BmmV8Mzf.woff2 +0 -0
- package/dist/assets/packet-7NZHBO7P-CQI3flND-CNQKKko7.js +0 -1
- package/dist/assets/pie-RZYD4A2V-CnmKE2Wa-D7x2hPr0.js +0 -1
- package/dist/assets/radar-I7S5WNFK-us6x-Z9R-CJKo59vs.js +0 -1
- package/dist/assets/railroad-3IZDKUUU-BKdz6pdN-BWgEHa7K.js +0 -1
- package/dist/assets/railroad-abnf-AHOZXSZD-m_CSSIfd-C8dNTAtf.js +0 -1
- package/dist/assets/railroad-ebnf-EBAXGLYW-Nu3nAG7C-Bq0k84yk.js +0 -1
- package/dist/assets/railroad-peg-LSFZ7HO6--Y3mD4R_-CUJ6D_j0.js +0 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-CuzgCL2v-CVYeQN2R.js +0 -1
- package/dist/assets/swimlanesDiagram-G3AALYLV-3bmSBtKs-evpAgPJ1.js +0 -8
- package/dist/assets/treeView-QDETBFTQ-D27HWG6Q-DVphSY2a.js +0 -1
- package/dist/assets/treemap-6X3UGDF4-CmGIU3H5-B7nhG_Cj.js +0 -1
- package/dist/assets/wardley-OPB4EBWU-CbHWsb2k-C9J5aiu8.js +0 -1
package/README.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
**Run a whole team of coding agents from your browser — and actually keep up with them.**
|
|
4
4
|
|
|
5
|
+
### 📖 Documentation — **[receptron.github.io/mulmoterminal](https://receptron.github.io/mulmoterminal/)**
|
|
6
|
+
|
|
7
|
+
- **User guide:** [English](https://receptron.github.io/mulmoterminal/guide/en/) — the grid
|
|
8
|
+
view, everyday workflows, the full feature list, configuration, and mobile push notifications.
|
|
9
|
+
- **ユーザーガイド:** [日本語](https://receptron.github.io/mulmoterminal/guide/ja/) —
|
|
10
|
+
グリッドの使い方・日々のワークフロー・機能一覧・設定・スマホ通知の設定はこちら。
|
|
11
|
+
|
|
5
12
|

|
|
6
13
|
|
|
7
14
|
MulmoTerminal turns [Claude Code](https://claude.com/claude-code) (and OpenAI's **Codex**)
|
|
@@ -14,12 +21,6 @@ ping to your phone when a task finishes. One `npx` command, no Electron, no conf
|
|
|
14
21
|
npx mulmoterminal # starts on http://localhost:34567 and opens your browser
|
|
15
22
|
```
|
|
16
23
|
|
|
17
|
-
- 📖 **User guide:** [English](https://receptron.github.io/mulmoterminal/guide/en/) — the
|
|
18
|
-
grid view, everyday workflows, the full feature list, configuration, and mobile push
|
|
19
|
-
notifications.
|
|
20
|
-
- 📖 **ユーザーガイド:** [日本語](https://receptron.github.io/mulmoterminal/guide/ja/) —
|
|
21
|
-
グリッドの使い方・日々のワークフロー・機能一覧・設定・スマホ通知の設定はこちら。
|
|
22
|
-
|
|
23
24
|

|
|
24
25
|
|
|
25
26
|
*The grid is a **cockpit for parallel agents** — here, four live Claude sessions, each in its own color-coded project. Every cell's header carries what you need to triage at a glance: **model · context %**, **token counts** (`⇡in ⇣out`), the **git branch / changes** chip, and an AI summary of what the agent is doing. A cell's **border color signals state** — working / done (blue), needs-you (amber — e.g. waiting on a permission), idle — with an attention chime so a stuck cell off-screen still pulls you back. Supervise many; only step in where you're called.*
|
|
@@ -78,6 +79,34 @@ asks the local server to open the OS file dialog and inserts the chosen path (wo
|
|
|
78
79
|
browser, including Chrome). The path is inserted at the cursor — it is not submitted, so you
|
|
79
80
|
can review it first.
|
|
80
81
|
|
|
82
|
+
<a id="clicking-a-file-path"></a>
|
|
83
|
+
|
|
84
|
+
**Clicking a file path** — the other direction. A path an agent *prints* becomes a link, and
|
|
85
|
+
**what it opens is chosen by its extension**, so each kind arrives as the thing it is rather
|
|
86
|
+
than as bytes (files within the session's working directory only):
|
|
87
|
+
|
|
88
|
+
| A clicked … | opens as |
|
|
89
|
+
|---|---|
|
|
90
|
+
| `.md` `.markdown` | **rendered** markdown in a new tab — the same sandboxed `…/md` HTML the Files preview uses. It follows your system light/dark setting, since under the sandbox CSP it can't ask the app which theme is on |
|
|
91
|
+
| `.json` | **indented** in a new tab (Chrome and Safari otherwise show one long line) |
|
|
92
|
+
| `.csv` `.tsv` | a **table** in a new tab, with a sticky header that scrolls inside its own box |
|
|
93
|
+
| source, config, logs, and `.txt` — 46 extensions | the app's own **Files** view (`/files?path=`), where CodeMirror highlights it, the tree is right there, and it can be edited |
|
|
94
|
+
| everything else — images, PDF, SVG, HTML, video | raw bytes in a new tab, which the browser renders better than an editor would |
|
|
95
|
+
|
|
96
|
+
Highlighting in the Files view covers the JS/TS family, JSON and Markdown (the modes
|
|
97
|
+
`cmEditor.ts` bundles); other languages open as plain text.
|
|
98
|
+
|
|
99
|
+
This set is **deliberately asymmetric** with the set the server serves as viewable text —
|
|
100
|
+
`.md` goes to the rendered viewer rather than the Files view, `.txt` does the opposite, and
|
|
101
|
+
dotfiles are server-only. The 45 extensions both sides agree on live in
|
|
102
|
+
`common/sourceExtensions.ts`, each side adds its own extras, and
|
|
103
|
+
`test/common/sourceExtensions.spec.ts` pins the asymmetry so it isn't "fixed" into symmetry.
|
|
104
|
+
|
|
105
|
+
> **Changing this?** The routing table is `ROUTE_BY_EXTENSION` / `IN_APP_EXTENSIONS` in
|
|
106
|
+
> `src/composables/terminalFilePathLinkProvider.ts`. Update this section, the
|
|
107
|
+
> `docs/guide/{en,ja}/features.md` row, and the link table in `docs/terminal-notes.md`
|
|
108
|
+
> together — all three went stale once already (#834).
|
|
109
|
+
|
|
81
110
|
---
|
|
82
111
|
|
|
83
112
|
## Install & run
|
|
@@ -413,10 +442,12 @@ The Settings modal (⚙) persists per-user UI choices to `~/.mulmoterminal/confi
|
|
|
413
442
|
| `soundFile` | Absolute path to a custom **attention sound** (played when a session needs attention). Empty/unset uses the built-in synthesized chime. |
|
|
414
443
|
| `prRepos` | `owner/repo` entries whose open PRs/issues the cross-repo **PRs & Issues** view aggregates (via your `gh` login). |
|
|
415
444
|
| `launchers` | `{ label, command }` entries offered in a grid cell's launcher besides Claude — a plain shell, `codex`, any interactive command. |
|
|
445
|
+
| `quickCommands` | `{ label, text, agents? }` phrases the **phone** offers as chips on a session's terminal view. Tapping one puts `text` in the input box; it is not sent until you press send. `agents` (`"claude"` / `"codex"` / `"shell"`) scopes a chip to session kinds — omit it to offer the chip everywhere. Empty by default. |
|
|
416
446
|
| `userMcpServers` | `{ id, url }` HTTP MCP servers merged into the **single-view** Claude session's `--mcp-config` (a `localhost` URL is reached over `host.docker.internal` in the Docker sandbox). Takes effect on the next session. |
|
|
417
447
|
| `buttons` | Header action buttons — see [Header buttons](#header-buttons). Omit to keep the defaults; set to replace them. |
|
|
418
448
|
| `chips` | Header info chips. Omit to keep the default set; `[]` hides all built-ins. |
|
|
419
|
-
| `pushEnabled` | `true` to send a **Web Push** to your registered devices
|
|
449
|
+
| `pushEnabled` | `true` to send a **Web Push** to your registered devices. Off by default; only sends while the **RemoteHost** channel is connected (see below). The master switch — `pushKinds` picks which moments. |
|
|
450
|
+
| `pushKinds` | Which moments push: `"finished"` (a turn ended, ✅) and/or `"waiting"` (the agent stopped to ask — a permission prompt or a question, ❓, **once per prompt**). Omit to keep both; `[]` for none. A kind added in a later version stays off until you tick it. |
|
|
420
451
|
| `worklogEnabled` | `true` to run the built-in **dev worklog** batch (see below). Off by default (each run spawns an LLM session, so it costs tokens). |
|
|
421
452
|
| `worklogIntervalHours` | Worklog cadence in hours (default `6`, clamped to `1`–`168`). |
|
|
422
453
|
| `terminalSubmit` | Which bytes Claude reads as **submit** vs **newline**: `"cr"` (default — Enter submits, Shift+Enter makes a newline) or `"esc-cr"` (for a Claude Code rebound the other way). Applies to the keyboard **and** the phone remote-view submit, for **Claude sessions only** (shell/codex keep plain Enter). See the [Configuration guide](https://receptron.github.io/mulmoterminal/guide/en/config.html#terminal-submit). |
|
|
@@ -781,7 +812,12 @@ Favorited collections get their own toolbar buttons.
|
|
|
781
812
|
pages so you notice an off-screen cell that needs you.
|
|
782
813
|
- **Zoom & filmstrip** — a cell's **⤢** enlarges one agent while the rest shrink to
|
|
783
814
|
thumbnails in a bottom **filmstrip**; click a thumbnail to switch, **⤡** to return to the
|
|
784
|
-
grid — so you can flip between "see everything" and "focus on one" in a click.
|
|
815
|
+
grid — so you can flip between "see everything" and "focus on one" in a click. While
|
|
816
|
+
zoomed, keys you bind walk the enlargement along the on-screen order without reaching for
|
|
817
|
+
the mouse — **opt-in, nothing is bound by default**, since any bound key is taken from the
|
|
818
|
+
terminal underneath. Add a `keymap` to `~/.mulmoterminal/config.json`; see the
|
|
819
|
+
[guide](https://receptron.github.io/mulmoterminal/guide/en/config.html#keymap)
|
|
820
|
+
for the syntax, the action list, and combinations a browser can never bind.
|
|
785
821
|
|
|
786
822
|

|
|
787
823
|
|
|
@@ -996,7 +1032,7 @@ same-origin-guarded.
|
|
|
996
1032
|
|
|
997
1033
|
| Endpoint | Purpose |
|
|
998
1034
|
| -------- | ------- |
|
|
999
|
-
| `GET\|POST /api/config` | User UI config (`cwdPresets`, `soundFile`, `prRepos`, `launchers`, `userMcpServers`, `providers`). |
|
|
1035
|
+
| `GET\|POST /api/config` | User UI config (`cwdPresets`, `soundFile`, `prRepos`, `launchers`, `quickCommands`, `userMcpServers`, `providers`). |
|
|
1000
1036
|
| `GET /api/sound` · `/api/dir-sound?cwd=` · `/api/dir-config?cwd=` | Custom / per-directory attention sound + per-dir config. |
|
|
1001
1037
|
| `GET /api/launch-options` | The Anthropic-compatible backends this server can reach, each with its models and — when it can't — the reason. Reports the **name** of the env var a key is read from, never the key. |
|
|
1002
1038
|
| `GET /api/notifications`(`/history`) · `POST /api/notifications/:id/clear` | Notification feed. |
|
|
@@ -1005,6 +1041,10 @@ same-origin-guarded.
|
|
|
1005
1041
|
| `GET /api/remote-host/status` · `POST /api/remote-host/{connect,disconnect}` | Companion phone-client link. Each response carries the command channel's `health` (`online` / `reconnecting` / `offline`, plus the last listener error), so the toolbar shows a dropped channel instead of the last state it happened to fetch. |
|
|
1006
1042
|
| `POST /api/open-dir` · `POST /api/pick-file` | Reveal a dir in Finder/Explorer; OS file-picker → path (`{ directory: true }` opens the folder picker — used by the launcher's Working-directory 📁 button). |
|
|
1007
1043
|
|
|
1044
|
+
The phone itself uses **none** of these routes — it reaches the host over Firestore command
|
|
1045
|
+
docs, not HTTP. Every command it can send, and the shapes it gets back, are in
|
|
1046
|
+
[`docs/remote-host-protocol.md`](docs/remote-host-protocol.md).
|
|
1047
|
+
|
|
1008
1048
|
### WebSocket: `/ws` (terminal)
|
|
1009
1049
|
|
|
1010
1050
|
A raw WebSocket carrying the terminal stream for one session. One PTY per
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { isObject } from "graphai";
|
|
2
|
+
|
|
3
|
+
// The guard both sides use before reading named fields off parsed JSON (config files, API
|
|
4
|
+
// responses, JSONL transcripts, request bodies). It was hand-copied into 29 files before this.
|
|
5
|
+
//
|
|
6
|
+
// Arrays are excluded deliberately, and that is not a nicety: TypeScript itself refuses
|
|
7
|
+
// `const r: Record<string, unknown> = []` ("index signature for type 'string' is missing"), so a
|
|
8
|
+
// guard that narrows an array to Record is telling the compiler something untrue — and callers
|
|
9
|
+
// that go on to `Object.entries()` the value would silently walk array indices as field names.
|
|
10
|
+
//
|
|
11
|
+
// graphai's own `isPlainObject` is exactly this (it also rejects Map/Date/class instances), but
|
|
12
|
+
// it is not exported from the package root, so the array check stays here.
|
|
13
|
+
export const isRecord = (value: unknown): value is Record<string, unknown> => isObject(value) && !Array.isArray(value);
|
package/common/keymap.ts
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// The user-defined keyboard shortcuts, in `~/.mulmoterminal/config.json` under `keymap`.
|
|
2
|
+
//
|
|
3
|
+
// There are NO defaults: an absent or empty `keymap` means the shortcuts are OFF. Every
|
|
4
|
+
// binding is something the user opted into, because any key this claims is a key the
|
|
5
|
+
// terminal underneath stops receiving — that trade is the user's to make, not ours.
|
|
6
|
+
//
|
|
7
|
+
// The server sanitizes and persists it; the browser matches keydowns against it. One
|
|
8
|
+
// definition here so the accepted syntax can't drift between the two.
|
|
9
|
+
//
|
|
10
|
+
// "keymap": { "zoom-next": "PageDown", "zoom-prev": "Shift+PageUp" }
|
|
11
|
+
|
|
12
|
+
// Actions a key can be bound to. Adding one here is all it takes for the config to accept it.
|
|
13
|
+
export const KEYMAP_ACTIONS = ["zoom-toggle", "zoom-next", "zoom-prev", "next-attention", "terminal-new", "terminal-new-adjacent", "terminal-close"] as const;
|
|
14
|
+
export type KeymapAction = (typeof KEYMAP_ACTIONS)[number];
|
|
15
|
+
|
|
16
|
+
export const isKeymapAction = (value: unknown): value is KeymapAction => typeof value === "string" && (KEYMAP_ACTIONS as readonly string[]).includes(value);
|
|
17
|
+
|
|
18
|
+
// action -> binding string. Absent action = unbound = that shortcut does nothing.
|
|
19
|
+
export type Keymap = Partial<Record<KeymapAction, string>>;
|
|
20
|
+
|
|
21
|
+
// A parsed binding. `key` is matched against `KeyboardEvent.key` exactly as the browser
|
|
22
|
+
// reports it, so it is case-sensitive for printable characters ("a" and "A" differ, the
|
|
23
|
+
// latter implying Shift).
|
|
24
|
+
export interface KeyBinding {
|
|
25
|
+
key: string;
|
|
26
|
+
shift: boolean;
|
|
27
|
+
alt: boolean;
|
|
28
|
+
ctrl: boolean;
|
|
29
|
+
meta: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Modifier spellings accepted in a binding string, mapped to the flag they set. "Cmd",
|
|
33
|
+
// "Command" and "Meta" are the same modifier; "Option" is macOS's name for Alt.
|
|
34
|
+
const MODIFIERS: Record<string, keyof Omit<KeyBinding, "key">> = {
|
|
35
|
+
shift: "shift",
|
|
36
|
+
alt: "alt",
|
|
37
|
+
option: "alt",
|
|
38
|
+
ctrl: "ctrl",
|
|
39
|
+
control: "ctrl",
|
|
40
|
+
meta: "meta",
|
|
41
|
+
cmd: "meta",
|
|
42
|
+
command: "meta",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Parse "Shift+PageUp" into its parts, or null when the string is malformed (empty, no
|
|
46
|
+
// key left after the modifiers, an unknown modifier, or a duplicate one). Callers treat
|
|
47
|
+
// null as "unbound" rather than throwing: a typo in a hand-edited config must cost the
|
|
48
|
+
// user that one shortcut, never the app.
|
|
49
|
+
export function parseKeyBinding(input: string): KeyBinding | null {
|
|
50
|
+
const parts = input.split("+").map((part) => part.trim());
|
|
51
|
+
if (parts.length === 0 || parts.some((part) => part === "")) return null;
|
|
52
|
+
const key = parts[parts.length - 1];
|
|
53
|
+
const binding: KeyBinding = { key, shift: false, alt: false, ctrl: false, meta: false };
|
|
54
|
+
for (const part of parts.slice(0, -1)) {
|
|
55
|
+
const flag = MODIFIERS[part.toLowerCase()];
|
|
56
|
+
if (!flag || binding[flag]) return null; // unknown, or named twice
|
|
57
|
+
binding[flag] = true;
|
|
58
|
+
}
|
|
59
|
+
// A lone modifier ("Shift") binds nothing usable.
|
|
60
|
+
return MODIFIERS[key.toLowerCase()] ? null : binding;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// The structural shape of a keydown a binding is matched against. A real KeyboardEvent
|
|
64
|
+
// satisfies it, and so does a plain test object — no DOM dependency.
|
|
65
|
+
export interface KeymapKeyEvent {
|
|
66
|
+
key: string;
|
|
67
|
+
shiftKey: boolean;
|
|
68
|
+
altKey: boolean;
|
|
69
|
+
ctrlKey: boolean;
|
|
70
|
+
metaKey: boolean;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Every modifier must match exactly, so a binding on "PageDown" does NOT fire for
|
|
74
|
+
// Shift+PageDown — that keystroke stays with the terminal (xterm's scrollback) unless the
|
|
75
|
+
// user binds it too.
|
|
76
|
+
export const matchesBinding = (binding: KeyBinding, e: KeymapKeyEvent): boolean =>
|
|
77
|
+
e.key === binding.key && e.shiftKey === binding.shift && e.altKey === binding.alt && e.ctrlKey === binding.ctrl && e.metaKey === binding.meta;
|
|
78
|
+
|
|
79
|
+
// The action this keydown is bound to, or null. Bindings that fail to parse are skipped.
|
|
80
|
+
export function actionForKey(keymap: Keymap, e: KeymapKeyEvent): KeymapAction | null {
|
|
81
|
+
for (const action of KEYMAP_ACTIONS) {
|
|
82
|
+
const raw = keymap[action];
|
|
83
|
+
if (raw === undefined) continue;
|
|
84
|
+
const binding = parseKeyBinding(raw);
|
|
85
|
+
if (binding && matchesBinding(binding, e)) return action;
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// What is wrong with one `keymap` entry. `fatal` separates a typo the user clearly meant to
|
|
91
|
+
// work (a binding we cannot parse — the shortcut would silently never fire) from an action
|
|
92
|
+
// name we simply do not know, which is what a config written for a NEWER version looks like
|
|
93
|
+
// and must stay loadable.
|
|
94
|
+
export interface KeymapProblem {
|
|
95
|
+
action: string;
|
|
96
|
+
binding: unknown;
|
|
97
|
+
reason: string;
|
|
98
|
+
fatal: boolean;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Report everything wrong with a `keymap`, for a caller that wants to tell the user instead
|
|
102
|
+
// of quietly dropping entries. Pure: the caller decides whether to warn, throw, or exit.
|
|
103
|
+
export function validateKeymap(input: unknown): KeymapProblem[] {
|
|
104
|
+
if (input === undefined || input === null) return [];
|
|
105
|
+
if (typeof input !== "object" || Array.isArray(input)) {
|
|
106
|
+
return [{ action: "keymap", binding: input, reason: "`keymap` must be an object of action -> key binding", fatal: true }];
|
|
107
|
+
}
|
|
108
|
+
const entries = Object.entries(input as Record<string, unknown>);
|
|
109
|
+
const bound = new Map<string, { action: KeymapAction; binding: string }[]>();
|
|
110
|
+
const problems = entries.flatMap(([action, binding]): KeymapProblem[] => {
|
|
111
|
+
if (!isKeymapAction(action)) {
|
|
112
|
+
return [{ action, binding, reason: `unknown action (known: ${KEYMAP_ACTIONS.join(", ")})`, fatal: false }];
|
|
113
|
+
}
|
|
114
|
+
if (typeof binding !== "string") return [{ action, binding, reason: "binding must be a string", fatal: true }];
|
|
115
|
+
const parsed = parseKeyBinding(binding);
|
|
116
|
+
if (parsed === null) {
|
|
117
|
+
return [{ action, binding, reason: 'unparseable key binding — expected e.g. "PageDown" or "Shift+PageUp"', fatal: true }];
|
|
118
|
+
}
|
|
119
|
+
// Grouped as PARSED, since "Shift+PageUp" and "shift+pageup" are one keystroke.
|
|
120
|
+
const key = canonicalBinding(parsed);
|
|
121
|
+
bound.set(key, [...(bound.get(key) ?? []), { action, binding }]);
|
|
122
|
+
return [];
|
|
123
|
+
});
|
|
124
|
+
return [...problems, ...duplicateWarnings(bound)];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Two actions on one keystroke: only one can fire, so the others silently never work.
|
|
128
|
+
//
|
|
129
|
+
// The winner is decided the way `actionForKey` decides it — first in KEYMAP_ACTIONS order —
|
|
130
|
+
// NOT by which came first in the config file. Reporting the config-order winner would name
|
|
131
|
+
// the wrong action whenever the two orders disagree, which is worse than not naming one.
|
|
132
|
+
function duplicateWarnings(bound: Map<string, { action: KeymapAction; binding: string }[]>): KeymapProblem[] {
|
|
133
|
+
return [...bound.values()].flatMap((claims) => {
|
|
134
|
+
if (claims.length < 2) return [];
|
|
135
|
+
const byDispatch = [...claims].sort((a, b) => KEYMAP_ACTIONS.indexOf(a.action) - KEYMAP_ACTIONS.indexOf(b.action));
|
|
136
|
+
const [winner, ...losers] = byDispatch;
|
|
137
|
+
return losers.map(({ action, binding }) => ({
|
|
138
|
+
action,
|
|
139
|
+
binding,
|
|
140
|
+
reason: `same keystroke as \`${winner.action}\` — only \`${winner.action}\` will fire`,
|
|
141
|
+
fatal: false,
|
|
142
|
+
}));
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// A binding's identity as a keystroke, for spotting two actions that claim the same one.
|
|
147
|
+
const canonicalBinding = (b: KeyBinding): string => `${b.shift ? "S" : ""}${b.alt ? "A" : ""}${b.ctrl ? "C" : ""}${b.meta ? "M" : ""}|${b.key}`;
|
|
148
|
+
|
|
149
|
+
// Keep only known actions bound to a parseable, non-empty string. Unknown keys and
|
|
150
|
+
// malformed bindings are dropped rather than rejecting the whole map, matching how the
|
|
151
|
+
// rest of the config treats one bad entry.
|
|
152
|
+
export function sanitizeKeymap(input: unknown): Keymap {
|
|
153
|
+
if (typeof input !== "object" || input === null || Array.isArray(input)) return {};
|
|
154
|
+
const entries = Object.entries(input as Record<string, unknown>).filter(
|
|
155
|
+
(entry): entry is [KeymapAction, string] => isKeymapAction(entry[0]) && typeof entry[1] === "string" && parseKeyBinding(entry[1]) !== null,
|
|
156
|
+
);
|
|
157
|
+
return Object.fromEntries(entries);
|
|
158
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// What the phone may ask the grid to start in a session's directory (#831). The host
|
|
2
|
+
// validates against this list and the grid turns each into a cell, so both sides read it.
|
|
3
|
+
//
|
|
4
|
+
// Deliberately NOT the configured `launchers` list: those are arbitrary user commands, and
|
|
5
|
+
// the phone picks from a fixed set the host can name. "shell" is the OS default shell, the
|
|
6
|
+
// same thing the header's new-terminal button opens.
|
|
7
|
+
export const LAUNCH_AGENTS = ["shell", "claude", "codex"] as const;
|
|
8
|
+
|
|
9
|
+
export type LaunchAgent = (typeof LAUNCH_AGENTS)[number];
|
|
10
|
+
|
|
11
|
+
export const isLaunchAgent = (value: unknown): value is LaunchAgent => LAUNCH_AGENTS.some((agent) => agent === value);
|
|
12
|
+
|
|
13
|
+
// The pub/sub channel the grid listens on for these requests. The grid is browser state, so
|
|
14
|
+
// the host cannot open a cell itself — it asks whichever tab is connected.
|
|
15
|
+
export const LAUNCH_TERMINAL_CHANNEL = "launch-terminal";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// The kinds of Web Push a session can raise. The server decides which one a hook warrants and
|
|
2
|
+
// the settings UI offers them as checkboxes, so the list is a value both sides read.
|
|
3
|
+
|
|
4
|
+
// Every kind that exists.
|
|
5
|
+
// finished — the turn ended, output is waiting to be reviewed.
|
|
6
|
+
// waiting — the agent is blocked on input (a permission prompt or a question), so answering
|
|
7
|
+
// from the phone unblocks real work. Fires once per prompt, which on a long task
|
|
8
|
+
// that asks repeatedly is what makes pushes feel frequent (#850).
|
|
9
|
+
export const PUSH_KINDS = ["finished", "waiting"] as const;
|
|
10
|
+
|
|
11
|
+
export type PushKind = (typeof PUSH_KINDS)[number];
|
|
12
|
+
|
|
13
|
+
// What a config with no `pushKinds` gets. Deliberately a SEPARATE list from PUSH_KINDS, not a
|
|
14
|
+
// copy of it: a kind added later must NOT start notifying people who never asked for it.
|
|
15
|
+
//
|
|
16
|
+
// That is exactly how this setting came to be needed — `waiting` was added in 36e9e72 and
|
|
17
|
+
// every user with push on began receiving it, with no way to decline. Add a new kind to
|
|
18
|
+
// PUSH_KINDS so it can be switched on, and leave it out of here so it stays opt-in.
|
|
19
|
+
export const DEFAULT_PUSH_KINDS: PushKind[] = ["finished", "waiting"];
|
|
20
|
+
|
|
21
|
+
export const isPushKind = (value: unknown): value is PushKind => PUSH_KINDS.some((kind) => kind === value);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SessionAgent } from "./sessionAgent.js";
|
|
2
|
+
|
|
3
|
+
// A phrase the user sends often, offered as a chip on the phone's terminal view (#830).
|
|
4
|
+
// The server validates and stores it; the settings UI edits it — both decide from this
|
|
5
|
+
// shape, so it is defined once here rather than mirrored on each side.
|
|
6
|
+
//
|
|
7
|
+
// `label` is the chip's face and `text` what it inserts, so a short chip can carry a long
|
|
8
|
+
// sentence. `agents` scopes it to session kinds; omitted means every kind.
|
|
9
|
+
export interface QuickCommand {
|
|
10
|
+
label: string;
|
|
11
|
+
text: string;
|
|
12
|
+
agents?: SessionAgent[];
|
|
13
|
+
}
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
// reconnecting — it died and is being re-subscribed with backoff (self-healing)
|
|
7
7
|
// offline — re-subscribing stopped helping, or nothing is connected at all;
|
|
8
8
|
// recovering needs a re-auth from the browser's parked session
|
|
9
|
+
|
|
10
|
+
import { isRecord } from "./isRecord.js";
|
|
9
11
|
export const RUNNER_HEALTH_STATES = ["online", "reconnecting", "offline"] as const;
|
|
10
12
|
export type RunnerHealthState = (typeof RUNNER_HEALTH_STATES)[number];
|
|
11
13
|
|
|
@@ -17,8 +19,6 @@ export interface RunnerHealth {
|
|
|
17
19
|
changedAt: number;
|
|
18
20
|
}
|
|
19
21
|
|
|
20
|
-
const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === "object" && value !== null;
|
|
21
|
-
|
|
22
22
|
export const isRunnerHealthState = (value: unknown): value is RunnerHealthState => RUNNER_HEALTH_STATES.some((state) => state === value);
|
|
23
23
|
|
|
24
24
|
/** Narrows a parsed HTTP payload. The client renders whatever this accepts, so a
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// What a remote-host session is running. The server decides it (from the PtyEntry, or from
|
|
2
|
+
// what tmux says is in the pane) and the settings UI offers it as the scope for a quick
|
|
3
|
+
// command, so the list of kinds is a value both sides read.
|
|
4
|
+
export const SESSION_AGENTS = ["claude", "codex", "shell"] as const;
|
|
5
|
+
|
|
6
|
+
// Null anywhere this appears means the host cannot tell — a session that outlived a restart
|
|
7
|
+
// exists only in tmux, and nothing recorded what launched it. That is distinct from "shell".
|
|
8
|
+
export type SessionAgent = (typeof SESSION_AGENTS)[number];
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
// (composables/terminalFilePathLinkProvider.ts, which also covers .txt).
|
|
10
10
|
// Each side adds its own extras next to its import; anything that belongs to only one of
|
|
11
11
|
// them does NOT belong here.
|
|
12
|
+
//
|
|
13
|
+
// Adding an extension changes what a clicked path DOES, which is documented — see the DOCS
|
|
14
|
+
// block in composables/terminalFilePathLinkProvider.ts for the three files to update (#834).
|
|
12
15
|
export const SOURCE_CODE_EXTENSIONS = [
|
|
13
16
|
".ts",
|
|
14
17
|
".tsx",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e}from"./chunk-Y2CYZVJY-Bdt8pFDJ-DsF7k-Jl.js";import{f as t}from"./src-Brzfja-q-ch1VcTiG.js";import{_ as n,g as r}from"./mermaid-parser.core-DxEa8E3F-
|
|
1
|
+
import{n as e}from"./chunk-Y2CYZVJY-Bdt8pFDJ-DsF7k-Jl.js";import{f as t}from"./src-Brzfja-q-ch1VcTiG.js";import{_ as n,g as r}from"./mermaid-parser.core-DxEa8E3F-D1ef8DoR.js";import"./chunk-WYO6CB5R-BlzOfotS-qckQG4MR.js";import"./chunk-VAUOI2AC-CS9QJ4yz-DviHNtmN.js";import{n as i,r as a,t as o}from"./chunk-MOJQB5TN-CMZRaeqt-CG59XkeN.js";import{t as s}from"./chunk-JWPE2WC7-BQ3zXr2k-DSd3Ct95.js";var c=r().RailroadAbnf.parser.LangiumParser,l=e(e=>{let t=e.alternatives.map(u);return t.length===1?t[0]:{type:`choice`,alternatives:t}},`transformAlternation`),u=e(e=>{let t=e.elements.map(f);return t.length===1?t[0]:{type:`sequence`,elements:t}},`transformConcatenation`),d=e(e=>{if(e.includes(`*`)){let[t,n]=e.split(`*`);return{min:t?parseInt(t,10):0,max:n?parseInt(n,10):1/0}}let t=parseInt(e,10);return{min:t,max:t}},`parseRepeat`),f=e(e=>{let t=p(e.primary);if(!e.repeat)return t;let{min:n,max:r}=d(e.repeat);return n===0&&r===1?{type:`optional`,element:t}:{type:`repetition`,element:t,min:n,max:r}},`transformElement`),p=e(e=>{switch(e.$type){case`AbnfStringLiteral`:return{type:`terminal`,value:e.value};case`AbnfNumVal`:return{type:`terminal`,value:e.value};case`AbnfRuleName`:return{type:`nonterminal`,name:e.name};case`AbnfGroup`:return l(e.element);case`AbnfOptionalGroup`:return{type:`optional`,element:l(e.element)};default:throw Error(`Unsupported ABNF primary node: ${e.$type}`)}},`transformPrimary`),m=e(e=>({name:e.name,definition:l(e.definition)}),`transformRule`),h=e(e=>{s(e,a),e.title&&a.setTitle(e.title),e.rules.map(e=>a.addRule(m(e)))},`populateDb`),g={parser:{parse:e(e=>{a.clear(),t.debug(`[ABNF Parser] Starting Langium parse`);let r=c.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new n(r);let i=r.value;t.debug(`[ABNF Parser] Parsed rules:`,i.rules.length),h(i),t.debug(`[ABNF Parser] Parse complete`)},`parse`),parser:{yy:a}},db:a,renderer:o,styles:i};export{g as diagram};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{f as e}from"./mermaid-parser.core-DxEa8E3F-D1ef8DoR.js";export{e as createArchitectureServices};
|