mulmoterminal 7.0.0 → 7.2.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.
Files changed (494) hide show
  1. package/README.md +49 -16
  2. package/bin/cli-args.d.ts +1 -0
  3. package/bin/cli-args.js +5 -0
  4. package/bin/mulmoterminal.js +23 -3
  5. package/bin/ssh-hint.d.ts +3 -0
  6. package/bin/ssh-hint.js +32 -0
  7. package/bin/stop.d.ts +3 -1
  8. package/bin/stop.js +33 -6
  9. package/blueprints/ask/checks/replies.mjs +20 -11
  10. package/blueprints/ask/skills/keep/SKILL.md +1 -1
  11. package/blueprints/cloudflare/checks/deploy-check.sh +16 -0
  12. package/blueprints/cloudflare/checks/handover.sh +14 -0
  13. package/blueprints/cloudflare/checks/page-renders.sh +39 -0
  14. package/blueprints/cloudflare/checks/prerequisites.sh +6 -0
  15. package/blueprints/cloudflare/checks/scaffold.sh +14 -0
  16. package/blueprints/cloudflare/checks/security-report.sh +11 -0
  17. package/blueprints/cloudflare/checks/security.sh +52 -0
  18. package/blueprints/cloudflare/checks/serve.sh +24 -0
  19. package/blueprints/cloudflare/checks/smoke.sh +6 -0
  20. package/blueprints/cloudflare/checks/spec.sh +12 -0
  21. package/blueprints/cloudflare/checks/tests-pass.sh +7 -0
  22. package/blueprints/cloudflare/manifest.json +22 -0
  23. package/blueprints/cloudflare/plan.json +83 -0
  24. package/blueprints/cloudflare/security/checklist.md +10 -0
  25. package/blueprints/cloudflare/skills/api/SKILL.md +27 -0
  26. package/blueprints/cloudflare/skills/auth/SKILL.md +29 -0
  27. package/blueprints/cloudflare/skills/data/SKILL.md +22 -0
  28. package/blueprints/cloudflare/skills/deploy/SKILL.md +33 -0
  29. package/blueprints/cloudflare/skills/handover/SKILL.md +36 -0
  30. package/blueprints/cloudflare/skills/prerequisites/SKILL.md +23 -0
  31. package/blueprints/cloudflare/skills/run-check/SKILL.md +23 -0
  32. package/blueprints/cloudflare/skills/scaffold/SKILL.md +51 -0
  33. package/blueprints/cloudflare/skills/security/SKILL.md +76 -0
  34. package/blueprints/cloudflare/skills/spec/SKILL.md +31 -0
  35. package/blueprints/cloudflare/skills/ui/SKILL.md +22 -0
  36. package/blueprints/cloudflare/spec/overview.md +33 -0
  37. package/blueprints/docs/checks/chaff.sh +1 -1
  38. package/blueprints/docs/checks/drafts.mjs +9 -6
  39. package/blueprints/docs/checks/places.d.mts +2 -2
  40. package/blueprints/docs/checks/places.mjs +23 -21
  41. package/blueprints/docs/manifest.json +1 -0
  42. package/blueprints/docs/skills/workspace/SKILL.md +1 -1
  43. package/blueprints/firebase/checks/page-renders.sh +12 -1
  44. package/blueprints/firebase/manifest.json +1 -4
  45. package/blueprints/firebase/skills/scaffold/SKILL.md +2 -1
  46. package/blueprints/firebase/skills/spec/SKILL.md +1 -1
  47. package/blueprints/from-collection/checks/actions.sh +14 -8
  48. package/blueprints/from-collection/checks/compare.mjs +69 -4
  49. package/blueprints/from-collection/checks/d1-verify.mjs +98 -0
  50. package/blueprints/from-collection/checks/firestore-verify.mjs +2 -16
  51. package/blueprints/from-collection/checks/import-d1.sh +36 -0
  52. package/blueprints/from-collection/checks/import-supabase.sh +35 -0
  53. package/blueprints/from-collection/checks/import-verify.mjs +5 -30
  54. package/blueprints/from-collection/checks/supabase-verify.mjs +82 -0
  55. package/blueprints/from-collection/checks/test-titles.mjs +13 -4
  56. package/blueprints/from-collection/manifest.json +2 -2
  57. package/blueprints/from-collection/skills/acceptance-cloudflare/SKILL.md +28 -0
  58. package/blueprints/from-collection/skills/acceptance-supabase/SKILL.md +30 -0
  59. package/blueprints/from-collection/skills/actions-cloudflare/SKILL.md +51 -0
  60. package/blueprints/from-collection/skills/actions-supabase/SKILL.md +50 -0
  61. package/blueprints/from-collection/skills/import-cloudflare/SKILL.md +54 -0
  62. package/blueprints/from-collection/skills/import-production-cloudflare/SKILL.md +29 -0
  63. package/blueprints/from-collection/skills/import-production-supabase/SKILL.md +28 -0
  64. package/blueprints/from-collection/skills/import-supabase/SKILL.md +60 -0
  65. package/blueprints/from-collection/spec/conversion.md +32 -2
  66. package/blueprints/from-collection/steps.json +74 -0
  67. package/blueprints/local/skills/scaffold/SKILL.md +2 -0
  68. package/blueprints/local/skills/spec/SKILL.md +1 -1
  69. package/blueprints/polish/checks/feedback.mjs +3 -1
  70. package/blueprints/polish/checks/kind.d.mts +9 -0
  71. package/blueprints/polish/checks/kind.mjs +27 -0
  72. package/blueprints/polish/checks/named.d.mts +6 -0
  73. package/blueprints/polish/checks/named.mjs +49 -0
  74. package/blueprints/polish/checks/report.mjs +12 -0
  75. package/blueprints/polish/checks/targets.mjs +93 -9
  76. package/blueprints/polish/checks/targetsView.mjs +4 -1
  77. package/blueprints/polish/checks/viewpoints.d.mts +22 -0
  78. package/blueprints/polish/checks/viewpoints.mjs +113 -0
  79. package/blueprints/polish/hearing.json +28 -1
  80. package/blueprints/polish/kinds.json +15 -0
  81. package/blueprints/polish/presets/blog/blog.md +19 -0
  82. package/blueprints/polish/presets/houkoku/houkoku.md +19 -0
  83. package/blueprints/polish/presets.json +27 -0
  84. package/blueprints/polish/skills/polish/SKILL.md +32 -2
  85. package/blueprints/polish/skills/report/SKILL.md +11 -2
  86. package/blueprints/polish/skills/survey/SKILL.md +18 -0
  87. package/blueprints/polish/viewpoints.json +89 -0
  88. package/blueprints/product/manifest.json +2 -2
  89. package/blueprints/product/skills/acceptance-supabase/SKILL.md +30 -0
  90. package/blueprints/product/steps.json +10 -1
  91. package/blueprints/refactor/skills/report/SKILL.md +2 -1
  92. package/blueprints/refactor/skills/survey/SKILL.md +1 -1
  93. package/blueprints/review/skills/report/SKILL.md +1 -1
  94. package/blueprints/style/checks/configured.d.mts +3 -0
  95. package/blueprints/style/checks/configured.mjs +16 -0
  96. package/blueprints/style/checks/counter.mjs +10 -2
  97. package/blueprints/style/checks/rules.mjs +2 -2
  98. package/blueprints/style/skills/counter/SKILL.md +5 -2
  99. package/blueprints/style/skills/report/SKILL.md +1 -1
  100. package/blueprints/style/skills/rules/SKILL.md +7 -2
  101. package/blueprints/supabase/checks/advisors.mjs +24 -0
  102. package/blueprints/supabase/checks/client-secrets.mjs +145 -0
  103. package/blueprints/supabase/checks/deploy-check.sh +33 -0
  104. package/blueprints/supabase/checks/handover.sh +14 -0
  105. package/blueprints/supabase/checks/linked-url.mjs +37 -0
  106. package/blueprints/supabase/checks/local-stack.sh +4 -0
  107. package/blueprints/supabase/checks/migrations-applied.mjs +49 -0
  108. package/blueprints/supabase/checks/page-renders.sh +39 -0
  109. package/blueprints/supabase/checks/prerequisites.sh +8 -0
  110. package/blueprints/supabase/checks/scaffold.sh +14 -0
  111. package/blueprints/supabase/checks/security-probe.mjs +244 -0
  112. package/blueprints/supabase/checks/security-report.sh +11 -0
  113. package/blueprints/supabase/checks/security.sh +49 -0
  114. package/blueprints/supabase/checks/serve.sh +25 -0
  115. package/blueprints/supabase/checks/smoke.sh +11 -0
  116. package/blueprints/supabase/checks/spec.sh +12 -0
  117. package/blueprints/supabase/checks/supabase-cli.mjs +31 -0
  118. package/blueprints/supabase/checks/tests-pass.sh +8 -0
  119. package/blueprints/supabase/manifest.json +27 -0
  120. package/blueprints/supabase/plan.json +76 -0
  121. package/blueprints/supabase/security/checklist.md +10 -0
  122. package/blueprints/supabase/skills/auth/SKILL.md +35 -0
  123. package/blueprints/supabase/skills/data/SKILL.md +50 -0
  124. package/blueprints/supabase/skills/deploy/SKILL.md +44 -0
  125. package/blueprints/supabase/skills/handover/SKILL.md +39 -0
  126. package/blueprints/supabase/skills/prerequisites/SKILL.md +24 -0
  127. package/blueprints/supabase/skills/run-check/SKILL.md +24 -0
  128. package/blueprints/supabase/skills/scaffold/SKILL.md +62 -0
  129. package/blueprints/supabase/skills/security/SKILL.md +87 -0
  130. package/blueprints/supabase/skills/spec/SKILL.md +32 -0
  131. package/blueprints/supabase/skills/ui/SKILL.md +25 -0
  132. package/blueprints/supabase/spec/overview.md +35 -0
  133. package/blueprints/verify/checks/facts.mjs +36 -8
  134. package/blueprints/verify/checks/factsView.mjs +11 -2
  135. package/blueprints/verify/checks/rules.d.mts +1 -0
  136. package/blueprints/verify/checks/rules.mjs +24 -2
  137. package/blueprints/verify/skills/extract/SKILL.md +2 -1
  138. package/blueprints/verify/skills/report/SKILL.md +4 -2
  139. package/common/agentAccounts.ts +5 -0
  140. package/common/agentEntries.ts +63 -0
  141. package/common/appActions.ts +62 -0
  142. package/common/blueprint/manifest.ts +17 -0
  143. package/common/blueprint/personLanguage.ts +26 -0
  144. package/common/blueprint/run.ts +8 -0
  145. package/common/blueprint/specRevisionPrompt.ts +3 -0
  146. package/common/blueprint/stepPrompt.ts +5 -1
  147. package/common/customAgents.ts +6 -0
  148. package/common/dirConfigSaveReport.ts +34 -0
  149. package/common/fileBackups.ts +18 -0
  150. package/common/headerActions.ts +74 -0
  151. package/common/headerButtonEntries.ts +102 -0
  152. package/common/headerChips.ts +103 -0
  153. package/common/hiddenCharacters.ts +75 -0
  154. package/common/keyRecording.ts +27 -0
  155. package/common/keymap.ts +122 -2
  156. package/common/keymapPresets.ts +100 -0
  157. package/common/mdPreviewMessage.ts +51 -3
  158. package/common/paletteSearchBox.ts +11 -0
  159. package/common/previewCodeBlocks.ts +47 -0
  160. package/common/providerEntries.ts +99 -0
  161. package/common/remoteServer.ts +11 -0
  162. package/common/themeEntries.ts +79 -0
  163. package/common/whatsNew.ts +71 -0
  164. package/dist/assets/{abnfDiagram-O67JEVCF-D8GL0pEj.js → abnfDiagram-O67JEVCF-DfYKie1f.js} +1 -1
  165. package/dist/assets/architecture-7GRP2DOG-BuiSlwXB.js +1 -0
  166. package/dist/assets/{architectureDiagram-NJMV4G6O-kyzehsRR.js → architectureDiagram-NJMV4G6O-vsLzjzQx.js} +1 -1
  167. package/dist/assets/{blockDiagram-BEXU5L5S-9hqLban3.js → blockDiagram-BEXU5L5S-DssDT1TO.js} +1 -1
  168. package/dist/assets/{c4Diagram-YGBWAQC7-CUJxINHi.js → c4Diagram-YGBWAQC7-P0CrW8QT.js} +1 -1
  169. package/dist/assets/channel-BrsbZpfz.js +1 -0
  170. package/dist/assets/{chunk-3FUC2YCW-Dx91F6cc.js → chunk-3FUC2YCW-CvTm_ho-.js} +1 -1
  171. package/dist/assets/{chunk-5DYCD2WN-e-5CFr2i.js → chunk-5DYCD2WN-BVnziItI.js} +1 -1
  172. package/dist/assets/{chunk-742MDFTN-DLMZmirF.js → chunk-742MDFTN-CccjktcU.js} +1 -1
  173. package/dist/assets/{chunk-7INBJB4K-CwRDZro8.js → chunk-7INBJB4K-Bf0Shv2-.js} +1 -1
  174. package/dist/assets/{chunk-7M6MHVWA-Gm5DDkAY.js → chunk-7M6MHVWA-BtwnZqTt.js} +1 -1
  175. package/dist/assets/{chunk-7PRAP22T-CoSNjn-E.js → chunk-7PRAP22T-59aFrPRm.js} +1 -1
  176. package/dist/assets/{chunk-GTNCS2PH-CLXhqupL.js → chunk-GTNCS2PH-CT1kVgRW.js} +1 -1
  177. package/dist/assets/{chunk-GWA4HPMP-DX5sIYil.js → chunk-GWA4HPMP-C94KrS1K.js} +1 -1
  178. package/dist/assets/{chunk-MBY4JIJT-Dzr4kplc.js → chunk-MBY4JIJT-D9Nn3Npg.js} +1 -1
  179. package/dist/assets/{chunk-NETBCI7D-DWhxcXYA.js → chunk-NETBCI7D-C-jbM8IP.js} +1 -1
  180. package/dist/assets/{chunk-O7XYJQB3-DaFEDFVm.js → chunk-O7XYJQB3-BQYzKLhh.js} +2 -2
  181. package/dist/assets/{chunk-UA2S7LBM-bYQCj6FB.js → chunk-UA2S7LBM-D7ovzGAr.js} +1 -1
  182. package/dist/assets/{chunk-WEXAMYUT-DlYEsAVg.js → chunk-WEXAMYUT-BmlxOjG3.js} +1 -1
  183. package/dist/assets/{chunk-Z7XXMR3K-Bn6kE_dK.js → chunk-Z7XXMR3K-BSVBhxk7.js} +1 -1
  184. package/dist/assets/{chunk-ZIGJFQKS-CIZD8LBc.js → chunk-ZIGJFQKS-x3IHLAdW.js} +1 -1
  185. package/dist/assets/{classDiagram-v2-NBCMYWYE-CHcNkGSD.js → classDiagram-v2-NBCMYWYE-C6eb81l4.js} +1 -1
  186. package/dist/assets/{cynefin-OW5HDTMX-BgabrmIk.js → cynefin-OW5HDTMX-CM1PFDYP.js} +1 -1
  187. package/dist/assets/{cynefinDiagram-VND7K2PF-DwdVdS9K.js → cynefinDiagram-VND7K2PF-CxhcOZTy.js} +1 -1
  188. package/dist/assets/{dagre-6A5THRUB-DaqNJgIs.js → dagre-6A5THRUB-D5O9prrg.js} +1 -1
  189. package/dist/assets/{diagram-22UHCM2B-BRDs0wCw.js → diagram-22UHCM2B-h7R_GutV.js} +1 -1
  190. package/dist/assets/{diagram-3UASUU5V-BeXv9OSL.js → diagram-3UASUU5V-DPmc5Ai4.js} +1 -1
  191. package/dist/assets/{diagram-ATOU4E4O-D2JkNL-b.js → diagram-ATOU4E4O-COI1PRUd.js} +1 -1
  192. package/dist/assets/{diagram-CDSNMT55-DeYKsB8E.js → diagram-CDSNMT55-ehWBAAIO.js} +1 -1
  193. package/dist/assets/{diagram-MLGK6HIB-BHFLtv-n.js → diagram-MLGK6HIB-CXxA6YAe.js} +1 -1
  194. package/dist/assets/{diagram-MPIPVDR6-Brtp-_IQ.js → diagram-MPIPVDR6-B6ANSQ5G.js} +1 -1
  195. package/dist/assets/{dist-C1cqjsKD.js → dist-B-lTjmBE.js} +1 -1
  196. package/dist/assets/{dist-CeWYOYWG.js → dist-B-mUmtv8.js} +1 -1
  197. package/dist/assets/{dist-BTX-l2yD.js → dist-BnY3MUXM.js} +1 -1
  198. package/dist/assets/dist-Bx7SWRAa.js +199 -0
  199. package/dist/assets/{dist-BSQTHmad.js → dist-CPuq5mCf.js} +1 -1
  200. package/dist/assets/{dist-CWSTMA5n.js → dist-CQ6YfKVL.js} +1 -1
  201. package/dist/assets/{dist-C1s7S4KF.js → dist-CYrIpcYc.js} +1 -1
  202. package/dist/assets/{dist-B2YXJLwR.js → dist-DHjjPgmc.js} +5 -5
  203. package/dist/assets/dist-DeIGO4lT.js +9 -0
  204. package/dist/assets/{dist-SAdtDOGy.js → dist-Dn4ta1sK.js} +1 -1
  205. package/dist/assets/{dist-B0sdAm9J.js → dist-Z8kzHarM.js} +1 -1
  206. package/dist/assets/{ebnfDiagram-ZINNZB2B-CE9roUQp.js → ebnfDiagram-ZINNZB2B-ByHtvU2q.js} +1 -1
  207. package/dist/assets/{elk-276RUBZZ-DQHLfTBN.js → elk-276RUBZZ-BF00ltRi.js} +1 -1
  208. package/dist/assets/{erDiagram-OPXOYQCR-Be7__jhY.js → erDiagram-OPXOYQCR-nW_7R9Lb.js} +1 -1
  209. package/dist/assets/eventmodeling-NTZA5JFV-C74VV9Z5.js +1 -0
  210. package/dist/assets/flowDiagram-KWPJA3E3-dKJG6rhg.js +1 -0
  211. package/dist/assets/{ganttDiagram-FUAMR5RP-IKV0i-z0.js → ganttDiagram-FUAMR5RP-DaWxnbRm.js} +1 -1
  212. package/dist/assets/{gitGraph-4MIJSDKK-EofSfZtF.js → gitGraph-4MIJSDKK-F7iDxaID.js} +1 -1
  213. package/dist/assets/{gitGraphDiagram-X574FWY7-DaljLxKK.js → gitGraphDiagram-X574FWY7-D9fTMDaK.js} +1 -1
  214. package/dist/assets/{index-0Wsb2_IF.js → index-ffQ8fSEK.js} +366 -359
  215. package/dist/assets/index-xE6PHMoe.css +1 -0
  216. package/dist/assets/{info-A6RAGUB7-B6ck_7jX.js → info-A6RAGUB7-BIvkp14y.js} +1 -1
  217. package/dist/assets/{infoDiagram-VRGFBTTK-C8jMc5nJ.js → infoDiagram-VRGFBTTK-CF3pSadX.js} +1 -1
  218. package/dist/assets/{ishikawaDiagram-OU5B5YK6-BOM9dJ1W.js → ishikawaDiagram-OU5B5YK6-CnStwZlQ.js} +1 -1
  219. package/dist/assets/javascript-CppQSorr.js +1 -0
  220. package/dist/assets/{journeyDiagram-ZHPQQLJL-DIYby1k-.js → journeyDiagram-ZHPQQLJL-X6FpP2mU.js} +1 -1
  221. package/dist/assets/{kanban-definition-PNTS6WVX-CAmlLeaS.js → kanban-definition-PNTS6WVX-DGW7MkGx.js} +1 -1
  222. package/dist/assets/{line-DLTV2ptB.js → line-G8J9PhiP.js} +1 -1
  223. package/dist/assets/{marp-BASsSHAw.js → marp-BZuPB_AE.js} +61 -61
  224. package/dist/assets/{mermaid-parser.core-CW3k1W-u.js → mermaid-parser.core-DlsoX-YC.js} +3 -3
  225. package/dist/assets/{mermaid.core-B_zPtmsr.js → mermaid.core-DPC-93k6.js} +5 -5
  226. package/dist/assets/{mindmap-definition-NLK3R4M7-Ji-mQMX0.js → mindmap-definition-NLK3R4M7-D-K5bSnd.js} +1 -1
  227. package/dist/assets/{packet-AYTQ26CC-MR7J7hYw.js → packet-AYTQ26CC-CmDsNYF8.js} +1 -1
  228. package/dist/assets/{pegDiagram-GJSIUBJH-D_wwEoYb.js → pegDiagram-GJSIUBJH-3rBm1VWr.js} +1 -1
  229. package/dist/assets/{pie-WAS4IAKB-DNuGDXby.js → pie-WAS4IAKB-DwWpCstp.js} +1 -1
  230. package/dist/assets/{pieDiagram-5QR66LMP-C1YCt7xK.js → pieDiagram-5QR66LMP-IolT-z46.js} +1 -1
  231. package/dist/assets/punycode.es6-BGceFjA3.js +1 -0
  232. package/dist/assets/{quadrantDiagram-O4NWA36T-DN-d9LoE.js → quadrantDiagram-O4NWA36T-Biq9fhdq.js} +1 -1
  233. package/dist/assets/{radar-RG4KPBEZ-dW6gQx6l.js → radar-RG4KPBEZ-DH0gPMqI.js} +1 -1
  234. package/dist/assets/{railroad-74A4TZTK-DnjwnHPx.js → railroad-74A4TZTK-SDx9VkwS.js} +1 -1
  235. package/dist/assets/railroad-abnf-HS5TGJTU-CCSPvBtI.js +1 -0
  236. package/dist/assets/railroad-ebnf-LZEXJU2U-DTWbjX18.js +1 -0
  237. package/dist/assets/railroad-peg-WCYAUIDC--dNY7xHy.js +1 -0
  238. package/dist/assets/{railroadDiagram-XR7U4H2S-DdfYiy6T.js → railroadDiagram-XR7U4H2S-kQzwizD6.js} +1 -1
  239. package/dist/assets/{requirementDiagram-PLB6GJNP-BzIEk2VN.js → requirementDiagram-PLB6GJNP-B3spDS6j.js} +1 -1
  240. package/dist/assets/{sankeyDiagram-IPEJSGJF-D_zpgGm7.js → sankeyDiagram-IPEJSGJF-DkXWUHlz.js} +1 -1
  241. package/dist/assets/{sequenceDiagram-PO4LG4MO-WLxiEHkc.js → sequenceDiagram-PO4LG4MO-Dt80gEIt.js} +1 -1
  242. package/dist/assets/{stateDiagram-v2-GCMORJYK-DYPJzs6R.js → stateDiagram-v2-GCMORJYK-CG5xmXxG.js} +1 -1
  243. package/dist/assets/{swimlanes-2SLR337P-C6VX1K4R.js → swimlanes-2SLR337P-B3S4pJ9E.js} +1 -1
  244. package/dist/assets/swimlanesDiagram-TC7HE7FX-5BPq1RA-.js +8 -0
  245. package/dist/assets/{timeline-definition-EJHVYXUP-DqVxaMbg.js → timeline-definition-EJHVYXUP-DuKW3pM-.js} +1 -1
  246. package/dist/assets/{treeView-Q6P3EWNA-CswdkPra.js → treeView-Q6P3EWNA-CeeuwKmy.js} +1 -1
  247. package/dist/assets/{treemap-WGGIJYW6-DlB2H8xd.js → treemap-WGGIJYW6-CFK6V3lz.js} +1 -1
  248. package/dist/assets/{usecaseDiagram-POWQR4AR-BkdJels6.js → usecaseDiagram-POWQR4AR-CYbKz4kt.js} +1 -1
  249. package/dist/assets/{vennDiagram-UO4OBE2U-Cj8A--rK.js → vennDiagram-UO4OBE2U-De8JmnbH.js} +1 -1
  250. package/dist/assets/vitesse-dark-BZCL-v6S.js +1 -0
  251. package/dist/assets/vitesse-light-VbXTXTou.js +1 -0
  252. package/dist/assets/{wardley-WFR3VGLG-B0WzVCNm.js → wardley-WFR3VGLG-BUvlimn0.js} +1 -1
  253. package/dist/assets/{wardleyDiagram-VNRHLVJA-BtKepEjT.js → wardleyDiagram-VNRHLVJA-e3jrcLF3.js} +1 -1
  254. package/dist/assets/{xychartDiagram-PMCCYNJV-BIVIXtl9.js → xychartDiagram-PMCCYNJV-C1Q2Y2pt.js} +1 -1
  255. package/dist/index.html +3 -3
  256. package/docs/guide/en/v1.10.0.md +51 -0
  257. package/docs/guide/en/v1.11.0.md +44 -0
  258. package/docs/guide/en/v1.11.1.md +35 -0
  259. package/docs/guide/en/v1.12.0.md +46 -0
  260. package/docs/guide/en/v1.7.1.md +24 -0
  261. package/docs/guide/en/v1.7.2.md +27 -0
  262. package/docs/guide/en/v1.8.0.md +57 -0
  263. package/docs/guide/en/v1.9.0.md +31 -0
  264. package/docs/guide/en/v1.9.1.md +40 -0
  265. package/docs/guide/en/v1.9.2.md +27 -0
  266. package/docs/guide/en/v2.0.0.md +198 -0
  267. package/docs/guide/en/v2.1.0.md +159 -0
  268. package/docs/guide/en/v2.1.1.md +77 -0
  269. package/docs/guide/en/v2.2.0.md +150 -0
  270. package/docs/guide/en/v2.3.0.md +117 -0
  271. package/docs/guide/en/v2.4.0.md +135 -0
  272. package/docs/guide/en/v2.5.0.md +120 -0
  273. package/docs/guide/en/v2.5.1.md +108 -0
  274. package/docs/guide/en/v2.5.2.md +113 -0
  275. package/docs/guide/en/v2.5.3.md +158 -0
  276. package/docs/guide/en/v2.6.0.md +194 -0
  277. package/docs/guide/en/v2.7.0.md +228 -0
  278. package/docs/guide/en/v2.8.0.md +293 -0
  279. package/docs/guide/en/v2.9.0.md +207 -0
  280. package/docs/guide/en/v2.9.1.md +84 -0
  281. package/docs/guide/en/v3.0.0.md +121 -0
  282. package/docs/guide/en/v4.0.0.md +142 -0
  283. package/docs/guide/en/v4.1.0.md +114 -0
  284. package/docs/guide/en/v4.1.1.md +109 -0
  285. package/docs/guide/en/v4.10.0.md +99 -0
  286. package/docs/guide/en/v4.10.1.md +98 -0
  287. package/docs/guide/en/v4.11.0.md +177 -0
  288. package/docs/guide/en/v4.12.0.md +134 -0
  289. package/docs/guide/en/v4.13.0.md +106 -0
  290. package/docs/guide/en/v4.14.0.md +143 -0
  291. package/docs/guide/en/v4.15.0.md +131 -0
  292. package/docs/guide/en/v4.16.0.md +90 -0
  293. package/docs/guide/en/v4.16.1.md +110 -0
  294. package/docs/guide/en/v4.17.0.md +127 -0
  295. package/docs/guide/en/v4.18.0.md +107 -0
  296. package/docs/guide/en/v4.19.0.md +150 -0
  297. package/docs/guide/en/v4.2.0.md +201 -0
  298. package/docs/guide/en/v4.20.0.md +142 -0
  299. package/docs/guide/en/v4.21.0.md +159 -0
  300. package/docs/guide/en/v4.22.0.md +122 -0
  301. package/docs/guide/en/v4.23.0.md +113 -0
  302. package/docs/guide/en/v4.24.0.md +71 -0
  303. package/docs/guide/en/v4.25.0.md +113 -0
  304. package/docs/guide/en/v4.26.0.md +113 -0
  305. package/docs/guide/en/v4.27.0.md +125 -0
  306. package/docs/guide/en/v4.3.0.md +157 -0
  307. package/docs/guide/en/v4.3.1.md +73 -0
  308. package/docs/guide/en/v4.4.0.md +261 -0
  309. package/docs/guide/en/v4.5.0.md +241 -0
  310. package/docs/guide/en/v4.5.1.md +137 -0
  311. package/docs/guide/en/v4.6.0.md +175 -0
  312. package/docs/guide/en/v4.6.1.md +177 -0
  313. package/docs/guide/en/v4.7.0.md +179 -0
  314. package/docs/guide/en/v4.7.1.md +120 -0
  315. package/docs/guide/en/v4.7.2.md +195 -0
  316. package/docs/guide/en/v4.7.3.md +148 -0
  317. package/docs/guide/en/v4.7.4.md +123 -0
  318. package/docs/guide/en/v4.7.5.md +126 -0
  319. package/docs/guide/en/v4.7.6.md +81 -0
  320. package/docs/guide/en/v4.8.0.md +187 -0
  321. package/docs/guide/en/v4.8.1.md +149 -0
  322. package/docs/guide/en/v4.8.2.md +202 -0
  323. package/docs/guide/en/v4.8.3.md +97 -0
  324. package/docs/guide/en/v4.8.4.md +115 -0
  325. package/docs/guide/en/v4.8.5.md +163 -0
  326. package/docs/guide/en/v4.9.0.md +100 -0
  327. package/docs/guide/en/v5.0.0.md +106 -0
  328. package/docs/guide/en/v5.0.1.md +69 -0
  329. package/docs/guide/en/v5.1.0.md +110 -0
  330. package/docs/guide/en/v5.2.0.md +108 -0
  331. package/docs/guide/en/v5.3.0.md +107 -0
  332. package/docs/guide/en/v5.4.0.md +131 -0
  333. package/docs/guide/en/v5.5.0.md +71 -0
  334. package/docs/guide/en/v5.6.0.md +58 -0
  335. package/docs/guide/en/v5.7.0.md +60 -0
  336. package/docs/guide/en/v5.8.0.md +94 -0
  337. package/docs/guide/en/v6.0.0.md +44 -0
  338. package/docs/guide/en/v6.1.0.md +45 -0
  339. package/docs/guide/en/v6.2.0.md +65 -0
  340. package/docs/guide/en/v6.3.0.md +65 -0
  341. package/docs/guide/en/v6.4.0.md +85 -0
  342. package/docs/guide/en/v6.5.0.md +62 -0
  343. package/docs/guide/en/v6.6.0.md +133 -0
  344. package/docs/guide/en/v6.7.0.md +91 -0
  345. package/docs/guide/en/v6.8.0.md +235 -0
  346. package/docs/guide/en/v7.0.0.md +117 -0
  347. package/docs/guide/en/v7.1.0.md +106 -0
  348. package/docs/guide/en/v7.2.0.md +182 -0
  349. package/docs/guide/ja/v1.10.0.md +51 -0
  350. package/docs/guide/ja/v1.11.0.md +44 -0
  351. package/docs/guide/ja/v1.11.1.md +37 -0
  352. package/docs/guide/ja/v1.12.0.md +48 -0
  353. package/docs/guide/ja/v1.7.1.md +26 -0
  354. package/docs/guide/ja/v1.7.2.md +29 -0
  355. package/docs/guide/ja/v1.8.0.md +57 -0
  356. package/docs/guide/ja/v1.9.0.md +33 -0
  357. package/docs/guide/ja/v1.9.1.md +40 -0
  358. package/docs/guide/ja/v1.9.2.md +31 -0
  359. package/docs/guide/ja/v2.0.0.md +173 -0
  360. package/docs/guide/ja/v2.1.0.md +132 -0
  361. package/docs/guide/ja/v2.1.1.md +62 -0
  362. package/docs/guide/ja/v2.2.0.md +150 -0
  363. package/docs/guide/ja/v2.3.0.md +108 -0
  364. package/docs/guide/ja/v2.4.0.md +130 -0
  365. package/docs/guide/ja/v2.5.0.md +115 -0
  366. package/docs/guide/ja/v2.5.1.md +103 -0
  367. package/docs/guide/ja/v2.5.2.md +112 -0
  368. package/docs/guide/ja/v2.5.3.md +155 -0
  369. package/docs/guide/ja/v2.6.0.md +192 -0
  370. package/docs/guide/ja/v2.7.0.md +228 -0
  371. package/docs/guide/ja/v2.8.0.md +284 -0
  372. package/docs/guide/ja/v2.9.0.md +198 -0
  373. package/docs/guide/ja/v2.9.1.md +80 -0
  374. package/docs/guide/ja/v3.0.0.md +116 -0
  375. package/docs/guide/ja/v4.0.0.md +139 -0
  376. package/docs/guide/ja/v4.1.0.md +111 -0
  377. package/docs/guide/ja/v4.1.1.md +107 -0
  378. package/docs/guide/ja/v4.10.0.md +98 -0
  379. package/docs/guide/ja/v4.10.1.md +95 -0
  380. package/docs/guide/ja/v4.11.0.md +174 -0
  381. package/docs/guide/ja/v4.12.0.md +135 -0
  382. package/docs/guide/ja/v4.13.0.md +101 -0
  383. package/docs/guide/ja/v4.14.0.md +140 -0
  384. package/docs/guide/ja/v4.15.0.md +126 -0
  385. package/docs/guide/ja/v4.16.0.md +86 -0
  386. package/docs/guide/ja/v4.16.1.md +106 -0
  387. package/docs/guide/ja/v4.17.0.md +125 -0
  388. package/docs/guide/ja/v4.18.0.md +102 -0
  389. package/docs/guide/ja/v4.19.0.md +148 -0
  390. package/docs/guide/ja/v4.2.0.md +196 -0
  391. package/docs/guide/ja/v4.20.0.md +135 -0
  392. package/docs/guide/ja/v4.21.0.md +156 -0
  393. package/docs/guide/ja/v4.22.0.md +120 -0
  394. package/docs/guide/ja/v4.23.0.md +109 -0
  395. package/docs/guide/ja/v4.24.0.md +69 -0
  396. package/docs/guide/ja/v4.25.0.md +111 -0
  397. package/docs/guide/ja/v4.26.0.md +110 -0
  398. package/docs/guide/ja/v4.27.0.md +121 -0
  399. package/docs/guide/ja/v4.3.0.md +157 -0
  400. package/docs/guide/ja/v4.3.1.md +72 -0
  401. package/docs/guide/ja/v4.4.0.md +256 -0
  402. package/docs/guide/ja/v4.5.0.md +235 -0
  403. package/docs/guide/ja/v4.5.1.md +140 -0
  404. package/docs/guide/ja/v4.6.0.md +172 -0
  405. package/docs/guide/ja/v4.6.1.md +174 -0
  406. package/docs/guide/ja/v4.7.0.md +174 -0
  407. package/docs/guide/ja/v4.7.1.md +120 -0
  408. package/docs/guide/ja/v4.7.2.md +196 -0
  409. package/docs/guide/ja/v4.7.3.md +143 -0
  410. package/docs/guide/ja/v4.7.4.md +119 -0
  411. package/docs/guide/ja/v4.7.5.md +124 -0
  412. package/docs/guide/ja/v4.7.6.md +78 -0
  413. package/docs/guide/ja/v4.8.0.md +190 -0
  414. package/docs/guide/ja/v4.8.1.md +147 -0
  415. package/docs/guide/ja/v4.8.2.md +198 -0
  416. package/docs/guide/ja/v4.8.3.md +95 -0
  417. package/docs/guide/ja/v4.8.4.md +116 -0
  418. package/docs/guide/ja/v4.8.5.md +162 -0
  419. package/docs/guide/ja/v4.9.0.md +99 -0
  420. package/docs/guide/ja/v5.0.0.md +104 -0
  421. package/docs/guide/ja/v5.0.1.md +69 -0
  422. package/docs/guide/ja/v5.1.0.md +106 -0
  423. package/docs/guide/ja/v5.2.0.md +106 -0
  424. package/docs/guide/ja/v5.3.0.md +103 -0
  425. package/docs/guide/ja/v5.4.0.md +125 -0
  426. package/docs/guide/ja/v5.5.0.md +69 -0
  427. package/docs/guide/ja/v5.6.0.md +57 -0
  428. package/docs/guide/ja/v5.7.0.md +60 -0
  429. package/docs/guide/ja/v5.8.0.md +75 -0
  430. package/docs/guide/ja/v6.0.0.md +44 -0
  431. package/docs/guide/ja/v6.1.0.md +45 -0
  432. package/docs/guide/ja/v6.2.0.md +65 -0
  433. package/docs/guide/ja/v6.3.0.md +42 -0
  434. package/docs/guide/ja/v6.4.0.md +72 -0
  435. package/docs/guide/ja/v6.5.0.md +49 -0
  436. package/docs/guide/ja/v6.6.0.md +101 -0
  437. package/docs/guide/ja/v6.7.0.md +88 -0
  438. package/docs/guide/ja/v6.8.0.md +234 -0
  439. package/docs/guide/ja/v7.0.0.md +114 -0
  440. package/docs/guide/ja/v7.1.0.md +100 -0
  441. package/docs/guide/ja/v7.2.0.md +174 -0
  442. package/package.json +6 -2
  443. package/server/backends/scheduler-boot.ts +27 -24
  444. package/server/backends/scheduler.ts +81 -3
  445. package/server/blueprint/executor.ts +25 -1
  446. package/server/blueprint/packs.ts +13 -7
  447. package/server/blueprint/routes.ts +20 -3
  448. package/server/blueprint/wiring.ts +1 -0
  449. package/server/config/agent-entry-routes.ts +123 -0
  450. package/server/config/app-config.ts +37 -15
  451. package/server/config/config-reload.ts +60 -0
  452. package/server/config/config-routes.ts +105 -23
  453. package/server/config/config-schema.ts +23 -8
  454. package/server/config/dir-config.ts +7 -3
  455. package/server/config/header-button-routes.ts +56 -0
  456. package/server/config/header-chip-routes.ts +56 -0
  457. package/server/config/header-config.ts +5 -6
  458. package/server/config/keymap-binding-route.ts +52 -0
  459. package/server/config/system-task-settings.ts +21 -0
  460. package/server/config/theme-entry-routes.ts +60 -0
  461. package/server/files/backup-store.ts +51 -0
  462. package/server/files/codeHighlight.ts +151 -0
  463. package/server/files/files-browse.ts +128 -25
  464. package/server/files/files-tree-routes.ts +102 -0
  465. package/server/files/mdPreviewEmbed.ts +2 -103
  466. package/server/files/mdPreviewReporter.ts +199 -0
  467. package/server/files/pathContainment.ts +10 -0
  468. package/server/files/previewCodeFence.ts +20 -0
  469. package/server/files/renderedDoc.ts +64 -5
  470. package/server/files/tree-ops.ts +143 -0
  471. package/server/index.ts +1 -1
  472. package/server/infra/on-listening.ts +6 -3
  473. package/server/routes/app-routes.ts +10 -1
  474. package/server/routes/dir-routes.ts +7 -0
  475. package/server/session/reap-schedule.ts +42 -14
  476. package/server/skills/mulmoterminal-config/SKILL.md +56 -18
  477. package/server/skills/mulmoterminal-dirs/SKILL.md +3 -1
  478. package/server/skills/mulmoterminal-header/SKILL.md +35 -8
  479. package/server/skills/mulmoterminal-keys/SKILL.md +38 -16
  480. package/server/skills/mulmoterminal-model/SKILL.md +12 -7
  481. package/server/skills/mulmoterminal-theme/SKILL.md +4 -2
  482. package/server/whatsNew/guidePage.ts +69 -0
  483. package/server/whatsNew/routes.ts +63 -0
  484. package/server/whatsNew/state.ts +43 -0
  485. package/dist/assets/architecture-7GRP2DOG-TdhGGkKM.js +0 -1
  486. package/dist/assets/channel-C2Xy1RUG.js +0 -1
  487. package/dist/assets/dist-ByH4MDuI.js +0 -9
  488. package/dist/assets/eventmodeling-NTZA5JFV-BXnmc8kB.js +0 -1
  489. package/dist/assets/flowDiagram-KWPJA3E3-C2T1HY21.js +0 -1
  490. package/dist/assets/index-BZZNuDnO.css +0 -1
  491. package/dist/assets/railroad-abnf-HS5TGJTU-COO_8DKu.js +0 -1
  492. package/dist/assets/railroad-ebnf-LZEXJU2U-H6w--gix.js +0 -1
  493. package/dist/assets/railroad-peg-WCYAUIDC-91dBFIdz.js +0 -1
  494. package/dist/assets/swimlanesDiagram-TC7HE7FX-CGKdav0R.js +0 -8
