mulmoterminal 2.4.0 → 2.5.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 +31 -4
- package/common/codeBlocks.ts +88 -0
- package/common/prPhase.ts +65 -0
- package/common/rateLimits.ts +37 -0
- package/common/terminalScrollSpeed.ts +31 -0
- package/common/workComment.ts +44 -0
- package/dist/assets/{abnfDiagram-VRR7QNED-RjiYivmv-CqId6L6N.js → abnfDiagram-VRR7QNED-RjiYivmv-BHMJcX-D.js} +1 -1
- package/dist/assets/architecture-TIHT7OUA-0NsD2xW1-BQjHKl14.js +1 -0
- package/dist/assets/{architectureDiagram-ZJ3FMSHR-3nWA91tG-CWhhLcfM.js → architectureDiagram-ZJ3FMSHR-3nWA91tG-izYrzJUx.js} +1 -1
- package/dist/assets/{blockDiagram-677ZJIJ3-BPuAJQRW-89yfYblY.js → blockDiagram-677ZJIJ3-BPuAJQRW-BD7LRmIw.js} +1 -1
- package/dist/assets/{c4Diagram-LMCZKHZV-C0LAqQso-Cq70UauP.js → c4Diagram-LMCZKHZV-C0LAqQso-daMFkQYF.js} +1 -1
- package/dist/assets/channel-7wUqSdoX-i1Oxds2I.js +1 -0
- package/dist/assets/{chunk-32BRIVSS-BRrYpgtb-Bj5FdJ3A.js → chunk-32BRIVSS-BRrYpgtb-Lp6IvDwQ.js} +1 -1
- package/dist/assets/{chunk-52WLFC77-BJ-ss3Xr-Cf6wft_5.js → chunk-52WLFC77-BJ-ss3Xr-CIA8l6cL.js} +1 -1
- package/dist/assets/{chunk-C7G6YPKG-BZEucKEL-CLA5COUu.js → chunk-C7G6YPKG-BZEucKEL-DBfTQdYh.js} +1 -1
- package/dist/assets/{chunk-EX3LRPZG-DLS6FBN1-ChsLH6_y.js → chunk-EX3LRPZG-DLS6FBN1-CmqVBBJi.js} +1 -1
- package/dist/assets/{chunk-FWX5IMBZ-DHLSFw1H-BIvnIbPU.js → chunk-FWX5IMBZ-DHLSFw1H-Br6MRw8l.js} +2 -2
- package/dist/assets/{chunk-HOUHSVGY-Bhlt8hXJ-Ck-Dxtw-.js → chunk-HOUHSVGY-Bhlt8hXJ-BDsryiai.js} +1 -1
- package/dist/assets/{chunk-ICXQ74PX-DwgHBX_g-CSlWVcWs.js → chunk-ICXQ74PX-DwgHBX_g-Dtq6hkL5.js} +1 -1
- package/dist/assets/{chunk-MOJQB5TN-CMZRaeqt-CYfpp2V7.js → chunk-MOJQB5TN-CMZRaeqt-xCxis4AD.js} +1 -1
- package/dist/assets/{chunk-OGEWGWER-8Qy4a8b5-DPIlRee4.js → chunk-OGEWGWER-8Qy4a8b5-CEKkrNHl.js} +1 -1
- package/dist/assets/{chunk-PUDLZKDR-DcrWQRYh-J86vFVAN.js → chunk-PUDLZKDR-DcrWQRYh-CNZIIdNp.js} +1 -1
- package/dist/assets/{chunk-Q4XR5HBZ-ZXVGkG8Z-D-40tbjk.js → chunk-Q4XR5HBZ-ZXVGkG8Z-BfnUuIf2.js} +1 -1
- package/dist/assets/{chunk-V7JOEXUC-DmGdheTX-7gJmS-F9.js → chunk-V7JOEXUC-DmGdheTX-Xhz9vrZQ.js} +1 -1
- package/dist/assets/{chunk-VAUOI2AC-CS9QJ4yz-DFjCsQc6.js → chunk-VAUOI2AC-CS9QJ4yz-CDhJXpTw.js} +1 -1
- package/dist/assets/{chunk-VR4S4FIN-C6a91eNY-Ba059rhB.js → chunk-VR4S4FIN-C6a91eNY-C6K2Be2R.js} +1 -1
- package/dist/assets/{chunk-WYO6CB5R-BlzOfotS-CTDodsnw.js → chunk-WYO6CB5R-BlzOfotS-C6J_J8TJ.js} +1 -1
- package/dist/assets/{chunk-ZGVPDNZ5-BYwxNFTK-CMF9FJ7K.js → chunk-ZGVPDNZ5-BYwxNFTK-DM76gWAP.js} +1 -1
- package/dist/assets/classDiagram-OUVF2IWQ-BYlRi6YQ-w-oQvzN_.js +1 -0
- package/dist/assets/classDiagram-v2-EOCWNBFH-BCHaYHxj-w-oQvzN_.js +1 -0
- package/dist/assets/cynefin-VYW2F7L2-CGYr2F6h-BbR1aAzz.js +1 -0
- package/dist/assets/{cynefinDiagram-TSTJHNR4-DHA9iPo--BUwfErNr.js → cynefinDiagram-TSTJHNR4-DHA9iPo--CygDupfu.js} +1 -1
- package/dist/assets/{dagre-VKFMJZFB--oJKqXBZ-FEoLipSZ.js → dagre-VKFMJZFB--oJKqXBZ-DKaBHoMD.js} +1 -1
- package/dist/assets/{diagram-FQU43EPY-CcJCB9bG-DwH2nER4.js → diagram-FQU43EPY-CcJCB9bG-BCdZM1XW.js} +1 -1
- package/dist/assets/{diagram-G47NLZAW-C_o-WGG1-BuSqgofP.js → diagram-G47NLZAW-C_o-WGG1-E50OCsch.js} +1 -1
- package/dist/assets/{diagram-NH7WQ7WH-CXJCYvY--CoMsubYQ.js → diagram-NH7WQ7WH-CXJCYvY--CmG9YdUu.js} +1 -1
- package/dist/assets/{diagram-OA4YK3LP-BMzeJ87A-Ds_49Q4I.js → diagram-OA4YK3LP-BMzeJ87A-CxPVNELp.js} +1 -1
- package/dist/assets/{diagram-WEI45ONY-D_93NKqo-DeDTEgI-.js → diagram-WEI45ONY-D_93NKqo-lP3U0GxX.js} +1 -1
- package/dist/assets/{ebnfDiagram-CCIWWBDH-CVai1Ii9-B84fKP1h.js → ebnfDiagram-CCIWWBDH-CVai1Ii9-DEA-ZvYv.js} +1 -1
- package/dist/assets/{erDiagram-Q63AITRT-CiABnA0s-QGRolPYH.js → erDiagram-Q63AITRT-CiABnA0s-uFB2JxeG.js} +1 -1
- package/dist/assets/eventmodeling-45OFAUF4-CWmqhm3a-DXHpv7ri.js +1 -0
- package/dist/assets/flowDiagram-23GEKE2U-DKSB5AiY-BAXt5ZcK.js +1 -0
- package/dist/assets/{ganttDiagram-NO4QXBWP-DQZvdFo1-BBXcle76.js → ganttDiagram-NO4QXBWP-DQZvdFo1-CnuwHsCh.js} +1 -1
- package/dist/assets/gitGraph-TEB2WS4Q-BR9qPwdN-C5VWobaj.js +1 -0
- package/dist/assets/{gitGraphDiagram-IHSO6WYX-C2ovBouh-BxGBV6Hj.js → gitGraphDiagram-IHSO6WYX-C2ovBouh-DMwS2Tlh.js} +1 -1
- package/dist/assets/index-BL1ivfaK.css +1 -0
- package/dist/assets/{index-_XRnaxAj.js → index-CIUVcLpS.js} +201 -199
- package/dist/assets/info-DKCQHKI2-Dplx5kMp-dN3WlTdS.js +1 -0
- package/dist/assets/{infoDiagram-FWYZ7A6U-7UnoB5AP-DCqG4iB9.js → infoDiagram-FWYZ7A6U-7UnoB5AP-D1UXK3im.js} +1 -1
- package/dist/assets/{ishikawaDiagram-FXEZZL3T-ByUDM_N2-Dkzr63Uo.js → ishikawaDiagram-FXEZZL3T-ByUDM_N2-eU53AoG2.js} +1 -1
- package/dist/assets/{journeyDiagram-5HDEW3XC-c5xIah9o-3K64pBTH.js → journeyDiagram-5HDEW3XC-c5xIah9o-BQNy69_H.js} +1 -1
- package/dist/assets/{kanban-definition-HUTT4EX6-Cnt6loYD-B0U7Mkg9.js → kanban-definition-HUTT4EX6-Cnt6loYD-C3Ya6-uo.js} +1 -1
- package/dist/assets/{lib-rFkGOb1O.js → lib-XZe86Xbo.js} +1 -1
- package/dist/assets/{line-D7ziSjKi-D25izB30.js → line-D7ziSjKi-DhimY3vn.js} +1 -1
- package/dist/assets/{marp-79PhoGbq.js → marp-DE0BYdnE.js} +1 -1
- package/dist/assets/{mermaid-parser.core-DxEa8E3F-BzSqp82H.js → mermaid-parser.core-DxEa8E3F-C43vNJPp.js} +2 -2
- package/dist/assets/{mermaid.core-V0OYwIz3-0p62b-zk.js → mermaid.core-V0OYwIz3-v8Oxyjlo.js} +3 -3
- package/dist/assets/{mindmap-definition-LN4V7U3C-CexN3O6L-BzFX06oy.js → mindmap-definition-LN4V7U3C-CexN3O6L-D3QMGDaH.js} +1 -1
- package/dist/assets/packet-7NZHBO7P-CQI3flND-0aWqmW17.js +1 -0
- package/dist/assets/{pegDiagram-2B236MQR-BiM4G0cW-BMRJsJZN.js → pegDiagram-2B236MQR-BiM4G0cW-Bt6FgrWE.js} +1 -1
- package/dist/assets/pie-RZYD4A2V-CnmKE2Wa-BuudBowH.js +1 -0
- package/dist/assets/{pieDiagram-ENE6RG2P-B_fBBS-2-Bl2zNUvh.js → pieDiagram-ENE6RG2P-B_fBBS-2-CEmdDz9t.js} +1 -1
- package/dist/assets/{quadrantDiagram-ABIIQ3AL-BigGVxCR-BzQ392_S.js → quadrantDiagram-ABIIQ3AL-BigGVxCR-4kYUj0VK.js} +1 -1
- package/dist/assets/radar-I7S5WNFK-us6x-Z9R-CoI_TbnK.js +1 -0
- package/dist/assets/railroad-3IZDKUUU-BKdz6pdN-D-Aq_3zs.js +1 -0
- package/dist/assets/railroad-abnf-AHOZXSZD-m_CSSIfd-CYcDcmr_.js +1 -0
- package/dist/assets/railroad-ebnf-EBAXGLYW-Nu3nAG7C-D-PAMLpZ.js +1 -0
- package/dist/assets/railroad-peg-LSFZ7HO6--Y3mD4R_-CAkKdvRU.js +1 -0
- package/dist/assets/{railroadDiagram-RFXS5EU6--lNIlG64-D-Yn0Dpg.js → railroadDiagram-RFXS5EU6--lNIlG64-BS12t8Yr.js} +1 -1
- package/dist/assets/{requirementDiagram-TGXJPOKE-D-B0Y5Wu-DIYnBCWZ.js → requirementDiagram-TGXJPOKE-D-B0Y5Wu-B1_SLwFo.js} +1 -1
- package/dist/assets/{sankeyDiagram-HTMAVEWB-DlutaXDv-Dsr5VJzO.js → sankeyDiagram-HTMAVEWB-DlutaXDv-CFF-xYsd.js} +1 -1
- package/dist/assets/{sequenceDiagram-DBY2YBRQ-BOLgtggH-mR1rIlJQ.js → sequenceDiagram-DBY2YBRQ-BOLgtggH-DJXV6qUI.js} +1 -1
- package/dist/assets/{stateDiagram-2N3HPSRC-jf_dXUEU-Cq2A7Udg.js → stateDiagram-2N3HPSRC-jf_dXUEU-BD6GsGtH.js} +1 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-CuzgCL2v-CGUWQotU.js +1 -0
- package/dist/assets/{swimlanes-5IMT3BWC-CYjtALQH-CtgZ4USf.js → swimlanes-5IMT3BWC-CYjtALQH-CHqwZOyR.js} +1 -1
- package/dist/assets/swimlanesDiagram-G3AALYLV-3bmSBtKs-YHo-FlZ7.js +8 -0
- package/dist/assets/{timeline-definition-FHXFAJF6-DYJ4oUm8-CpuMCUFK.js → timeline-definition-FHXFAJF6-DYJ4oUm8-Dsee2TjT.js} +1 -1
- package/dist/assets/treeView-QDETBFTQ-D27HWG6Q-XRfUQQF3.js +1 -0
- package/dist/assets/treemap-6X3UGDF4-CmGIU3H5-DV-AlqRD.js +1 -0
- package/dist/assets/{vennDiagram-L72KCM5P-CdKAHoek-B-FSFzf3.js → vennDiagram-L72KCM5P-CdKAHoek-d0nAypXX.js} +1 -1
- package/dist/assets/wardley-OPB4EBWU-CbHWsb2k-Dvuabdt5.js +1 -0
- package/dist/assets/{wardleyDiagram-EHGQE667-BF6c4_CW-CB01oT0C.js → wardleyDiagram-EHGQE667-BF6c4_CW-BStK630L.js} +1 -1
- package/dist/assets/{xychartDiagram-FW5EYKEG-CGiKngj7-D1vr6bQ-.js → xychartDiagram-FW5EYKEG-CGiKngj7--qXz8KqF.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +4 -4
- package/server/agents/claude-args.ts +10 -1
- package/server/agents/codex-rate-limits.spec.ts +74 -0
- package/server/agents/codex-rate-limits.ts +94 -0
- package/server/agents/codex-rollout.ts +79 -0
- package/server/agents/pr-clone-prompt.ts +30 -0
- package/server/agents/rate-limit-persist.ts +59 -0
- package/server/agents/rate-limit-probe.spec.ts +66 -0
- package/server/agents/rate-limit-probe.ts +114 -0
- package/server/agents/rate-limit-routes.ts +66 -0
- package/server/agents/rate-limit-store.spec.ts +91 -0
- package/server/agents/rate-limit-store.ts +73 -0
- package/server/agents/statusline.spec.ts +66 -0
- package/server/agents/statusline.ts +48 -0
- package/server/cli-init.ts +2 -2
- package/server/config/app-config.ts +60 -4
- package/server/config/config-routes.ts +10 -1
- package/server/config/config-schema.ts +1 -1
- package/server/config/header-context.ts +9 -8
- package/server/files/pick-file.ts +5 -3
- package/server/files/win-folder-dialog.ts +112 -0
- package/server/git/gitRemote.ts +14 -31
- package/server/git/pr-footer.ts +17 -4
- package/server/git/prPhase.ts +40 -12
- package/server/git/remote-ref.ts +50 -0
- package/server/git/repo-root-sync.ts +55 -0
- package/server/git/work-comment.ts +126 -0
- package/server/git/worktree-pr.ts +1 -0
- package/server/index.ts +32 -0
- package/server/infra/tmux.ts +40 -0
- package/server/routes/app-routes.ts +5 -0
- package/server/routes/dir-routes.ts +54 -3
- package/server/session/last-turn.ts +4 -0
- package/server/session/session-reads.ts +17 -1
- package/server/session/spawn-claude.ts +39 -16
- package/dist/assets/architecture-TIHT7OUA-0NsD2xW1-CfmmIOkB.js +0 -1
- package/dist/assets/channel-7wUqSdoX-Bc9EmVWS.js +0 -1
- package/dist/assets/classDiagram-OUVF2IWQ-BYlRi6YQ-Dk_90vZD.js +0 -1
- package/dist/assets/classDiagram-v2-EOCWNBFH-BCHaYHxj-Dk_90vZD.js +0 -1
- package/dist/assets/cynefin-VYW2F7L2-CGYr2F6h-B1MlaJhj.js +0 -1
- package/dist/assets/eventmodeling-45OFAUF4-CWmqhm3a-C0eB3veQ.js +0 -1
- package/dist/assets/flowDiagram-23GEKE2U-DKSB5AiY-BAfA7eXR.js +0 -1
- package/dist/assets/gitGraph-TEB2WS4Q-BR9qPwdN-CotjI6SA.js +0 -1
- package/dist/assets/index-B7LbAi10.css +0 -1
- package/dist/assets/info-DKCQHKI2-Dplx5kMp-DKHWzOor.js +0 -1
- package/dist/assets/packet-7NZHBO7P-CQI3flND-DyAt_QAV.js +0 -1
- package/dist/assets/pie-RZYD4A2V-CnmKE2Wa-nlXXCSHg.js +0 -1
- package/dist/assets/radar-I7S5WNFK-us6x-Z9R-Dnn5SKpT.js +0 -1
- package/dist/assets/railroad-3IZDKUUU-BKdz6pdN-DjF0R5Zr.js +0 -1
- package/dist/assets/railroad-abnf-AHOZXSZD-m_CSSIfd-GH7jGfhq.js +0 -1
- package/dist/assets/railroad-ebnf-EBAXGLYW-Nu3nAG7C-CH8TSRFp.js +0 -1
- package/dist/assets/railroad-peg-LSFZ7HO6--Y3mD4R_-QfsXYBPl.js +0 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-CuzgCL2v-D7ipapeO.js +0 -1
- package/dist/assets/swimlanesDiagram-G3AALYLV-3bmSBtKs-D1yGYNNh.js +0 -8
- package/dist/assets/treeView-QDETBFTQ-D27HWG6Q-nxCnsDvO.js +0 -1
- package/dist/assets/treemap-6X3UGDF4-CmGIU3H5-7UfnOcZW.js +0 -1
- package/dist/assets/wardley-OPB4EBWU-CbHWsb2k-DHZIQf1x.js +0 -1
package/README.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
**Run a whole team of coding agents from your browser — and actually keep up with them.**
|
|
4
4
|
|
|
5
|
+
A **browser terminal** for **parallel AI coding agents**: several **Claude Code** and **Codex**
|
|
6
|
+
sessions side by side, each in its own cell, with the one that needs you marked in colour. Vibe
|
|
7
|
+
coding with a single agent needs nothing but a shell — this is for when you run several and lose
|
|
8
|
+
track of which is waiting. Sessions survive a reload (tmux), work isolates in **git worktrees**,
|
|
9
|
+
and a **phone push** reaches you when a turn finishes.
|
|
10
|
+
|
|
5
11
|
### 📖 Documentation — **[receptron.github.io/mulmoterminal](https://receptron.github.io/mulmoterminal/)**
|
|
6
12
|
|
|
7
13
|
- **User guide:** [English](https://receptron.github.io/mulmoterminal/guide/en/) — the grid
|
|
@@ -21,6 +27,13 @@ ping to your phone when a task finishes. One `npx` command, no Electron, no conf
|
|
|
21
27
|
npx mulmoterminal # starts on http://localhost:34567 and opens your browser
|
|
22
28
|
```
|
|
23
29
|
|
|
30
|
+
> **Something looks wrong?** Type `/mulmoterminal-bug-report` in any MulmoTerminal session. The
|
|
31
|
+
> bundled skill hears the symptom out, checks your **real** config, schema and version to see
|
|
32
|
+
> whether the behaviour is configuration or by design, searches the existing issues — and only
|
|
33
|
+
> helps you file one if none of that explains it, with the environment collected and secrets
|
|
34
|
+
> masked. Getting you unstuck is the goal; an issue is what is left when the first three steps
|
|
35
|
+
> fail.
|
|
36
|
+
|
|
24
37
|

|
|
25
38
|
|
|
26
39
|
*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.*
|
|
@@ -455,7 +468,7 @@ The Settings modal (⚙) persists per-user UI choices to `~/.mulmoterminal/confi
|
|
|
455
468
|
|
|
456
469
|

|
|
457
470
|
|
|
458
|
-
*Open it from the ⚙ button in the toolbar. Pick a **theme**, set the **terminal font size**, set a custom **attention sound**, list the repos the cross-repo **PRs & Issues** view should aggregate, add **launch commands** for grid cells, and register your own **MCP servers** — no need to hand-edit the config file. Note that **theme
|
|
471
|
+
*Open it from the ⚙ button in the toolbar. Pick a **theme**, set the **terminal font size** and **scroll speed**, set a custom **attention sound**, list the repos the cross-repo **PRs & Issues** view should aggregate, add **launch commands** for grid cells, and register your own **MCP servers** — no need to hand-edit the config file. Note that **theme, font size and scroll speed are stored per browser** (they're display preferences, so a phone and a desktop keep their own); the rest live in `~/.mulmoterminal/config.json` and are shared by every client.*
|
|
459
472
|
|
|
460
473
|
| Field | Meaning |
|
|
461
474
|
| ------------ | ------- |
|
|
@@ -468,16 +481,24 @@ The Settings modal (⚙) persists per-user UI choices to `~/.mulmoterminal/confi
|
|
|
468
481
|
| `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. |
|
|
469
482
|
| `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. |
|
|
470
483
|
| `buttons` | Header action buttons — see [Header buttons](#header-buttons). Omit to keep the defaults; set to replace them. |
|
|
471
|
-
| `chips` | Header info chips. Omit to keep the default set; `[]` hides all built-ins. |
|
|
484
|
+
| `chips` | Header info chips (`dir` / `git` / `work` / `diff` / `ctx` / `usage` / `status` / `tools`, or custom text). Omit to keep the default set; `[]` hides all built-ins. `work` shows which PR / issue the cell is on (`#977 → #966`) and clears itself when the PR merges — see the [Configuration guide](https://receptron.github.io/mulmoterminal/guide/en/config.html#work-chip). |
|
|
472
485
|
| `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. |
|
|
473
486
|
| `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. |
|
|
474
487
|
| `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). |
|
|
475
488
|
| `worklogIntervalHours` | Worklog cadence in hours (default `6`, clamped to `1`–`168`). |
|
|
476
489
|
| `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). |
|
|
477
490
|
| `copyOnSelect` | `true` puts a **mouse selection on the clipboard the moment it settles**, with no key pressed (the PuTTY / iTerm2 behaviour). **Off by default** — it changes the clipboard when you may only have meant to highlight something. No Settings UI: edit the file and reload the tab. Composes with the `copy` keymap action rather than replacing it. Over plain `http://` the browser gives a page no clipboard access, so a fallback asks xterm to copy instead; see the [Configuration guide](https://receptron.github.io/mulmoterminal/guide/en/config.html#copy-on-select). |
|
|
478
|
-
| `
|
|
491
|
+
| `issueWorkComments` | Let a cell **comment on the issue it is working on**: once when it starts, and again when its PR merges (closing the issue if GitHub has not already). The comment names the working **directory** it happened in — the folder name only, never the path — so a reader can tell which clone. **Off by default**; it writes to GitHub, often on somebody else's issue. Needs `gh` logged in. See the [Configuration guide](https://receptron.github.io/mulmoterminal/guide/en/config.html#issue-work-comments). |
|
|
492
|
+
| `prWorkdirFooter` | Ends a PR body with `work in <clone>` — the directory name of the clone the work happened in, so a PR says which of several side-by-side checkouts produced it. Applies to **both** paths that open PRs here: **⧉ Open PR** appends it to the PR it creates, and every Claude session is told to end the bodies it writes with the same line (the name is resolved by the server, so a session inside a managed worktree still names the main checkout). **On by default**; set `false` to opt out — read per PR and per session spawn, so no restart is needed (there is no Settings control for it). Appending is idempotent: an existing PR never gets a second copy. |
|
|
479
493
|
| `fontFamily` | The **terminal font** every session renders in — a CSS font-family stack, e.g. `"'Cica', 'MS Gothic', monospace"`. No Settings UI: edit the file, then **restart** (this config is read once at startup). Unset uses the built-in stack (JetBrains Mono / Fira Code / Menlo / Consolas, then CJK faces for Japanese, Korean and Chinese). Unlike the per-browser font **size**, this is one value for the whole host — it names fonts, and which fonts exist is a property of the machine. A directory can override it. See the [Configuration guide](https://receptron.github.io/mulmoterminal/guide/en/config.html#font-family). |
|
|
480
494
|
|
|
495
|
+
Every MulmoTerminal on the machine shares this one file, so an older build could save over a key a
|
|
496
|
+
newer one wrote. It doesn't: a **top-level key this version doesn't recognise is written back
|
|
497
|
+
untouched**, which is what makes running two versions side by side — or downgrading for a while —
|
|
498
|
+
safe. A mistyped key survives on the same rule, which is deliberate: a line you can still see is
|
|
499
|
+
easier to debug than one that silently vanished. See the
|
|
500
|
+
[Configuration guide](https://receptron.github.io/mulmoterminal/guide/en/config.html#unknown-keys).
|
|
501
|
+
|
|
481
502
|
#### Header buttons
|
|
482
503
|
|
|
483
504
|
Each terminal header shows configurable **action buttons**. Omitting `buttons` (globally or per-dir)
|
|
@@ -862,7 +883,9 @@ Each grid cell's header shows two badges for its session, refreshed when a turn
|
|
|
862
883
|
current-gen Opus / Sonnet / Fable / Mythos, **200k** otherwise). A session running on a
|
|
863
884
|
[provider model](#agents-claude--codex) shows that model's name and its published window
|
|
864
885
|
(`Kimi K2.7 Code · ctx 12%`); a model in neither list keeps the label and hides the %,
|
|
865
|
-
since the window is never guessed.
|
|
886
|
+
since the window is never guessed. A reading **past 100%** shows `ctx ?` instead of the
|
|
887
|
+
number: the window is a hard cap, so an impossible percentage means the built-in window
|
|
888
|
+
table is out of date for that model rather than that the session is over-full.
|
|
866
889
|
- **Token badge** — `⇡<in> ⇣<out>`: cumulative input (fresh + cache-read + cache-creation)
|
|
867
890
|
and output tokens for the session, k/M-formatted, with a full breakdown in the tooltip.
|
|
868
891
|
|
|
@@ -958,6 +981,10 @@ Favorited collections get their own toolbar buttons.
|
|
|
958
981
|
**Option** is treated as Meta so Claude's Alt-key bindings work. If your Claude Code is
|
|
959
982
|
rebound so Enter and Shift+Enter behave backwards, flip them with
|
|
960
983
|
[`terminalSubmit`](https://receptron.github.io/mulmoterminal/guide/en/config.html#terminal-submit).
|
|
984
|
+
- **Scroll speed** — one wheel notch or trackpad swipe moves the terminal the same distance
|
|
985
|
+
whether you're reading a shell's scrollback or a full-screen app like Claude Code. If a
|
|
986
|
+
two-finger scroll on a Mac trackpad flies past what you were reading, turn **terminal scroll
|
|
987
|
+
speed** down in Settings (0.25×–3×, per browser — it's a property of the pointing device).
|
|
961
988
|
- **No accidental page zoom** — `Ctrl`+wheel and a trackpad pinch would rescale the whole
|
|
962
989
|
page and drag the layout and the terminal's fit along with it, so both are ignored.
|
|
963
990
|
Keyboard zoom (`Cmd`/`Ctrl` `+` / `-`) still works when you mean it, and a phone's finger
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Fenced code blocks in a markdown string (#865).
|
|
2
|
+
//
|
|
3
|
+
// The point is to hand another app EXACTLY what the agent wrote. Selecting the same text off
|
|
4
|
+
// the terminal picks up the leading padding xterm draws with it, which is what breaks a paste
|
|
5
|
+
// into Discord or Slack — so the source here is the agent's own transcript markdown, never the
|
|
6
|
+
// screen.
|
|
7
|
+
//
|
|
8
|
+
// Deliberately not a markdown parser. It answers one question — where do the fences sit — and
|
|
9
|
+
// the cases below are the ones a real reply produces:
|
|
10
|
+
//
|
|
11
|
+
// - ``` and ~~~ are both fences, and a run of 3+ of the same character opens one
|
|
12
|
+
// - a fence closes only on the SAME character, at least as long as the opener. That is what
|
|
13
|
+
// lets a shell snippet contain ``` inside a ~~~~ block without cutting it short
|
|
14
|
+
// - an info string (```ts, ```bash) names the language; the rest of that line is dropped
|
|
15
|
+
// - a fence may be indented up to 3 spaces, per CommonMark
|
|
16
|
+
// - an UNCLOSED fence still yields a block, running to the end of the text. An agent whose
|
|
17
|
+
// output was cut off mid-block is exactly when someone reaches for this, and refusing to
|
|
18
|
+
// return anything would read as "there is no code here"
|
|
19
|
+
|
|
20
|
+
export interface FencedBlock {
|
|
21
|
+
/** The info string's first word, lower-cased — `ts`, `bash` — or null when absent. */
|
|
22
|
+
lang: string | null;
|
|
23
|
+
/** The block's contents, verbatim apart from the trailing newline. */
|
|
24
|
+
body: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface Fence {
|
|
28
|
+
/** The run itself, so its LENGTH can gate what is allowed to close it. */
|
|
29
|
+
run: string;
|
|
30
|
+
/** Everything after the run — the info string on an opener, empty on a closer. */
|
|
31
|
+
info: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Scanned by hand rather than with a pattern: the obvious regex (` {0,3}(`{3,}|~{3,})(.*)`)
|
|
35
|
+
// nests two quantifiers under an alternation, which the linter flags for super-linear
|
|
36
|
+
// backtracking. This walk is plainly one pass, and reads closer to the rule it implements.
|
|
37
|
+
// Up to three leading spaces are allowed (CommonMark); a fourth makes it an indented code
|
|
38
|
+
// block, not a fence.
|
|
39
|
+
function fenceOf(line: string): Fence | null {
|
|
40
|
+
let i = 0;
|
|
41
|
+
while (i < 3 && line[i] === " ") i++;
|
|
42
|
+
const char = line[i];
|
|
43
|
+
if (char !== "`" && char !== "~") return null;
|
|
44
|
+
let length = 0;
|
|
45
|
+
while (line[i + length] === char) length++;
|
|
46
|
+
if (length < 3) return null;
|
|
47
|
+
return { run: line.slice(i, i + length), info: line.slice(i + length) };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// A closing fence is the same character, at least as long, with nothing after it. Compared
|
|
51
|
+
// after `.trim()` because trailing whitespace is invisible and would otherwise leave the block
|
|
52
|
+
// silently unclosed.
|
|
53
|
+
const closes = (line: string, opener: string): boolean => {
|
|
54
|
+
const fence = fenceOf(line);
|
|
55
|
+
return !!fence && fence.run[0] === opener[0] && fence.run.length >= opener.length && fence.info.trim() === "";
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export function fencedBlocks(markdown: string): FencedBlock[] {
|
|
59
|
+
const lines = markdown.split("\n");
|
|
60
|
+
const blocks: FencedBlock[] = [];
|
|
61
|
+
let i = 0;
|
|
62
|
+
while (i < lines.length) {
|
|
63
|
+
const open = fenceOf(lines[i]);
|
|
64
|
+
if (!open) {
|
|
65
|
+
i++;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const fence = open.run;
|
|
69
|
+
const info = open.info.trim();
|
|
70
|
+
const body: string[] = [];
|
|
71
|
+
i++;
|
|
72
|
+
while (i < lines.length && !closes(lines[i], fence)) {
|
|
73
|
+
body.push(lines[i]);
|
|
74
|
+
i++;
|
|
75
|
+
}
|
|
76
|
+
i++; // step over the closing fence (or past the end, for an unclosed block)
|
|
77
|
+
blocks.push({ lang: info ? (info.split(/\s+/)[0].toLowerCase() ?? null) : null, body: body.join("\n") });
|
|
78
|
+
}
|
|
79
|
+
return blocks;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The block a reader means by "the code you just gave me": the last one. Null when the reply
|
|
83
|
+
* has no fenced block, or when the only ones are empty — an empty clipboard would look
|
|
84
|
+
* exactly like a copy that silently failed. */
|
|
85
|
+
export function lastFencedBlock(markdown: string): FencedBlock | null {
|
|
86
|
+
const withBody = fencedBlocks(markdown).filter((b) => b.body.trim() !== "");
|
|
87
|
+
return withBody.length ? withBody[withBody.length - 1] : null;
|
|
88
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// What a cell is working on, as it travels between the two sides: the branch's PR, its phase in
|
|
2
|
+
// the review loop, and the issue that PR closes. Shared because BOTH sides decide from it — the
|
|
3
|
+
// server derives it from `gh`, the client decides what the header chip shows — and it used to be
|
|
4
|
+
// two declarations with a "keep them in sync" comment on one of them (src/components/rosterPhase.ts).
|
|
5
|
+
|
|
6
|
+
// Ordered roughly along the lifecycle so the client can pick a colour/label per phase.
|
|
7
|
+
// `none` = no PR for this branch yet (still local work); `ready` = open, CI green, no
|
|
8
|
+
// changes requested — i.e. waiting to merge.
|
|
9
|
+
export type PrPhase = "none" | "draft" | "ci-failing" | "changes-requested" | "ci-running" | "ready" | "merged" | "closed";
|
|
10
|
+
|
|
11
|
+
export const PR_PHASES: readonly PrPhase[] = ["none", "draft", "ci-failing", "changes-requested", "ci-running", "ready", "merged", "closed"];
|
|
12
|
+
|
|
13
|
+
export const isPrPhase = (v: unknown): v is PrPhase => typeof v === "string" && (PR_PHASES as readonly string[]).includes(v);
|
|
14
|
+
|
|
15
|
+
// The /api/pr-phase response. `phase` and `prUrl` predate the rest and the roster reads them;
|
|
16
|
+
// the numbers are what lets a cell say WHICH work it is on rather than just how far along it is.
|
|
17
|
+
export interface WorkItem {
|
|
18
|
+
phase: PrPhase;
|
|
19
|
+
pr: number | null;
|
|
20
|
+
prUrl: string | null;
|
|
21
|
+
issue: number | null;
|
|
22
|
+
issueUrl: string | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const EMPTY_WORK_ITEM: Readonly<WorkItem> = { phase: "none", pr: null, prUrl: null, issue: null, issueUrl: null };
|
|
26
|
+
|
|
27
|
+
// GitHub's closing keywords, as GitHub itself matches them: any of these, then optional
|
|
28
|
+
// whitespace/colon, then #N. Anything else in the body is a mention, not a link — a PR that
|
|
29
|
+
// says "related to #12" is not working on #12.
|
|
30
|
+
// One character class for the separator, not `\s*:?\s+`: two adjacent whitespace quantifiers
|
|
31
|
+
// backtrack super-linearly on a long body, and a PR description is user input.
|
|
32
|
+
//
|
|
33
|
+
// `[1-9]\d*` rather than `\d+`: there is no issue #0, and `#0123` is not issue 123 — both are
|
|
34
|
+
// typos, and a typo that renders as a link to somebody else's issue is worse than no chip.
|
|
35
|
+
const CLOSING_KEYWORD = /\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)[:\s]+#([1-9]\d*)/i;
|
|
36
|
+
|
|
37
|
+
// Digits from a body or a branch name are unbounded, and `Number("9".repeat(20))` is 1e20 — which
|
|
38
|
+
// would render in the chip as "#1e+20" and link nowhere. Anything past the safe-integer range is
|
|
39
|
+
// not an issue number (found by Codex review).
|
|
40
|
+
function toIssueNumber(digits: string): number | null {
|
|
41
|
+
const n = Number(digits);
|
|
42
|
+
return Number.isSafeInteger(n) && n > 0 ? n : null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// The issue a PR body says it closes, or null. Deliberately blind to the full-URL form
|
|
46
|
+
// (`Fixes https://github.com/o/r/issues/12`): that can name ANOTHER repository, and a number
|
|
47
|
+
// shown next to this cell's PR has to belong to the same repo to be clickable and true.
|
|
48
|
+
export function issueRefFromPrBody(body: string | null | undefined): number | null {
|
|
49
|
+
const found = typeof body === "string" ? CLOSING_KEYWORD.exec(body) : null;
|
|
50
|
+
return found ? toIssueNumber(found[1]) : null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// A branch named after its issue the way this repo names them: `fix/966-preserve-unknown-keys`.
|
|
54
|
+
// Requires a type prefix and a hyphen after the digits, so `chore/dep-updates-20260728` is not
|
|
55
|
+
// read as issue #20260728.
|
|
56
|
+
const BRANCH_ISSUE = /^[a-z][a-z-]*\/([1-9]\d*)-/;
|
|
57
|
+
|
|
58
|
+
// A CANDIDATE, never an answer: `release/2026-07-28-hotfix` yields 2026 here, and no pattern can
|
|
59
|
+
// tell that apart from a branch for issue #2026 — the year and the number have the same shape.
|
|
60
|
+
// The caller must confirm the issue exists before showing it, or a cell claims to be working on
|
|
61
|
+
// somebody else's issue. Named for the doubt so a call site can't forget it.
|
|
62
|
+
export function issueCandidateFromBranch(branch: string | null | undefined): number | null {
|
|
63
|
+
const found = typeof branch === "string" ? BRANCH_ISSUE.exec(branch) : null;
|
|
64
|
+
return found ? toIssueNumber(found[1]) : null;
|
|
65
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// The rate-limit wire shape, and the one parser for it (#387).
|
|
2
|
+
//
|
|
3
|
+
// Here rather than on either side because BOTH decide from it: the server reads it back out of its
|
|
4
|
+
// own cache file, and the browser reads it off `/api/rate-limits`. Written twice it was written
|
|
5
|
+
// twice differently — jscpd caught the copies before they had a chance to drift.
|
|
6
|
+
//
|
|
7
|
+
// Everything is optional on purpose. A window can be absent because the agent is not installed,
|
|
8
|
+
// because the plan is API-key billed, because no session has answered yet, or because upstream
|
|
9
|
+
// dropped the field (anthropics/claude-code#40094). None of those are zero, and a gauge reading
|
|
10
|
+
// 0% when the truth is 83% is the worst thing this data can do.
|
|
11
|
+
|
|
12
|
+
export interface RateLimitWindow {
|
|
13
|
+
usedPercentage: number; // 0-100, fractional
|
|
14
|
+
resetsAt_sec: number | null; // Unix epoch seconds
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface RateLimits {
|
|
18
|
+
fiveHour: RateLimitWindow | null;
|
|
19
|
+
sevenDay: RateLimitWindow | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === "object" && value !== null;
|
|
23
|
+
const finiteNumber = (value: unknown): number | null => (typeof value === "number" && Number.isFinite(value) ? value : null);
|
|
24
|
+
|
|
25
|
+
export function parseRateLimitWindow(raw: unknown): RateLimitWindow | null {
|
|
26
|
+
if (!isRecord(raw)) return null;
|
|
27
|
+
const used = finiteNumber(raw.usedPercentage);
|
|
28
|
+
return used === null ? null : { usedPercentage: used, resetsAt_sec: finiteNumber(raw.resetsAt_sec) };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Null when neither window survives, so a caller can tell "nothing to show" from "0% used". */
|
|
32
|
+
export function parseRateLimits(raw: unknown): RateLimits | null {
|
|
33
|
+
if (!isRecord(raw)) return null;
|
|
34
|
+
const fiveHour = parseRateLimitWindow(raw.fiveHour);
|
|
35
|
+
const sevenDay = parseRateLimitWindow(raw.sevenDay);
|
|
36
|
+
return fiveHour || sevenDay ? { fiveHour, sevenDay } : null;
|
|
37
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// How far one wheel/trackpad gesture scrolls the terminal, as a multiplier on the xterm
|
|
2
|
+
// default (1 = xterm's own speed). Shared because both sides decide from the same numbers:
|
|
3
|
+
// the Settings stepper bounds its own buttons, and the client re-validates what it reads back
|
|
4
|
+
// out of localStorage.
|
|
5
|
+
//
|
|
6
|
+
// It drives BOTH scrolling paths, which are different code: xterm's `scrollSensitivity` for
|
|
7
|
+
// the normal buffer's scrollback, and the wheel-report accumulator in ./mouseReports for a
|
|
8
|
+
// full-screen TUI in the alternate buffer (Claude Code). One setting, because to the person
|
|
9
|
+
// scrolling they are the same gesture.
|
|
10
|
+
export const TERMINAL_SCROLL_SPEED_DEFAULT = 1;
|
|
11
|
+
// A macOS trackpad emits a wheel event every few pixels, so the useful range sits below 1 —
|
|
12
|
+
// 0.25 is "a swipe moves a paragraph", still fast enough to cross a long transcript. Above 3
|
|
13
|
+
// a single flick outruns the renderer and lands nowhere readable, which is the complaint this
|
|
14
|
+
// setting exists to answer.
|
|
15
|
+
export const TERMINAL_SCROLL_SPEED_MIN = 0.25;
|
|
16
|
+
export const TERMINAL_SCROLL_SPEED_MAX = 3;
|
|
17
|
+
export const TERMINAL_SCROLL_SPEED_STEP = 0.25;
|
|
18
|
+
|
|
19
|
+
// Clamp rather than reject an out-of-range number, for the same reason the font size does:
|
|
20
|
+
// honouring the direction the user asked for reads as working, where snapping back to the
|
|
21
|
+
// default reads as ignored. Non-numeric input means "nothing set here" and stays null so the
|
|
22
|
+
// caller can fall back. Values are snapped to the step so the label stays readable (0.75, not
|
|
23
|
+
// 0.7499999) and so the stepper can't drift off its own grid.
|
|
24
|
+
export function normalizeScrollSpeed(input: unknown): number | null {
|
|
25
|
+
if (typeof input !== "number" || !Number.isFinite(input)) return null;
|
|
26
|
+
const clamped = Math.min(TERMINAL_SCROLL_SPEED_MAX, Math.max(TERMINAL_SCROLL_SPEED_MIN, input));
|
|
27
|
+
const snapped = Math.round(clamped / TERMINAL_SCROLL_SPEED_STEP) * TERMINAL_SCROLL_SPEED_STEP;
|
|
28
|
+
// The multiply above reintroduces binary-float noise (0.30000000000000004); two decimals is
|
|
29
|
+
// more than the step needs and leaves the value safe to compare and to render.
|
|
30
|
+
return Math.round(snapped * 100) / 100;
|
|
31
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// The comments MulmoTerminal leaves on an issue: "a cell is on this" and "it was merged" (#979).
|
|
2
|
+
// Pure, and shared, because the marker is the whole idempotency story — the server writes it and
|
|
3
|
+
// the server reads it back, and a mismatch between the two would post the same comment on every
|
|
4
|
+
// poll of every tab.
|
|
5
|
+
|
|
6
|
+
export type WorkCommentKind = "start" | "merged";
|
|
7
|
+
|
|
8
|
+
// An HTML comment, so it is invisible in the rendered issue but survives a round-trip through the
|
|
9
|
+
// GitHub API. Keyed by kind AND directory: the same issue worked on from a second clone is a
|
|
10
|
+
// second, honest line in the thread, not a duplicate to suppress.
|
|
11
|
+
//
|
|
12
|
+
// The directory is percent-encoded, not interpolated raw: a folder may legally be called
|
|
13
|
+
// `foo-->bar`, and that string ends the HTML comment early — the rest spills into the rendered
|
|
14
|
+
// issue as text (Codex review). Encoding also keeps a newline or a backtick in a path from
|
|
15
|
+
// reshaping the comment. Ordinary names encode to themselves, so markers already posted still
|
|
16
|
+
// match.
|
|
17
|
+
export function workCommentMarker(kind: WorkCommentKind, dir: string): string {
|
|
18
|
+
return `<!-- mulmoterminal:work:${kind} dir=${encodeURIComponent(dir)} -->`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// The directory a comment names. The BASENAME only: the point is "which of my clones", and a full
|
|
22
|
+
// path on a public issue leaks the machine's layout (and, on a work machine, project names).
|
|
23
|
+
export function workCommentDirLabel(cwd: string): string {
|
|
24
|
+
// Split-and-take-last rather than trimming a trailing separator with a regex: an anchored
|
|
25
|
+
// `[/\\]+$` backtracks super-linearly, and a path is user input.
|
|
26
|
+
const parts = cwd.split(/[/\\]/).filter((part) => part !== "");
|
|
27
|
+
return parts[parts.length - 1] ?? cwd;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// `pr` is the number the merge came in as, when there is one — a reader of the issue should not
|
|
31
|
+
// have to go looking for which PR closed it.
|
|
32
|
+
export function workCommentBody(kind: WorkCommentKind, dir: string, pr: number | null): string {
|
|
33
|
+
const marker = workCommentMarker(kind, dir);
|
|
34
|
+
if (kind === "start") return `Working on this in \`${dir}\`.\n\n${marker}`;
|
|
35
|
+
const where = pr === null ? "Merged." : `Merged in #${pr}.`;
|
|
36
|
+
return `${where} Work done in \`${dir}\`.\n\n${marker}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Has this exact comment already been left? Matching on the marker rather than the prose means an
|
|
40
|
+
// edited comment, or a change to the wording above, still counts as "already said".
|
|
41
|
+
export function alreadyCommented(bodies: readonly string[], kind: WorkCommentKind, dir: string): boolean {
|
|
42
|
+
const marker = workCommentMarker(kind, dir);
|
|
43
|
+
return bodies.some((body) => typeof body === "string" && body.includes(marker));
|
|
44
|
+
}
|
|
@@ -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-C43vNJPp.js";import"./chunk-WYO6CB5R-BlzOfotS-C6J_J8TJ.js";import"./chunk-VAUOI2AC-CS9QJ4yz-CDhJXpTw.js";import{n as i,r as a,t as o}from"./chunk-MOJQB5TN-CMZRaeqt-xCxis4AD.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-C43vNJPp.js";export{e as createArchitectureServices};
|