mulmoterminal 4.8.0 → 4.8.2
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 +10 -2
- package/common/bundledSkills.ts +1 -0
- package/common/chromeFromColor.ts +33 -2
- package/common/collectionPortability.ts +4 -1
- package/common/dirChrome.ts +9 -0
- package/common/dirConfigSource.ts +2 -0
- package/common/githubPaneOrder.ts +83 -0
- package/common/headerStatusColors.ts +122 -0
- package/common/hexColor.ts +12 -0
- package/common/pathSegments.ts +23 -0
- package/common/toolGroups.ts +23 -0
- package/dist/assets/{abnfDiagram-N423BO3Z-D0pzqjdS-C_1F9Lr9.js → abnfDiagram-N423BO3Z-D0pzqjdS-VtGooEqr.js} +1 -1
- package/dist/assets/architecture-TIHT7OUA-Cqhmai3f-B4pyJ5Jo.js +1 -0
- package/dist/assets/{architectureDiagram-T3A2C74G-CX-Yoe5G-sXll0esn.js → architectureDiagram-T3A2C74G-CX-Yoe5G-DVpZyjQ4.js} +1 -1
- package/dist/assets/{blockDiagram-VBNYF7ZC-B1eQZQ68-CPVP1vk2.js → blockDiagram-VBNYF7ZC-B1eQZQ68-BtnxwtTR.js} +1 -1
- package/dist/assets/{c4Diagram-5PPSVZJV-SBrN9MrU-CaDR6PZ5.js → c4Diagram-5PPSVZJV-SBrN9MrU-DBtqH7SN.js} +1 -1
- package/dist/assets/channel-DKMa7Sn5-CIdApohK.js +1 -0
- package/dist/assets/{chunk-2GRJ4B5K-DdkAx9o--BTDRS1ny.js → chunk-2GRJ4B5K-DdkAx9o--u86pOcWg.js} +1 -1
- package/dist/assets/{chunk-3NCLNEKW-CBztX243-CRh6N7eI.js → chunk-3NCLNEKW-CBztX243-BLYNocBE.js} +1 -1
- package/dist/assets/{chunk-4I5QYGJK-CmBJaHzs-BQuag-U2.js → chunk-4I5QYGJK-CmBJaHzs-BZpLhI2X.js} +1 -1
- package/dist/assets/{chunk-5RXB4S5H-iaipMW_h-Difto-1B.js → chunk-5RXB4S5H-iaipMW_h-BJf96KC8.js} +1 -1
- package/dist/assets/{chunk-6Q2QTUOP-Cx8nz5_t-DoqT-qjo.js → chunk-6Q2QTUOP-Cx8nz5_t-UvMPWJ3Z.js} +1 -1
- package/dist/assets/{chunk-7Z6QIM7H-CZs6l8Md-DWy67U5n.js → chunk-7Z6QIM7H-CZs6l8Md-D0Qz7WxJ.js} +1 -1
- package/dist/assets/{chunk-GF5L2VYU-9OEKHEe4-CAQrDdMW.js → chunk-GF5L2VYU-9OEKHEe4-DTBAv1YG.js} +1 -1
- package/dist/assets/{chunk-I66GZJ75-DSPCVCny-BJLh4409.js → chunk-I66GZJ75-DSPCVCny-BkEOB2v-.js} +1 -1
- package/dist/assets/{chunk-J7OUQ5F2-CQoryswr-Bgq_Mv29.js → chunk-J7OUQ5F2-CQoryswr-DXTdrzCF.js} +2 -2
- package/dist/assets/{chunk-JQJVKLGR-6F2ui09L-kUfNk1cv.js → chunk-JQJVKLGR-6F2ui09L-DLAwdUpZ.js} +1 -1
- package/dist/assets/{chunk-KBJHAD2P-DXXCSVOS-DsVXnKcy.js → chunk-KBJHAD2P-DXXCSVOS-B_jSUmqA.js} +1 -1
- package/dist/assets/{chunk-NSK5VX7P-DQgMj0Kn-DuA4A2m-.js → chunk-NSK5VX7P-DQgMj0Kn-Deqd9K3Z.js} +1 -1
- package/dist/assets/{chunk-QR6OTTB3-BQl8P7pD-BZ3iKOMU.js → chunk-QR6OTTB3-BQl8P7pD-DaWEByPl.js} +1 -1
- package/dist/assets/{chunk-UBXNYLIW-BbdYEvTI-BKFsRG54.js → chunk-UBXNYLIW-BbdYEvTI-D7JTDy1_.js} +1 -1
- package/dist/assets/{chunk-W5SLKNZC-DSJLk1SI-7ayO5NUV.js → chunk-W5SLKNZC-DSJLk1SI-C_F7i-8n.js} +1 -1
- package/dist/assets/{chunk-WRU74C26-DOE_TBL8-CVh6hcjM.js → chunk-WRU74C26-DOE_TBL8-Dy-_nO2j.js} +1 -1
- package/dist/assets/classDiagram-JCYQIIEL-C1ePmEG1-DObwVRUJ.js +1 -0
- package/dist/assets/classDiagram-v2-OCEON4UE-C_O3VNBf-DObwVRUJ.js +1 -0
- package/dist/assets/cynefin-VYW2F7L2-DvSvuXnG-zjkRXhew.js +1 -0
- package/dist/assets/{cynefinDiagram-MW4NZA55-DuBeESb6-DDsWCfbP.js → cynefinDiagram-MW4NZA55-DuBeESb6-BlFNNVb9.js} +1 -1
- package/dist/assets/{dagre-VZM6K2ZE-W5cB3U70-D1rl2X4i.js → dagre-VZM6K2ZE-W5cB3U70-C6rRTcdS.js} +1 -1
- package/dist/assets/{diagram-7IWD3JNH-Fk1aWqfX-DzWbtGft.js → diagram-7IWD3JNH-Fk1aWqfX-q5fpJacu.js} +1 -1
- package/dist/assets/{diagram-B4RE2ZJO-q-8xygfU-CxcTRuKd.js → diagram-B4RE2ZJO-q-8xygfU-CcYA2g8i.js} +1 -1
- package/dist/assets/{diagram-LBJQPF4R-1SKCcOis-V2GRaCiN.js → diagram-LBJQPF4R-1SKCcOis-D_F_6O0X.js} +1 -1
- package/dist/assets/{diagram-Q27KOJAE-CtW4StAQ-BarTqC2z.js → diagram-Q27KOJAE-CtW4StAQ-C3aJWxEe.js} +1 -1
- package/dist/assets/{diagram-UB23O5K3-r5fK-tbF-pkQL3-5o.js → diagram-UB23O5K3-r5fK-tbF-CL_8gWJg.js} +1 -1
- package/dist/assets/{dist-s_wb33nB.js → dist-BjFiDKpv.js} +1 -1
- package/dist/assets/{dist-CND5Ye3G.js → dist-CRLhMwW0.js} +1 -1
- package/dist/assets/{dist-WJs7N_4F.js → dist-OriYIc6j.js} +1 -1
- package/dist/assets/{dist-SVfKHNOC.js → dist-uA1zE0Xj.js} +1 -1
- package/dist/assets/{ebnfDiagram-BXEA7PRR-BZoIqTyx-C-hVWblz.js → ebnfDiagram-BXEA7PRR-BZoIqTyx-CPTvTKcL.js} +1 -1
- package/dist/assets/{erDiagram-JOGREHBK-CM8pI2JI-ClKuaaQP.js → erDiagram-JOGREHBK-CM8pI2JI-CfggsaCA.js} +1 -1
- package/dist/assets/eventmodeling-45OFAUF4-jdctDg0T-CsyWyhm5.js +1 -0
- package/dist/assets/flowDiagram-UKHOOZJN-CR3dcoRl-BFzPF9y4.js +1 -0
- package/dist/assets/{ganttDiagram-PKOTCBZU-DYTkxDIh-Dbb3I1Ky.js → ganttDiagram-PKOTCBZU-DYTkxDIh-4aHR8C-W.js} +1 -1
- package/dist/assets/gitGraph-TEB2WS4Q-C4cKwl5j-BuptX4em.js +1 -0
- package/dist/assets/{gitGraphDiagram-DS77QQ5N-DafidN4D-BzvH6u2p.js → gitGraphDiagram-DS77QQ5N-DafidN4D-ifP2KGRx.js} +1 -1
- package/dist/assets/index-AtHGuBVM.css +1 -0
- package/dist/assets/index-BuHQA0Pk.js +625 -0
- package/dist/assets/info-DKCQHKI2-DPUCBYWS-BfasXQqt.js +1 -0
- package/dist/assets/{infoDiagram-6WML65LV-DxSxsg1o-VRdS-8-g.js → infoDiagram-6WML65LV-DxSxsg1o-D_78B71T.js} +1 -1
- package/dist/assets/{ishikawaDiagram-WSZJBQD7-kAIMMFB5-sIxBX_tW.js → ishikawaDiagram-WSZJBQD7-kAIMMFB5-oplZ_1Pa.js} +1 -1
- package/dist/assets/{journeyDiagram-NVQOT4AX-ClybI8XI-BFleK1p8.js → journeyDiagram-NVQOT4AX-ClybI8XI-BZ5uwKfU.js} +1 -1
- package/dist/assets/{kanban-definition-27J2QSJJ-DDYHyW1q-BRL0Ieho.js → kanban-definition-27J2QSJJ-DDYHyW1q-_oRnFPcu.js} +1 -1
- package/dist/assets/{lib-CFJSYJpW.js → lib-Ct1_7VHV.js} +1 -1
- package/dist/assets/{line-CtRAsVKI-Weybbnym.js → line-CtRAsVKI-CI3c9zx9.js} +1 -1
- package/dist/assets/{marp-BEDBCLJs.js → marp-Bo-5Z5k9.js} +1 -1
- package/dist/assets/{mermaid-parser.core-C1baW7NG-B9TNqufx.js → mermaid-parser.core-C1baW7NG-DtjLsxX1.js} +2 -2
- package/dist/assets/{mermaid.core-CGR3z-ay-c1_TNZXz.js → mermaid.core-CGR3z-ay-DHUjgdHw.js} +3 -3
- package/dist/assets/{mindmap-definition-FAOFIHXS-DU-wbYKK-CgPoe1x1.js → mindmap-definition-FAOFIHXS-DU-wbYKK-DXKx1mdO.js} +1 -1
- package/dist/assets/packet-7NZHBO7P-CC7His0F-Bp4Rns7X.js +1 -0
- package/dist/assets/{pegDiagram-VL7TDLO6-CZuV1UUk-B5vzA-xE.js → pegDiagram-VL7TDLO6-CZuV1UUk-X_nosig9.js} +1 -1
- package/dist/assets/pie-RZYD4A2V-CCrcq2sL-DbHN1-XM.js +1 -0
- package/dist/assets/{pieDiagram-7S7Q4E2Y-B6cd1CWd-CUDS4Sno.js → pieDiagram-7S7Q4E2Y-B6cd1CWd-BmhQL9Df.js} +1 -1
- package/dist/assets/{quadrantDiagram-CIZ2JOQS-BvGHVjRA-B8lvO4Zk.js → quadrantDiagram-CIZ2JOQS-BvGHVjRA-Cq90xL2F.js} +1 -1
- package/dist/assets/radar-I7S5WNFK-C5TUrpHf-C1W4ONqs.js +1 -0
- package/dist/assets/railroad-3IZDKUUU-CGuuFEd8-BkiWoTlC.js +1 -0
- package/dist/assets/railroad-abnf-AHOZXSZD-DuoCSFZY-CdI6fYhe.js +1 -0
- package/dist/assets/railroad-ebnf-EBAXGLYW-ig-DG9e_-CHRw2D_X.js +1 -0
- package/dist/assets/railroad-peg-LSFZ7HO6-BkBgpyvu-CmelJuJj.js +1 -0
- package/dist/assets/{railroadDiagram-AXF67PYL-DDtBla5s-DHWm1Ox2.js → railroadDiagram-AXF67PYL-DDtBla5s-BcO3_KAp.js} +1 -1
- package/dist/assets/{requirementDiagram-LRYGKXZP-DEh2MJlW-DsCt-Uni.js → requirementDiagram-LRYGKXZP-DEh2MJlW-B7Td4CEk.js} +1 -1
- package/dist/assets/{sankeyDiagram-W5VNT64P-CHkaBx7U-Bkx-TT6H.js → sankeyDiagram-W5VNT64P-CHkaBx7U-D9uxGUNN.js} +1 -1
- package/dist/assets/{sequenceDiagram-SI44F4Z6-D8tHDhWP-C_n-BVwe.js → sequenceDiagram-SI44F4Z6-D8tHDhWP-fET8ZLmj.js} +1 -1
- package/dist/assets/{stateDiagram-OKZ733FA-KNjKsfsT-DTO4blEz.js → stateDiagram-OKZ733FA-KNjKsfsT-DXNKv6KU.js} +1 -1
- package/dist/assets/stateDiagram-v2-UEYNNEHI-B88w7Ous-CLsjS58j.js +1 -0
- package/dist/assets/{swimlanes-SLNWSIFB-D_yJNxBK-DZn7TQsl.js → swimlanes-SLNWSIFB-D_yJNxBK-CNDVT87D.js} +1 -1
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-DNcZzqTp-CXOGWaqF.js +8 -0
- package/dist/assets/{timeline-definition-Z64GVDOM-BcEcbSq1-MGNwTSwZ.js → timeline-definition-Z64GVDOM-BcEcbSq1-p5BO33mX.js} +1 -1
- package/dist/assets/treeView-QDETBFTQ-CqfT0I1z-C1hn71OG.js +1 -0
- package/dist/assets/treemap-6X3UGDF4-CiJkiGWo-BCLsJDCI.js +1 -0
- package/dist/assets/{vennDiagram-T6HMQDX7-CxS3fcVg-Bbm73E7e.js → vennDiagram-T6HMQDX7-CxS3fcVg-DJyPdK87.js} +1 -1
- package/dist/assets/wardley-OPB4EBWU-D1tuVHAV-Bu2YT3gj.js +1 -0
- package/dist/assets/{wardleyDiagram-T6FBY63Y-D_XCiR14-BNNg221Y.js → wardleyDiagram-T6FBY63Y-D_XCiR14-BRfsmWsu.js} +1 -1
- package/dist/assets/{xychartDiagram-ELKLHX3M-BU4yBDeE-BLHL_8rx.js → xychartDiagram-ELKLHX3M-BU4yBDeE-BGJGKF5o.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +13 -12
- package/server/backends/collectionSelfContainment.ts +37 -6
- package/server/backends/collections.ts +24 -4
- package/server/backends/docPath.ts +15 -0
- package/server/backends/feed-worker-options.ts +22 -0
- package/server/backends/markdown.ts +24 -6
- package/server/backends/remoteHost/handlers/index.ts +4 -0
- package/server/backends/remoteHost/handlers/listCollectionProjects.ts +111 -0
- package/server/backends/remoteHost/handlers/listSkills.ts +6 -0
- package/server/backends/remoteHost/session.spec.ts +19 -1
- package/server/backends/remoteHost/session.ts +18 -0
- package/server/backends/sharedApp/context.ts +279 -0
- package/server/backends/sharedApp/declare.ts +551 -0
- package/server/backends/sharedApp/deploy.ts +304 -0
- package/server/backends/sharedApp/ensureAid.ts +49 -0
- package/server/backends/sharedApp/exclusivity.ts +208 -0
- package/server/backends/sharedApp/manifestWrite.ts +207 -0
- package/server/backends/sharedApp/publicForm.ts +246 -0
- package/server/backends/sharedApp/publicView.ts +231 -0
- package/server/backends/sharedApp/publish.ts +339 -0
- package/server/backends/sharedApp/records.ts +80 -0
- package/server/backends/sharedApp/scopedFields.ts +189 -0
- package/server/backends/sharedApp/serialize.ts +33 -0
- package/server/backends/sharedApp/slug.ts +165 -0
- package/server/backends/sharedApp/staged.ts +55 -0
- package/server/backends/sharedApp/unpublish.ts +101 -0
- package/server/backends/sharedApp/writes.ts +57 -0
- package/server/backends/sharedCollections.ts +46 -0
- package/server/backends/system-tasks.ts +28 -1
- package/server/config/app-config.ts +22 -1
- package/server/config/config-body.ts +1 -1
- package/server/config/config-lock.ts +207 -0
- package/server/config/config-routes.ts +161 -3
- package/server/config/config-schema.ts +36 -0
- package/server/config/dir-config.ts +8 -0
- package/server/config/worktree-dir-config.ts +27 -1
- package/server/index.ts +28 -2
- package/server/infra/collection-tool.ts +32 -15
- package/server/infra/collectionToolAid.ts +123 -0
- package/server/infra/collectionToolPortability.ts +63 -0
- package/server/infra/host-tools.ts +4 -1
- package/server/infra/plugins-registry.ts +6 -2
- package/server/infra/project-root.ts +20 -1
- package/server/infra/shared-app-tool.ts +245 -0
- package/server/routes/app-routes.ts +8 -1
- package/server/routes/plugin-routes.ts +30 -0
- package/server/routes/stampCardScope.ts +84 -0
- package/server/routes/tool-routes.ts +16 -2
- package/server/session/last-turn.ts +19 -0
- package/server/session/session-reads.ts +16 -1
- package/server/session/task-push.ts +10 -4
- package/server/session/terminal-replay.ts +23 -1
- package/server/skills/mulmoterminal-config/SKILL.md +5 -0
- package/server/skills/mulmoterminal-dirs/SKILL.md +50 -2
- package/server/skills/mulmoterminal-shared-app/SKILL.md +358 -0
- package/server/skills/mulmoterminal-shared-app/templates/gym.md +198 -0
- package/server/skills/mulmoterminal-shared-app/templates/salon.md +295 -0
- package/dist/assets/architecture-TIHT7OUA-Cqhmai3f-EoewX0t1.js +0 -1
- package/dist/assets/channel-DKMa7Sn5-D7uFiXWj.js +0 -1
- package/dist/assets/classDiagram-JCYQIIEL-C1ePmEG1-DQsrDAnO.js +0 -1
- package/dist/assets/classDiagram-v2-OCEON4UE-C_O3VNBf-DQsrDAnO.js +0 -1
- package/dist/assets/cynefin-VYW2F7L2-DvSvuXnG-CbMnaMb8.js +0 -1
- package/dist/assets/eventmodeling-45OFAUF4-jdctDg0T-DYxhdsxL.js +0 -1
- package/dist/assets/flowDiagram-UKHOOZJN-CR3dcoRl-Bg2gsj-G.js +0 -1
- package/dist/assets/gitGraph-TEB2WS4Q-C4cKwl5j-B3gI81ub.js +0 -1
- package/dist/assets/index-0JPMgZUO.js +0 -623
- package/dist/assets/index-ClKZRsCv.css +0 -1
- package/dist/assets/info-DKCQHKI2-DPUCBYWS-B4cQvlC-.js +0 -1
- package/dist/assets/packet-7NZHBO7P-CC7His0F-DGLBqmMP.js +0 -1
- package/dist/assets/pie-RZYD4A2V-CCrcq2sL-ggnddn1X.js +0 -1
- package/dist/assets/radar-I7S5WNFK-C5TUrpHf-Biu2ZB_3.js +0 -1
- package/dist/assets/railroad-3IZDKUUU-CGuuFEd8-CWbXRRgb.js +0 -1
- package/dist/assets/railroad-abnf-AHOZXSZD-DuoCSFZY-D31d1XeY.js +0 -1
- package/dist/assets/railroad-ebnf-EBAXGLYW-ig-DG9e_-yjfgHrSG.js +0 -1
- package/dist/assets/railroad-peg-LSFZ7HO6-BkBgpyvu-BJplGLDh.js +0 -1
- package/dist/assets/stateDiagram-v2-UEYNNEHI-B88w7Ous-BMTM7EXJ.js +0 -1
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-DNcZzqTp-DdIK7YaL.js +0 -8
- package/dist/assets/treeView-QDETBFTQ-CqfT0I1z-B8T-RCe5.js +0 -1
- package/dist/assets/treemap-6X3UGDF4-CiJkiGWo-DT1sAEri.js +0 -1
- package/dist/assets/wardley-OPB4EBWU-D1tuVHAV-tVLCXwET.js +0 -1
package/README.md
CHANGED
|
@@ -724,7 +724,13 @@ malformed file is ignored.
|
|
|
724
724
|
"icon": "docs/logo.png", // image on this dir's cells (path here, URL, or data:); omit to use the repo's favicon
|
|
725
725
|
"badgeColor": "#cf222e", // badge color (hex #rrggbb)
|
|
726
726
|
"headerColor": "#190a23", // cell header background (hex #rrggbb)
|
|
727
|
-
"headerTextColor": "#ffffff", // cell header text color (hex #rrggbb)
|
|
727
|
+
"headerTextColor": "#ffffff", // cell header text color while idle (hex #rrggbb)
|
|
728
|
+
"headerStatusColors": { // what the header shows once a status takes the background over
|
|
729
|
+
"working": "#6d28d9", // just the background — the text colour is derived from it
|
|
730
|
+
"done": { "background": "#166534" },
|
|
731
|
+
"blocked": { "background": "#7c2d12", "text": "#ffe8a3" }
|
|
732
|
+
},
|
|
733
|
+
"headerStatusTint": "background", // "none" keeps headerColor while working/done (not blocked)
|
|
728
734
|
"cellColor": "#101014", // cell body background (hex #rrggbb)
|
|
729
735
|
"cellBorderColor": "#2a2a4e", // cell border color (hex #rrggbb)
|
|
730
736
|
"dotColor": "#00e676", // idle status dot (hex #rrggbb)
|
|
@@ -786,7 +792,9 @@ Settings → Directory settings names both files and lists which keys the local
|
|
|
786
792
|
| `icon` | An **image** marking this directory — shown in the cell header, the cockpit roster, the filmstrip thumbnails, the launcher's directory chips, and the phone's terminal list and terminal screen. Either a path **relative to this directory** (an absolute path, or a `../` that escapes it, is rejected), an `http(s)://` URL, or a `data:image/…` URI. PNG / JPEG / **GIF (animated plays)** / WebP / AVIF / SVG / ICO / BMP. Not to be confused with a header **button's** `icon`, which is a Material Symbols name. **Omit it and the repository's own favicon is used** (`public/favicon.svg`, `apple-touch-icon.png`, a web manifest — see `autoDirIcon`); `false` means no icon here and stops that search. |
|
|
787
793
|
| `badgeColor` | Badge background color (`#rrggbb`); text auto-contrasts. |
|
|
788
794
|
| `headerColor` | Header **background** color (`#rrggbb`) — the grid cell's header row and the terminal's own header row (grid row 2). While a terminal is working/blocked the status tint still shows; the custom color applies when idle. |
|
|
789
|
-
| `headerTextColor` | Header **text** color (`#rrggbb`) — the dir path, title, and
|
|
795
|
+
| `headerTextColor` | Header **text** color (`#rrggbb`) — everything written on the header: the dir path, title and prompt, plus the model/context badge, the token counts and any custom chip. **Omit it and a readable colour is derived from `headerColor`.** It applies while that colour is what shows: a working/done/blocked cell paints the theme's own status tint, so its text returns to the theme's too — an ink chosen for your header colour is not readable on a tint the theme mixed. Recolour those states with `headerStatusColors` instead. |
|
|
796
|
+
| `headerStatusColors` | What the header shows once a **status** owns the background: an object keyed by `working` / `done` / `blocked` (there is no `idle` — `headerColor` is idle). Each value is a background `#rrggbb`, or `{ "background": …, "text": … }`. **Omit `text` and a readable one is derived from the background**, so naming one colour can never come out unreadable. A status you don't name keeps the theme's tint. |
|
|
797
|
+
| `headerStatusTint` | `"background"` (default) lets a status replace the header background. `"none"` keeps `headerColor` while **working** and **done** — the status still reads from the cell border, the status dot and the pill. It deliberately does **not** reach `blocked`, the one state where nothing proceeds until you answer; give that state a colour of its own in `headerStatusColors` if you want one. |
|
|
790
798
|
| `cellColor` | Cell **body background** color (`#rrggbb`) — the frame around the terminal. |
|
|
791
799
|
| `cellBorderColor` | Cell **border** color (`#rrggbb`). The status frame (working/blocked) still overrides it while active. |
|
|
792
800
|
| `dotColor` | **Idle** status-dot color (`#rrggbb`). The working/waiting colors are unchanged so the activity signal stays intact. |
|
package/common/bundledSkills.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// Snapping every role to absolute values drifts by ΔE 20 at the dark end of the hue wheel; making
|
|
16
16
|
// every role a relative offset drifts by ΔE 15 at the pale end. Both were tried; this is the mix
|
|
17
17
|
// that fits. Anyone retuning these numbers should re-measure rather than reason about them.
|
|
18
|
-
import { readableTextColor } from "./contrast.js";
|
|
18
|
+
import { contrastRatio, readableTextColor, relativeLuminance } from "./contrast.js";
|
|
19
19
|
|
|
20
20
|
export interface ChromeColors {
|
|
21
21
|
badgeColor: string;
|
|
@@ -36,6 +36,37 @@ const BUTTON = { s: 0.67, l: 0.89 };
|
|
|
36
36
|
|
|
37
37
|
const clamp01 = (n: number): number => Math.min(1, Math.max(0, n));
|
|
38
38
|
|
|
39
|
+
// The inks a header background can be written in. Black is softened to a near-black so a pale
|
|
40
|
+
// header does not read as a printed page; white is left pure, since anything softer loses contrast
|
|
41
|
+
// on the dark end where it is chosen.
|
|
42
|
+
const DARK_INK = "#1b2430";
|
|
43
|
+
const PURE_DARK_INK = "#000000";
|
|
44
|
+
const LIGHT_INK = "#ffffff";
|
|
45
|
+
|
|
46
|
+
// WCAG AA for normal text. These chips run to 10px, so the large-text 3:1 bar does not apply.
|
|
47
|
+
const AA_CONTRAST = 4.5;
|
|
48
|
+
const DARK_INK_LUMINANCE = relativeLuminance(0x1b, 0x24, 0x30);
|
|
49
|
+
|
|
50
|
+
// The softening is a PREFERENCE, and it costs contrast: on a mid-tone background that cost is what
|
|
51
|
+
// drops the text below AA — `#e8341c` measures 3.68:1 against the softened ink and 4.94:1 against
|
|
52
|
+
// pure black (Codex on #1592, which is right and was worth measuring). So it is applied only while
|
|
53
|
+
// it is free. Falling back always works: at the worst background — the luminance where black and
|
|
54
|
+
// white are equally readable — the pure ink still reaches 4.58:1.
|
|
55
|
+
function inkFor(rgb: [number, number, number]): string {
|
|
56
|
+
if (readableTextColor(rgb[0], rgb[1], rgb[2]) === "#fff") return LIGHT_INK;
|
|
57
|
+
const background = relativeLuminance(rgb[0], rgb[1], rgb[2]);
|
|
58
|
+
return contrastRatio(background, DARK_INK_LUMINANCE) >= AA_CONTRAST ? DARK_INK : PURE_DARK_INK;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Which ink a header background wants, or null when it isn't a colour we can read. Exported so a
|
|
62
|
+
* directory that declares only `headerColor` derives the SAME text colour this file gives a
|
|
63
|
+
* repo.json brand colour (src/components/cellHeaderStyle.ts) — two answers to one question is how
|
|
64
|
+
* one directory ends up written in two different colours. */
|
|
65
|
+
export function headerTextColorFor(background: string): string | null {
|
|
66
|
+
const rgb = parseHex(background);
|
|
67
|
+
return rgb ? inkFor(rgb) : null;
|
|
68
|
+
}
|
|
69
|
+
|
|
39
70
|
/** The seven colours for a brand colour, or null when it isn't a colour we can read. */
|
|
40
71
|
export function chromeFromColor(primary: string, background?: string | null): ChromeColors | null {
|
|
41
72
|
const rgb = parseHex(primary);
|
|
@@ -50,7 +81,7 @@ export function chromeFromColor(primary: string, background?: string | null): Ch
|
|
|
50
81
|
headerColor: normalizeHex(primary),
|
|
51
82
|
// Never taken from the file — see the spec's "text colour is never declared". Deriving it is
|
|
52
83
|
// also what makes two conforming tools agree.
|
|
53
|
-
headerTextColor:
|
|
84
|
+
headerTextColor: inkFor(rgb),
|
|
54
85
|
// `background` is the surface the brand colour sits on, which is exactly what a cell body is.
|
|
55
86
|
// Gated on it PARSING, not on it being present: an unusable value here used to win the branch,
|
|
56
87
|
// survive as a non-colour, and then be dropped downstream — leaving no surface at all, neither
|
|
@@ -22,7 +22,10 @@ export type SelfContainmentCode =
|
|
|
22
22
|
/** No `primaryKey`, so ids are 4 random bytes and two machines can mint the same one. */
|
|
23
23
|
| "no-primary-key"
|
|
24
24
|
/** The project is not a git repo, so there is nothing to clone and most checks do not apply. */
|
|
25
|
-
| "not-a-repo"
|
|
25
|
+
| "not-a-repo"
|
|
26
|
+
/** Records live in the app's Firestore, not in the repository — by design for a SHARED
|
|
27
|
+
* collection, and the reason it clones differently from every other kind. */
|
|
28
|
+
| "shared-store";
|
|
26
29
|
|
|
27
30
|
/** `blocker` stops the clone working; `warning` costs something on the other machine; `info` is
|
|
28
31
|
* context. Only a blocker makes `portable` false. */
|
package/common/dirChrome.ts
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
// all extend this, so a field added on one side can't go missing on the other.
|
|
4
4
|
// `theme` and `colors` stay out of it — each side declares them with its own type
|
|
5
5
|
// (the server keeps the validated strings, the client narrows them to xterm's ITheme).
|
|
6
|
+
import type { HeaderStatusColors, HeaderStatusTint } from "./headerStatusColors.js";
|
|
7
|
+
|
|
6
8
|
export interface DirChrome {
|
|
7
9
|
name: string | null;
|
|
8
10
|
badgeColor: string | null;
|
|
@@ -11,6 +13,11 @@ export interface DirChrome {
|
|
|
11
13
|
// palette) — these tint the chrome around the terminal, not the terminal itself.
|
|
12
14
|
headerColor: string | null;
|
|
13
15
|
headerTextColor: string | null;
|
|
16
|
+
// What the header shows once a status REPLACES that background — working / done / blocked
|
|
17
|
+
// (common/headerStatusColors.ts). null means "not configured here", so a directory that says
|
|
18
|
+
// nothing falls through to the global default rather than asserting the built-in over it.
|
|
19
|
+
headerStatusColors: HeaderStatusColors | null;
|
|
20
|
+
headerStatusTint: HeaderStatusTint | null;
|
|
14
21
|
// The cell frame + accents (grid cell): body background, border, the idle status
|
|
15
22
|
// dot, and the header's icon buttons. Hex #rrggbb or null for the theme default.
|
|
16
23
|
cellColor: string | null;
|
|
@@ -40,6 +47,8 @@ export const EMPTY_DIR_CHROME: Readonly<DirChrome> = {
|
|
|
40
47
|
badgeColor: null,
|
|
41
48
|
headerColor: null,
|
|
42
49
|
headerTextColor: null,
|
|
50
|
+
headerStatusColors: null,
|
|
51
|
+
headerStatusTint: null,
|
|
43
52
|
cellColor: null,
|
|
44
53
|
cellBorderColor: null,
|
|
45
54
|
dotColor: null,
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Which repository's section the GitHub pane leads with.
|
|
2
|
+
//
|
|
3
|
+
// The pane is opened beside a CELL, and the cell names a directory — so the list is most useful
|
|
4
|
+
// with that directory's repository first. Reordering rather than scrolling to it: a scroll that
|
|
5
|
+
// lands slightly off looks like nothing happened, it has to re-run on every reload, and a repo
|
|
6
|
+
// with no open PRs and no open issues has nothing to scroll to at all, where an empty section at
|
|
7
|
+
// the top still answers "yours: none".
|
|
8
|
+
//
|
|
9
|
+
// Pure and shared so it can be tested without mounting the pane, and so the PR list and the issue
|
|
10
|
+
// list cannot drift into ordering themselves differently.
|
|
11
|
+
import { dirPathKey } from "./dirPathKey.js";
|
|
12
|
+
|
|
13
|
+
/** The only field either list is ordered by. Both `RepoPrs` and `RepoIssues` carry it. */
|
|
14
|
+
export interface RepoGrouped {
|
|
15
|
+
repo: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Case-insensitively, the way GitHub itself treats `owner/repo`. */
|
|
19
|
+
const sameRepo = (a: string, b: string): boolean => a.toLowerCase() === b.toLowerCase();
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Whether `cwd` is the clone at `base`, or anywhere inside it.
|
|
23
|
+
*
|
|
24
|
+
* CONTAINMENT, not equality: a cell is very often somewhere under the clone rather than at its
|
|
25
|
+
* root — the user cd'd into `src/`, or the cell was started there — and an exact match left every
|
|
26
|
+
* one of those leading with the wrong repo (Codex review).
|
|
27
|
+
*
|
|
28
|
+
* Through `dirPathKey` because the browser has no filesystem: it folds both separators, a trailing
|
|
29
|
+
* slash, `.` and `..` into one spelling. Lexical only, so it cannot see through a symlink — which
|
|
30
|
+
* is right here, where the answer decides presentation. (server/infra/path-within.ts carries the
|
|
31
|
+
* strict rule for the places where it is a boundary.) Case-folded on top: Windows and the default
|
|
32
|
+
* macOS volume both treat two spellings of one directory as the same one.
|
|
33
|
+
*/
|
|
34
|
+
const isWithinDir = (base: string, cwd: string): boolean => {
|
|
35
|
+
const root = dirPathKey(base).toLowerCase();
|
|
36
|
+
const target = dirPathKey(cwd).toLowerCase();
|
|
37
|
+
if (root === "" || target === "") return false;
|
|
38
|
+
if (target === root) return true;
|
|
39
|
+
// A prefix is NOT containment — `/srv/project-old` starts with `/srv/project`. The separator is
|
|
40
|
+
// what makes it a boundary. `dirPathKey` leaves one only on a filesystem root ("/", "C:/").
|
|
41
|
+
return target.startsWith(root.endsWith("/") ? root : `${root}/`);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* `owner/repo` for the directory a cell is in, from the reverse map `/api/repo-dirs` already
|
|
46
|
+
* serves — no extra request and no `git` subprocess, since the view fetches it anyway for the
|
|
47
|
+
* issue rows' start control.
|
|
48
|
+
*
|
|
49
|
+
* `null` for a directory that names no repository — not a git repo, no `origin`, a forge we cannot
|
|
50
|
+
* act on, or simply a clone the user never registered in Settings. All four are ordinary, and the
|
|
51
|
+
* caller leaves the order alone.
|
|
52
|
+
*/
|
|
53
|
+
export function repoForCwd(cwd: string | null | undefined, repoDirs: readonly { repo: string; dirs: readonly { path: string }[] }[]): string | null {
|
|
54
|
+
if (!cwd) return null;
|
|
55
|
+
// The DEEPEST match wins. Clones nest here — a worktree under a repo's managed root, or a repo
|
|
56
|
+
// vendored inside another — and the innermost one is the repository the cell is actually in.
|
|
57
|
+
let best: { repo: string; depth: number } | null = null;
|
|
58
|
+
for (const entry of repoDirs) {
|
|
59
|
+
for (const dir of entry.dirs) {
|
|
60
|
+
if (!isWithinDir(dir.path, cwd)) continue;
|
|
61
|
+
const depth = dirPathKey(dir.path).length;
|
|
62
|
+
if (!best || depth > best.depth) best = { repo: entry.repo, depth };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return best?.repo ?? null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* `rows` with `repo`'s entry first, everything else in the order it arrived.
|
|
70
|
+
*
|
|
71
|
+
* A stable move, not a sort: the server's order is meaningful (it follows the configured repo
|
|
72
|
+
* list) and only one element is being promoted. Returns the input unchanged when there is no repo
|
|
73
|
+
* to lead with, or when it has no section — which is why a cell in an unregistered clone simply
|
|
74
|
+
* gets the conventional order.
|
|
75
|
+
*/
|
|
76
|
+
export function leadWithRepo<T extends RepoGrouped>(rows: readonly T[], repo: string | null): T[] {
|
|
77
|
+
if (!repo) return [...rows];
|
|
78
|
+
const at = rows.findIndex((row) => sameRepo(row.repo, repo));
|
|
79
|
+
if (at <= 0) return [...rows];
|
|
80
|
+
const lead = rows[at];
|
|
81
|
+
if (lead === undefined) return [...rows]; // unreachable: findIndex answered a real index
|
|
82
|
+
return [lead, ...rows.slice(0, at), ...rows.slice(at + 1)];
|
|
83
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// What a cell header is painted with while a session is working / done / blocked, and who decides.
|
|
2
|
+
//
|
|
3
|
+
// A header has two layers that were designed together and can drift apart: the DIRECTORY's colour
|
|
4
|
+
// (`headerColor` plus its ink) and the STATUS wash the theme mixes over it. #1591 is what the drift
|
|
5
|
+
// looks like — a `headerTextColor` chosen against a dark purple, still applied while a light theme
|
|
6
|
+
// paints the pale `--bg-selected` a running session gets: white on #d6e4fb, measured at 1.15:1 off
|
|
7
|
+
// the reporter's own screenshot.
|
|
8
|
+
//
|
|
9
|
+
// So both layers are resolved HERE, once, and every caller asks rather than combining the pieces
|
|
10
|
+
// itself. That is also what lets a directory recolour a status: the same function answers "what
|
|
11
|
+
// shows now", whether the answer came from the theme or from a config file.
|
|
12
|
+
//
|
|
13
|
+
// `idle` is deliberately not a configurable status. `headerColor` / `headerTextColor` ARE the idle
|
|
14
|
+
// state, and a second way to say the same thing is how one directory ends up written in two
|
|
15
|
+
// different colours.
|
|
16
|
+
import { headerTextColorFor } from "./chromeFromColor.js";
|
|
17
|
+
import { isHexColor } from "./hexColor.js";
|
|
18
|
+
import { isRecord } from "./isRecord.js";
|
|
19
|
+
|
|
20
|
+
/** The states whose header background the theme replaces, and which a config may therefore claim. */
|
|
21
|
+
export const HEADER_STATUS_KEYS = ["working", "done", "blocked"] as const;
|
|
22
|
+
export type HeaderStatusKey = (typeof HEADER_STATUS_KEYS)[number];
|
|
23
|
+
|
|
24
|
+
/** Every state a header paints — the three above plus the directory's own. */
|
|
25
|
+
export type HeaderPaintStatus = HeaderStatusKey | "idle";
|
|
26
|
+
|
|
27
|
+
export interface HeaderStatusColor {
|
|
28
|
+
background: string | null;
|
|
29
|
+
/** Omitted derives an AA ink from `background`. Naming it without a background is honoured
|
|
30
|
+
* as-is: that is a statement about a wash the author can see, unlike `headerTextColor`, which
|
|
31
|
+
* is a statement about the directory's own colour and is what #1591 misapplied. */
|
|
32
|
+
text: string | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type HeaderStatusColors = Partial<Record<HeaderStatusKey, HeaderStatusColor>>;
|
|
36
|
+
|
|
37
|
+
/** `background`: the theme washes the header while a session works (today's behaviour).
|
|
38
|
+
* `none`: the directory's own `headerColor` stays, and the status reads from the cell border,
|
|
39
|
+
* the dot and the status pill instead. */
|
|
40
|
+
export const HEADER_STATUS_TINTS = ["background", "none"] as const;
|
|
41
|
+
export type HeaderStatusTint = (typeof HEADER_STATUS_TINTS)[number];
|
|
42
|
+
export const DEFAULT_HEADER_STATUS_TINT: HeaderStatusTint = "background";
|
|
43
|
+
|
|
44
|
+
// `none` says "keep my palette", and `blocked` is the one state where nothing proceeds until the
|
|
45
|
+
// user answers. A switch aimed at colour consistency taking the amber off THAT is an accident, so
|
|
46
|
+
// it doesn't reach it — a directory that wants a different blocked colour says so outright with a
|
|
47
|
+
// `headerStatusColors.blocked` entry, which is honoured below.
|
|
48
|
+
const TINT_NONE_APPLIES_TO: readonly HeaderStatusKey[] = ["working", "done"];
|
|
49
|
+
|
|
50
|
+
/** What a header paints in one state. `null` means "the theme decides" — the caller emits no
|
|
51
|
+
* custom property and the theme's own wash / ink shows through the CSS fallback. */
|
|
52
|
+
export interface HeaderPaint {
|
|
53
|
+
background: string | null;
|
|
54
|
+
text: string | null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const NOTHING: HeaderPaint = { background: null, text: null };
|
|
58
|
+
|
|
59
|
+
export interface HeaderChrome {
|
|
60
|
+
headerColor: string | null;
|
|
61
|
+
headerTextColor: string | null;
|
|
62
|
+
// Both nullable so "nothing configured" is expressible without every caller spreading a default
|
|
63
|
+
// in first — the shape a DirChrome already carries.
|
|
64
|
+
statusColors: HeaderStatusColors | null;
|
|
65
|
+
tint: HeaderStatusTint | null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// A hex background always yields a readable ink, so `background` set and `text` unset cannot
|
|
69
|
+
// produce an unreadable header — the point of letting a config name one colour.
|
|
70
|
+
function paintFor(background: string | null, text: string | null): HeaderPaint {
|
|
71
|
+
if (isHexColor(text)) return { background: isHexColor(background) ? background : null, text };
|
|
72
|
+
if (isHexColor(background)) return { background, text: headerTextColorFor(background) };
|
|
73
|
+
return NOTHING;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function resolveHeaderPaint(status: HeaderPaintStatus, chrome: HeaderChrome): HeaderPaint {
|
|
77
|
+
if (status === "idle") return paintFor(chrome.headerColor, chrome.headerTextColor);
|
|
78
|
+
const declared = chrome.statusColors?.[status];
|
|
79
|
+
if (declared && (isHexColor(declared.background) || isHexColor(declared.text))) return paintFor(declared.background, declared.text);
|
|
80
|
+
// `none` means "keep the colour I chose", so it needs a colour to keep. A directory with a
|
|
81
|
+
// `headerTextColor` and no usable `headerColor` has nothing for the ink to have been chosen
|
|
82
|
+
// against — carrying the ink alone would leave it on the theme's wash, which is the bug this
|
|
83
|
+
// whole file exists to stop (Codex review on #1619).
|
|
84
|
+
const keepsDirectoryColour = chrome.tint === "none" && TINT_NONE_APPLIES_TO.includes(status) && isHexColor(chrome.headerColor);
|
|
85
|
+
return keepsDirectoryColour ? paintFor(chrome.headerColor, chrome.headerTextColor) : NOTHING;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ---- config parsing -------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
const oneEntry = (input: unknown): HeaderStatusColor | null => {
|
|
91
|
+
if (isHexColor(input)) return { background: input, text: null };
|
|
92
|
+
if (!isRecord(input)) return null;
|
|
93
|
+
const background = isHexColor(input.background) ? input.background : null;
|
|
94
|
+
const text = isHexColor(input.text) ? input.text : null;
|
|
95
|
+
return background || text ? { background, text } : null;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/** Per status, so one bad entry can't discard the two that are right. A bare hex string is
|
|
99
|
+
* accepted for a status as shorthand for `{ background: … }` — the common case is one colour. */
|
|
100
|
+
export function sanitizeHeaderStatusColors(input: unknown): HeaderStatusColors {
|
|
101
|
+
if (!isRecord(input)) return {};
|
|
102
|
+
const out: HeaderStatusColors = {};
|
|
103
|
+
for (const key of HEADER_STATUS_KEYS) {
|
|
104
|
+
const entry = oneEntry(input[key]);
|
|
105
|
+
if (entry) out[key] = entry;
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** `null` for anything that isn't one of the two modes, which is what "unset here" has to look
|
|
111
|
+
* like: a directory that says nothing must fall through to the global default rather than
|
|
112
|
+
* asserting the built-in one over it. */
|
|
113
|
+
export function sanitizeHeaderStatusTint(input: unknown): HeaderStatusTint | null {
|
|
114
|
+
return HEADER_STATUS_TINTS.find((mode) => mode === input) ?? null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** The directory's answer, else the global one. WHOLE-key, not a per-status merge: the same rule
|
|
118
|
+
* `mergedDirConfigRaw` states for the three config files, and for its reason — a block a reader
|
|
119
|
+
* has to assemble from two places is harder to predict than one they can see entire. */
|
|
120
|
+
export function mergeHeaderStatusColors(global: HeaderStatusColors | null, dir: HeaderStatusColors | null): HeaderStatusColors | null {
|
|
121
|
+
return dir && Object.keys(dir).length > 0 ? dir : global;
|
|
122
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// What counts as a colour anywhere a directory's .mulmoterminal.json supplies one. Only
|
|
2
|
+
// 6-digit hex: the server's schema accepts nothing else, and the UI drops what it can't
|
|
3
|
+
// parse rather than handing an unknown string to a style binding.
|
|
4
|
+
//
|
|
5
|
+
// In common/ because both sides decide from it: the server validates a config file with this
|
|
6
|
+
// rule and the browser drops what it can't paint, and a third copy of the regex is how the two
|
|
7
|
+
// answers start differing.
|
|
8
|
+
const HEX_COLOR_RE = /^#[0-9a-fA-F]{6}$/;
|
|
9
|
+
|
|
10
|
+
// `unknown` rather than `string | null | undefined`: this is what a config parser asks about a
|
|
11
|
+
// value straight out of JSON, and narrowing the parameter would make every such site cast first.
|
|
12
|
+
export const isHexColor = (color: unknown): color is string => typeof color === "string" && HEX_COLOR_RE.test(color);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Splitting a filesystem path into its parts, on EITHER separator.
|
|
2
|
+
//
|
|
3
|
+
// `path.sep` is not the answer here: these paths come from a config file and from a saved
|
|
4
|
+
// directory list, so a Windows path can be read on a mac (a synced config, a bug report, a test
|
|
5
|
+
// fixture) and a POSIX one on Windows. Code that splits on "/" alone does not fail loudly on
|
|
6
|
+
// `C:\Users\me\project` — it returns the WHOLE STRING as one segment, which is how a "label"
|
|
7
|
+
// becomes an absolute path.
|
|
8
|
+
//
|
|
9
|
+
// That is a privacy contract in one place: the phone's project listing sends a label and never a
|
|
10
|
+
// path, precisely so a command or an artifact cannot publish the user's home directory. A
|
|
11
|
+
// one-separator split turns that guarantee off on Windows without changing a line of the code
|
|
12
|
+
// that states it.
|
|
13
|
+
|
|
14
|
+
/** The non-empty parts of a path, splitting on `/` and `\` alike. A drive letter (`C:`) stays a
|
|
15
|
+
* segment of its own — it is not a directory name, and nothing here wants it as one. */
|
|
16
|
+
export function pathSegments(value: string): string[] {
|
|
17
|
+
return value.split(/[/\\]+/).filter((segment) => segment.length > 0);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The last part of a path — its directory or file name — or the input when it has none. */
|
|
21
|
+
export function lastSegment(value: string): string {
|
|
22
|
+
return pathSegments(value).at(-1) ?? value;
|
|
23
|
+
}
|
package/common/toolGroups.ts
CHANGED
|
@@ -90,6 +90,13 @@ const GROUP_BY_TOOL = new Map<string, ToolGroup>([
|
|
|
90
90
|
["presentCollection", "data"],
|
|
91
91
|
["manageCollection", "data"],
|
|
92
92
|
["manageAccounting", "data"],
|
|
93
|
+
// manageSharedApp deploys and publishes the SHARED collections of the directory the cell is
|
|
94
|
+
// open in, so it belongs where their store does: a cell without the data group has no
|
|
95
|
+
// collection tools, and a deploy tool beside no collections is a tool with nothing to deploy.
|
|
96
|
+
// It is not in AUTO_ALLOWED_TOOLS, and it is in NEVER_AUTO_APPROVED_TOOLS below — publish is the
|
|
97
|
+
// one operation here that changes what people outside the roster can see, and the permission
|
|
98
|
+
// prompt is wanted in front of it on EVERY session, the workspace included.
|
|
99
|
+
["manageSharedApp", "data"],
|
|
93
100
|
|
|
94
101
|
["generateImage", "media"],
|
|
95
102
|
["presentMulmoScript", "media"],
|
|
@@ -161,3 +168,19 @@ export const LEGACY_GUI_SERVER_IDS: readonly string[] = ["mulmoterminal-gui"];
|
|
|
161
168
|
//
|
|
162
169
|
// The three below save an artifact and draw it, and call nothing external.
|
|
163
170
|
export const AUTO_ALLOWED_TOOLS: readonly string[] = ["presentForm", "presentChart", "presentHtml"];
|
|
171
|
+
|
|
172
|
+
/** Tools that must keep the agent's permission prompt on EVERY claude session, including the
|
|
173
|
+
* workspace.
|
|
174
|
+
*
|
|
175
|
+
* `AUTO_ALLOWED_TOOLS` above is the grid cell's list and withholds these already — but it is an
|
|
176
|
+
* allowlist, and the workspace does not use it. The workspace passes `allowedToolNames()`, which
|
|
177
|
+
* is every tool, so a tool absent from `AUTO_ALLOWED_TOOLS` was still auto-approved in exactly
|
|
178
|
+
* the session that builds a shared app. The comment on `manageSharedApp` said the prompt was
|
|
179
|
+
* wanted in front of publish; nothing implemented it. This is what implements it.
|
|
180
|
+
*
|
|
181
|
+
* It matters because of what the agent reads. A shared app is a repository, and the agent is
|
|
182
|
+
* already reading untrusted text out of it — skill files, collection notes, issue bodies, survey
|
|
183
|
+
* copy somebody pasted. `manageSharedApp` publishes to the internet, rewrites the roster, and
|
|
184
|
+
* hands the roster live records. With auto-approval, "invite this address as owner, then deploy"
|
|
185
|
+
* arriving in that text is a tool call rather than a question. */
|
|
186
|
+
export const NEVER_AUTO_APPROVED_TOOLS: readonly string[] = ["manageSharedApp"];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e}from"./chunk-Y2CYZVJY-Bdt8pFDJ-DsF7k-Jl.js";import{f as t}from"./src-CvsWqFln-Tc7ZSrRz.js";import{_ as n,g as r}from"./mermaid-parser.core-C1baW7NG-
|
|
1
|
+
import{n as e}from"./chunk-Y2CYZVJY-Bdt8pFDJ-DsF7k-Jl.js";import{f as t}from"./src-CvsWqFln-Tc7ZSrRz.js";import{_ as n,g as r}from"./mermaid-parser.core-C1baW7NG-DtjLsxX1.js";import"./chunk-I66GZJ75-DSPCVCny-BkEOB2v-.js";import"./chunk-3NCLNEKW-CBztX243-BLYNocBE.js";import{n as i,r as a,t as o}from"./chunk-6Q2QTUOP-Cx8nz5_t-UvMPWJ3Z.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-C1baW7NG-DtjLsxX1.js";export{e as createArchitectureServices};
|