package/README.md CHANGED
@@ -176,15 +176,25 @@ than as bytes (files within the session's working directory only):
176
176
  | `.json` | **indented** in a new tab (Chrome and Safari otherwise show one long line) |
177
177
  | `.csv` `.tsv` | a **table** in a new tab, with a sticky header that scrolls inside its own box |
178
178
  | 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 |
179
- | images, PDF, SVG, HTML, video | raw bytes in a new tab, which the browser renders better than an editor would |
179
+ | `.html` `.htm` | the **rendered page** in a new tab, under the same sandboxed policy as the Files pane's Preview: its scripts run at an opaque origin with no fetch or XHR (so they cannot reach the app or its API), and an image beside it loads by its relative path (a relative stylesheet or script does not). The raw route answers `.html` as plain text, so opening one there showed its source |
180
+ | images, PDF, SVG, video, audio | raw bytes in a new tab, which the browser renders better than an editor would |
180
181
  | everything else — `.xlsx`, `.docx`, `.zip`, a `Makefile` | the app's own **Files** view. A tab cannot display these, so opening one there is not a view — it is a download starting with no warning. The pane names the file and offers **Open in OS**, which hands it to the application that owns it (Excel for an `.xlsx`) |
181
182
 
183
+ **A line after the path is followed.** `src/a.ts:42`, `src/a.ts:42:7` (gcc, clang, `eslint -f unix`,
184
+ stack traces) and `src/a.ts(12,5)` (tsc) are one link. In the pane, and in the Files view, the file
185
+ opens as text with the caret on that line and column — an HTML page, an SVG or a table included, and a
186
+ tab reading in Preview switches to Edit. A route that renders the file in a new tab (`.md`, `.json`,
187
+ `.csv`, `.html` with no cell enlarged) ignores the line. When the pane beside an enlarged cell takes
188
+ the click, the keyboard stays in the terminal; the full-screen Files view, with no terminal beside it,
189
+ puts it in the editor. (ESLint's default output puts the path on a line of its own, with no line number to follow.)
190
+
182
191
  **While a grid cell is enlarged, the [Files pane](#files-view-browse--edit) takes the click first** — every
183
- row above except the last one, since the pane is the same editor plus a Markdown preview. The
184
- file opens *beside* the terminal that printed it, and the pane opens itself if it was closed.
185
- It declines, leaving the routing above untouched, when nothing is enlarged, when the path is
186
- not under that cell's own directory (the pane cannot walk above its root), or for the raw-bytes
187
- row, where it would only show an empty editor.
192
+ row above except PDF, video and audio, since the pane is the same editor with a Preview for
193
+ Markdown, HTML and SVG, and shows an image as a picture. The file opens *beside* the terminal
194
+ that printed it, and the pane opens itself if it was closed. It declines, leaving the routing
195
+ above untouched, when nothing is enlarged, when the path is not under that cell's own directory
196
+ (the pane cannot walk above its root), or for a PDF, a video or an audio file, which only a
197
+ browser tab can play.
188
198
 
189
199
  Highlighting in the Files view covers the JS/TS family, JSON and Markdown (the modes
190
200
  `cmEditor.ts` bundles); other languages open as plain text.
@@ -298,7 +308,8 @@ mulmoterminal
298
308
 
299
309
  **Stopping it.** `Ctrl+C` in the terminal that started it — or, if you can no longer find that
300
310
  terminal, **Settings → Quit MulmoTerminal** in the browser, or **`npx mulmoterminal@latest stop`**
301
- from any terminal (installed globally, just `mulmoterminal stop`). All three run the same shutdown:
311
+ from any terminal (installed globally, just `mulmoterminal stop`; it stops every server running on
312
+ this machine, and `--port <port>` stops only that one). All three run the same shutdown:
302
313
  with `tmux` installed the agent sessions survive and come back under **Settings → Sessions that
303
314
  survived a restart**; without it they end with the server.
304
315
 
@@ -624,6 +635,17 @@ up for Codex, loaded by description.
624
635
 
625
636
  ---
626
637
 
638
+ ## Running the server on another machine
639
+
640
+ The server can live on a Linux box, a VPS or a container while your browser stays on your laptop:
641
+ start it there over SSH (`npx mulmoterminal`), and reach it through an SSH tunnel
642
+ (`ssh -N -L 34567:127.0.0.1:34567 you@server`, then open `http://localhost:34567`). Started over
643
+ SSH, the launcher opens no browser on the server and prints that command. It still listens on
644
+ loopback only — nothing is opened to the network. Logging the agents in on the server, Docker, and
645
+ the few actions that act on the server's machine: [Run the server on another machine](https://receptron.github.io/mulmoterminal/guide/en/remote.html).
646
+
647
+ ---
648
+
627
649
  ## Session persistence (tmux)
628
650
 
629
651
  If **`tmux` is installed**, MulmoTerminal runs each Claude session and launcher inside
@@ -767,7 +789,7 @@ The Settings modal (the gear button) persists per-user UI choices to `~/.mulmote
767
789
 
768
790
  ![The Settings modal — a sidebar of grouped sections beside the one being edited](https://raw.githubusercontent.com/receptron/mulmoterminal/main/docs/guide/images/settings-tabs.png)
769
791
 
770
- *Open it from the gear button in the toolbar. The **sidebar** groups the sections — Appearance, Projects, Header & launch, Input, Models & servers, Notifications, Integrations, Sessions, Help — and one is on screen at a time; on a phone the sidebar becomes a picker above the section. The interface is available in **English, 日本語, 简体中文, 繁體中文 and 한국어**: it follows your browser's language unless you pick one under **Language** (per browser, like the theme). Translated so far: this modal, the grid's status words, and every button's hover tip and screen-reader label; the rest of the app's words are still English. Under the title, a **Version** row shows what is running: the version from the shipped `package.json`, plus a `commit <sha>` chip on a git checkout — there the version is only whatever was last released, so the commit is what identifies the build. When something newer exists, the row is followed by the header badge's update notice, command included. Pick a **theme**, set the **terminal font size**, **font** and **scroll speed** (and whether sending **returns to the latest output**), set a custom **attention sound**, list the repos the cross-repo **PRs & Issues** view should aggregate, add **launch commands** for grid cells, register your own **MCP servers**, and turn on the switches for what this app writes on your behalf — **issue work comments**, the **PR clone footer**, the **closing summary**, the **decision digest**, the **dev worklog** — no need to hand-edit the config file. Four settings stay with their skill because a form would be the wrong tool for them (`keymap`, `themes`, `providers`, `buttons`/`chips`); Settings shows what each is doing now and launches that skill. Note that **theme, font size, scroll speed and the return-to-latest switch 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.*
792
+ *Open it from the gear button in the toolbar. The **sidebar** groups the sections — Appearance, Projects, Header & launch, Input, Models & servers, Notifications, Integrations, Sessions, Help — and one is on screen at a time; on a phone the sidebar becomes a picker above the section. The interface is available in **English, 日本語, 简体中文, 繁體中文 and 한국어**: it follows your browser's language unless you pick one under **Language** (per browser, like the theme). Translated so far: this modal, the grid's status words, and every button's hover tip and screen-reader label; the rest of the app's words are still English. Under the title, a **Version** row shows what is running: the version from the shipped `package.json`, plus a `commit <sha>` chip on a git checkout — there the version is only whatever was last released, so the commit is what identifies the build. When something newer exists, the row is followed by the header badge's update notice, command included. Pick a **theme**, set the **terminal font size**, **font** and **scroll speed** (and whether sending **returns to the latest output**), set a custom **attention sound**, list the repos the cross-repo **PRs & Issues** view should aggregate, add **launch commands** for grid cells, register your own **MCP servers**, and turn on the switches for what this app writes on your behalf — **issue work comments**, the **PR clone footer**, the **closing summary**, the **decision digest**, the **dev worklog** — no need to hand-edit the config file. Three settings stay with their skill because a form would be the wrong tool for them (`keymap`, `themes`, `buttons`/`chips`); Settings shows what each is doing now and launches that skill. Backends (`providers`), custom agents and accounts are added and removed in **Models and backends**. Note that **theme, font size, scroll speed and the return-to-latest switch 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.*
771
793
 
772
794
  | Field | Meaning |
773
795
  | ------------ | ------- |
@@ -789,7 +811,7 @@ The Settings modal (the gear button) persists per-user UI choices to `~/.mulmote
789
811
  | `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. |
790
812
  | `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). Editable in Settings → **Sessions and background tasks**. |
791
813
  | `worklogIntervalHours` | Worklog cadence in hours (default `6`, clamped to `1`–`168`). A stepper in the same Settings section covers the range. |
792
- | `feedRefreshEnabled` | `false` stops the hourly **collection/feed refresh** (one scheduled task per root — the workspace and every saved project directory). **On by default**; only an explicit `false` turns it off, so an existing config keeps the behaviour it has. Takes effect at the next server start — the scheduler registers once at boot. Feeds still refresh on demand. Checkbox in Settings → **Sessions**. |
814
+ | `feedRefreshEnabled` | `false` stops the hourly **collection/feed refresh** (one scheduled task per root — the workspace and every saved project directory). **On by default**; only an explicit `false` turns it off, so an existing config keeps the behaviour it has. Saved from Settings (or any `POST /api/config`), it applies at once — the running scheduler rebuilds its built-in tasks; a hand-edit applies after **Settings → Reload config file** (or a restart). Feeds still refresh on demand. Checkbox in Settings → **Sessions**. |
793
815
  | `calendarSyncEnabled` | `false` stops the hourly **Google Calendar sync** (pulls changed events into collections that declare a calendar). Same default and the same restart rule as `feedRefreshEnabled`; calendar collections still sync on demand. Checkbox in the same Settings section. With both off and `worklogEnabled` off, no **built-in** task is registered. Your own tasks in `config/scheduler/tasks.json` are unaffected — these switches do not touch them, and an enabled one still registers and still runs the tick loop. Only when there is no enabled user task either does the scheduler stop writing its state file and run logs. |
794
816
  | `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). Settings → **Terminal keys** offers both, worded as behaviour. |
795
817
  | `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. There is a checkbox in Settings → **Terminal keys**, applied at once; a hand edit of the file needs a **server restart, then a tab reload** (the server reads this file once at startup, and the browser reads the value from it on load). 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). |
@@ -801,6 +823,7 @@ The Settings modal (the gear button) persists per-user UI choices to `~/.mulmote
801
823
  | `autoDirIcon` | Whether a project that sets no `icon` shows the favicon its repository already ships (`public/favicon.svg`, `apple-touch-icon.png`, a web manifest — first hit wins, ordered by how the image survives at 14px). **On by default**; Settings → **Directory appearance** has the switch. A single project opts out with `"icon": false` in its own `.mulmoterminal.json`, which this does not override. A key that was written and got it wrong shows nothing rather than falling back — a broken setting has to look broken. `true` / `false` only. |
802
824
  | `cockpitLines` | `{ summary, prompt, response }` — how many lines each **cockpit-roster** row shows before it clamps (default `2` / `2` / `3`, each clamped to `1`–`20`). Raising them trades how many sessions fit on screen for reading a long one in place. Three steppers in Settings → **Waiting rows**. |
803
825
  | `showLoadAverage` | Whether the grid header draws this machine's **load average** beside the 5h / 7d usage windows, as a percentage of its cores (`load 334%` = a 20-core machine with 66.8 runnable processes). **On by default**; amber at 100%, red at 200%, hover for the raw 1 / 5 / 15-minute figures. Settings → **Grid header read-outs** has the switch. A host that keeps no load average (Windows) shows nothing rather than `0%`, whatever this says. `true` / `false` only. |
826
+ | `remoteServer` | **Experimental.** The server runs on another machine than your browser (an SSH tunnel — [guide](https://receptron.github.io/mulmoterminal/guide/en/remote.html)): the actions that would act on the server's screen (the file dialog, the file manager, the OS app) are hidden or say why, and a dropped file is always uploaded. **Off unless `true`**; set it in the server's config. |
804
827
  | `fontFamily` | The **terminal font** every session renders in — a CSS font-family stack, e.g. `"'Cica', 'MS Gothic', monospace"`. Set it in Settings → **Terminal font**, applied at once; editing the file instead needs a **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). |
805
828
 
806
829
  Every MulmoTerminal on the machine shares this one file, so an older build could save over a key a
@@ -819,9 +842,14 @@ are items in the **path menu** (click the directory path on the terminal header)
819
842
  set** with your list (it is not merged on top), so listing your own — even a **shorter** one — is how
820
843
  you drop, reorder, or swap them.
821
844
  A button has an `id`, `label`, and a `run` of `"shell"` (run a command), `"input"` (send text to the
822
- agent), `"open"`, or `"action"` (act on the cell itself — `action: "restart"` ends the agent and
823
- starts it again in the same cell on the same conversation, which is how an MCP / config / plugin
824
- change takes effect; it costs a resume and asks nothing first). An `open` button targets one of `url` / `reveal` (OS file manager) / `files`
845
+ agent), `"open"`, or `"action"` (act on the cell itself, named like the `keymap` actions so one operation is
846
+ a button, a key and a command-palette entry — `action: "terminal-restart"` ends the agent and starts
847
+ it again in the same cell on the same conversation, which is how an MCP / config / plugin change
848
+ takes effect; it costs a resume and asks nothing first. `"terminal-new-here"` opens the launch panel
849
+ on the cell's directory, and `"pane-files"` / `"pane-prompts"` / … / `"terminal-timeline"` /
850
+ `"terminal-talk"` / `"terminal-park"` / `"terminal-copy-code"` / `"terminal-insert-path"` / `"terminal-reveal"` /
851
+ `"terminal-voice"` / `"terminal-diff"` / `"terminal-note"` do what the cell's menus and buttons do, and `"screen-*"` / `"settings-open"` /
852
+ `"sound-toggle"` / `"view-toggle"` / `"order-*"` / `"page-next"` / `"page-prev"` do what the toolbar and the page tabs do; the old name `"restart"` still works). An `open` button targets one of `url` / `reveal` (OS file manager) / `files`
825
853
  (in-app explorer) / `view` (a built-in overlay) / `terminal` (a dir → a new cell running `$SHELL`,
826
854
  opened next to the current one) / `pr: true` (open the current branch's PR — the button is hidden when
827
855
  there's no open PR) / `pickFile: true` (OS file dialog → insert the path).
@@ -881,7 +909,7 @@ channel is connected (its Google sign-in supplies the notification auth). With R
881
909
  disconnected, or with no device registered, the toggle is a no-op.
882
910
 
883
911
  **Dev worklog (cross-clone).** Set `worklogEnabled: true` in
884
- `~/.mulmoterminal/config.json` (and **restart** — the scheduler reads its tasks at boot)
912
+ `~/.mulmoterminal/config.json` (or switch it on in Settings, which applies at once; after a hand-edit use Settings → **Reload config file**)
885
913
  to register a built-in scheduled task. Every `worklogIntervalHours` (default 6) it spawns
886
914
  a Claude session that reviews the work you did across **all your saved working dirs**
887
915
  (`cwdPresets`) since it last ran, and writes it up as a short manager-style report.
@@ -1244,7 +1272,12 @@ rooted at **that terminal's project directory** — so after Claude says "wrote
1244
1272
  you can jump straight there to read or edit it. The left pane is a lazy-loaded directory
1245
1273
  tree; clicking a file opens it in a **CodeMirror** editor (Markdown / JS-TS / JSON
1246
1274
  highlighting, everything else as plain text). Markdown files get a **Preview** toggle
1247
- that renders via the server's sandboxed `…/md` HTML. **Save** (or ⌘/Ctrl-S) writes back.
1275
+ that renders via the server's sandboxed `…/md` HTML; a `.csv` / `.tsv` previews as a table (the
1276
+ same `…/table` document a clicked path opens, in the app's colours), and an `.html` / `.svg` as the
1277
+ page or picture it is. **Save** (or ⌘/Ctrl-S) writes back. **History** lists the versions the pane
1278
+ kept of the file (in `~/.mulmoterminal/backups`, taken when it is opened or reloaded here and before a save
1279
+ here replaces it — the newest few), to compare with the buffer or restore as an undoable edit (unsaved edits
1280
+ are kept as a version first).
1248
1281
 
1249
1282
  **The open view follows the file on disk.** When an agent in another cell — or any editor —
1250
1283
  rewrites what you are looking at, the editor and the preview catch up on their own; there is
@@ -1266,11 +1299,11 @@ shrinks the pane rather than reflowing xterm into garbage. It works in both zoom
1266
1299
  terminals, and whether it's open plus how wide it is are remembered per browser.
1267
1300
 
1268
1301
  **Coming back looks the way you left it.** The pane remembers the open file, the directories
1269
- you had expanded, and — for a Markdown file — whether you were reading it in **Preview** or
1302
+ you had expanded, and — for a file with a Preview — whether you were reading it in **Preview** or
1270
1303
  editing it: per cell while the session lasts, and per directory across a browser reload (the
1271
1304
  first pane to open on that directory claims it, so a second terminal in the same repository
1272
1305
  starts on its own empty tree). A remembered Preview only comes back over that same file while
1273
- it is still Markdown the server can render; anything else opens in the editor.
1306
+ it is still a file the pane can preview; anything else opens in the editor.
1274
1307
 
1275
1308
  It comes back to **where** you were, too: the line at the top of the editor, the line the caret was
1276
1309
  on, and how far down the tree was scrolled. The top line matters on its own — scrolling moves
package/bin/cli-args.d.ts CHANGED
@@ -25,4 +25,5 @@ export interface RunningInstance {
25
25
  port: number | null;
26
26
  }
27
27
  export declare function stopCommandFor(pkgDir: string): string;
28
+ export declare function stopCommandForThis(stopCommand: string, port: number, instances: readonly { port: number | null }[]): string;
28
29
  export declare function runningInstancesPrompt(instances: readonly RunningInstance[], stopCommand?: string): string;
package/bin/cli-args.js CHANGED
@@ -366,6 +366,11 @@ export function saysYes(answer) {
366
366
  // they are: npx unpacks into `<cache>/_npx/<hash>/node_modules/mulmoterminal`.
367
367
  const NPX_INSTALL = /[/\\]_npx[/\\]/;
368
368
 
369
+ /** The stop command the ready banner shows for the server on `port`: the plain one while it is the
370
+ * only server, and one narrowed to its port once another is running, which the plain one would stop
371
+ * too (#2683). `instances` is the registry, this server included. */
372
+ export const stopCommandForThis = (stopCommand, port, instances) => (instances.some((i) => i.port !== port) ? `${stopCommand} --port ${port}` : stopCommand);
373
+
369
374
  export const stopCommandFor = (pkgDir) => (NPX_INSTALL.test(String(pkgDir)) ? "npx mulmoterminal@latest stop" : "mulmoterminal stop");
370
375
 
371
376
  export function runningInstancesPrompt(instances, stopCommand = "mulmoterminal stop") {
@@ -14,6 +14,7 @@ import { dirname, join, resolve } from "node:path";
14
14
  import { fileURLToPath } from "node:url";
15
15
  import { computeUpdateNotice, isUpdateCheckDisabled } from "./update-check.js";
16
16
  import { detectNpxCacheDir, npxCacheHintLines } from "./npx-cache-hint.js";
17
+ import { configuredRemoteServer, sshTunnelHintLines } from "./ssh-hint.js";
17
18
  import { planAfterServerExit } from "./server-supervision.js";
18
19
  import { waitUntilReady } from "./wait-ready.js";
19
20
  import {
@@ -29,6 +30,7 @@ import {
29
30
  secondInstancePrompt,
30
31
  runningInstancesPrompt,
31
32
  stopCommandFor,
33
+ stopCommandForThis,
32
34
  SECOND_INSTANCE_NOTE,
33
35
  nodeMeetsMinimum,
34
36
  unsupportedNodeMessage,
@@ -109,6 +111,17 @@ function readConfiguredDefaultAgent() {
109
111
  }
110
112
  }
111
113
 
114
+ // The experimental `remoteServer` (#2669): the browser is on another machine, so the launcher opens
115
+ // none here even when started without SSH (a service, a remote desktop). A missing or unreadable
116
+ // file is simply "not set".
117
+ function readRemoteServer() {
118
+ try {
119
+ return configuredRemoteServer(JSON.parse(readFileSync(CONFIG_FILE, "utf8")));
120
+ } catch {
121
+ return false;
122
+ }
123
+ }
124
+
112
125
  // The command for `agent`, honouring its `<AGENT>_BIN` override.
113
126
  //
114
127
  // The override is the point rather than a detail: the gate used to look for the literal word
@@ -478,11 +491,16 @@ async function choosePort(requested, explicit) {
478
491
  //
479
492
  // `url` is where the BROWSER goes and `note` is what launchTarget wants said about that choice —
480
493
  // two different facts since #1889, and the note is null whenever the URL already covers it.
481
- function announceReady(url, note, noOpen) {
482
- printReadyBanner(url, STOP_COMMAND);
494
+ function announceReady(url, note, noOpen, sshHint, port) {
495
+ printReadyBanner(url, stopCommandForThis(STOP_COMMAND, port, liveInstances()));
483
496
  // Either the address a widened bind serves other machines on, or why the browser was NOT sent
484
497
  // to `localhost`. Null whenever the URL above already said everything.
485
498
  if (note) log(note);
499
+ // Over SSH the browser is on the other machine (see ssh-hint.js): say how to reach it, open nothing.
500
+ if (sshHint) {
501
+ sshHint.forEach((line) => log(line));
502
+ return;
503
+ }
486
504
  if (noOpen) return;
487
505
  try {
488
506
  // The command is a hardcoded literal; url is built by browserUrl from a numeric port, so it
@@ -549,7 +567,9 @@ function runServer({ port, probedAddress, localhostIsUnambiguous, noOpen, launch
549
567
  readyStarted = true;
550
568
  const localhostIsOurs = localhostIsUnambiguous && serverSaysLocalhostIsOurs !== false;
551
569
  const { url, note } = launchTarget(reachHost, port, localhostIsOurs);
552
- cancelReady = waitUntilReady(port, () => announceReady(url, note, noOpen), { host: reachHost });
570
+ cancelReady = waitUntilReady(port, () => announceReady(url, note, noOpen, sshTunnelHintLines(process.env, port, readRemoteServer()), port), {
571
+ host: reachHost,
572
+ });
553
573
  };
554
574
  // The same message answers a second question now: whether this lifetime ever bound at all,
555
575
  // which is what a restart is allowed to depend on. Recorded BEFORE the address is looked at —
@@ -0,0 +1,3 @@
1
+ export declare function inSshSession(env: Record<string, string | undefined>): boolean;
2
+ export declare const configuredRemoteServer: (config: unknown) => boolean;
3
+ export declare function sshTunnelHintLines(env: Record<string, string | undefined>, port: number, remoteServer?: boolean): string[] | null;
@@ -0,0 +1,32 @@
1
+ // A launcher started over SSH is on a machine whose screen nobody is looking at: the browser that
2
+ // should open this is on the other end of the connection. So it does not try to open one here — on
3
+ // a remote Mac that would put a window on a display no one sees — and it says how to reach the
4
+ // server from there instead: an SSH port forward, the route the FAQ recommends. Nothing here reads
5
+ // the process; the caller passes its environment.
6
+
7
+ /** Whether this process runs inside an SSH login. sshd sets both variables; either is enough. */
8
+ export function inSshSession(env) {
9
+ return Boolean(env.SSH_CONNECTION || env.SSH_CLIENT);
10
+ }
11
+
12
+ /** The experimental `remoteServer` out of a parsed `~/.mulmoterminal/config.json`: true only for a
13
+ * literal `true`, like the server's own reading of it (common/remoteServer.ts). */
14
+ export const configuredRemoteServer = (config) => config !== null && typeof config === "object" && config.remoteServer === true;
15
+
16
+ /**
17
+ * The lines that tell someone on another machine how to open this server, or null when the browser
18
+ * is expected here: not an SSH login, and `remoteServer` not set. The host is left for them to fill
19
+ * in: SSH_CONNECTION names the address THIS side answered on, which behind NAT or a container is not
20
+ * one their machine can reach.
21
+ */
22
+ export function sshTunnelHintLines(env, port, remoteServer = false) {
23
+ const ssh = inSshSession(env);
24
+ if (!ssh && !remoteServer) return null;
25
+ const user = env.USER || env.LOGNAME || "<user>";
26
+ const why = ssh ? "Started over SSH" : "remoteServer is set in config.json";
27
+ return [
28
+ `${why}, so no browser is opened here. On your own machine, run:`,
29
+ ` ssh -N -L ${port}:127.0.0.1:${port} ${user}@<this-host>`,
30
+ `then open http://localhost:${port} there.`,
31
+ ];
32
+ }
package/bin/stop.d.ts CHANGED
@@ -16,10 +16,12 @@ export interface StopResult {
16
16
  stubborn: StubbornInstance[];
17
17
  unconfirmed: InstanceEntry[];
18
18
  }
