mulmoterminal 2.2.0 → 2.4.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 +52 -1
- package/bin/cli-args.d.ts +1 -0
- package/bin/cli-args.js +16 -0
- package/bin/mulmoterminal.js +2 -1
- package/common/dirConfigSource.ts +101 -0
- package/common/githubRepo.ts +22 -0
- package/common/terminalClipboard.ts +19 -0
- package/dist/assets/{abnfDiagram-VRR7QNED-RjiYivmv-6R9yRbUC.js → abnfDiagram-VRR7QNED-RjiYivmv-CqId6L6N.js} +1 -1
- package/dist/assets/architecture-TIHT7OUA-0NsD2xW1-CfmmIOkB.js +1 -0
- package/dist/assets/{architectureDiagram-ZJ3FMSHR-3nWA91tG-DzS5ABJE.js → architectureDiagram-ZJ3FMSHR-3nWA91tG-CWhhLcfM.js} +1 -1
- package/dist/assets/{blockDiagram-677ZJIJ3-BPuAJQRW-Cw9RkJxY.js → blockDiagram-677ZJIJ3-BPuAJQRW-89yfYblY.js} +1 -1
- package/dist/assets/{c4Diagram-LMCZKHZV-C0LAqQso-CWltXMHP.js → c4Diagram-LMCZKHZV-C0LAqQso-Cq70UauP.js} +1 -1
- package/dist/assets/channel-7wUqSdoX-Bc9EmVWS.js +1 -0
- package/dist/assets/{chunk-32BRIVSS-BRrYpgtb-DrrsZ2u5.js → chunk-32BRIVSS-BRrYpgtb-Bj5FdJ3A.js} +1 -1
- package/dist/assets/{chunk-52WLFC77-BJ-ss3Xr-WJMq_L8L.js → chunk-52WLFC77-BJ-ss3Xr-Cf6wft_5.js} +1 -1
- package/dist/assets/{chunk-C7G6YPKG-BZEucKEL-BafXJkh7.js → chunk-C7G6YPKG-BZEucKEL-CLA5COUu.js} +1 -1
- package/dist/assets/{chunk-EX3LRPZG-DLS6FBN1-Q8lsrJXs.js → chunk-EX3LRPZG-DLS6FBN1-ChsLH6_y.js} +1 -1
- package/dist/assets/{chunk-FWX5IMBZ-DHLSFw1H-Cgm5-zaI.js → chunk-FWX5IMBZ-DHLSFw1H-BIvnIbPU.js} +2 -2
- package/dist/assets/{chunk-HOUHSVGY-Bhlt8hXJ-Cp5J5cOD.js → chunk-HOUHSVGY-Bhlt8hXJ-Ck-Dxtw-.js} +1 -1
- package/dist/assets/{chunk-ICXQ74PX-DwgHBX_g-CLdnp-d2.js → chunk-ICXQ74PX-DwgHBX_g-CSlWVcWs.js} +1 -1
- package/dist/assets/{chunk-MOJQB5TN-CMZRaeqt-BsIB0ZXR.js → chunk-MOJQB5TN-CMZRaeqt-CYfpp2V7.js} +1 -1
- package/dist/assets/{chunk-OGEWGWER-8Qy4a8b5-DlTM-RTP.js → chunk-OGEWGWER-8Qy4a8b5-DPIlRee4.js} +1 -1
- package/dist/assets/{chunk-PUDLZKDR-DcrWQRYh-XujP_H1v.js → chunk-PUDLZKDR-DcrWQRYh-J86vFVAN.js} +1 -1
- package/dist/assets/{chunk-Q4XR5HBZ-ZXVGkG8Z-DhxnzVDy.js → chunk-Q4XR5HBZ-ZXVGkG8Z-D-40tbjk.js} +1 -1
- package/dist/assets/{chunk-V7JOEXUC-DmGdheTX-C-xPmCQA.js → chunk-V7JOEXUC-DmGdheTX-7gJmS-F9.js} +1 -1
- package/dist/assets/{chunk-VAUOI2AC-CS9QJ4yz-Cng5RPUP.js → chunk-VAUOI2AC-CS9QJ4yz-DFjCsQc6.js} +1 -1
- package/dist/assets/{chunk-VR4S4FIN-C6a91eNY-Bmb0moA3.js → chunk-VR4S4FIN-C6a91eNY-Ba059rhB.js} +1 -1
- package/dist/assets/{chunk-WYO6CB5R-BlzOfotS-DmR4SyZL.js → chunk-WYO6CB5R-BlzOfotS-CTDodsnw.js} +1 -1
- package/dist/assets/{chunk-ZGVPDNZ5-BYwxNFTK-CD8nNM8_.js → chunk-ZGVPDNZ5-BYwxNFTK-CMF9FJ7K.js} +1 -1
- package/dist/assets/classDiagram-OUVF2IWQ-BYlRi6YQ-Dk_90vZD.js +1 -0
- package/dist/assets/classDiagram-v2-EOCWNBFH-BCHaYHxj-Dk_90vZD.js +1 -0
- package/dist/assets/cynefin-VYW2F7L2-CGYr2F6h-B1MlaJhj.js +1 -0
- package/dist/assets/{cynefinDiagram-TSTJHNR4-DHA9iPo--pv1DCSfy.js → cynefinDiagram-TSTJHNR4-DHA9iPo--BUwfErNr.js} +1 -1
- package/dist/assets/{dagre-VKFMJZFB--oJKqXBZ-BPcGEiSl.js → dagre-VKFMJZFB--oJKqXBZ-FEoLipSZ.js} +1 -1
- package/dist/assets/{diagram-FQU43EPY-CcJCB9bG-CCOrHV0U.js → diagram-FQU43EPY-CcJCB9bG-DwH2nER4.js} +1 -1
- package/dist/assets/{diagram-G47NLZAW-C_o-WGG1-Crm_IB8Y.js → diagram-G47NLZAW-C_o-WGG1-BuSqgofP.js} +1 -1
- package/dist/assets/{diagram-NH7WQ7WH-CXJCYvY--UcH8gkTE.js → diagram-NH7WQ7WH-CXJCYvY--CoMsubYQ.js} +1 -1
- package/dist/assets/{diagram-OA4YK3LP-BMzeJ87A-7C9Me5sS.js → diagram-OA4YK3LP-BMzeJ87A-Ds_49Q4I.js} +1 -1
- package/dist/assets/{diagram-WEI45ONY-D_93NKqo-BBztQ9hK.js → diagram-WEI45ONY-D_93NKqo-DeDTEgI-.js} +1 -1
- package/dist/assets/{ebnfDiagram-CCIWWBDH-CVai1Ii9-TkJNOqEw.js → ebnfDiagram-CCIWWBDH-CVai1Ii9-B84fKP1h.js} +1 -1
- package/dist/assets/{erDiagram-Q63AITRT-CiABnA0s-DS2blJuc.js → erDiagram-Q63AITRT-CiABnA0s-QGRolPYH.js} +1 -1
- package/dist/assets/eventmodeling-45OFAUF4-CWmqhm3a-C0eB3veQ.js +1 -0
- package/dist/assets/flowDiagram-23GEKE2U-DKSB5AiY-BAfA7eXR.js +1 -0
- package/dist/assets/{ganttDiagram-NO4QXBWP-DQZvdFo1-ZB_lhurA.js → ganttDiagram-NO4QXBWP-DQZvdFo1-BBXcle76.js} +1 -1
- package/dist/assets/gitGraph-TEB2WS4Q-BR9qPwdN-CotjI6SA.js +1 -0
- package/dist/assets/{gitGraphDiagram-IHSO6WYX-C2ovBouh-Vm6I27hg.js → gitGraphDiagram-IHSO6WYX-C2ovBouh-BxGBV6Hj.js} +1 -1
- package/dist/assets/index-B7LbAi10.css +1 -0
- package/dist/assets/{index-BP05wTLW.js → index-_XRnaxAj.js} +202 -202
- package/dist/assets/info-DKCQHKI2-Dplx5kMp-DKHWzOor.js +1 -0
- package/dist/assets/{infoDiagram-FWYZ7A6U-7UnoB5AP-Cr34YkAb.js → infoDiagram-FWYZ7A6U-7UnoB5AP-DCqG4iB9.js} +1 -1
- package/dist/assets/{ishikawaDiagram-FXEZZL3T-ByUDM_N2-at4P1zMV.js → ishikawaDiagram-FXEZZL3T-ByUDM_N2-Dkzr63Uo.js} +1 -1
- package/dist/assets/{journeyDiagram-5HDEW3XC-c5xIah9o-D4ZV57k7.js → journeyDiagram-5HDEW3XC-c5xIah9o-3K64pBTH.js} +1 -1
- package/dist/assets/{kanban-definition-HUTT4EX6-Cnt6loYD-BiJ3JUjX.js → kanban-definition-HUTT4EX6-Cnt6loYD-B0U7Mkg9.js} +1 -1
- package/dist/assets/{lib-Du_IVDkN.js → lib-rFkGOb1O.js} +1 -1
- package/dist/assets/{line-D7ziSjKi-CPM0FMWw.js → line-D7ziSjKi-D25izB30.js} +1 -1
- package/dist/assets/{marp-BBre1w8X.js → marp-79PhoGbq.js} +1 -1
- package/dist/assets/{mermaid-parser.core-DxEa8E3F-CO4zEYzL.js → mermaid-parser.core-DxEa8E3F-BzSqp82H.js} +2 -2
- package/dist/assets/{mermaid.core-V0OYwIz3-CzPQxhwu.js → mermaid.core-V0OYwIz3-0p62b-zk.js} +3 -3
- package/dist/assets/{mindmap-definition-LN4V7U3C-CexN3O6L-Nez_xicT.js → mindmap-definition-LN4V7U3C-CexN3O6L-BzFX06oy.js} +1 -1
- package/dist/assets/packet-7NZHBO7P-CQI3flND-DyAt_QAV.js +1 -0
- package/dist/assets/{pegDiagram-2B236MQR-BiM4G0cW-0AEyhHwQ.js → pegDiagram-2B236MQR-BiM4G0cW-BMRJsJZN.js} +1 -1
- package/dist/assets/pie-RZYD4A2V-CnmKE2Wa-nlXXCSHg.js +1 -0
- package/dist/assets/{pieDiagram-ENE6RG2P-B_fBBS-2-DXBEfgPl.js → pieDiagram-ENE6RG2P-B_fBBS-2-Bl2zNUvh.js} +1 -1
- package/dist/assets/{quadrantDiagram-ABIIQ3AL-BigGVxCR-dZ16XJ8-.js → quadrantDiagram-ABIIQ3AL-BigGVxCR-BzQ392_S.js} +1 -1
- package/dist/assets/radar-I7S5WNFK-us6x-Z9R-Dnn5SKpT.js +1 -0
- package/dist/assets/railroad-3IZDKUUU-BKdz6pdN-DjF0R5Zr.js +1 -0
- package/dist/assets/railroad-abnf-AHOZXSZD-m_CSSIfd-GH7jGfhq.js +1 -0
- package/dist/assets/railroad-ebnf-EBAXGLYW-Nu3nAG7C-CH8TSRFp.js +1 -0
- package/dist/assets/railroad-peg-LSFZ7HO6--Y3mD4R_-QfsXYBPl.js +1 -0
- package/dist/assets/{railroadDiagram-RFXS5EU6--lNIlG64-DHNapLB9.js → railroadDiagram-RFXS5EU6--lNIlG64-D-Yn0Dpg.js} +1 -1
- package/dist/assets/{requirementDiagram-TGXJPOKE-D-B0Y5Wu-C-9SVxpD.js → requirementDiagram-TGXJPOKE-D-B0Y5Wu-DIYnBCWZ.js} +1 -1
- package/dist/assets/{sankeyDiagram-HTMAVEWB-DlutaXDv-Cv-mFz76.js → sankeyDiagram-HTMAVEWB-DlutaXDv-Dsr5VJzO.js} +1 -1
- package/dist/assets/{sequenceDiagram-DBY2YBRQ-BOLgtggH-BR0sUcBe.js → sequenceDiagram-DBY2YBRQ-BOLgtggH-mR1rIlJQ.js} +1 -1
- package/dist/assets/{stateDiagram-2N3HPSRC-jf_dXUEU-DjqY9a1u.js → stateDiagram-2N3HPSRC-jf_dXUEU-Cq2A7Udg.js} +1 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-CuzgCL2v-D7ipapeO.js +1 -0
- package/dist/assets/{swimlanes-5IMT3BWC-CYjtALQH-DV8Ha8XJ.js → swimlanes-5IMT3BWC-CYjtALQH-CtgZ4USf.js} +1 -1
- package/dist/assets/swimlanesDiagram-G3AALYLV-3bmSBtKs-D1yGYNNh.js +8 -0
- package/dist/assets/{timeline-definition-FHXFAJF6-DYJ4oUm8-D2oq5bFn.js → timeline-definition-FHXFAJF6-DYJ4oUm8-CpuMCUFK.js} +1 -1
- package/dist/assets/treeView-QDETBFTQ-D27HWG6Q-nxCnsDvO.js +1 -0
- package/dist/assets/treemap-6X3UGDF4-CmGIU3H5-7UfnOcZW.js +1 -0
- package/dist/assets/{vennDiagram-L72KCM5P-CdKAHoek-I-wP-h9k.js → vennDiagram-L72KCM5P-CdKAHoek-B-FSFzf3.js} +1 -1
- package/dist/assets/wardley-OPB4EBWU-CbHWsb2k-DHZIQf1x.js +1 -0
- package/dist/assets/{wardleyDiagram-EHGQE667-BF6c4_CW-BkrH7vLX.js → wardleyDiagram-EHGQE667-BF6c4_CW-CB01oT0C.js} +1 -1
- package/dist/assets/{xychartDiagram-FW5EYKEG-CGiKngj7-DMYCP46V.js → xychartDiagram-FW5EYKEG-CGiKngj7-D1vr6bQ-.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/server/agents/claude-args.ts +5 -0
- package/server/agents/session-summary-prompt.ts +49 -0
- package/server/config/app-config.ts +13 -0
- package/server/config/config-routes.ts +6 -1
- package/server/config/dir-config.ts +83 -0
- package/server/config/env.ts +12 -0
- package/server/config/workspace.ts +17 -7
- package/server/git/github-star.ts +63 -0
- package/server/index.ts +10 -5
- package/server/infra/allowed-origin.ts +106 -25
- package/server/infra/hide-error-stacks.ts +15 -0
- package/server/infra/spa-fallback.ts +18 -0
- package/server/routes/app-routes.ts +2 -2
- package/server/routes/dir-routes.ts +14 -2
- package/server/routes/repo-routes.ts +25 -0
- package/server/skills/mulmoterminal-config/SKILL.md +28 -1
- package/dist/assets/architecture-TIHT7OUA-0NsD2xW1-C95zkDEU.js +0 -1
- package/dist/assets/channel-7wUqSdoX-BYaGw2yS.js +0 -1
- package/dist/assets/classDiagram-OUVF2IWQ-BYlRi6YQ-EDJcEZLp.js +0 -1
- package/dist/assets/classDiagram-v2-EOCWNBFH-BCHaYHxj-EDJcEZLp.js +0 -1
- package/dist/assets/cynefin-VYW2F7L2-CGYr2F6h-Co-phwWx.js +0 -1
- package/dist/assets/eventmodeling-45OFAUF4-CWmqhm3a-D-dhMOz8.js +0 -1
- package/dist/assets/flowDiagram-23GEKE2U-DKSB5AiY-KiTeh-IZ.js +0 -1
- package/dist/assets/gitGraph-TEB2WS4Q-BR9qPwdN-Cd1rBWSG.js +0 -1
- package/dist/assets/index-CGwcisib.css +0 -1
- package/dist/assets/info-DKCQHKI2-Dplx5kMp-Dic4-0_Y.js +0 -1
- package/dist/assets/packet-7NZHBO7P-CQI3flND-BBywyu3N.js +0 -1
- package/dist/assets/pie-RZYD4A2V-CnmKE2Wa-CoicOL1H.js +0 -1
- package/dist/assets/radar-I7S5WNFK-us6x-Z9R-BeFXvO0F.js +0 -1
- package/dist/assets/railroad-3IZDKUUU-BKdz6pdN-BZY5Z4oN.js +0 -1
- package/dist/assets/railroad-abnf-AHOZXSZD-m_CSSIfd-Dubi7PGc.js +0 -1
- package/dist/assets/railroad-ebnf-EBAXGLYW-Nu3nAG7C-wnb7FBOh.js +0 -1
- package/dist/assets/railroad-peg-LSFZ7HO6--Y3mD4R_-P7vJjL8M.js +0 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-CuzgCL2v-DjKerI1O.js +0 -1
- package/dist/assets/swimlanesDiagram-G3AALYLV-3bmSBtKs-Dm5TS_6E.js +0 -8
- package/dist/assets/treeView-QDETBFTQ-D27HWG6Q-H3pflPYY.js +0 -1
- package/dist/assets/treemap-6X3UGDF4-CmGIU3H5-CIoNkikG.js +0 -1
- package/dist/assets/wardley-OPB4EBWU-CbHWsb2k-JQdCXyum.js +0 -1
package/README.md
CHANGED
|
@@ -93,6 +93,13 @@ than as bytes (files within the session's working directory only):
|
|
|
93
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
94
|
| everything else — images, PDF, SVG, HTML, video | raw bytes in a new tab, which the browser renders better than an editor would |
|
|
95
95
|
|
|
96
|
+
**While a grid cell is enlarged, the [Files pane](#files-view-browse--edit) takes the click first** — every
|
|
97
|
+
row above except the last one, since the pane is the same editor plus a Markdown preview. The
|
|
98
|
+
file opens *beside* the terminal that printed it, and the pane opens itself if it was closed.
|
|
99
|
+
It declines, leaving the routing above untouched, when nothing is enlarged, when the path is
|
|
100
|
+
not under that cell's own directory (the pane cannot walk above its root), or for the raw-bytes
|
|
101
|
+
row, where it would only show an empty editor.
|
|
102
|
+
|
|
96
103
|
Highlighting in the Files view covers the JS/TS family, JSON and Markdown (the modes
|
|
97
104
|
`cmEditor.ts` bundles); other languages open as plain text.
|
|
98
105
|
|
|
@@ -223,6 +230,7 @@ The launcher detects it and prints the exact, OS-appropriate removal command; ru
|
|
|
223
230
|
- [Session model](#session-model)
|
|
224
231
|
- [Session lifecycle](#session-lifecycle)
|
|
225
232
|
- [Claude hook injection](#claude-hook-injection)
|
|
233
|
+
- [Closing summary](#closing-summary)
|
|
226
234
|
- [Session discovery & titles](#session-discovery--titles)
|
|
227
235
|
- [Project structure](#project-structure)
|
|
228
236
|
- [Testing](#testing)
|
|
@@ -287,7 +295,8 @@ today — **Claude Code** (the default) and **Codex**.
|
|
|
287
295
|
- **Claude** — spawned as `claude` (override with `CLAUDE_BIN`). The server passes
|
|
288
296
|
`--session-id <uuid>`, so it knows the live session's id even before its transcript
|
|
289
297
|
file exists, and injects activity hooks + the GUI MCP per spawn (see
|
|
290
|
-
[Claude hook injection](#claude-hook-injection))
|
|
298
|
+
[Claude hook injection](#claude-hook-injection)) plus the
|
|
299
|
+
[closing summary](#closing-summary) instruction.
|
|
291
300
|
- **Codex** — spawned as `codex` (override with `CODEX_BIN`; `CODEX_MODEL` sets
|
|
292
301
|
`--model`). Codex runs on its own WebSocket (`/ws/codex`) and its sessions appear in the
|
|
293
302
|
sidebar next to Claude's. Because Codex only mints its rollout id **after** the first
|
|
@@ -465,6 +474,7 @@ The Settings modal (⚙) persists per-user UI choices to `~/.mulmoterminal/confi
|
|
|
465
474
|
| `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). |
|
|
466
475
|
| `worklogIntervalHours` | Worklog cadence in hours (default `6`, clamped to `1`–`168`). |
|
|
467
476
|
| `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
|
+
| `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). |
|
|
468
478
|
| `prWorkdirFooter` | Ends the body of a PR **⧉ Open PR** creates 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. **On by default**; set `false` to opt out — read from the file per PR, so no restart is needed (there is no Settings control for it). Only applied to PRs this app creates (pressing the button again on an existing PR never re-appends). |
|
|
469
479
|
| `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). |
|
|
470
480
|
|
|
@@ -617,6 +627,12 @@ as the reload signal, so colors, palette, font size and grid order update withou
|
|
|
617
627
|
anything. There is no filesystem watcher, so an edit made **outside** a session (your own
|
|
618
628
|
editor) is picked up when the terminal is next opened.
|
|
619
629
|
|
|
630
|
+
**Checking what took effect.** Settings → **Directory settings** lists your recent directories
|
|
631
|
+
and expands each one to the values in force, with a swatch per color and the path of the file
|
|
632
|
+
they came from. It also names the keys it **dropped** (a color that isn't `#rrggbb`, a size out
|
|
633
|
+
of range) and the keys it doesn't read at all (`badgeColour`, a global-only setting) — which is
|
|
634
|
+
what tells "I never set that" apart from "I set it and it didn't take".
|
|
635
|
+
|
|
620
636
|
---
|
|
621
637
|
|
|
622
638
|
## Running
|
|
@@ -768,6 +784,10 @@ shrinks the pane rather than reflowing xterm into garbage. It works in both zoom
|
|
|
768
784
|
(cockpit roster and thumbnail filmstrip), the pane re-roots as you walk the zoom between
|
|
769
785
|
terminals, and whether it's open plus how wide it is are remembered per browser.
|
|
770
786
|
|
|
787
|
+
The toggle is not the only way in: while a cell is enlarged, **clicking a file path the agent
|
|
788
|
+
printed** opens it here too, rather than in a new tab or full-screen — see
|
|
789
|
+
[Clicking a file path](#clicking-a-file-path).
|
|
790
|
+
|
|
771
791
|
All reads and writes go through `GET/PUT /api/files/browse/*?cwd=&path=`, and every
|
|
772
792
|
`path` is **contained within the project root** (server-side) — `..`/absolute escapes
|
|
773
793
|
are rejected for reads and writes alike, so editing can't reach outside the directory
|
|
@@ -920,6 +940,11 @@ Favorited collections get their own toolbar buttons.
|
|
|
920
940
|
session.
|
|
921
941
|
- **Notifications** (🔔) — a toolbar bell with an unread badge and a dropdown of active
|
|
922
942
|
notifications; click a row to jump to its session.
|
|
943
|
+
- **Star MulmoTerminal** — a star button in the grid toolbar that stars the project on GitHub
|
|
944
|
+
through your own `gh` login, in one click. It is a one-time ask: once the repo is starred the
|
|
945
|
+
button is gone for good and stops calling the server at all. It shows **only when `gh` can
|
|
946
|
+
answer** — with no `gh`, no login, or no network, one click couldn't star anything, so nothing
|
|
947
|
+
is shown and nothing is recorded. Set `gh` up later and the button appears by itself.
|
|
923
948
|
- **Voice input** — dictate a prompt via on-device Whisper (`POST /api/transcribe`, macOS
|
|
924
949
|
only; the model downloads on first use). Settings picks **the language you dictate in**
|
|
925
950
|
(per browser): your browser's, whisper's own per-clip detection, or a fixed one. Worth
|
|
@@ -1102,6 +1127,7 @@ same-origin-guarded.
|
|
|
1102
1127
|
| `GET /api/worktrees?cwd=` · `GET /api/worktrees/diff?cwd=` | List managed worktrees / diff one vs its base. |
|
|
1103
1128
|
| `POST /api/worktrees/create` · `/remove` · `/push` · `/pr` | Create on `agent/<slug>`, remove (managed root only), push, open a PR (`gh`, else compare URL). |
|
|
1104
1129
|
| `GET /api/prs` · `GET /api/issues` | Open PRs / issues across the configured `prRepos` (via `gh`). |
|
|
1130
|
+
| `GET /api/github/star` · `POST /api/github/star` | Whether you have starred MulmoTerminal, and star it (via `gh`). `starred: null` means `gh` could not answer, and hides the button. |
|
|
1105
1131
|
|
|
1106
1132
|
**Workspace views**
|
|
1107
1133
|
|
|
@@ -1128,6 +1154,7 @@ same-origin-guarded.
|
|
|
1128
1154
|
| -------- | ------- |
|
|
1129
1155
|
| `GET\|POST /api/config` | User UI config (`cwdPresets`, `soundFile`, `soundKinds`, `sounds`, `prRepos`, `launchers`, `quickCommands`, `userMcpServers`, `providers`). |
|
|
1130
1156
|
| `GET /api/sound?kind=` · `/api/dir-sound?cwd=&kind=` · `/api/sound-preset/:id` · `/api/dir-config?cwd=` | Custom / per-directory / preset attention sound + per-dir config. `kind` selects a config entry, never a path. |
|
|
1157
|
+
| `GET /api/dir-config-detail?cwd=` | The same per-dir config, **plus** the settings a running terminal doesn't need (`provider`, `model`, `skills`, `addDirs`, header button/chip **labels**), **plus** which keys the file set and how each fared (applied / dropped in validation / not a setting at all). Read-only; backs the Settings modal's **Directory settings** preview. Unlike the other `?cwd=` routes this one does **not** fall back to the default workspace — it reports on the directory it was asked about, so a path that no longer exists comes back as `exists:false`. Sound paths and button commands stay server-side. |
|
|
1131
1158
|
| `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. |
|
|
1132
1159
|
| `GET /api/notifications`(`/history`) · `POST /api/notifications/:id/clear` | Notification feed. |
|
|
1133
1160
|
| `POST /api/transcribe`(`/model`…) | Voice-input transcription (Whisper, macOS). |
|
|
@@ -1328,6 +1355,30 @@ knows the live session's id — even before the session's `.jsonl` file exists.
|
|
|
1328
1355
|
|
|
1329
1356
|
---
|
|
1330
1357
|
|
|
1358
|
+
## Closing summary
|
|
1359
|
+
|
|
1360
|
+
Every Claude session is spawned with `claude --append-system-prompt '<text>'`, asking the
|
|
1361
|
+
agent to end a reply with a short summary **when it hands control back** — the work is
|
|
1362
|
+
finished, or it is stopping to ask a question. Coming back to a grid cell after a while, the
|
|
1363
|
+
standing request and what came of it are otherwise only recoverable by scrolling the whole
|
|
1364
|
+
session.
|
|
1365
|
+
|
|
1366
|
+
The summary states three things: the request **for the conversation as a whole** (not the
|
|
1367
|
+
last message — several turns of refinement do not replace what was asked first), what was
|
|
1368
|
+
achieved, and what was not and why. It is written in the language of the conversation, and
|
|
1369
|
+
placed last with nothing after it.
|
|
1370
|
+
|
|
1371
|
+
It is deliberately **not** written on every turn: mid-work replies and short factual answers
|
|
1372
|
+
carry no standing request, and a summary that always appears stops being read. The wording
|
|
1373
|
+
lives in `server/agents/session-summary-prompt.ts`; there is no setting to turn it off.
|
|
1374
|
+
|
|
1375
|
+
Passed inline rather than as `--append-system-prompt-file` for the same reason `--settings`
|
|
1376
|
+
is: the sandbox spawn runs in a container that cannot read a host path.
|
|
1377
|
+
|
|
1378
|
+
Codex sessions are unaffected — the CLI has no equivalent flag.
|
|
1379
|
+
|
|
1380
|
+
---
|
|
1381
|
+
|
|
1331
1382
|
## Session discovery & titles
|
|
1332
1383
|
|
|
1333
1384
|
Claude stores each project's sessions as JSONL files under
|
package/bin/cli-args.d.ts
CHANGED
|
@@ -10,3 +10,4 @@ export declare const SECOND_INSTANCE_NOTE: string;
|
|
|
10
10
|
export declare const MIN_NODE_LABEL: string;
|
|
11
11
|
export declare function nodeMeetsMinimum(version: string): boolean;
|
|
12
12
|
export declare function serverNodeArgs(serverEntry: string, launchDir: string): string[];
|
|
13
|
+
export declare function serverSpawnEnv(env: Record<string, string | undefined>, port: number, cwd: string): Record<string, string | undefined>;
|
package/bin/cli-args.js
CHANGED
|
@@ -150,3 +150,19 @@ export function nodeMeetsMinimum(version) {
|
|
|
150
150
|
export function serverNodeArgs(serverEntry, launchDir) {
|
|
151
151
|
return ["--import", "tsx", `--env-file-if-exists=${join(launchDir, ".env")}`, serverEntry];
|
|
152
152
|
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The environment the launcher spawns the server with.
|
|
156
|
+
*
|
|
157
|
+
* Only the two values the server cannot work out for itself. In particular NOT `NODE_ENV`:
|
|
158
|
+
* the server hands its own environment to every PTY it spawns, so a `NODE_ENV=production`
|
|
159
|
+
* set here reaches every terminal in every cell — where yarn v1 reads it and installs
|
|
160
|
+
* WITHOUT devDependencies while still reporting success (#955). Express is pinned to
|
|
161
|
+
* production separately, in the server, so nothing here needs to say it.
|
|
162
|
+
*
|
|
163
|
+
* A `NODE_ENV` the user exported themselves passes through untouched: this decides what the
|
|
164
|
+
* launcher adds, not what it removes.
|
|
165
|
+
*/
|
|
166
|
+
export function serverSpawnEnv(env, port, cwd) {
|
|
167
|
+
return { ...env, PORT: String(port), CLAUDE_CWD: cwd };
|
|
168
|
+
}
|
package/bin/mulmoterminal.js
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
nodeMeetsMinimum,
|
|
27
27
|
MIN_NODE_LABEL,
|
|
28
28
|
serverNodeArgs,
|
|
29
|
+
serverSpawnEnv,
|
|
29
30
|
} from "./cli-args.js";
|
|
30
31
|
|
|
31
32
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -272,7 +273,7 @@ function runServer(port, noOpen, cwd, onChild) {
|
|
|
272
273
|
// package directory, so serverNodeArgs makes that path absolute (#795).
|
|
273
274
|
const server = spawn(process.execPath, serverNodeArgs(SERVER_ENTRY, process.cwd()), {
|
|
274
275
|
cwd: PKG_DIR,
|
|
275
|
-
env:
|
|
276
|
+
env: serverSpawnEnv(process.env, port, cwd),
|
|
276
277
|
stdio: ["inherit", "inherit", "pipe"],
|
|
277
278
|
});
|
|
278
279
|
let stderrTail = "";
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// Where a directory's settings actually came from, for the settings modal's preview.
|
|
2
|
+
//
|
|
3
|
+
// The rest of the app only ever needs the RESOLVED config — the values a terminal adopts.
|
|
4
|
+
// That is exactly what makes a mistyped or rejected setting invisible: the file is on disk,
|
|
5
|
+
// the app reads it, and the value silently isn't there. This says which keys the file set,
|
|
6
|
+
// which of them survived validation, and which the app doesn't read at all.
|
|
7
|
+
|
|
8
|
+
// Every key `loadDirConfig` reads. A key outside this list is a typo as far as the app is
|
|
9
|
+
// concerned, however sensible it looks — `server/config/dir-config.spec.ts` pins the two
|
|
10
|
+
// together so a field added to the loader can't quietly go missing here.
|
|
11
|
+
export const DIR_CONFIG_KEYS = [
|
|
12
|
+
"name",
|
|
13
|
+
"badgeColor",
|
|
14
|
+
"headerColor",
|
|
15
|
+
"headerTextColor",
|
|
16
|
+
"cellColor",
|
|
17
|
+
"cellBorderColor",
|
|
18
|
+
"dotColor",
|
|
19
|
+
"buttonColor",
|
|
20
|
+
"fontSize",
|
|
21
|
+
"fontFamily",
|
|
22
|
+
"orderPriority",
|
|
23
|
+
"theme",
|
|
24
|
+
"colors",
|
|
25
|
+
"sound",
|
|
26
|
+
"sounds",
|
|
27
|
+
"buttons",
|
|
28
|
+
"chips",
|
|
29
|
+
"skills",
|
|
30
|
+
"provider",
|
|
31
|
+
"model",
|
|
32
|
+
"addDirs",
|
|
33
|
+
] as const;
|
|
34
|
+
|
|
35
|
+
export interface DirConfigSource {
|
|
36
|
+
// Keys the file set that the app is using.
|
|
37
|
+
applied: string[];
|
|
38
|
+
// Keys the file set that the app read and then dropped — an out-of-range number, a colour
|
|
39
|
+
// that isn't #rrggbb, a sound outside the directory. The value is gone; the key is the clue.
|
|
40
|
+
ignored: string[];
|
|
41
|
+
// Keys the app doesn't read at all: a misspelling (`badgeColour`), or a setting that belongs
|
|
42
|
+
// in the global config instead of a directory's file.
|
|
43
|
+
unknown: string[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const EMPTY_DIR_CONFIG_SOURCE: DirConfigSource = { applied: [], ignored: [], unknown: [] };
|
|
47
|
+
|
|
48
|
+
// The settings a directory can make that the per-cell config deliberately doesn't carry — the
|
|
49
|
+
// browser has no use for them while running a terminal, but the preview has to SHOW them or a
|
|
50
|
+
// key like `provider` can only be named as applied, never read back.
|
|
51
|
+
//
|
|
52
|
+
// Labels only for buttons and chips: their `cmd` / `text` is what the button would type into
|
|
53
|
+
// the session, which is not what "did my config take effect" needs, and is the sort of thing a
|
|
54
|
+
// screenshot of the settings screen shouldn't carry. Sound paths stay out for the same reason
|
|
55
|
+
// they stay out of the per-cell config — that path is the confinement boundary.
|
|
56
|
+
export interface DirConfigExtras {
|
|
57
|
+
provider: string | null;
|
|
58
|
+
model: string | null;
|
|
59
|
+
skills: string[] | null;
|
|
60
|
+
addDirs: string[] | null;
|
|
61
|
+
buttonLabels: string[];
|
|
62
|
+
chipLabels: string[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const EMPTY_DIR_CONFIG_EXTRAS: DirConfigExtras = {
|
|
66
|
+
provider: null,
|
|
67
|
+
model: null,
|
|
68
|
+
skills: null,
|
|
69
|
+
addDirs: null,
|
|
70
|
+
buttonLabels: [],
|
|
71
|
+
chipLabels: [],
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// "The loader kept nothing for this key" — null/undefined, but also the empty collections the
|
|
75
|
+
// loader returns when every entry in a map or list was rejected.
|
|
76
|
+
function isEmptyValue(value: unknown): boolean {
|
|
77
|
+
if (value === null || value === undefined) return true;
|
|
78
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
79
|
+
if (typeof value === "object") return Object.keys(value as Record<string, unknown>).length === 0;
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// The keys a resolved config actually holds a value for. Taken as a SET by describeDirConfig
|
|
84
|
+
// rather than the config object itself: the loader's type is an interface with named fields,
|
|
85
|
+
// which no amount of `Record<string, unknown>` will accept without a cast.
|
|
86
|
+
export function keysWithValue(resolved: object): Set<string> {
|
|
87
|
+
const entries: [string, unknown][] = Object.entries(resolved);
|
|
88
|
+
return new Set(entries.filter(([, value]) => !isEmptyValue(value)).map(([key]) => key));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// `raw` is the file as parsed; `kept` is which keys survived the loader (see keysWithValue).
|
|
92
|
+
export function describeDirConfig(raw: Record<string, unknown>, kept: ReadonlySet<string>): DirConfigSource {
|
|
93
|
+
const known = new Set<string>(DIR_CONFIG_KEYS);
|
|
94
|
+
const source: DirConfigSource = { applied: [], ignored: [], unknown: [] };
|
|
95
|
+
for (const key of Object.keys(raw)) {
|
|
96
|
+
if (!known.has(key)) source.unknown.push(key);
|
|
97
|
+
else if (kept.has(key)) source.applied.push(key);
|
|
98
|
+
else source.ignored.push(key);
|
|
99
|
+
}
|
|
100
|
+
return source;
|
|
101
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// This project's own GitHub repository, and the wire shape of the star endpoints that act on it.
|
|
2
|
+
// Both sides decide from these — the server builds the `gh api` path and types its response,
|
|
3
|
+
// the client parses that response — so they live here rather than as two copies.
|
|
4
|
+
import { isRecord } from "./isRecord.js";
|
|
5
|
+
|
|
6
|
+
export const GITHUB_REPO = "receptron/mulmoterminal";
|
|
7
|
+
|
|
8
|
+
// GitHub's "star a repository" endpoint for this repo: GET reports the current state, PUT stars it.
|
|
9
|
+
export const STAR_API_PATH = `/user/starred/${GITHUB_REPO}`;
|
|
10
|
+
|
|
11
|
+
// GET/POST /api/github/star. `null` means the server could not tell — no `gh`, not logged in,
|
|
12
|
+
// offline. That is a DIFFERENT answer from "not starred": one click cannot star anything in that
|
|
13
|
+
// state, so the client hides the button rather than offering one that would do nothing.
|
|
14
|
+
export interface GithubStarState {
|
|
15
|
+
starred: boolean | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Read a star state off an untrusted response body. Anything unparseable reads as "cannot tell",
|
|
19
|
+
// which is the same degradation as a failed request, so the caller needs no separate error path.
|
|
20
|
+
export function parseStarState(value: unknown): boolean | null {
|
|
21
|
+
return isRecord(value) && typeof value.starred === "boolean" ? value.starred : null;
|
|
22
|
+
}
|
|
@@ -50,3 +50,22 @@ export function clipboardActionFor(keymap: Keymap, e: ClipboardKeyEvent, hasSele
|
|
|
50
50
|
// to be reversed.
|
|
51
51
|
return action === "copy" && !hasSelection ? null : action;
|
|
52
52
|
}
|
|
53
|
+
|
|
54
|
+
// Copy-on-select: the text a settled selection should put on the clipboard, or null to leave the
|
|
55
|
+
// clipboard alone. `lastCopied` is what this terminal wrote last, so an unchanged selection is not
|
|
56
|
+
// written twice.
|
|
57
|
+
//
|
|
58
|
+
// Unlike the rules above, this one ends in a clipboard WRITE — no keystroke happened, so nothing in
|
|
59
|
+
// the browser was ever going to copy anything by itself. That is what makes the two skips here
|
|
60
|
+
// matter more than they look:
|
|
61
|
+
//
|
|
62
|
+
// - Whitespace only. Dragging across empty terminal space selects spaces, and silently replacing
|
|
63
|
+
// the user's clipboard with a run of them is this feature's worst failure. Anyone who really
|
|
64
|
+
// wants indentation still has the `copy` keymap action.
|
|
65
|
+
// - Unchanged text. A second identical write buys nothing and costs a duplicate entry in the OS
|
|
66
|
+
// clipboard history (Win+V), which is the same reason the caller waits for the selection to
|
|
67
|
+
// settle instead of writing on every onSelectionChange.
|
|
68
|
+
export function selectionToCopy(enabled: boolean, selection: string, lastCopied: string | null): string | null {
|
|
69
|
+
if (!enabled || selection.trim() === "" || selection === lastCopied) return null;
|
|
70
|
+
return selection;
|
|
71
|
+
}
|
|
@@ -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-BzSqp82H.js";import"./chunk-WYO6CB5R-BlzOfotS-CTDodsnw.js";import"./chunk-VAUOI2AC-CS9QJ4yz-DFjCsQc6.js";import{n as i,r as a,t as o}from"./chunk-MOJQB5TN-CMZRaeqt-CYfpp2V7.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-BzSqp82H.js";export{e as createArchitectureServices};
|