@cyberismo/backend 1.1.2 → 1.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 (301) hide show
  1. package/README.md +21 -0
  2. package/dist/app.js +19 -7
  3. package/dist/app.js.map +1 -1
  4. package/dist/auth/index.d.ts +1 -1
  5. package/dist/auth/index.js +1 -1
  6. package/dist/auth/index.js.map +1 -1
  7. package/dist/auth/keycloak.d.ts +2 -3
  8. package/dist/auth/keycloak.js +6 -0
  9. package/dist/auth/keycloak.js.map +1 -1
  10. package/dist/auth/mock.d.ts +12 -8
  11. package/dist/auth/mock.js +119 -31
  12. package/dist/auth/mock.js.map +1 -1
  13. package/dist/auth/types.d.ts +7 -1
  14. package/dist/domain/cards/index.js +11 -84
  15. package/dist/domain/cards/index.js.map +1 -1
  16. package/dist/domain/cards/lib.js +2 -11
  17. package/dist/domain/cards/lib.js.map +1 -1
  18. package/dist/domain/cards/schema.js +9 -3
  19. package/dist/domain/cards/schema.js.map +1 -1
  20. package/dist/domain/cards/service.d.ts +2 -5
  21. package/dist/domain/cards/service.js +8 -15
  22. package/dist/domain/cards/service.js.map +1 -1
  23. package/dist/domain/events/index.d.ts +15 -0
  24. package/dist/domain/events/index.js +94 -0
  25. package/dist/domain/events/index.js.map +1 -0
  26. package/dist/domain/events/lifetime.d.ts +14 -0
  27. package/dist/domain/events/lifetime.js +25 -0
  28. package/dist/domain/events/lifetime.js.map +1 -0
  29. package/dist/domain/events/project-events.d.ts +67 -0
  30. package/dist/domain/events/project-events.js +200 -0
  31. package/dist/domain/events/project-events.js.map +1 -0
  32. package/dist/domain/events/schema.d.ts +22 -0
  33. package/dist/domain/events/schema.js +20 -0
  34. package/dist/domain/events/schema.js.map +1 -0
  35. package/dist/domain/project/index.js +17 -5
  36. package/dist/domain/project/index.js.map +1 -1
  37. package/dist/domain/project/schema.d.ts +8 -0
  38. package/dist/domain/project/schema.js +34 -6
  39. package/dist/domain/project/schema.js.map +1 -1
  40. package/dist/domain/project/service.d.ts +9 -5
  41. package/dist/domain/project/service.js +22 -16
  42. package/dist/domain/project/service.js.map +1 -1
  43. package/dist/domain/projects/index.js +5 -1
  44. package/dist/domain/projects/index.js.map +1 -1
  45. package/dist/domain/resources/index.js +74 -2
  46. package/dist/domain/resources/index.js.map +1 -1
  47. package/dist/domain/resources/schema.d.ts +8 -2
  48. package/dist/domain/resources/schema.js +6 -1
  49. package/dist/domain/resources/schema.js.map +1 -1
  50. package/dist/domain/resources/service.d.ts +13 -1
  51. package/dist/domain/resources/service.js +45 -2
  52. package/dist/domain/resources/service.js.map +1 -1
  53. package/dist/domain/skills/index.d.ts +15 -0
  54. package/dist/domain/skills/index.js +52 -0
  55. package/dist/domain/skills/index.js.map +1 -0
  56. package/dist/domain/skills/schema.d.ts +16 -0
  57. package/dist/domain/skills/schema.js +18 -0
  58. package/dist/domain/skills/schema.js.map +1 -0
  59. package/dist/domain/skills/service.d.ts +14 -0
  60. package/dist/domain/skills/service.js +16 -0
  61. package/dist/domain/skills/service.js.map +1 -0
  62. package/dist/export.d.ts +3 -2
  63. package/dist/export.js +53 -38
  64. package/dist/export.js.map +1 -1
  65. package/dist/index.d.ts +1 -0
  66. package/dist/index.js +3 -0
  67. package/dist/index.js.map +1 -1
  68. package/dist/main.js +22 -7
  69. package/dist/main.js.map +1 -1
  70. package/dist/middleware/auth.d.ts +5 -0
  71. package/dist/middleware/auth.js +20 -4
  72. package/dist/middleware/auth.js.map +1 -1
  73. package/dist/middleware/commandManager.d.ts +2 -0
  74. package/dist/middleware/commandManager.js +1 -0
  75. package/dist/middleware/commandManager.js.map +1 -1
  76. package/dist/overlay.d.ts +45 -0
  77. package/dist/overlay.js +157 -0
  78. package/dist/overlay.js.map +1 -0
  79. package/dist/project-errors.d.ts +18 -0
  80. package/dist/project-errors.js +25 -0
  81. package/dist/project-errors.js.map +1 -0
  82. package/dist/project-registry.d.ts +9 -1
  83. package/dist/project-registry.js +37 -9
  84. package/dist/project-registry.js.map +1 -1
  85. package/dist/public/THIRD-PARTY.txt +528 -435
  86. package/dist/public/assets/__vite-browser-external-PaJ7c2l8.js +1 -0
  87. package/dist/public/assets/abnfDiagram-VCTEODGH-DB2dfOGe.js +1 -0
  88. package/dist/public/assets/architecture-7GRP2DOG-2V0X8hxt.js +1 -0
  89. package/dist/public/assets/architectureDiagram-5GKGNRK7-CRhUR2Ul.js +36 -0
  90. package/dist/public/assets/blockDiagram-I7D4REHJ-DJKsYF1v.js +129 -0
  91. package/dist/public/assets/c4Diagram-7LVT6UL2-BTwMzoAF.js +38 -0
  92. package/dist/public/assets/channel-C0JtPpMA.js +1 -0
  93. package/dist/public/assets/chunk-4HAMMTFA-DjjgrBsB.js +62 -0
  94. package/dist/public/assets/chunk-75Z2AOVW-CKqXRGJV.js +2 -0
  95. package/dist/public/assets/chunk-DU6HZSFF-iJGdiRt7.js +127 -0
  96. package/dist/public/assets/chunk-F27PBJKO-tDfs6zeO.js +1 -0
  97. package/dist/public/assets/chunk-FOHPRMQF-DzxwRta7.js +161 -0
  98. package/dist/public/assets/chunk-GMAD6QVW-DxHVEaGj.js +72 -0
  99. package/dist/public/assets/chunk-GVQU2GXP-AMxD4_Ta.js +1 -0
  100. package/dist/public/assets/{chunk-EX3LRPZG-Db0XYyxy.js → chunk-IMKFNOWR-0rz8QrYC.js} +2 -2
  101. package/dist/public/assets/chunk-L3NEJ4N5-BK3KAKMe.js +1 -0
  102. package/dist/public/assets/chunk-OSK3NFVY-B8MdrI_M.js +10 -0
  103. package/dist/public/assets/chunk-P2QGCYS3-D3J5AcHg.js +1 -0
  104. package/dist/public/assets/{chunk-VR4S4FIN-tUhbELKr.js → chunk-POPQ4Y6H-CsYg_XkK.js} +1 -1
  105. package/dist/public/assets/{chunk-HOUHSVGY-L35GPy9G.js → chunk-PWAF6VOD-D9cdEIZp.js} +1 -1
  106. package/dist/public/assets/chunk-SHT3W25Y-1jjokCf3.js +168 -0
  107. package/dist/public/assets/{chunk-MOJQB5TN-D1shKbMs.js → chunk-SVP7TREG-CIQMl7hl.js} +1 -1
  108. package/dist/public/assets/{chunk-V7JOEXUC-71b0ABpv.js → chunk-TICWLB2K-Ccsy2PYz.js} +3 -3
  109. package/dist/public/assets/chunk-XXDRQBXY-B7bWyHjt.js +1 -0
  110. package/dist/public/assets/classDiagram-ZZMXUADV-mWyXNAXi.js +1 -0
  111. package/dist/public/assets/classDiagram-v2-VYDZK3BY-mWyXNAXi.js +1 -0
  112. package/dist/public/assets/cose-bilkent-JH36ORCC-DPzc3Pv4.js +1 -0
  113. package/dist/public/assets/cynefin-OW5HDTMX-BIVDCYOU.js +1 -0
  114. package/dist/public/assets/cynefinDiagram-5FMLGOSQ-_V_nrIhD.js +62 -0
  115. package/dist/public/assets/cytoscape.esm-NgwPT4ez.js +321 -0
  116. package/dist/public/assets/dagre-C-rnJpZF.js +1 -0
  117. package/dist/public/assets/dagre-GXQ25YYZ-CJc0wc6f.js +4 -0
  118. package/dist/public/assets/diagram-S7CK7UJ4-25IjIKyo.js +30 -0
  119. package/dist/public/assets/diagram-UQ7AKVKN-C5VvsV7x.js +41 -0
  120. package/dist/public/assets/{diagram-FQU43EPY-DS-ryuRg.js → diagram-VSXAHHWV-Dw3rKVL_.js} +3 -3
  121. package/dist/public/assets/diagram-VX7I27RA-De_UQv8C.js +24 -0
  122. package/dist/public/assets/diagram-Z3DM3KII-Bd-65QQW.js +24 -0
  123. package/dist/public/assets/dist-BDCdLWk4.js +1 -0
  124. package/dist/public/assets/ebnfDiagram-PWID7BFC-CrxdET5U.js +1 -0
  125. package/dist/public/assets/erDiagram-RLTQ6QDP-BKo0Uyna.js +99 -0
  126. package/dist/public/assets/eventmodeling-NTZA5JFV-DKkAdJZd.js +1 -0
  127. package/dist/public/assets/flowDiagram-HODETNUW-CEE2wjdt.js +1 -0
  128. package/dist/public/assets/ganttDiagram-EL5Y4UJY-BHFn8vk0.js +292 -0
  129. package/dist/public/assets/gitGraph-4MIJSDKK-Bpvnb45u.js +1 -0
  130. package/dist/public/assets/{gitGraphDiagram-IHSO6WYX-CksvJvsQ.js → gitGraphDiagram-WWUBYQGX-Bw0EoxBY.js} +9 -9
  131. package/dist/public/assets/index-D3HS7YxF.js +2228 -0
  132. package/dist/public/assets/index-D6WqGtsG.js +27876 -0
  133. package/dist/public/assets/{index-COSSLZqt.css → index-Sv3WxZ_8.css} +1 -1
  134. package/dist/public/assets/info-A6RAGUB7-BvvG_zrk.js +1 -0
  135. package/dist/public/assets/infoDiagram-27XIBGKW-CtpdJmtL.js +2 -0
  136. package/dist/public/assets/{ishikawaDiagram-FXEZZL3T-CiLpASIW.js → ishikawaDiagram-5VMMS53U-KvT4DU1u.js} +2 -2
  137. package/dist/public/assets/journeyDiagram-3NMN7TZE-PhGRkXkq.js +139 -0
  138. package/dist/public/assets/{kanban-definition-HUTT4EX6-DYP_x6zD.js → kanban-definition-UXKFOSKX-Oyx5QgRq.js} +4 -4
  139. package/dist/public/assets/{katex-DolUETbr.js → katex-ZlcWpGUi.js} +3 -3
  140. package/dist/public/assets/{line-C8Sfsgt8.js → line-Dr41HTk1.js} +1 -1
  141. package/dist/public/assets/linear-DDBi9FwZ.js +1 -0
  142. package/dist/public/assets/mermaid-parser.core-DsIAxDBa.js +7 -0
  143. package/dist/public/assets/{mindmap-definition-LN4V7U3C-3IAd4gp3.js → mindmap-definition-YA3MSWOX-r8hvd6gP.js} +1 -1
  144. package/dist/public/assets/packet-AYTQ26CC-B0EpJ2Sc.js +1 -0
  145. package/dist/public/assets/path-B_0haWDX.js +1 -0
  146. package/dist/public/assets/pegDiagram-XKGWAZYB-DaNkQTQF.js +1 -0
  147. package/dist/public/assets/pie-WAS4IAKB-Dt2zttL9.js +1 -0
  148. package/dist/public/assets/pieDiagram-E7YTZNPT-x0XWEGbd.js +39 -0
  149. package/dist/public/assets/{quadrantDiagram-ABIIQ3AL-B3czMbdS.js → quadrantDiagram-AXDQQJYC-B9dlNtZl.js} +3 -3
  150. package/dist/public/assets/radar-RG4KPBEZ-DjbR4-5l.js +1 -0
  151. package/dist/public/assets/railroad-74A4TZTK-Bk3B5r_R.js +1 -0
  152. package/dist/public/assets/railroad-abnf-HS5TGJTU-DxaWfACD.js +1 -0
  153. package/dist/public/assets/railroad-ebnf-LZEXJU2U-DscxXZO0.js +1 -0
  154. package/dist/public/assets/railroad-peg-WCYAUIDC-BCWzRFzw.js +1 -0
  155. package/dist/public/assets/railroadDiagram-O6MQD6OU-DhwsoX6A.js +1 -0
  156. package/dist/public/assets/{requirementDiagram-TGXJPOKE-CmvJWy_S.js → requirementDiagram-BXWQKSXE-BiqZ6axf.js} +2 -2
  157. package/dist/public/assets/rolldown-runtime-B0Z9INg1.js +1 -0
  158. package/dist/public/assets/{rough.esm-By172zw-.js → rough.esm-Dy-Kn_BL.js} +1 -1
  159. package/dist/public/assets/{sankeyDiagram-HTMAVEWB-tAeOzMTn.js → sankeyDiagram-P5KCCOFB-C4rb2pZ9.js} +1 -1
  160. package/dist/public/assets/{sequenceDiagram-DBY2YBRQ-r0pwijM4.js → sequenceDiagram-WJ2MYXX4-DRKTnGD7.js} +9 -9
  161. package/dist/public/assets/sizeCapture-INFHLROL-3uHXrFqA.js +1 -0
  162. package/dist/public/assets/src-an_qrnIj.js +1 -0
  163. package/dist/public/assets/stateDiagram-D77RDMKH-xxdybg5Z.js +1 -0
  164. package/dist/public/assets/stateDiagram-v2-MP3YSRHH-CXzmJy_a.js +1 -0
  165. package/dist/public/assets/swimlanes-42K2YHIH-DyGI1gI6.js +1 -0
  166. package/dist/public/assets/swimlanesDiagram-VR7AAH4N-7hqb6VoW.js +8 -0
  167. package/dist/public/assets/timeline-definition-24CTP7MA-C2pIp77Q.js +120 -0
  168. package/dist/public/assets/treeView-Q6P3EWNA-CxRtaaB8.js +1 -0
  169. package/dist/public/assets/treemap-WGGIJYW6-G1IHiwTa.js +1 -0
  170. package/dist/public/assets/{vennDiagram-L72KCM5P-ti2qNcvo.js → vennDiagram-4TSXK5OY-CwimQRR9.js} +2 -2
  171. package/dist/public/assets/wardley-WFR3VGLG-DkQ3TjZX.js +1 -0
  172. package/dist/public/assets/{wardleyDiagram-EHGQE667-C8lblm8x.js → wardleyDiagram-VM6X3IG4-BOXpwLCv.js} +15 -15
  173. package/dist/public/assets/{xychartDiagram-FW5EYKEG-US-_k5eu.js → xychartDiagram-S5SC5T6Z-ChOPYsHc.js} +3 -3
  174. package/dist/public/index.html +19 -26
  175. package/dist/public/sbom.cdx.json +1 -0
  176. package/package.json +12 -13
  177. package/src/app.ts +19 -7
  178. package/src/auth/index.ts +1 -1
  179. package/src/auth/keycloak.ts +8 -3
  180. package/src/auth/mock.ts +141 -35
  181. package/src/auth/types.ts +8 -1
  182. package/src/domain/cards/index.ts +12 -102
  183. package/src/domain/cards/lib.ts +2 -14
  184. package/src/domain/cards/schema.ts +11 -3
  185. package/src/domain/cards/service.ts +9 -24
  186. package/src/domain/events/index.ts +112 -0
  187. package/src/domain/events/lifetime.ts +27 -0
  188. package/src/domain/events/project-events.ts +254 -0
  189. package/src/domain/events/schema.ts +21 -0
  190. package/src/domain/project/index.ts +31 -3
  191. package/src/domain/project/schema.ts +39 -6
  192. package/src/domain/project/service.ts +42 -17
  193. package/src/domain/projects/index.ts +5 -1
  194. package/src/domain/resources/index.ts +85 -2
  195. package/src/domain/resources/schema.ts +12 -2
  196. package/src/domain/resources/service.ts +55 -3
  197. package/src/domain/skills/index.ts +61 -0
  198. package/src/domain/skills/schema.ts +19 -0
  199. package/src/domain/skills/service.ts +18 -0
  200. package/src/export.ts +60 -42
  201. package/src/index.ts +3 -0
  202. package/src/main.ts +26 -8
  203. package/src/middleware/auth.ts +23 -4
  204. package/src/middleware/commandManager.ts +3 -0
  205. package/src/overlay.ts +175 -0
  206. package/src/project-errors.ts +33 -0
  207. package/src/project-registry.ts +41 -10
  208. package/dist/domain/cards/presence.d.ts +0 -50
  209. package/dist/domain/cards/presence.js +0 -93
  210. package/dist/domain/cards/presence.js.map +0 -1
  211. package/dist/public/assets/abnfDiagram-VRR7QNED-Djr4KJ5E.js +0 -1
  212. package/dist/public/assets/arc-DrR4k_H-.js +0 -1
  213. package/dist/public/assets/architecture-TIHT7OUA-XL6N-4o9.js +0 -1
  214. package/dist/public/assets/architectureDiagram-ZJ3FMSHR-DsMU2Uwa.js +0 -36
  215. package/dist/public/assets/blockDiagram-677ZJIJ3-DRBmQUQJ.js +0 -132
  216. package/dist/public/assets/c4Diagram-LMCZKHZV-E0rM02LD.js +0 -10
  217. package/dist/public/assets/channel-D5fnImJy.js +0 -1
  218. package/dist/public/assets/chunk-32BRIVSS-BnDY0aSV.js +0 -1
  219. package/dist/public/assets/chunk-52WLFC77-N5YugKaZ.js +0 -10
  220. package/dist/public/assets/chunk-5FCAYU7R-I3_t_Ic_.js +0 -1
  221. package/dist/public/assets/chunk-5HE753X5-Dxz40rzH.js +0 -1
  222. package/dist/public/assets/chunk-5JV3BV7I-CNwCKRLH.js +0 -1
  223. package/dist/public/assets/chunk-5TONJI2A-ezWQ_Z2j.js +0 -2
  224. package/dist/public/assets/chunk-7BUUIJ7U-Bb538aSH.js +0 -1
  225. package/dist/public/assets/chunk-BIQX33UG-DS132-R_.js +0 -1
  226. package/dist/public/assets/chunk-C7G6YPKG-BN-cNjoj.js +0 -1
  227. package/dist/public/assets/chunk-CQNSW5MT-D8Mn1I73.js +0 -1
  228. package/dist/public/assets/chunk-CYSBUYHQ-Dc64Dwdo.js +0 -1
  229. package/dist/public/assets/chunk-EMLP6XTP-aIK0V8GX.js +0 -1
  230. package/dist/public/assets/chunk-FWX5IMBZ-BF_P2-Do.js +0 -2
  231. package/dist/public/assets/chunk-ICXQ74PX-Bb8zaHde.js +0 -2
  232. package/dist/public/assets/chunk-JG7HCLWE-BtAXK3iQ.js +0 -2
  233. package/dist/public/assets/chunk-KEIR6QF5-DEvma7oi.js +0 -161
  234. package/dist/public/assets/chunk-MOZMSUNE-5FIqIBsY.js +0 -1
  235. package/dist/public/assets/chunk-OGEWGWER-BVIFy9xz.js +0 -1
  236. package/dist/public/assets/chunk-OSBZ3O6U-Cfd7kf1R.js +0 -1
  237. package/dist/public/assets/chunk-PUDLZKDR-Db3FSNR9.js +0 -156
  238. package/dist/public/assets/chunk-Q4XR5HBZ-BuKFRUzR.js +0 -70
  239. package/dist/public/assets/chunk-QBLGF6JB-DUGZXFh-.js +0 -1
  240. package/dist/public/assets/chunk-R7FJI6CG-D7mUd-qN.js +0 -1
  241. package/dist/public/assets/chunk-RYQCIY6F-C_3rESBd.js +0 -1
  242. package/dist/public/assets/chunk-U6XO7XAA-C3UhlwA1.js +0 -2
  243. package/dist/public/assets/chunk-VAUOI2AC-C-GBw18h.js +0 -1
  244. package/dist/public/assets/chunk-WYO6CB5R-BQoO62-B.js +0 -127
  245. package/dist/public/assets/chunk-XXDRQBXY-Co8n4ZHT.js +0 -1
  246. package/dist/public/assets/chunk-YOTPTUD7-DhCRKU1A.js +0 -1
  247. package/dist/public/assets/chunk-ZGVPDNZ5-hpz2Ryq5.js +0 -62
  248. package/dist/public/assets/chunk-ZIRB5QZD-BpeS1aZ7.js +0 -32
  249. package/dist/public/assets/classDiagram-OUVF2IWQ-CXdVorPK.js +0 -1
  250. package/dist/public/assets/classDiagram-v2-EOCWNBFH-CXdVorPK.js +0 -1
  251. package/dist/public/assets/colors-Cg5Vh3br.js +0 -1
  252. package/dist/public/assets/cose-bilkent-JH36ORCC-4GoRbloS.js +0 -1
  253. package/dist/public/assets/cynefin-VYW2F7L2-BPH-25fI.js +0 -1
  254. package/dist/public/assets/cynefinDiagram-TSTJHNR4-PEf16euv.js +0 -62
  255. package/dist/public/assets/cytoscape.esm-C3UvhD5t.js +0 -321
  256. package/dist/public/assets/dagre-Buvkdvvj.js +0 -1
  257. package/dist/public/assets/dagre-VKFMJZFB-DPsCMBF1.js +0 -4
  258. package/dist/public/assets/diagram-G47NLZAW-CzUoYkFf.js +0 -24
  259. package/dist/public/assets/diagram-NH7WQ7WH-MhGnE2fh.js +0 -24
  260. package/dist/public/assets/diagram-OA4YK3LP-CsQ9aFR_.js +0 -30
  261. package/dist/public/assets/diagram-WEI45ONY-Er3q49ew.js +0 -41
  262. package/dist/public/assets/dist-BzwexiH1.js +0 -1
  263. package/dist/public/assets/ebnfDiagram-CCIWWBDH-AnJAsk14.js +0 -1
  264. package/dist/public/assets/erDiagram-Q63AITRT-DulA16my.js +0 -85
  265. package/dist/public/assets/eventmodeling-45OFAUF4-CoIhTpHU.js +0 -1
  266. package/dist/public/assets/flowDiagram-23GEKE2U-BGMXUq18.js +0 -1
  267. package/dist/public/assets/ganttDiagram-NO4QXBWP-DAvCPT7_.js +0 -292
  268. package/dist/public/assets/gitGraph-TEB2WS4Q-BcPsZts5.js +0 -1
  269. package/dist/public/assets/index-Y8Ckwau7.js +0 -447
  270. package/dist/public/assets/info-DKCQHKI2-7VGzrAVB.js +0 -1
  271. package/dist/public/assets/infoDiagram-FWYZ7A6U-D_by7lSL.js +0 -2
  272. package/dist/public/assets/journeyDiagram-5HDEW3XC-DpvFmk7c.js +0 -139
  273. package/dist/public/assets/linear-BWTvbP1l.js +0 -1
  274. package/dist/public/assets/map-BaFkSB1l.js +0 -1
  275. package/dist/public/assets/mermaid-parser.core--8614tkK.js +0 -4
  276. package/dist/public/assets/ordinal-BdBDtYTt.js +0 -1
  277. package/dist/public/assets/packet-7NZHBO7P-KYt33H0C.js +0 -1
  278. package/dist/public/assets/path-HTXttWil.js +0 -1
  279. package/dist/public/assets/pegDiagram-2B236MQR-BQNFxYz2.js +0 -1
  280. package/dist/public/assets/pie-RZYD4A2V-xKz9s9I4.js +0 -1
  281. package/dist/public/assets/pieDiagram-ENE6RG2P-Cy13tg8_.js +0 -39
  282. package/dist/public/assets/radar-I7S5WNFK-B4QTX-fX.js +0 -1
  283. package/dist/public/assets/railroad-3IZDKUUU-Co_9rPVC.js +0 -1
  284. package/dist/public/assets/railroad-abnf-AHOZXSZD-Bhy_c3ol.js +0 -1
  285. package/dist/public/assets/railroad-ebnf-EBAXGLYW-B9ENjG34.js +0 -1
  286. package/dist/public/assets/railroad-peg-LSFZ7HO6-CyJxDBsB.js +0 -1
  287. package/dist/public/assets/railroadDiagram-RFXS5EU6-M2j5SJF2.js +0 -1
  288. package/dist/public/assets/range-EplFIxc7.js +0 -1
  289. package/dist/public/assets/sizeCapture-X5ZJPWSS-B0uUizjq.js +0 -1
  290. package/dist/public/assets/src-DOh7_en_.js +0 -1
  291. package/dist/public/assets/stateDiagram-2N3HPSRC-a9IdkSQG.js +0 -1
  292. package/dist/public/assets/stateDiagram-v2-6OUMAXLB-s2blPtZ-.js +0 -1
  293. package/dist/public/assets/swimlanes-5IMT3BWC-5w0Cdxk6.js +0 -2
  294. package/dist/public/assets/swimlanesDiagram-G3AALYLV-BWa-KRed.js +0 -8
  295. package/dist/public/assets/time-BM2d9MsQ.js +0 -1
  296. package/dist/public/assets/timeline-definition-FHXFAJF6-CvXlv_4Y.js +0 -120
  297. package/dist/public/assets/treeView-QDETBFTQ-B9cxjfpB.js +0 -1
  298. package/dist/public/assets/treemap-6X3UGDF4-BplEq4hB.js +0 -1
  299. package/dist/public/assets/treemap-BfjIY-m6.js +0 -1
  300. package/dist/public/assets/wardley-OPB4EBWU-BX2ghg5e.js +0 -1
  301. package/src/domain/cards/presence.ts +0 -124