19
- export type StopArgs = { help: true } | { error: string } | { force: boolean };
19
+ export type StopArgs = { help: true } | { error: string } | { force: boolean; port: number | null };
20
20
 
21
21
  export declare const STOP_USAGE: string;
22
22
  export declare function parseStopArgs(args: readonly string[]): StopArgs;
23
+ export declare function selectByPort(instances: readonly InstanceEntry[], port: number | null): InstanceEntry[];
24
+ export declare function noServerOnPortReport(port: number, instances: readonly InstanceEntry[]): string[];
23
25
  export interface ConfirmDeps {
24
26
  owners?: (port: number) => Promise<number[] | null>;
25
27
  }
package/bin/stop.js CHANGED
@@ -14,7 +14,7 @@
14
14
  // Windows user find or kill anything.
15
15
  import { dirname } from "node:path";
16
16
  import { fileURLToPath } from "node:url";
17
- import { stopCommandFor } from "./cli-args.js";
17
+ import { parsePortArg, stopCommandFor } from "./cli-args.js";
18
18
  import { isProcessAlive, liveInstances } from "./instances.js";
19
19
  import { portOwners } from "./port-owner.js";
20
20
 
@@ -140,10 +140,11 @@ export function stopReport({ stopped, stubborn, unconfirmed }, platform = proces
140
140
  export const stopExitCode = ({ stubborn, unconfirmed }) => (stubborn.length + unconfirmed.length ? 1 : 0);
141
141
 
142
142
  export const STOP_USAGE = [
143
- "Usage: mulmoterminal stop [--force]",
143
+ "Usage: mulmoterminal stop [--port <port>] [--force]",
144
144
  "",
145
145
  "Stops every running MulmoTerminal server on this machine, from any terminal.",
146
146
  "",
147
+ " --port Stop only the server on that port (the one at http://localhost:<port>).",
147
148
  " --force Also stop a registered server that is no longer answering. Off by default:",
148
149
  " a server that crashed leaves its entry behind, and that pid may since have",
149
150
  " been given to an unrelated program.",
@@ -153,9 +154,26 @@ export const STOP_USAGE = [
153
154
  * argument handling is testable — and so `stop --help` can never be read as "stop everything". */
154
155
  export function parseStopArgs(args) {
155
156
  if (args.includes("--help") || args.includes("-h")) return { help: true };
156
- const unknown = args.filter((a) => a !== "--force");
157
+ const portAt = args.indexOf("--port");
158
+ const portArgs = portAt === -1 ? [] : [portAt, portAt + 1];
159
+ const unknown = args.filter((a, i) => a !== "--force" && !portArgs.includes(i));
157
160
  if (unknown.length) return { error: `Unknown argument for stop: ${unknown.join(" ")}` };
158
- return { force: args.includes("--force") };
161
+ const force = args.includes("--force");
162
+ if (portAt === -1) return { force, port: null };
163
+ // The launcher's own reading of --port, so the two accept the same values. The environment's
164
+ // PORT is not consulted: a `stop` narrowed by a variable nobody typed here would surprise.
165
+ const parsed = parsePortArg(args, {}, null);
166
+ return "error" in parsed ? { error: parsed.error } : { force, port: parsed.port };
167
+ }
168
+
169
+ /** The servers a `stop` is about: all of them, or only the one on `port` (#2683). */
170
+ export const selectByPort = (instances, port) => (port === null ? instances : instances.filter((i) => i.port === port));
171
+
172
+ /** What `stop --port` says when nothing is registered there — and what IS running, since a wrong
173
+ * port is the likeliest reason. */
174
+ export function noServerOnPortReport(port, instances) {
175
+ const running = instances.map(describeInstance);
176
+ return [`No MulmoTerminal server is running on port ${port}.`, running.length ? `Running: ${running.join(", ")}` : "MulmoTerminal is not running."];
159
177
  }
160
178
 
161
179
  export async function runStop(args = []) {
@@ -169,7 +187,16 @@ export async function runStop(args = []) {
169
187
  console.error(STOP_USAGE);
170
188
  process.exit(2);
171
189
  }
172
- const result = await stopInstances(liveInstances(), { force: parsed.force });
173
- stopReport(result).forEach((line) => console.log(line));
190
+ const all = liveInstances();
191
+ const targets = selectByPort(all, parsed.port);
192
+ // Nothing on that port is the state asked for, like "nothing was running": not a failure.
193
+ if (parsed.port !== null && !targets.length) {
194
+ noServerOnPortReport(parsed.port, all).forEach((line) => console.log(line));
195
+ process.exit(0);
196
+ }
197
+ const result = await stopInstances(targets, { force: parsed.force });
198
+ // A retry must name the same server, or `--force` would stop every one.
199
+ const stopCommand = parsed.port === null ? selfStopCommand() : `${selfStopCommand()} --port ${parsed.port}`;
200
+ stopReport(result, process.platform, stopCommand).forEach((line) => console.log(line));
174
201
  process.exit(stopExitCode(result));
175
202
  }
@@ -9,7 +9,7 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs";
9
9
  import { fromBase } from "./base.mjs";
10
10
  const { fail, quotationProblems, readJson } = await import(fromBase("chaff.mjs"));
11
11
  const { digest, documentSource, documentsNamed, fingerprint } = await import(fromBase("documents.mjs"));
12
- const { headingsOf, namesPlace } = await import(fromBase("places.mjs"));
12
+ const { headingsIn, namesPlace, placeNamesIn, treeOf } = await import(fromBase("places.mjs"));
13
13
 
14
14
  const REPLIES = ".blueprint/replies.json";
15
15
  const REPLIES_PAGE = ".blueprint/replies.md";
@@ -30,13 +30,25 @@ if (answers?.keep === KEEP && documents.includes(FAQ)) fail(`${FAQ} is one of th
30
30
  const indented = (line) => " " + line;
31
31
  const nonEmpty = (value) => typeof value === "string" && value.trim() !== "";
32
32
 
33
- // Read once per document, and only for one of the documents: a source that is not is refused by the quotation check.
34
- const headingCache = new Map();
35
- const headingAt = (source, address) => {
33
+ // Each document's tree is read once, and only for one of the documents: a source that is not is refused by the
34
+ // quotation check. From it come a section's heading and a place's name (第4条第2項), either of which names the place.
35
+ const placeCache = new Map();
36
+ const placeAt = (source, address) => {
36
37
  const resolved = documentSource(documents)(source);
37
- if (!resolved.path) return undefined;
38
- if (!headingCache.has(resolved.path)) headingCache.set(resolved.path, headingsOf(resolved.path));
39
- return headingCache.get(resolved.path).get(address.trim());
38
+ if (!resolved.path) return {};
39
+ if (!placeCache.has(resolved.path)) {
40
+ const tree = treeOf(resolved.path);
41
+ placeCache.set(resolved.path, { headings: headingsIn(tree), names: placeNamesIn(tree) });
42
+ }
43
+ const { headings, names } = placeCache.get(resolved.path);
44
+ return { heading: headings.get(address.trim()), name: names.get(address.trim()) };
45
+ };
46
+
47
+ // The address as written needs no tree; otherwise the heading or the place's name, from the document's tree.
48
+ const answerNames = (answer, citation) => {
49
+ if (answer.includes(citation.address.trim())) return true;
50
+ const { heading, name } = placeAt(String(citation.source), citation.address);
51
+ return namesPlace(answer, citation.address, heading, name);
40
52
  };
41
53
 
42
54
  const replyProblem = (reply) => {
@@ -49,10 +61,7 @@ const replyProblem = (reply) => {
49
61
  if (!Array.isArray(citations)) return `${which}: citations must be an array`;
50
62
  if (reply.found && citations.length === 0) return `${which}: an answer found in the documents quotes them`;
51
63
  if (!citations.every((citation) => nonEmpty(citation?.address))) return `${which}: every quotation needs an "address"`;
52
- const unnamed = citations.filter(
53
- (citation) =>
54
- !reply.answer.includes(citation.address.trim()) && !namesPlace(reply.answer, citation.address, headingAt(String(citation.source), citation.address)),
55
- );
64
+ const unnamed = citations.filter((citation) => !answerNames(reply.answer, citation));
56
65
  if (unnamed.length > 0) return `${which}: the answer does not name ${unnamed.map((citation) => citation.address).join(", ")}, which it quotes`;
57
66
  if (!reply.found && (!Array.isArray(reply.searched) || !reply.searched.some(nonEmpty))) return `${which}: say what was searched ("searched")`;
58
67
  return null;
@@ -18,7 +18,7 @@ Otherwise add to `FAQ.md` in this folder (create it if there is none). **Add aft
18
18
  never edit or reorder what is there** — the check compares the beginning of the file with what it held
19
19
  before. For each question: the question as a `##` heading, word for word; the answer; and where it is
20
20
  written (the document, and the place named as in the answer: 第4条第2項, or a section's heading in 「」 —
21
- never chaff's `h1.3`). For a question the documents do not answer, say so — that is worth keeping
21
+ in an English FAQ, in "…" — never chaff's `h1.3`). For a question the documents do not answer, say so — that is worth keeping
22
22
  too.
23
23
 
24
24
  ## Done when
@@ -0,0 +1,16 @@
1
+ #!/bin/sh
2
+ # The published Worker serves the build this deploy made: the deploy writes a fresh id to .blueprint/build-id and ships
3
+ # the same id as /blueprint-build.txt, and records the URL it published to in .blueprint/deploy-url. The published app
4
+ # answers /api/health, sends the security headers, and its page renders.
5
+ set -eu
6
+ [ -s .blueprint/deploy-url ] || { echo "missing .blueprint/deploy-url; the deploy step writes the URL it published to" >&2; exit 1; }
7
+ url=$(tr -d ' \n\r' < .blueprint/deploy-url)
8
+ url=${url%/}
9
+ case "$url" in https://*) ;; *) echo ".blueprint/deploy-url is not an https URL: $url" >&2; exit 1 ;; esac
10
+ [ -s .blueprint/build-id ] || { echo "missing .blueprint/build-id; the deploy step writes it before deploying" >&2; exit 1; }
11
+ expected=$(cat .blueprint/build-id)
12
+ served=$(curl -fsS --max-time 20 "$url/blueprint-build.txt")
13
+ [ "$served" = "$expected" ] || { echo "$url serves build $served, this deploy made $expected" >&2; exit 1; }
14
+ curl -fsS --max-time 20 "$url/api/health" | grep -q '"ok":true' || { echo "$url/api/health does not answer {\"ok\":true}" >&2; exit 1; }
15
+ curl -s -D - -o /dev/null --max-time 20 "$url/" | tr -d '\r' | grep -qi '^content-security-policy:' || { echo "$url/ is published without a Content-Security-Policy" >&2; exit 1; }
16
+ sh "$(dirname "$0")/page-renders.sh" "$url/"
@@ -0,0 +1,14 @@
1
+ #!/bin/sh
2
+ # The README tells a non-engineer how to run the app on this computer, how to publish a change, and how to back up the
3
+ # data; the first page the finished build shows says where the published app is and what to try to see it works.
4
+ set -eu
5
+ [ -s README.md ] || { echo "missing README.md" >&2; exit 1; }
6
+ grep -q "yarn start" README.md || { echo "README.md does not say how to run it on this computer (yarn start)" >&2; exit 1; }
7
+ grep -q "wrangler deploy\|yarn deploy" README.md || { echo "README.md does not say how to publish a change (yarn deploy)" >&2; exit 1; }
8
+ grep -q "d1 export" README.md || { echo "README.md does not say how to back up the data (wrangler d1 export)" >&2; exit 1; }
9
+ start=.blueprint/start-here.md
10
+ [ -s "$start" ] || { echo "missing $start" >&2; exit 1; }
11
+ [ -s .blueprint/deploy-url ] || { echo "missing .blueprint/deploy-url" >&2; exit 1; }
12
+ url=$(tr -d ' \n\r' < .blueprint/deploy-url)
13
+ grep -qF "${url%/}" "$start" || { echo "$start does not name the published URL (${url%/})" >&2; exit 1; }
14
+ grep -q '^- \[ \] ' "$start" || { echo "$start has no checklist of what to try (- [ ] …)" >&2; exit 1; }
@@ -0,0 +1,39 @@
1
+ #!/bin/sh
2
+ # The served page actually starts: a build can deploy, report its id and answer 200, and still be a
3
+ # blank page because the app threw on load (a missing config does exactly that). Rendered in headless
4
+ # Chrome, the page must show some text. Without a Chrome this cannot be judged, and says so.
5
+ set -eu
6
+ url="$1"
7
+ chrome=""
8
+ for candidate in "${CHROME:-}" "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" google-chrome chromium chromium-browser; do
9
+ [ -n "$candidate" ] || continue
10
+ if [ -x "$candidate" ] || command -v "$candidate" >/dev/null 2>&1; then chrome="$candidate"; break; fi
11
+ done
12
+ [ -n "$chrome" ] || { echo "no Chrome found (set CHROME=...); could not confirm that $url renders" >&2; exit 0; }
13
+ # Headless Chrome can print the DOM and then not exit, so it is waited on for the DOM, not for exit.
14
+ RENDER_LIMIT_SECONDS=60
15
+ work=$(mktemp -d)
16
+ "$chrome" --headless=new --disable-gpu --no-first-run --user-data-dir="$work/profile" --virtual-time-budget=15000 --dump-dom "$url" >"$work/dom.html" 2>/dev/null &
17
+ pid=$!
18
+ # Chrome can still be writing its profile when it is stopped, and under set -e a failed rm in this trap would turn a page
19
+ # that rendered into a failed check; the scratch folder is left behind instead.
20
+ trap 'kill "$pid" 2>/dev/null || true; rm -rf "$work" 2>/dev/null || true' EXIT
21
+ waited=0
22
+ until grep -q "</html>" "$work/dom.html" 2>/dev/null || [ "$waited" -ge "$RENDER_LIMIT_SECONDS" ] || ! kill -0 "$pid" 2>/dev/null; do
23
+ sleep 1
24
+ waited=$((waited + 1))
25
+ done
26
+ # A Chrome that crashed before printing the page says nothing about the app: like no Chrome, it cannot judge.
27
+ if ! grep -q "</html>" "$work/dom.html" 2>/dev/null && ! kill -0 "$pid" 2>/dev/null; then
28
+ status=0
29
+ wait "$pid" || status=$?
30
+ if [ "$status" -ne 0 ]; then
31
+ echo "Chrome ($chrome) exited with status $status before printing the page; could not confirm that $url renders" >&2
32
+ exit 0
33
+ fi
34
+ fi
35
+ node -e 'let s="";process.stdin.on("data",(c)=>s+=c).on("end",()=>{
36
+ const body = (s.match(/<body[^>]*>([\s\S]*)<\/body>/i) || [, ""])[1];
37
+ const text = body.replace(/<(script|style|noscript)[\s\S]*?<\/\1>/gi, "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
38
+ if (!text) { console.error(process.argv[1] + " renders a blank page: the app failed to start. Open it in a browser and read the console error."); process.exit(1); }
39
+ })' "$url" <"$work/dom.html"
@@ -0,0 +1,6 @@
1
+ #!/bin/sh
2
+ # Node is new enough for wrangler, and yarn answers. wrangler itself is a dev dependency of the project.
3
+ set -eu
4
+ node -e 'process.exit(Number(process.versions.node.split(".")[0]) >= 22 ? 0 : 1)' ||
5
+ { echo "Node.js 22 or later is needed (wrangler requires it); this is $(node -v)" >&2; exit 1; }
6
+ yarn --version >/dev/null 2>&1 || { echo "yarn is missing (corepack enable)" >&2; exit 1; }
@@ -0,0 +1,14 @@
1
+ #!/bin/sh
2
+ # The layout the later checks rely on exists, and the skeleton builds and its tests pass inside the Workers runtime.
3
+ set -eu
4
+ for f in package.json wrangler.jsonc src/worker/index.ts index.html vitest.config.ts; do
5
+ [ -f "$f" ] || { echo "missing $f" >&2; exit 1; }
6
+ done
7
+ for d in migrations test; do
8
+ [ -d "$d" ] || { echo "missing $d/" >&2; exit 1; }
9
+ done
10
+ grep -q '"start"' package.json || { echo "package.json has no start script" >&2; exit 1; }
11
+ grep -q '"d1_databases"' wrangler.jsonc || { echo "wrangler.jsonc binds no D1 database" >&2; exit 1; }
12
+ grep -q '"assets"' wrangler.jsonc || { echo "wrangler.jsonc serves no assets" >&2; exit 1; }
13
+ yarn build
14
+ yarn test
@@ -0,0 +1,11 @@
1
+ #!/bin/sh
2
+ # The security review's report exists, walks every OWASP Top 10:2025 category, and leaves no HIGH or
3
+ # MEDIUM finding open or merely accepted. A finding is one line: `- <HIGH|MEDIUM|LOW> <fixed|open|accepted>: …`.
4
+ set -eu
5
+ report=.blueprint/security-review.md
6
+ [ -s "$report" ] || { echo "missing $report" >&2; exit 1; }
7
+ for category in A01 A02 A03 A04 A05 A06 A07 A08 A09 A10; do
8
+ grep -q "^#.*$category" "$report" || { echo "$report has no section for $category (OWASP Top 10:2025)" >&2; exit 1; }
9
+ done
10
+ open=$(grep -nE '^- (HIGH|MEDIUM) (open|accepted)' "$report" || true)
11
+ [ -z "$open" ] || { printf 'HIGH / MEDIUM findings must be fixed, but are open or accepted in %s:\n%s\n' "$report" "$open" >&2; exit 1; }
@@ -0,0 +1,52 @@
1
+ #!/bin/sh
2
+ # The security review is proven by the running Worker, not by the review's word: the app started with wrangler dev is
3
+ # sent the requests an attack would send (a cross-site state change, a malformed body) and must refuse them, its pages
4
+ # and its API carry the headers a browser needs, its secrets stay out of any repository, its dependencies have no known
5
+ # high-severity hole, and the review's own report and tests are in place.
6
+ set -eu
7
+ sh "$(dirname "$0")/security-report.sh"
8
+ [ -f test/security.test.ts ] || { echo "missing test/security.test.ts" >&2; exit 1; }
9
+ # Local secrets live in .dev.vars (or .env); the folder becomes a repository whenever its person adds git.
10
+ for secrets in .dev.vars .env; do
11
+ if [ -f "$secrets" ] && ! grep -qxE "/?$(printf '%s' "$secrets" | sed 's/\./\\./g')(\\*)?" .gitignore 2>/dev/null; then
12
+ echo "$secrets exists and .gitignore does not ignore it; add a line $secrets to .gitignore" >&2
13
+ exit 1
14
+ fi
15
+ done
16
+ yarn test >/dev/null || { echo "yarn test fails" >&2; yarn test >&2 || true; exit 1; }
17
+ # Read from the summary, not the exit code: an audit that could not reach the registry must not pass.
18
+ yarn audit --groups dependencies --json 2>/dev/null | node -e 'let s="";process.stdin.on("data",(c)=>s+=c).on("end",()=>{
19
+ const summary = s.split("\n").filter(Boolean).map((line) => JSON.parse(line)).find((entry) => entry.type === "auditSummary");
20
+ if (!summary) { console.error("yarn audit did not report a summary (is the registry reachable?)"); process.exit(1); }
21
+ const { high = 0, critical = 0 } = summary.data.vulnerabilities;
22
+ if (high + critical > 0) { console.error(`yarn audit: ${high} high and ${critical} critical vulnerabilities in dependencies; run yarn audit --groups dependencies`); process.exit(1); }
23
+ })'
24
+
25
+ . "$(dirname "$0")/serve.sh"
26
+ base="http://127.0.0.1:$port"
27
+ status() { curl -s -o /dev/null -w '%{http_code}' --max-time 5 "$@"; }
28
+ fail() { echo "$1" >&2; exit 1; }
29
+
30
+ # A state change sent from another site's page.
31
+ code=$(status -X POST -H "Origin: https://attacker.example" -H "Content-Type: application/json" -d '{}' "$base/api/blueprint-security-probe")
32
+ [ "$code" = 403 ] || fail "POST /api/… from Origin https://attacker.example answered $code; a state change from another origin must be refused with 403 before routing and sign-in"
33
+
34
+ # The page is served as a static asset, which does not go through the Worker: its headers come from public/_headers.
35
+ for target in / /api/health; do
36
+ headers=$(curl -s -D - -o /dev/null --max-time 5 "$base$target" | tr -d '\r')
37
+ has() { printf '%s' "$headers" | grep -qiE "$1"; }
38
+ has '^x-content-type-options: *nosniff' || fail "$target is served without X-Content-Type-Options: nosniff"
39
+ has '^content-security-policy:' || fail "$target is served without a Content-Security-Policy"
40
+ has "^content-security-policy:.*frame-ancestors|^x-frame-options: *(deny|sameorigin)" || fail "$target can be framed by another site (no frame-ancestors in the CSP, no X-Frame-Options)"
41
+ ! has '^x-powered-by:' || fail "$target carries X-Powered-By"
42
+ done
43
+
44
+ # A malformed body is refused at the entry, before routing — a Worker reads JSON per route, so a check against a path no
45
+ # route handles proves nothing unless the body is read once, up front — and is answered without the Worker's insides.
46
+ response=$(curl -s --max-time 5 -w '\n%{http_code}' -X POST -H "Origin: $base" -H "Content-Type: application/json" --data '{"broken' "$base/api/blueprint-security-probe")
47
+ code=$(printf '%s' "$response" | tail -n 1)
48
+ body=$(printf '%s' "$response" | sed '$d')
49
+ [ "$code" = 400 ] || fail "a malformed JSON body to /api/… answered $code; it must be refused with 400 where the Worker reads JSON bodies, before routing"
50
+ if printf '%s' "$body" | grep -qE 'node_modules|at [^ ]+ \(|SyntaxError: |src/worker/'; then
51
+ fail "a malformed JSON body is answered with a stack trace or the parser's error: $(printf '%s' "$body" | head -c 300)"
52
+ fi