@@ -0,0 +1,27 @@
1
+ /**
2
+ Cyberismo
3
+ Copyright © Cyberismo Ltd and contributors 2026
4
+ This program is free software: you can redistribute it and/or modify it under
5
+ the terms of the GNU Affero General Public License version 3 as published by
6
+ the Free Software Foundation.
7
+ This program is distributed in the hope that it will be useful, but WITHOUT
8
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9
+ FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
10
+ details. You should have received a copy of the GNU Affero General Public
11
+ License along with this program. If not, see <https://www.gnu.org/licenses/>.
12
+ */
13
+
14
+ const MAX_LIFETIME_MS = 30 * 60_000;
15
+ const ROTATION_MARGIN_MS = 30_000;
16
+
17
+ /** Rotation deadline in ms from now. */
18
+ export function computeLifetimeMs(exp: number, now = Date.now()): number {
19
+ const jittered = MAX_LIFETIME_MS * (0.6 + Math.random() * 0.4);
20
+ const remaining = exp * 1000 - now;
21
+ // Jitter may only shorten: the token `exp` clamp is hard.
22
+ const untilExpiry =
23
+ remaining > ROTATION_MARGIN_MS
24
+ ? remaining - ROTATION_MARGIN_MS
25
+ : Math.max(remaining, 0);
26
+ return Math.min(untilExpiry, jittered);
27
+ }
@@ -0,0 +1,254 @@
1
+ /**
2
+ Cyberismo
3
+ Copyright © Cyberismo Ltd and contributors 2026
4
+ This program is free software: you can redistribute it and/or modify it under
5
+ the terms of the GNU Affero General Public License version 3 as published by
6
+ the Free Software Foundation.
7
+ This program is distributed in the hope that it will be useful, but WITHOUT
8
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9
+ FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
10
+ details. You should have received a copy of the GNU Affero General Public
11
+ License along with this program. If not, see <https://www.gnu.org/licenses/>.
12
+ */
13
+
14
+ import { randomUUID } from 'node:crypto';
15
+ import type { SSEMessage } from 'hono/streaming';
16
+
17
+ const LEASE_MS = 90_000;
18
+ const EXPIRY_INTERVAL_MS = 30_000;
19
+ const RETIRED_LEASE_MS = 15_000;
20
+
21
+ export interface PresenceEntry {
22
+ userId: string;
23
+ userName: string;
24
+ mode: 'viewing' | 'editing';
25
+ }
26
+
27
+ export interface Capabilities {
28
+ canSeeIdentity: boolean;
29
+ canDeclareEditing: boolean;
30
+ canSeePresence: boolean;
31
+ }
32
+
33
+ /** The socket one connection's client is written to. A retired connection
34
+ * must not be written to at all; `deliver` and `closeQuietly` enforce that,
35
+ * since the route has already ended the stream by then. */
36
+ export interface Delivery {
37
+ send(message: SSEMessage): void;
38
+ close(): void;
39
+ terminate(message: SSEMessage): void;
40
+ }
41
+
42
+ interface Connection {
43
+ user: { id: string; name: string };
44
+ capabilities: Capabilities;
45
+ delivery: Delivery;
46
+ cardKey: string | null;
47
+ mode: PresenceEntry['mode'];
48
+ sequence: number;
49
+ expiresAt: number;
50
+ retired: boolean;
51
+ /** Last presence list serialized to this connection, for per-recipient dedup. */
52
+ lastSent: string | null;
53
+ }
54
+
55
+ /** The open subscriptions of one project and the presence leases they hold. */
56
+ export class ProjectEvents {
57
+ private connections = new Map<string, Connection>();
58
+ private expiryTimer: ReturnType<typeof setInterval>;
59
+
60
+ constructor() {
61
+ this.expiryTimer = setInterval(() => this.expire(), EXPIRY_INTERVAL_MS);
62
+ this.expiryTimer.unref();
63
+ }
64
+
65
+ connect(
66
+ user: Connection['user'],
67
+ capabilities: Capabilities,
68
+ delivery: Delivery,
69
+ ): string {
70
+ const connectionId = randomUUID();
71
+ this.connections.set(connectionId, {
72
+ user,
73
+ capabilities,
74
+ delivery,
75
+ cardKey: null,
76
+ mode: 'viewing',
77
+ sequence: -1,
78
+ expiresAt: 0,
79
+ retired: false,
80
+ lastSent: null,
81
+ });
82
+ // EventSource cannot read response headers, so the stream is the only
83
+ // way back with a connection handle.
84
+ this.deliver(connectionId, {
85
+ event: 'ready',
86
+ data: JSON.stringify({ connectionId }),
87
+ });
88
+ return connectionId;
89
+ }
90
+
91
+ update(
92
+ connectionId: string,
93
+ userId: string,
94
+ sequence: number,
95
+ cardKey: string | null,
96
+ mode: PresenceEntry['mode'],
97
+ ): boolean {
98
+ const connection = this.connections.get(connectionId);
99
+ if (!connection || connection.retired || connection.user.id !== userId) {
100
+ return false;
101
+ }
102
+ // Monotonic per connection, so a delayed request cannot undo a newer one.
103
+ if (sequence <= connection.sequence) return true;
104
+ // Coerced rather than rejected: a fake editing warning deters others from editing.
105
+ if (!connection.capabilities.canDeclareEditing) mode = 'viewing';
106
+ if (connection.cardKey === cardKey && connection.mode === mode) {
107
+ connection.sequence = sequence;
108
+ connection.expiresAt = Date.now() + LEASE_MS;
109
+ return true;
110
+ }
111
+ this.applyMove(connection, sequence, cardKey, mode, Date.now());
112
+ return true;
113
+ }
114
+
115
+ private applyMove(
116
+ connection: Connection,
117
+ sequence: number,
118
+ cardKey: string | null,
119
+ mode: PresenceEntry['mode'],
120
+ now: number,
121
+ ): void {
122
+ const previous = connection.cardKey;
123
+ if (previous !== cardKey) connection.lastSent = null;
124
+ Object.assign(connection, {
125
+ sequence,
126
+ cardKey,
127
+ mode,
128
+ expiresAt: now + LEASE_MS,
129
+ });
130
+ if (previous) this.presenceUpdated(previous);
131
+ if (cardKey && cardKey !== previous) this.presenceUpdated(cardKey);
132
+ }
133
+
134
+ disconnect(connectionId: string): void {
135
+ const connection = this.connections.get(connectionId);
136
+ if (!connection) return;
137
+ this.connections.delete(connectionId);
138
+ this.closeQuietly(connection);
139
+ if (connection.cardKey) this.presenceUpdated(connection.cardKey);
140
+ }
141
+
142
+ // `users()` dedups by user id, so a retiring connection's entry can outlive
143
+ // its socket without observers seeing a change.
144
+ retire(connectionId: string): void {
145
+ const connection = this.connections.get(connectionId);
146
+ if (!connection) return;
147
+ connection.retired = true;
148
+ // The entry only has to bridge the client's reconnect. Left on its full
149
+ // lease it would go on reporting the card and mode held at retirement,
150
+ // and `editing` wins in users(), so it would mask the live entry the
151
+ // replacement declares.
152
+ connection.expiresAt = Math.min(
153
+ connection.expiresAt,
154
+ Date.now() + RETIRED_LEASE_MS,
155
+ );
156
+ }
157
+
158
+ cardsUpdated(cardKeys: string[], user: Connection['user']): void {
159
+ for (const cardKey of new Set(cardKeys)) {
160
+ const withIdentity = this.encode('card.updated', {
161
+ cardKey,
162
+ userId: user.id,
163
+ userName: user.name,
164
+ });
165
+ const withoutIdentity = this.encode('card.updated', { cardKey });
166
+ for (const [connectionId, connection] of this.onCard(cardKey)) {
167
+ this.deliver(
168
+ connectionId,
169
+ connection.capabilities.canSeeIdentity
170
+ ? withIdentity
171
+ : withoutIdentity,
172
+ );
173
+ }
174
+ }
175
+ }
176
+
177
+ dispose(): void {
178
+ clearInterval(this.expiryTimer);
179
+ const connections = [...this.connections.values()];
180
+ this.connections.clear();
181
+ for (const connection of connections) {
182
+ this.closeQuietly(connection);
183
+ }
184
+ }
185
+
186
+ private users(cardKey: string): PresenceEntry[] {
187
+ const users = new Map<string, PresenceEntry>();
188
+ for (const { user, cardKey: key, mode } of this.connections.values()) {
189
+ if (key !== cardKey) continue;
190
+ if (!users.has(user.id) || mode === 'editing') {
191
+ users.set(user.id, { userId: user.id, userName: user.name, mode });
192
+ }
193
+ }
194
+ // Sorted so identical occupancy always serializes the same way, which
195
+ // the per-recipient dedup in presenceUpdated() depends on.
196
+ return [...users.values()].sort((a, b) => a.userId.localeCompare(b.userId));
197
+ }
198
+
199
+ private presenceUpdated(cardKey: string): void {
200
+ const users = this.users(cardKey);
201
+ const serialized = JSON.stringify(users);
202
+ const message = this.encode('presence.updated', { cardKey, users });
203
+ for (const [connectionId, connection] of this.onCard(cardKey)) {
204
+ // Presence exists to keep editors from colliding, so a reader has no use
205
+ // for it and is not told who else is here. Readers still declare their
206
+ // own presence, which is why users() keeps them: editors see readers.
207
+ if (!connection.capabilities.canSeePresence) continue;
208
+ if (connection.lastSent === serialized) continue;
209
+ this.deliver(connectionId, message);
210
+ connection.lastSent = serialized;
211
+ }
212
+ }
213
+
214
+ // Subscription governs delivery.
215
+ private onCard(cardKey: string): [string, Connection][] {
216
+ return [...this.connections].filter(([, c]) => c.cardKey === cardKey);
217
+ }
218
+
219
+ private encode(event: string, data: unknown): SSEMessage {
220
+ return { event, data: JSON.stringify(data) };
221
+ }
222
+
223
+ private deliver(connectionId: string, message: SSEMessage): void {
224
+ const connection = this.connections.get(connectionId);
225
+ // onCard() still yields a retired connection: it keeps its cardKey so the
226
+ // occupant list does not change while its replacement connects.
227
+ if (!connection || connection.retired) return;
228
+ try {
229
+ connection.delivery.send(message);
230
+ } catch {
231
+ this.disconnect(connectionId);
232
+ }
233
+ }
234
+
235
+ private expire(): void {
236
+ for (const [connectionId, connection] of this.connections) {
237
+ if (connection.expiresAt > Date.now()) continue;
238
+ if (!connection.retired && !connection.cardKey) continue;
239
+ const cardKey = connection.cardKey;
240
+ if (connection.retired) this.connections.delete(connectionId);
241
+ else connection.cardKey = null;
242
+ if (cardKey) this.presenceUpdated(cardKey);
243
+ }
244
+ }
245
+
246
+ private closeQuietly(connection: Connection): void {
247
+ if (connection.retired) return;
248
+ try {
249
+ connection.delivery.close();
250
+ } catch {
251
+ /* already closed */
252
+ }
253
+ }
254
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ Cyberismo
3
+ Copyright © Cyberismo Ltd and contributors 2026
4
+ This program is free software: you can redistribute it and/or modify it under
5
+ the terms of the GNU Affero General Public License version 3 as published by
6
+ the Free Software Foundation.
7
+ This program is distributed in the hope that it will be useful, but WITHOUT
8
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9
+ FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
10
+ details. You should have received a copy of the GNU Affero General Public
11
+ License along with this program. If not, see <https://www.gnu.org/licenses/>.
12
+ */
13
+
14
+ import { z } from 'zod';
15
+
16
+ export const presenceSchema = z.object({
17
+ connectionId: z.uuid(),
18
+ sequence: z.number().int().nonnegative(),
19
+ cardKey: z.string().min(1).nullable(),
20
+ mode: z.enum(['viewing', 'editing']),
21
+ });
@@ -12,13 +12,16 @@
12
12
  */
13
13
 
14
14
  import { Hono } from 'hono';
15
+ import { disableSSG } from 'hono/ssg';
15
16
  import { zValidator } from '../../middleware/zvalidator.js';
16
17
  import {
17
18
  addHubSchema,
18
19
  cleanSchema,
19
20
  importModuleSchema,
20
21
  moduleParamSchema,
22
+ moduleVersionsQuerySchema,
21
23
  removeHubSchema,
24
+ updateModuleSchema,
22
25
  updateProjectSchema,
23
26
  } from './schema.js';
24
27
  import * as projectService from './service.js';
@@ -57,14 +60,39 @@ router.post(
57
60
  '/modules/:module/update',
58
61
  requireRole(UserRole.Admin),
59
62
  zValidator('param', moduleParamSchema),
63
+ zValidator('json', updateModuleSchema),
60
64
  async (c) => {
61
65
  const commands = c.get('commands');
62
66
  const { module } = c.req.valid('param');
63
- await projectService.updateModule(commands, module);
67
+ const { range } = c.req.valid('json');
68
+ await projectService.updateModule(commands, module, range);
64
69
  return c.json({ message: 'Module updated' });
65
70
  },
66
71
  );
67
72
 
73
+ // These reach the module sources over the network, which an export must not do.
74
+ router.get(
75
+ '/modules/versions',
76
+ disableSSG(),
77
+ requireRole(UserRole.Admin),
78
+ zValidator('query', moduleVersionsQuerySchema),
79
+ async (c) => {
80
+ const commands = c.get('commands');
81
+ const target = c.req.valid('query');
82
+ return c.json(await projectService.listModuleVersions(commands, target));
83
+ },
84
+ );
85
+
86
+ router.get(
87
+ '/modules/update-plan',
88
+ disableSSG(),
89
+ requireRole(UserRole.Admin),
90
+ async (c) => {
91
+ const commands = c.get('commands');
92
+ return c.json(await projectService.getUpdatePlan(commands));
93
+ },
94
+ );
95
+
68
96
  router.get('/modules/importable', requireRole(UserRole.Reader), async (c) => {
69
97
  const commands = c.get('commands');
70
98
  const modules = await projectService.getImportableModules(commands);
@@ -77,8 +105,8 @@ router.post(
77
105
  zValidator('json', importModuleSchema),
78
106
  async (c) => {
79
107
  const commands = c.get('commands');
80
- const { source } = c.req.valid('json');
81
- await projectService.importModule(commands, source);
108
+ const { source, range } = c.req.valid('json');
109
+ await projectService.importModule(commands, source, range);
82
110
  return c.json({ message: 'Module imported successfully' });
83
111
  },
84
112
  );
@@ -51,11 +51,44 @@ export const cleanSchema = z.object({
51
51
  dryRun: z.boolean(),
52
52
  });
53
53
 
54
+ // Credentials in a source would be stored in cardsConfig.json and echoed in
55
+ // query strings, logs and errors.
56
+ const gitSourceSchema = z
57
+ .string()
58
+ .min(1)
59
+ .refine((s) => s.startsWith('https://') || s.startsWith('git@'), {
60
+ message: 'Source must be a git URL (https:// or git@)',
61
+ })
62
+ .refine(
63
+ (s) => {
64
+ if (!s.startsWith('https://')) return true;
65
+ try {
66
+ const { username, password } = new URL(s);
67
+ return username === '' && password === '';
68
+ } catch {
69
+ return false;
70
+ }
71
+ },
72
+ { message: 'Source must be a valid URL without credentials' },
73
+ );
74
+
75
+ // A blank range is a valid semver range meaning '*', which would unpin.
76
+ const rangeSchema = z.string().trim().min(1);
77
+
54
78
  export const importModuleSchema = z.object({
55
- source: z
56
- .string()
57
- .min(1)
58
- .refine((s) => s.startsWith('https://') || s.startsWith('git@'), {
59
- message: 'Source must be a git URL (https:// or git@)',
60
- }),
79
+ source: gitSourceSchema,
80
+ range: rangeSchema.optional(),
61
81
  });
82
+
83
+ export const updateModuleSchema = z.object({
84
+ range: rangeSchema.optional(),
85
+ });
86
+
87
+ export const moduleVersionsQuerySchema = z
88
+ .object({
89
+ module: z.string().min(1).optional(),
90
+ source: gitSourceSchema.optional(),
91
+ })
92
+ .refine((q) => (q.module === undefined) !== (q.source === undefined), {
93
+ message: 'Provide exactly one of module or source',
94
+ });
@@ -16,12 +16,14 @@ import {
16
16
  type CommandManager,
17
17
  type HubFetchFailure,
18
18
  type ModuleSettingFromHub,
19
+ type ProjectModuleInfo,
20
+ type UpdatePlan,
19
21
  } from '@cyberismo/data-handler';
20
22
 
21
23
  export type { CleanResult } from '@cyberismo/data-handler';
22
24
 
23
- export interface ProjectModule {
24
- name: string;
25
+ // Never carries the module's source location.
26
+ export interface ProjectModule extends ProjectModuleInfo {
25
27
  cardKeyPrefix: string;
26
28
  }
27
29
 
@@ -58,19 +60,17 @@ export interface HubInfo {
58
60
 
59
61
  async function toModuleInfo(
60
62
  commands: CommandManager,
61
- moduleName: string,
63
+ row: ProjectModuleInfo,
62
64
  ): Promise<ProjectModule> {
63
65
  try {
64
- const data = await commands.showCmd.showModule(moduleName);
66
+ const data = await commands.modulesCmd.show(row.name);
65
67
  return {
66
- name: data.name || moduleName,
67
- cardKeyPrefix: data.cardKeyPrefix || moduleName,
68
+ ...row,
69
+ name: data.name || row.name,
70
+ cardKeyPrefix: data.cardKeyPrefix || row.name,
68
71
  };
69
72
  } catch {
70
- return {
71
- name: moduleName,
72
- cardKeyPrefix: moduleName,
73
- };
73
+ return { ...row, cardKeyPrefix: row.name };
74
74
  }
75
75
  }
76
76
 
@@ -79,9 +79,9 @@ export async function getProject(
79
79
  ): Promise<ProjectInfo> {
80
80
  return commands.consistent(async () => {
81
81
  const project = await commands.showCmd.showProject();
82
- const modules = await commands.showCmd.showModules();
82
+ const modules = await commands.modulesCmd.inventory();
83
83
  const moduleDetails = await Promise.all(
84
- modules.map((mod) => toModuleInfo(commands, mod.name)),
84
+ modules.map((row) => toModuleInfo(commands, row)),
85
85
  );
86
86
 
87
87
  const gitRemoteUrl = (await commands.showCmd.showGitRemoteUrl()) ?? null;
@@ -125,12 +125,33 @@ export async function updateProject(
125
125
  return getProject(commands);
126
126
  }
127
127
 
128
- export async function updateModule(commands: CommandManager, module: string) {
129
- await commands.importCmd.updateModule(module);
128
+ export async function updateModule(
129
+ commands: CommandManager,
130
+ module: string,
131
+ range?: string,
132
+ ) {
133
+ await commands.modulesCmd.update(range ? { module, range } : { module });
130
134
  }
131
135
 
132
136
  export async function updateAllModules(commands: CommandManager) {
133
- await commands.importCmd.updateAllModules();
137
+ await commands.modulesCmd.update({});
138
+ }
139
+
140
+ export async function getUpdatePlan(
141
+ commands: CommandManager,
142
+ ): Promise<UpdatePlan> {
143
+ return commands.modulesCmd.planUpdate({});
144
+ }
145
+
146
+ export async function listModuleVersions(
147
+ commands: CommandManager,
148
+ target: { module?: string; source?: string },
149
+ ): Promise<string[]> {
150
+ return commands.modulesCmd.listVersions(
151
+ target.source !== undefined
152
+ ? { source: target.source }
153
+ : { module: target.module! },
154
+ );
134
155
  }
135
156
 
136
157
  export async function deleteModule(commands: CommandManager, module: string) {
@@ -158,15 +179,19 @@ export async function getImportableModules(
158
179
  export async function importModule(
159
180
  commands: CommandManager,
160
181
  source: string,
182
+ range?: string,
161
183
  ): Promise<void> {
162
- await commands.importCmd.importModule(source);
184
+ await commands.modulesCmd.install(
185
+ source,
186
+ range ? { version: range } : undefined,
187
+ );
163
188
  }
164
189
 
165
190
  export async function getHubs(commands: CommandManager): Promise<HubInfo[]> {
166
191
  await populateHubCache(commands);
167
192
  const hubs = await commands.showCmd.showHubDetails();
168
193
  const importedModules = new Set(
169
- (await commands.showCmd.showModules()).map((mod) => mod.name),
194
+ (await commands.modulesCmd.list()).map((mod) => mod.name),
170
195
  );
171
196
  return hubs.map((hub) => ({
172
197
  location: hub.location,
@@ -15,6 +15,7 @@ import { Hono } from 'hono';
15
15
  import { zValidator } from '../../middleware/zvalidator.js';
16
16
  import type { ProjectRegistry } from '../../project-registry.js';
17
17
  import { requireRole, getCurrentUser } from '../../middleware/auth.js';
18
+ import { isProjectReadOnly } from '../../overlay.js';
18
19
  import { UserRole } from '../../types.js';
19
20
  import { createProjectSchema, cloneProjectSchema } from './schema.js';
20
21
  import * as projectsService from './service.js';
@@ -39,7 +40,10 @@ export function createProjectsRouter(
39
40
  */
40
41
  router.get('/', requireRole(UserRole.Reader), (c) => {
41
42
  return c.json({
42
- projects: registry.list(),
43
+ projects: registry.list().map((project) => ({
44
+ ...project,
45
+ readOnly: isProjectReadOnly(project.prefix),
46
+ })),
43
47
  canCreateProjects: !!multiProjectRoot,
44
48
  });
45
49
  });
@@ -20,8 +20,9 @@ import { resourceParamsSchema } from '../../common/validationSchemas.js';
20
20
  import { zValidator } from '../../middleware/zvalidator.js';
21
21
  import {
22
22
  validateResourceParamsSchema,
23
+ prefixIdentifierParamsSchema,
24
+ skillPreviewBodySchema,
23
25
  updateOperationBodySchema,
24
- workflowGraphParamsSchema,
25
26
  workflowGraphQuerySchema,
26
27
  } from './schema.js';
27
28
  import { requireRole } from '../../middleware/auth.js';
@@ -188,7 +189,7 @@ router.get(
188
189
  const commands = c.get('commands');
189
190
  return resourceService.listWorkflowGraphParams(commands);
190
191
  }),
191
- zValidator('param', workflowGraphParamsSchema),
192
+ zValidator('param', prefixIdentifierParamsSchema),
192
193
  zValidator('query', workflowGraphQuerySchema),
193
194
  async (c) => {
194
195
  const commands = c.get('commands');
@@ -211,6 +212,88 @@ router.get(
211
212
  },
212
213
  );
213
214
 
215
+ /**
216
+ * @swagger
217
+ * /api/resources/{prefix}/skills/{identifier}/preview:
218
+ * post:
219
+ * summary: Render a skill's instructions for the configuration editor
220
+ * description: Applies Handlebars templating to the skill content and
221
+ * returns the rendered markdown. For template skills the
222
+ * logic program in query.lp is executed first and its result
223
+ * is used as the template context. Unlike the MCP get_skill
224
+ * tool, the skill does not need to be enabled by the logic
225
+ * programs. Pass skillContent and/or skillQuery to render
226
+ * unsaved editor content instead of what is stored on disk.
227
+ * parameters:
228
+ * - in: path
229
+ * name: prefix
230
+ * required: true
231
+ * schema:
232
+ * type: string
233
+ * - in: path
234
+ * name: identifier
235
+ * required: true
236
+ * schema:
237
+ * type: string
238
+ * requestBody:
239
+ * required: false
240
+ * content:
241
+ * application/json:
242
+ * schema:
243
+ * type: object
244
+ * properties:
245
+ * skillContent:
246
+ * type: string
247
+ * description: Unsaved skill.md content to render.
248
+ * skillQuery:
249
+ * type: string
250
+ * description: Unsaved query.lp content to run.
251
+ * cardKey:
252
+ * type: string
253
+ * description: Card context for a card-specific skill.
254
+ * responses:
255
+ * 200:
256
+ * description: Rendered instructions
257
+ * content:
258
+ * application/json:
259
+ * schema:
260
+ * type: object
261
+ * properties:
262
+ * instructions:
263
+ * type: string
264
+ * description: The rendered skill content as markdown.
265
+ * 400:
266
+ * description: Invalid path parameters, or the query or template failed
267
+ * 404:
268
+ * description: Skill or card not found
269
+ */
270
+ router.post(
271
+ '/:prefix/skills/:identifier/preview',
272
+ requireRole(UserRole.Admin),
273
+ zValidator('param', prefixIdentifierParamsSchema),
274
+ zValidator('json', skillPreviewBodySchema),
275
+ async (c) => {
276
+ const commands = c.get('commands');
277
+ const { prefix, identifier } = c.req.valid('param');
278
+ const body = c.req.valid('json');
279
+ const skillName = `${prefix}/skills/${identifier}`;
280
+ try {
281
+ const instructions = await resourceService.previewSkill(
282
+ commands,
283
+ skillName,
284
+ body,
285
+ );
286
+ return c.json({ instructions });
287
+ } catch (error) {
288
+ const message = error instanceof Error ? error.message : 'Unknown error';
289
+ if (message.includes('not found')) {
290
+ return c.json({ error: message }, 404);
291
+ }
292
+ return c.json({ error: message }, 400);
293
+ }
294
+ },
295
+ );
296
+
214
297
  router.post(
215
298
  '/:prefix/:type/:identifier/operation',
216
299
  requireRole(UserRole.Admin, [UserRole.Connector]),