@piecemaker-legal/piecemaker 1.0.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 (1424) hide show
  1. package/LICENSE +718 -0
  2. package/README.md +80 -0
  3. package/dist/Mistral-Icon-Gradient-RGB.svg +12 -0
  4. package/dist/api-docs.html +901 -0
  5. package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  6. package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  7. package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  8. package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  9. package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  10. package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  11. package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  12. package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  13. package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  14. package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  15. package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  16. package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  17. package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  18. package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  19. package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  20. package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  21. package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  22. package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  23. package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  24. package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  25. package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  26. package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  27. package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  28. package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  29. package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  30. package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  31. package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  32. package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  33. package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  34. package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  35. package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  36. package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  37. package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  38. package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  39. package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  40. package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  41. package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  42. package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  43. package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  44. package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  45. package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  46. package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  47. package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  48. package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  49. package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  50. package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  51. package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  52. package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  53. package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  54. package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  55. package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  56. package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  57. package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  58. package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  59. package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  60. package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  61. package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  62. package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  63. package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  64. package/dist/assets/abnfDiagram-N423BO3Z-DjEx6DRO.js +1 -0
  65. package/dist/assets/arc-DYt0oU_6.js +1 -0
  66. package/dist/assets/architectureDiagram-T3A2C74G-Dn6DhYDe.js +36 -0
  67. package/dist/assets/blockDiagram-VBNYF7ZC-CsvhTLZi.js +132 -0
  68. package/dist/assets/c4Diagram-5PPSVZJV-BuPnjHm6.js +10 -0
  69. package/dist/assets/channel-Czt0N91A.js +1 -0
  70. package/dist/assets/chunk-2GRJ4B5K-BZJJc1-J.js +1 -0
  71. package/dist/assets/chunk-2Q5K7J3B-6stdrDbe.js +1 -0
  72. package/dist/assets/chunk-5RXB4S5H-D5UwN5cg.js +231 -0
  73. package/dist/assets/chunk-5VM5RSS4-BDB8AQ_I.js +15 -0
  74. package/dist/assets/chunk-6Q2QTUOP-RhjQ-NDz.js +88 -0
  75. package/dist/assets/chunk-GF5L2VYU-CJuiA7R4.js +206 -0
  76. package/dist/assets/chunk-JWPE2WC7-DjFkIZqh.js +1 -0
  77. package/dist/assets/chunk-KBJHAD2P-BeSQsoqv.js +1 -0
  78. package/dist/assets/chunk-RYQCIY6F-CPgwsb0o.js +1 -0
  79. package/dist/assets/chunk-XXDRQBXY-CMMVEsX3.js +1 -0
  80. package/dist/assets/classDiagram-JCYQIIEL-BrnQyrwR.js +1 -0
  81. package/dist/assets/classDiagram-v2-OCEON4UE-BrnQyrwR.js +1 -0
  82. package/dist/assets/cose-bilkent-JH36ORCC-XAdYMfi5.js +1 -0
  83. package/dist/assets/cynefin-VYW2F7L2-d2GpDBn-.js +178 -0
  84. package/dist/assets/cynefinDiagram-MW4NZA55-CGtqUrU1.js +62 -0
  85. package/dist/assets/cytoscape.esm-C10L9Icf.js +331 -0
  86. package/dist/assets/dagre-VZM6K2ZE-BxujKIsf.js +4 -0
  87. package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  88. package/dist/assets/diagram-7IWD3JNH-CJn9hobL.js +30 -0
  89. package/dist/assets/diagram-B4RE2ZJO-DF0Cnjzz.js +3 -0
  90. package/dist/assets/diagram-LBJQPF4R-Bq4d3NfR.js +24 -0
  91. package/dist/assets/diagram-Q27KOJAE-DDdfoXt-.js +24 -0
  92. package/dist/assets/diagram-UB23O5K3-TSFk7FuJ.js +41 -0
  93. package/dist/assets/ebnfDiagram-BXEA7PRR-DRvm2Wy9.js +1 -0
  94. package/dist/assets/erDiagram-JOGREHBK-BmdN66-A.js +85 -0
  95. package/dist/assets/flowDiagram-UKHOOZJN-Dsp5LwJz.js +156 -0
  96. package/dist/assets/ganttDiagram-PKOTCBZU-CYfCQ4Yi.js +292 -0
  97. package/dist/assets/gitGraphDiagram-DS77QQ5N-DEzmuv8v.js +106 -0
  98. package/dist/assets/graph-Cx-EqisW.js +1 -0
  99. package/dist/assets/index-CqFxV_pT.css +32 -0
  100. package/dist/assets/index-dWwHeqcq.js +4989 -0
  101. package/dist/assets/infoDiagram-6WML65LV-B1EfV5Bm.js +2 -0
  102. package/dist/assets/init-Gi6I4Gst.js +1 -0
  103. package/dist/assets/ishikawaDiagram-WSZJBQD7-OuQJfUii.js +70 -0
  104. package/dist/assets/journeyDiagram-NVQOT4AX-DHIilb_4.js +139 -0
  105. package/dist/assets/kanban-definition-27J2QSJJ-D4CvKWBP.js +89 -0
  106. package/dist/assets/layout-D6BstSzF.js +1 -0
  107. package/dist/assets/linear-i_Nzyzap.js +1 -0
  108. package/dist/assets/map-CV9uif0g.js +1 -0
  109. package/dist/assets/mermaid.core-DbSHejzG.js +312 -0
  110. package/dist/assets/mindmap-definition-FAOFIHXS-DqhC-BPZ.js +96 -0
  111. package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  112. package/dist/assets/pegDiagram-VL7TDLO6-pBqvOEvm.js +1 -0
  113. package/dist/assets/pieDiagram-7S7Q4E2Y-BH53iuaz.js +39 -0
  114. package/dist/assets/quadrantDiagram-CIZ2JOQS-CvW9ldJo.js +7 -0
  115. package/dist/assets/railroadDiagram-AXF67PYL-CERi66oR.js +1 -0
  116. package/dist/assets/requirementDiagram-LRYGKXZP-DbcP4MGI.js +84 -0
  117. package/dist/assets/sankeyDiagram-W5VNT64P-DO9bPHWO.js +40 -0
  118. package/dist/assets/sequenceDiagram-SI44F4Z6-D3zd2eJS.js +162 -0
  119. package/dist/assets/server.browser-CtX3Hdl5.js +27 -0
  120. package/dist/assets/sizeCapture-X5ZJPWSS-mcEXJiem.js +1 -0
  121. package/dist/assets/stateDiagram-OKZ733FA-DMqQgpiD.js +1 -0
  122. package/dist/assets/stateDiagram-v2-UEYNNEHI-DprF_P9y.js +1 -0
  123. package/dist/assets/swimlanes-SLNWSIFB-Cb8rIhB1.js +2 -0
  124. package/dist/assets/swimlanesDiagram-ULZ7WXOC-C6NVYMho.js +8 -0
  125. package/dist/assets/timeline-definition-Z64GVDOM-BQ4r_QsK.js +120 -0
  126. package/dist/assets/vendor-codemirror-CYrSt7-U.js +41 -0
  127. package/dist/assets/vendor-react-BGZc9oRE.js +59 -0
  128. package/dist/assets/vendor-xterm-CS4rQ5Mr.js +66 -0
  129. package/dist/assets/vennDiagram-T6HMQDX7-BEkKGTum.js +34 -0
  130. package/dist/assets/wardleyDiagram-T6FBY63Y-kJwmgH-h.js +78 -0
  131. package/dist/assets/xychartDiagram-ELKLHX3M-CSJRTKCr.js +7 -0
  132. package/dist/clear-cache.html +85 -0
  133. package/dist/convert-icons.md +53 -0
  134. package/dist/favicon.png +0 -0
  135. package/dist/favicon.svg +9 -0
  136. package/dist/generate-icons.js +49 -0
  137. package/dist/icons/claude-ai-icon.svg +1 -0
  138. package/dist/icons/codex-white.svg +3 -0
  139. package/dist/icons/codex.svg +3 -0
  140. package/dist/icons/cursor-white.svg +12 -0
  141. package/dist/icons/cursor.svg +1 -0
  142. package/dist/icons/icon-128x128.png +0 -0
  143. package/dist/icons/icon-128x128.svg +12 -0
  144. package/dist/icons/icon-144x144.png +0 -0
  145. package/dist/icons/icon-144x144.svg +12 -0
  146. package/dist/icons/icon-152x152.png +0 -0
  147. package/dist/icons/icon-152x152.svg +12 -0
  148. package/dist/icons/icon-192x192.png +0 -0
  149. package/dist/icons/icon-192x192.svg +12 -0
  150. package/dist/icons/icon-384x384.png +0 -0
  151. package/dist/icons/icon-384x384.svg +12 -0
  152. package/dist/icons/icon-512x512.png +0 -0
  153. package/dist/icons/icon-512x512.svg +12 -0
  154. package/dist/icons/icon-72x72.png +0 -0
  155. package/dist/icons/icon-72x72.svg +12 -0
  156. package/dist/icons/icon-96x96.png +0 -0
  157. package/dist/icons/icon-96x96.svg +12 -0
  158. package/dist/icons/icon-template.svg +12 -0
  159. package/dist/index.html +60 -0
  160. package/dist/logo-128.png +0 -0
  161. package/dist/logo-256.png +0 -0
  162. package/dist/logo-32.png +0 -0
  163. package/dist/logo-512.png +0 -0
  164. package/dist/logo-64.png +0 -0
  165. package/dist/logo.svg +17 -0
  166. package/dist/manifest.json +61 -0
  167. package/dist/screenshots/cli-selection.png +0 -0
  168. package/dist/screenshots/desktop-main.png +0 -0
  169. package/dist/screenshots/mobile-chat.png +0 -0
  170. package/dist/screenshots/tools-modal.png +0 -0
  171. package/dist/sw.js +125 -0
  172. package/dist-server/server/index.js +345 -0
  173. package/dist-server/server/index.js.map +1 -0
  174. package/dist-server/server/load-env.js +46 -0
  175. package/dist-server/server/load-env.js.map +1 -0
  176. package/dist-server/server/modules/agent/agent.module.js +38 -0
  177. package/dist-server/server/modules/agent/agent.module.js.map +1 -0
  178. package/dist-server/server/modules/agent/agent.routes.js +1161 -0
  179. package/dist-server/server/modules/agent/agent.routes.js.map +1 -0
  180. package/dist-server/server/modules/agent/index.js +3 -0
  181. package/dist-server/server/modules/agent/index.js.map +1 -0
  182. package/dist-server/server/modules/agent/tests/agent.routes.test.js +176 -0
  183. package/dist-server/server/modules/agent/tests/agent.routes.test.js.map +1 -0
  184. package/dist-server/server/modules/assets/assets.routes.js +130 -0
  185. package/dist-server/server/modules/assets/assets.routes.js.map +1 -0
  186. package/dist-server/server/modules/assets/index.js +4 -0
  187. package/dist-server/server/modules/assets/index.js.map +1 -0
  188. package/dist-server/server/modules/assets/services/image-assets.service.js +95 -0
  189. package/dist-server/server/modules/assets/services/image-assets.service.js.map +1 -0
  190. package/dist-server/server/modules/assets/tests/image-assets.service.test.js +56 -0
  191. package/dist-server/server/modules/assets/tests/image-assets.service.test.js.map +1 -0
  192. package/dist-server/server/modules/auth/auth.middleware.js +133 -0
  193. package/dist-server/server/modules/auth/auth.middleware.js.map +1 -0
  194. package/dist-server/server/modules/auth/auth.module.js +29 -0
  195. package/dist-server/server/modules/auth/auth.module.js.map +1 -0
  196. package/dist-server/server/modules/auth/auth.routes.js +45 -0
  197. package/dist-server/server/modules/auth/auth.routes.js.map +1 -0
  198. package/dist-server/server/modules/auth/auth.service.js +113 -0
  199. package/dist-server/server/modules/auth/auth.service.js.map +1 -0
  200. package/dist-server/server/modules/auth/index.js +9 -0
  201. package/dist-server/server/modules/auth/index.js.map +1 -0
  202. package/dist-server/server/modules/auth/tests/auth.service.test.js +80 -0
  203. package/dist-server/server/modules/auth/tests/auth.service.test.js.map +1 -0
  204. package/dist-server/server/modules/browser-use/browser-use-mcp.js +352 -0
  205. package/dist-server/server/modules/browser-use/browser-use-mcp.js.map +1 -0
  206. package/dist-server/server/modules/browser-use/browser-use-mcp.routes.js +107 -0
  207. package/dist-server/server/modules/browser-use/browser-use-mcp.routes.js.map +1 -0
  208. package/dist-server/server/modules/browser-use/browser-use-runtime.js +10 -0
  209. package/dist-server/server/modules/browser-use/browser-use-runtime.js.map +1 -0
  210. package/dist-server/server/modules/browser-use/browser-use.routes.js +93 -0
  211. package/dist-server/server/modules/browser-use/browser-use.routes.js.map +1 -0
  212. package/dist-server/server/modules/browser-use/browser-use.service.js +686 -0
  213. package/dist-server/server/modules/browser-use/browser-use.service.js.map +1 -0
  214. package/dist-server/server/modules/browser-use/index.js +11 -0
  215. package/dist-server/server/modules/browser-use/index.js.map +1 -0
  216. package/dist-server/server/modules/browser-use/tests/browser-use.service.test.js +8 -0
  217. package/dist-server/server/modules/browser-use/tests/browser-use.service.test.js.map +1 -0
  218. package/dist-server/server/modules/cli/cli.js +17 -0
  219. package/dist-server/server/modules/cli/cli.js.map +1 -0
  220. package/dist-server/server/modules/cli/cli.module.js +93 -0
  221. package/dist-server/server/modules/cli/cli.module.js.map +1 -0
  222. package/dist-server/server/modules/cli/cli.service.js +230 -0
  223. package/dist-server/server/modules/cli/cli.service.js.map +1 -0
  224. package/dist-server/server/modules/cli/index.js +3 -0
  225. package/dist-server/server/modules/cli/index.js.map +1 -0
  226. package/dist-server/server/modules/cli/pwa-launcher.js +116 -0
  227. package/dist-server/server/modules/cli/pwa-launcher.js.map +1 -0
  228. package/dist-server/server/modules/cli/sandbox.service.js +332 -0
  229. package/dist-server/server/modules/cli/sandbox.service.js.map +1 -0
  230. package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js +154 -0
  231. package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js.map +1 -0
  232. package/dist-server/server/modules/cli/tests/cli.service.test.js +91 -0
  233. package/dist-server/server/modules/cli/tests/cli.service.test.js.map +1 -0
  234. package/dist-server/server/modules/cli/tests/sandbox.service.test.js +55 -0
  235. package/dist-server/server/modules/cli/tests/sandbox.service.test.js.map +1 -0
  236. package/dist-server/server/modules/commands/commands.module.js +20 -0
  237. package/dist-server/server/modules/commands/commands.module.js.map +1 -0
  238. package/dist-server/server/modules/commands/commands.routes.js +498 -0
  239. package/dist-server/server/modules/commands/commands.routes.js.map +1 -0
  240. package/dist-server/server/modules/commands/index.js +3 -0
  241. package/dist-server/server/modules/commands/index.js.map +1 -0
  242. package/dist-server/server/modules/commands/tests/commands.test.js +91 -0
  243. package/dist-server/server/modules/commands/tests/commands.test.js.map +1 -0
  244. package/dist-server/server/modules/database/connection.js +125 -0
  245. package/dist-server/server/modules/database/connection.js.map +1 -0
  246. package/dist-server/server/modules/database/index.js +23 -0
  247. package/dist-server/server/modules/database/index.js.map +1 -0
  248. package/dist-server/server/modules/database/init-db.js +18 -0
  249. package/dist-server/server/modules/database/init-db.js.map +1 -0
  250. package/dist-server/server/modules/database/migrations.js +465 -0
  251. package/dist-server/server/modules/database/migrations.js.map +1 -0
  252. package/dist-server/server/modules/database/repositories/api-keys.js +72 -0
  253. package/dist-server/server/modules/database/repositories/api-keys.js.map +1 -0
  254. package/dist-server/server/modules/database/repositories/app-config.js +47 -0
  255. package/dist-server/server/modules/database/repositories/app-config.js.map +1 -0
  256. package/dist-server/server/modules/database/repositories/credentials.js +68 -0
  257. package/dist-server/server/modules/database/repositories/credentials.js.map +1 -0
  258. package/dist-server/server/modules/database/repositories/github-tokens.js +54 -0
  259. package/dist-server/server/modules/database/repositories/github-tokens.js.map +1 -0
  260. package/dist-server/server/modules/database/repositories/notification-channel-endpoints.js +100 -0
  261. package/dist-server/server/modules/database/repositories/notification-channel-endpoints.js.map +1 -0
  262. package/dist-server/server/modules/database/repositories/notification-preferences.js +82 -0
  263. package/dist-server/server/modules/database/repositories/notification-preferences.js.map +1 -0
  264. package/dist-server/server/modules/database/repositories/projects.db.js +172 -0
  265. package/dist-server/server/modules/database/repositories/projects.db.js.map +1 -0
  266. package/dist-server/server/modules/database/repositories/provider-models.js +112 -0
  267. package/dist-server/server/modules/database/repositories/provider-models.js.map +1 -0
  268. package/dist-server/server/modules/database/repositories/push-subscriptions.js +49 -0
  269. package/dist-server/server/modules/database/repositories/push-subscriptions.js.map +1 -0
  270. package/dist-server/server/modules/database/repositories/scan-state.db.js +31 -0
  271. package/dist-server/server/modules/database/repositories/scan-state.db.js.map +1 -0
  272. package/dist-server/server/modules/database/repositories/scheduled-messages.db.js +71 -0
  273. package/dist-server/server/modules/database/repositories/scheduled-messages.db.js.map +1 -0
  274. package/dist-server/server/modules/database/repositories/session-drafts.db.js +105 -0
  275. package/dist-server/server/modules/database/repositories/session-drafts.db.js.map +1 -0
  276. package/dist-server/server/modules/database/repositories/sessions.db.js +507 -0
  277. package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -0
  278. package/dist-server/server/modules/database/repositories/user-preferences.db.js +65 -0
  279. package/dist-server/server/modules/database/repositories/user-preferences.db.js.map +1 -0
  280. package/dist-server/server/modules/database/repositories/users.js +88 -0
  281. package/dist-server/server/modules/database/repositories/users.js.map +1 -0
  282. package/dist-server/server/modules/database/repositories/vapid-keys.js +38 -0
  283. package/dist-server/server/modules/database/repositories/vapid-keys.js.map +1 -0
  284. package/dist-server/server/modules/database/schema.js +291 -0
  285. package/dist-server/server/modules/database/schema.js.map +1 -0
  286. package/dist-server/server/modules/database/tests/projects.db.integration.test.js +64 -0
  287. package/dist-server/server/modules/database/tests/projects.db.integration.test.js.map +1 -0
  288. package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js +102 -0
  289. package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js.map +1 -0
  290. package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js +79 -0
  291. package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js.map +1 -0
  292. package/dist-server/server/modules/database/tests/sessions.db.integration.test.js +93 -0
  293. package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -0
  294. package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js +105 -0
  295. package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js.map +1 -0
  296. package/dist-server/server/modules/file-tree/file-tree.module.js +95 -0
  297. package/dist-server/server/modules/file-tree/file-tree.module.js.map +1 -0
  298. package/dist-server/server/modules/file-tree/file-tree.routes.js +208 -0
  299. package/dist-server/server/modules/file-tree/file-tree.routes.js.map +1 -0
  300. package/dist-server/server/modules/file-tree/file-tree.service.js +584 -0
  301. package/dist-server/server/modules/file-tree/file-tree.service.js.map +1 -0
  302. package/dist-server/server/modules/file-tree/index.js +3 -0
  303. package/dist-server/server/modules/file-tree/index.js.map +1 -0
  304. package/dist-server/server/modules/file-tree/tests/file-tree.routes.test.js +123 -0
  305. package/dist-server/server/modules/file-tree/tests/file-tree.routes.test.js.map +1 -0
  306. package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js +311 -0
  307. package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js.map +1 -0
  308. package/dist-server/server/modules/git/git-branch.service.js +15 -0
  309. package/dist-server/server/modules/git/git-branch.service.js.map +1 -0
  310. package/dist-server/server/modules/git/git-parsing.service.js +61 -0
  311. package/dist-server/server/modules/git/git-parsing.service.js.map +1 -0
  312. package/dist-server/server/modules/git/git.module.js +14 -0
  313. package/dist-server/server/modules/git/git.module.js.map +1 -0
  314. package/dist-server/server/modules/git/git.routes.js +1347 -0
  315. package/dist-server/server/modules/git/git.routes.js.map +1 -0
  316. package/dist-server/server/modules/git/index.js +3 -0
  317. package/dist-server/server/modules/git/index.js.map +1 -0
  318. package/dist-server/server/modules/git/tests/git-branch.service.test.js +52 -0
  319. package/dist-server/server/modules/git/tests/git-branch.service.test.js.map +1 -0
  320. package/dist-server/server/modules/git/tests/git-init.routes.test.js +132 -0
  321. package/dist-server/server/modules/git/tests/git-init.routes.test.js.map +1 -0
  322. package/dist-server/server/modules/git/tests/git.test.js +80 -0
  323. package/dist-server/server/modules/git/tests/git.test.js.map +1 -0
  324. package/dist-server/server/modules/notifications/index.js +20 -0
  325. package/dist-server/server/modules/notifications/index.js.map +1 -0
  326. package/dist-server/server/modules/notifications/notifications.routes.js +115 -0
  327. package/dist-server/server/modules/notifications/notifications.routes.js.map +1 -0
  328. package/dist-server/server/modules/notifications/services/desktop-notification-clients.service.js +97 -0
  329. package/dist-server/server/modules/notifications/services/desktop-notification-clients.service.js.map +1 -0
  330. package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js +249 -0
  331. package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js.map +1 -0
  332. package/dist-server/server/modules/notifications/tests/notification-orchestrator.integration.test.js +44 -0
  333. package/dist-server/server/modules/notifications/tests/notification-orchestrator.integration.test.js.map +1 -0
  334. package/dist-server/server/modules/notifications/vapid-keys.service.js +28 -0
  335. package/dist-server/server/modules/notifications/vapid-keys.service.js.map +1 -0
  336. package/dist-server/server/modules/notifications/websocket/desktop-notifications-websocket.service.js +79 -0
  337. package/dist-server/server/modules/notifications/websocket/desktop-notifications-websocket.service.js.map +1 -0
  338. package/dist-server/server/modules/plugins/index.js +9 -0
  339. package/dist-server/server/modules/plugins/index.js.map +1 -0
  340. package/dist-server/server/modules/plugins/plugin-process.service.js +191 -0
  341. package/dist-server/server/modules/plugins/plugin-process.service.js.map +1 -0
  342. package/dist-server/server/modules/plugins/plugin-registry.service.js +415 -0
  343. package/dist-server/server/modules/plugins/plugin-registry.service.js.map +1 -0
  344. package/dist-server/server/modules/plugins/plugins.module.js +30 -0
  345. package/dist-server/server/modules/plugins/plugins.module.js.map +1 -0
  346. package/dist-server/server/modules/plugins/plugins.routes.js +70 -0
  347. package/dist-server/server/modules/plugins/plugins.routes.js.map +1 -0
  348. package/dist-server/server/modules/plugins/plugins.service.js +123 -0
  349. package/dist-server/server/modules/plugins/plugins.service.js.map +1 -0
  350. package/dist-server/server/modules/plugins/tests/plugins.service.test.js +28 -0
  351. package/dist-server/server/modules/plugins/tests/plugins.service.test.js.map +1 -0
  352. package/dist-server/server/modules/projects/index.js +9 -0
  353. package/dist-server/server/modules/projects/index.js.map +1 -0
  354. package/dist-server/server/modules/projects/projects.routes.js +205 -0
  355. package/dist-server/server/modules/projects/projects.routes.js.map +1 -0
  356. package/dist-server/server/modules/projects/services/project-clone.service.js +220 -0
  357. package/dist-server/server/modules/projects/services/project-clone.service.js.map +1 -0
  358. package/dist-server/server/modules/projects/services/project-delete.service.js +89 -0
  359. package/dist-server/server/modules/projects/services/project-delete.service.js.map +1 -0
  360. package/dist-server/server/modules/projects/services/project-management.service.js +90 -0
  361. package/dist-server/server/modules/projects/services/project-management.service.js.map +1 -0
  362. package/dist-server/server/modules/projects/services/project-star.service.js +60 -0
  363. package/dist-server/server/modules/projects/services/project-star.service.js.map +1 -0
  364. package/dist-server/server/modules/projects/services/projects-has-taskmaster.service.js +167 -0
  365. package/dist-server/server/modules/projects/services/projects-has-taskmaster.service.js.map +1 -0
  366. package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js +188 -0
  367. package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js.map +1 -0
  368. package/dist-server/server/modules/projects/tests/project-clone.service.test.js +126 -0
  369. package/dist-server/server/modules/projects/tests/project-clone.service.test.js.map +1 -0
  370. package/dist-server/server/modules/projects/tests/project-management.service.test.js +85 -0
  371. package/dist-server/server/modules/projects/tests/project-management.service.test.js.map +1 -0
  372. package/dist-server/server/modules/projects/tests/project-star.service.test.js +95 -0
  373. package/dist-server/server/modules/projects/tests/project-star.service.test.js.map +1 -0
  374. package/dist-server/server/modules/projects/tests/projects-has-taskmaster.service.test.js +87 -0
  375. package/dist-server/server/modules/projects/tests/projects-has-taskmaster.service.test.js.map +1 -0
  376. package/dist-server/server/modules/providers/index.js +12 -0
  377. package/dist-server/server/modules/providers/index.js.map +1 -0
  378. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +151 -0
  379. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -0
  380. package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js +46 -0
  381. package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js.map +1 -0
  382. package/dist-server/server/modules/providers/list/claude/claude-mcp.provider.js +103 -0
  383. package/dist-server/server/modules/providers/list/claude/claude-mcp.provider.js.map +1 -0
  384. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +267 -0
  385. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -0
  386. package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js +1058 -0
  387. package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js.map +1 -0
  388. package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js +141 -0
  389. package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js.map +1 -0
  390. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +924 -0
  391. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -0
  392. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js +205 -0
  393. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js.map +1 -0
  394. package/dist-server/server/modules/providers/list/claude/claude.provider.js +23 -0
  395. package/dist-server/server/modules/providers/list/claude/claude.provider.js.map +1 -0
  396. package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js +204 -0
  397. package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js.map +1 -0
  398. package/dist-server/server/modules/providers/list/codex/codex-auth.provider.js +84 -0
  399. package/dist-server/server/modules/providers/list/codex/codex-auth.provider.js.map +1 -0
  400. package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js +32 -0
  401. package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js.map +1 -0
  402. package/dist-server/server/modules/providers/list/codex/codex-mcp.provider.js +107 -0
  403. package/dist-server/server/modules/providers/list/codex/codex-mcp.provider.js.map +1 -0
  404. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +109 -0
  405. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -0
  406. package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js +498 -0
  407. package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js.map +1 -0
  408. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +153 -0
  409. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -0
  410. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +1955 -0
  411. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -0
  412. package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js +62 -0
  413. package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js.map +1 -0
  414. package/dist-server/server/modules/providers/list/codex/codex.provider.js +23 -0
  415. package/dist-server/server/modules/providers/list/codex/codex.provider.js.map +1 -0
  416. package/dist-server/server/modules/providers/list/cursor/cursor-auth.provider.js +118 -0
  417. package/dist-server/server/modules/providers/list/cursor/cursor-auth.provider.js.map +1 -0
  418. package/dist-server/server/modules/providers/list/cursor/cursor-mcp.provider.js +80 -0
  419. package/dist-server/server/modules/providers/list/cursor/cursor-mcp.provider.js.map +1 -0
  420. package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js +100 -0
  421. package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js.map +1 -0
  422. package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js +305 -0
  423. package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js.map +1 -0
  424. package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js +111 -0
  425. package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js.map +1 -0
  426. package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js +566 -0
  427. package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js.map +1 -0
  428. package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js +35 -0
  429. package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js.map +1 -0
  430. package/dist-server/server/modules/providers/list/cursor/cursor.provider.js +21 -0
  431. package/dist-server/server/modules/providers/list/cursor/cursor.provider.js.map +1 -0
  432. package/dist-server/server/modules/providers/list/mistral/mistral-auth.provider.js +147 -0
  433. package/dist-server/server/modules/providers/list/mistral/mistral-auth.provider.js.map +1 -0
  434. package/dist-server/server/modules/providers/list/mistral/mistral-mcp.provider.js +15 -0
  435. package/dist-server/server/modules/providers/list/mistral/mistral-mcp.provider.js.map +1 -0
  436. package/dist-server/server/modules/providers/list/mistral/mistral-models.provider.js +69 -0
  437. package/dist-server/server/modules/providers/list/mistral/mistral-models.provider.js.map +1 -0
  438. package/dist-server/server/modules/providers/list/mistral/mistral-runtime.provider.js +326 -0
  439. package/dist-server/server/modules/providers/list/mistral/mistral-runtime.provider.js.map +1 -0
  440. package/dist-server/server/modules/providers/list/mistral/mistral-session-synchronizer.provider.js +12 -0
  441. package/dist-server/server/modules/providers/list/mistral/mistral-session-synchronizer.provider.js.map +1 -0
  442. package/dist-server/server/modules/providers/list/mistral/mistral-sessions.provider.js +180 -0
  443. package/dist-server/server/modules/providers/list/mistral/mistral-sessions.provider.js.map +1 -0
  444. package/dist-server/server/modules/providers/list/mistral/mistral-skills.provider.js +15 -0
  445. package/dist-server/server/modules/providers/list/mistral/mistral-skills.provider.js.map +1 -0
  446. package/dist-server/server/modules/providers/list/mistral/mistral.provider.js +25 -0
  447. package/dist-server/server/modules/providers/list/mistral/mistral.provider.js.map +1 -0
  448. package/dist-server/server/modules/providers/list/opencode/opencode-auth.provider.js +91 -0
  449. package/dist-server/server/modules/providers/list/opencode/opencode-auth.provider.js.map +1 -0
  450. package/dist-server/server/modules/providers/list/opencode/opencode-mcp.provider.js +181 -0
  451. package/dist-server/server/modules/providers/list/opencode/opencode-mcp.provider.js.map +1 -0
  452. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +257 -0
  453. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -0
  454. package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.js +368 -0
  455. package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.js.map +1 -0
  456. package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.test.js +225 -0
  457. package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.test.js.map +1 -0
  458. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +136 -0
  459. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -0
  460. package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js +404 -0
  461. package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js.map +1 -0
  462. package/dist-server/server/modules/providers/list/opencode/opencode-skills.provider.js +62 -0
  463. package/dist-server/server/modules/providers/list/opencode/opencode-skills.provider.js.map +1 -0
  464. package/dist-server/server/modules/providers/list/opencode/opencode.provider.js +21 -0
  465. package/dist-server/server/modules/providers/list/opencode/opencode.provider.js.map +1 -0
  466. package/dist-server/server/modules/providers/provider.registry.js +33 -0
  467. package/dist-server/server/modules/providers/provider.registry.js.map +1 -0
  468. package/dist-server/server/modules/providers/provider.routes.js +676 -0
  469. package/dist-server/server/modules/providers/provider.routes.js.map +1 -0
  470. package/dist-server/server/modules/providers/services/mcp.service.js +84 -0
  471. package/dist-server/server/modules/providers/services/mcp.service.js.map +1 -0
  472. package/dist-server/server/modules/providers/services/provider-auth.service.js +25 -0
  473. package/dist-server/server/modules/providers/services/provider-auth.service.js.map +1 -0
  474. package/dist-server/server/modules/providers/services/provider-capabilities.service.js +94 -0
  475. package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -0
  476. package/dist-server/server/modules/providers/services/provider-models.service.js +289 -0
  477. package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -0
  478. package/dist-server/server/modules/providers/services/provider-runtime.service.js +66 -0
  479. package/dist-server/server/modules/providers/services/provider-runtime.service.js.map +1 -0
  480. package/dist-server/server/modules/providers/services/provider-token-usage.service.js +372 -0
  481. package/dist-server/server/modules/providers/services/provider-token-usage.service.js.map +1 -0
  482. package/dist-server/server/modules/providers/services/session-conversations-search.service.js +974 -0
  483. package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -0
  484. package/dist-server/server/modules/providers/services/session-history-cache.service.js +87 -0
  485. package/dist-server/server/modules/providers/services/session-history-cache.service.js.map +1 -0
  486. package/dist-server/server/modules/providers/services/session-synchronizer.service.js +130 -0
  487. package/dist-server/server/modules/providers/services/session-synchronizer.service.js.map +1 -0
  488. package/dist-server/server/modules/providers/services/sessions-watcher.service.js +214 -0
  489. package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -0
  490. package/dist-server/server/modules/providers/services/sessions.service.js +519 -0
  491. package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -0
  492. package/dist-server/server/modules/providers/services/skills.service.js +22 -0
  493. package/dist-server/server/modules/providers/services/skills.service.js.map +1 -0
  494. package/dist-server/server/modules/providers/shared/base/abstract.provider.js +13 -0
  495. package/dist-server/server/modules/providers/shared/base/abstract.provider.js.map +1 -0
  496. package/dist-server/server/modules/providers/shared/mcp/mcp.provider.js +102 -0
  497. package/dist-server/server/modules/providers/shared/mcp/mcp.provider.js.map +1 -0
  498. package/dist-server/server/modules/providers/shared/skills/skills.provider.js +207 -0
  499. package/dist-server/server/modules/providers/shared/skills/skills.provider.js.map +1 -0
  500. package/dist-server/server/modules/providers/tests/claude-auth.test.js +174 -0
  501. package/dist-server/server/modules/providers/tests/claude-auth.test.js.map +1 -0
  502. package/dist-server/server/modules/providers/tests/claude-sessions.test.js +470 -0
  503. package/dist-server/server/modules/providers/tests/claude-sessions.test.js.map +1 -0
  504. package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js +16 -0
  505. package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js.map +1 -0
  506. package/dist-server/server/modules/providers/tests/claude-token-budget.test.js +85 -0
  507. package/dist-server/server/modules/providers/tests/claude-token-budget.test.js.map +1 -0
  508. package/dist-server/server/modules/providers/tests/codex-message-editing.test.js +338 -0
  509. package/dist-server/server/modules/providers/tests/codex-message-editing.test.js.map +1 -0
  510. package/dist-server/server/modules/providers/tests/codex-session-fork.test.js +54 -0
  511. package/dist-server/server/modules/providers/tests/codex-session-fork.test.js.map +1 -0
  512. package/dist-server/server/modules/providers/tests/codex-sessions.test.js +491 -0
  513. package/dist-server/server/modules/providers/tests/codex-sessions.test.js.map +1 -0
  514. package/dist-server/server/modules/providers/tests/mcp.test.js +291 -0
  515. package/dist-server/server/modules/providers/tests/mcp.test.js.map +1 -0
  516. package/dist-server/server/modules/providers/tests/opencode-models.test.js +103 -0
  517. package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -0
  518. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +424 -0
  519. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -0
  520. package/dist-server/server/modules/providers/tests/provider-attachment-history.test.js +205 -0
  521. package/dist-server/server/modules/providers/tests/provider-attachment-history.test.js.map +1 -0
  522. package/dist-server/server/modules/providers/tests/provider-models.service.test.js +268 -0
  523. package/dist-server/server/modules/providers/tests/provider-models.service.test.js.map +1 -0
  524. package/dist-server/server/modules/providers/tests/provider-runtime.service.test.js +126 -0
  525. package/dist-server/server/modules/providers/tests/provider-runtime.service.test.js.map +1 -0
  526. package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js +329 -0
  527. package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js.map +1 -0
  528. package/dist-server/server/modules/providers/tests/provider.routes.test.js +161 -0
  529. package/dist-server/server/modules/providers/tests/provider.routes.test.js.map +1 -0
  530. package/dist-server/server/modules/providers/tests/session-fork.test.js +113 -0
  531. package/dist-server/server/modules/providers/tests/session-fork.test.js.map +1 -0
  532. package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js +120 -0
  533. package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js.map +1 -0
  534. package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js +90 -0
  535. package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js.map +1 -0
  536. package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js +122 -0
  537. package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js.map +1 -0
  538. package/dist-server/server/modules/providers/tests/sessions-details.test.js +62 -0
  539. package/dist-server/server/modules/providers/tests/sessions-details.test.js.map +1 -0
  540. package/dist-server/server/modules/providers/tests/sessions.service.test.js +131 -0
  541. package/dist-server/server/modules/providers/tests/sessions.service.test.js.map +1 -0
  542. package/dist-server/server/modules/providers/tests/skills.test.js +398 -0
  543. package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -0
  544. package/dist-server/server/modules/scheduled-messages/index.js +5 -0
  545. package/dist-server/server/modules/scheduled-messages/index.js.map +1 -0
  546. package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js +47 -0
  547. package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js.map +1 -0
  548. package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js +163 -0
  549. package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js.map +1 -0
  550. package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js +95 -0
  551. package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js.map +1 -0
  552. package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js +255 -0
  553. package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js.map +1 -0
  554. package/dist-server/server/modules/settings/index.js +3 -0
  555. package/dist-server/server/modules/settings/index.js.map +1 -0
  556. package/dist-server/server/modules/settings/settings.module.js +35 -0
  557. package/dist-server/server/modules/settings/settings.module.js.map +1 -0
  558. package/dist-server/server/modules/settings/settings.routes.js +34 -0
  559. package/dist-server/server/modules/settings/settings.routes.js.map +1 -0
  560. package/dist-server/server/modules/settings/settings.service.js +116 -0
  561. package/dist-server/server/modules/settings/settings.service.js.map +1 -0
  562. package/dist-server/server/modules/settings/tests/settings.service.test.js +48 -0
  563. package/dist-server/server/modules/settings/tests/settings.service.test.js.map +1 -0
  564. package/dist-server/server/modules/system/index.js +3 -0
  565. package/dist-server/server/modules/system/index.js.map +1 -0
  566. package/dist-server/server/modules/system/system.module.js +44 -0
  567. package/dist-server/server/modules/system/system.module.js.map +1 -0
  568. package/dist-server/server/modules/system/system.routes.js +16 -0
  569. package/dist-server/server/modules/system/system.routes.js.map +1 -0
  570. package/dist-server/server/modules/system/system.service.js +45 -0
  571. package/dist-server/server/modules/system/system.service.js.map +1 -0
  572. package/dist-server/server/modules/system/tests/system.service.test.js +100 -0
  573. package/dist-server/server/modules/system/tests/system.service.test.js.map +1 -0
  574. package/dist-server/server/modules/taskmaster/index.js +3 -0
  575. package/dist-server/server/modules/taskmaster/index.js.map +1 -0
  576. package/dist-server/server/modules/taskmaster/taskmaster.module.js +20 -0
  577. package/dist-server/server/modules/taskmaster/taskmaster.module.js.map +1 -0
  578. package/dist-server/server/modules/taskmaster/taskmaster.routes.js +1372 -0
  579. package/dist-server/server/modules/taskmaster/taskmaster.routes.js.map +1 -0
  580. package/dist-server/server/modules/taskmaster/taskmaster.service.js +107 -0
  581. package/dist-server/server/modules/taskmaster/taskmaster.service.js.map +1 -0
  582. package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js +180 -0
  583. package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js.map +1 -0
  584. package/dist-server/server/modules/taskmaster/tests/taskmaster.service.test.js +85 -0
  585. package/dist-server/server/modules/taskmaster/tests/taskmaster.service.test.js.map +1 -0
  586. package/dist-server/server/modules/user/index.js +3 -0
  587. package/dist-server/server/modules/user/index.js.map +1 -0
  588. package/dist-server/server/modules/user/tests/user.service.test.js +123 -0
  589. package/dist-server/server/modules/user/tests/user.service.test.js.map +1 -0
  590. package/dist-server/server/modules/user/user.module.js +56 -0
  591. package/dist-server/server/modules/user/user.module.js.map +1 -0
  592. package/dist-server/server/modules/user/user.routes.js +88 -0
  593. package/dist-server/server/modules/user/user.routes.js.map +1 -0
  594. package/dist-server/server/modules/user/user.service.js +134 -0
  595. package/dist-server/server/modules/user/user.service.js.map +1 -0
  596. package/dist-server/server/modules/voice/index.js +3 -0
  597. package/dist-server/server/modules/voice/index.js.map +1 -0
  598. package/dist-server/server/modules/voice/tests/voice.service.test.js +87 -0
  599. package/dist-server/server/modules/voice/tests/voice.service.test.js.map +1 -0
  600. package/dist-server/server/modules/voice/voice.module.js +44 -0
  601. package/dist-server/server/modules/voice/voice.module.js.map +1 -0
  602. package/dist-server/server/modules/voice/voice.routes.js +87 -0
  603. package/dist-server/server/modules/voice/voice.routes.js.map +1 -0
  604. package/dist-server/server/modules/voice/voice.service.js +148 -0
  605. package/dist-server/server/modules/voice/voice.service.js.map +1 -0
  606. package/dist-server/server/modules/websocket/index.js +11 -0
  607. package/dist-server/server/modules/websocket/index.js.map +1 -0
  608. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js +232 -0
  609. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js.map +1 -0
  610. package/dist-server/server/modules/websocket/services/chat-session-writer.service.js +135 -0
  611. package/dist-server/server/modules/websocket/services/chat-session-writer.service.js.map +1 -0
  612. package/dist-server/server/modules/websocket/services/chat-websocket.service.js +467 -0
  613. package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -0
  614. package/dist-server/server/modules/websocket/services/plugin-websocket-proxy.service.js +52 -0
  615. package/dist-server/server/modules/websocket/services/plugin-websocket-proxy.service.js.map +1 -0
  616. package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js +94 -0
  617. package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js.map +1 -0
  618. package/dist-server/server/modules/websocket/services/shell-websocket.service.js +472 -0
  619. package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -0
  620. package/dist-server/server/modules/websocket/services/websocket-auth.service.js +36 -0
  621. package/dist-server/server/modules/websocket/services/websocket-auth.service.js.map +1 -0
  622. package/dist-server/server/modules/websocket/services/websocket-server.service.js +90 -0
  623. package/dist-server/server/modules/websocket/services/websocket-server.service.js.map +1 -0
  624. package/dist-server/server/modules/websocket/services/websocket-state.service.js +14 -0
  625. package/dist-server/server/modules/websocket/services/websocket-state.service.js.map +1 -0
  626. package/dist-server/server/modules/websocket/services/websocket-writer.service.js +32 -0
  627. package/dist-server/server/modules/websocket/services/websocket-writer.service.js.map +1 -0
  628. package/dist-server/server/modules/websocket/tests/chat-attachment-filter.test.js +40 -0
  629. package/dist-server/server/modules/websocket/tests/chat-attachment-filter.test.js.map +1 -0
  630. package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js +263 -0
  631. package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js.map +1 -0
  632. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js +275 -0
  633. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js.map +1 -0
  634. package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js +106 -0
  635. package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js.map +1 -0
  636. package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js +177 -0
  637. package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js.map +1 -0
  638. package/dist-server/server/modules/websocket/tests/websocket-heartbeat.service.test.js +61 -0
  639. package/dist-server/server/modules/websocket/tests/websocket-heartbeat.service.test.js.map +1 -0
  640. package/dist-server/server/modules/worktrees/index.js +3 -0
  641. package/dist-server/server/modules/worktrees/index.js.map +1 -0
  642. package/dist-server/server/modules/worktrees/services/worktree-create-and-open.service.js +37 -0
  643. package/dist-server/server/modules/worktrees/services/worktree-create-and-open.service.js.map +1 -0
  644. package/dist-server/server/modules/worktrees/services/worktree-create.service.js +67 -0
  645. package/dist-server/server/modules/worktrees/services/worktree-create.service.js.map +1 -0
  646. package/dist-server/server/modules/worktrees/services/worktree-git.service.js +172 -0
  647. package/dist-server/server/modules/worktrees/services/worktree-git.service.js.map +1 -0
  648. package/dist-server/server/modules/worktrees/services/worktree-list.service.js +91 -0
  649. package/dist-server/server/modules/worktrees/services/worktree-list.service.js.map +1 -0
  650. package/dist-server/server/modules/worktrees/services/worktree-merge.service.js +118 -0
  651. package/dist-server/server/modules/worktrees/services/worktree-merge.service.js.map +1 -0
  652. package/dist-server/server/modules/worktrees/services/worktree-open.service.js +58 -0
  653. package/dist-server/server/modules/worktrees/services/worktree-open.service.js.map +1 -0
  654. package/dist-server/server/modules/worktrees/services/worktree-remove.service.js +65 -0
  655. package/dist-server/server/modules/worktrees/services/worktree-remove.service.js.map +1 -0
  656. package/dist-server/server/modules/worktrees/tests/worktree-create-and-open.service.test.js +58 -0
  657. package/dist-server/server/modules/worktrees/tests/worktree-create-and-open.service.test.js.map +1 -0
  658. package/dist-server/server/modules/worktrees/tests/worktree-create.service.test.js +85 -0
  659. package/dist-server/server/modules/worktrees/tests/worktree-create.service.test.js.map +1 -0
  660. package/dist-server/server/modules/worktrees/tests/worktree-git.service.test.js +58 -0
  661. package/dist-server/server/modules/worktrees/tests/worktree-git.service.test.js.map +1 -0
  662. package/dist-server/server/modules/worktrees/tests/worktree-list.service.test.js +34 -0
  663. package/dist-server/server/modules/worktrees/tests/worktree-list.service.test.js.map +1 -0
  664. package/dist-server/server/modules/worktrees/tests/worktree-merge.service.test.js +137 -0
  665. package/dist-server/server/modules/worktrees/tests/worktree-merge.service.test.js.map +1 -0
  666. package/dist-server/server/modules/worktrees/tests/worktree-open.service.test.js +99 -0
  667. package/dist-server/server/modules/worktrees/tests/worktree-open.service.test.js.map +1 -0
  668. package/dist-server/server/modules/worktrees/tests/worktree-remove.service.test.js +157 -0
  669. package/dist-server/server/modules/worktrees/tests/worktree-remove.service.test.js.map +1 -0
  670. package/dist-server/server/modules/worktrees/tests/worktrees.routes.test.js +167 -0
  671. package/dist-server/server/modules/worktrees/tests/worktrees.routes.test.js.map +1 -0
  672. package/dist-server/server/modules/worktrees/worktrees.module.js +97 -0
  673. package/dist-server/server/modules/worktrees/worktrees.module.js.map +1 -0
  674. package/dist-server/server/modules/worktrees/worktrees.routes.js +84 -0
  675. package/dist-server/server/modules/worktrees/worktrees.routes.js.map +1 -0
  676. package/dist-server/server/piecemaker/activation/claude-mcp.cjs +109 -0
  677. package/dist-server/server/piecemaker/activation/claude-mcp.cjs.map +1 -0
  678. package/dist-server/server/piecemaker/activation/claude-plugins.cjs +60 -0
  679. package/dist-server/server/piecemaker/activation/claude-plugins.cjs.map +1 -0
  680. package/dist-server/server/piecemaker/activation/codex-mcp.cjs +93 -0
  681. package/dist-server/server/piecemaker/activation/codex-mcp.cjs.map +1 -0
  682. package/dist-server/server/piecemaker/activation/errors.cjs +14 -0
  683. package/dist-server/server/piecemaker/activation/errors.cjs.map +1 -0
  684. package/dist-server/server/piecemaker/activation/library.cjs +281 -0
  685. package/dist-server/server/piecemaker/activation/library.cjs.map +1 -0
  686. package/dist-server/server/piecemaker/activation/library.test.js +281 -0
  687. package/dist-server/server/piecemaker/activation/library.test.js.map +1 -0
  688. package/dist-server/server/piecemaker/activation/markdown.cjs +103 -0
  689. package/dist-server/server/piecemaker/activation/markdown.cjs.map +1 -0
  690. package/dist-server/server/piecemaker/activation/paths.cjs +103 -0
  691. package/dist-server/server/piecemaker/activation/paths.cjs.map +1 -0
  692. package/dist-server/server/piecemaker/activation/paths.test.js +91 -0
  693. package/dist-server/server/piecemaker/activation/paths.test.js.map +1 -0
  694. package/dist-server/server/piecemaker/activation/routes.cjs +100 -0
  695. package/dist-server/server/piecemaker/activation/routes.cjs.map +1 -0
  696. package/dist-server/server/piecemaker/activation/routes.test.js +307 -0
  697. package/dist-server/server/piecemaker/activation/routes.test.js.map +1 -0
  698. package/dist-server/server/piecemaker/activation/snapshot.cjs +28 -0
  699. package/dist-server/server/piecemaker/activation/snapshot.cjs.map +1 -0
  700. package/dist-server/server/piecemaker/anonymizer/anonymizer.test.js +282 -0
  701. package/dist-server/server/piecemaker/anonymizer/anonymizer.test.js.map +1 -0
  702. package/dist-server/server/piecemaker/anonymizer/client-config.cjs +224 -0
  703. package/dist-server/server/piecemaker/anonymizer/client-config.cjs.map +1 -0
  704. package/dist-server/server/piecemaker/anonymizer/codex-http.test.js +19 -0
  705. package/dist-server/server/piecemaker/anonymizer/codex-http.test.js.map +1 -0
  706. package/dist-server/server/piecemaker/anonymizer/dictionary.cjs +163 -0
  707. package/dist-server/server/piecemaker/anonymizer/dictionary.cjs.map +1 -0
  708. package/dist-server/server/piecemaker/anonymizer/lifecycle.js +20 -0
  709. package/dist-server/server/piecemaker/anonymizer/lifecycle.js.map +1 -0
  710. package/dist-server/server/piecemaker/anonymizer/lifecycle.test.js +35 -0
  711. package/dist-server/server/piecemaker/anonymizer/lifecycle.test.js.map +1 -0
  712. package/dist-server/server/piecemaker/anonymizer/providers.cjs +236 -0
  713. package/dist-server/server/piecemaker/anonymizer/providers.cjs.map +1 -0
  714. package/dist-server/server/piecemaker/anonymizer/proxy.cjs +338 -0
  715. package/dist-server/server/piecemaker/anonymizer/proxy.cjs.map +1 -0
  716. package/dist-server/server/piecemaker/anonymizer/rewrite.cjs +249 -0
  717. package/dist-server/server/piecemaker/anonymizer/rewrite.cjs.map +1 -0
  718. package/dist-server/server/piecemaker/anonymizer/service.cjs +217 -0
  719. package/dist-server/server/piecemaker/anonymizer/service.cjs.map +1 -0
  720. package/dist-server/server/piecemaker/harness/chat-harness.js +157 -0
  721. package/dist-server/server/piecemaker/harness/chat-harness.js.map +1 -0
  722. package/dist-server/server/piecemaker/harness/chat-harness.test.js +120 -0
  723. package/dist-server/server/piecemaker/harness/chat-harness.test.js.map +1 -0
  724. package/dist-server/server/piecemaker/harness/citation-instructions.js +17 -0
  725. package/dist-server/server/piecemaker/harness/citation-instructions.js.map +1 -0
  726. package/dist-server/server/piecemaker/harness/citation-store.js +72 -0
  727. package/dist-server/server/piecemaker/harness/citation-store.js.map +1 -0
  728. package/dist-server/server/piecemaker/harness/citation-turn.js +211 -0
  729. package/dist-server/server/piecemaker/harness/citation-turn.js.map +1 -0
  730. package/dist-server/server/piecemaker/harness/citation-turn.test.js +144 -0
  731. package/dist-server/server/piecemaker/harness/citation-turn.test.js.map +1 -0
  732. package/dist-server/server/piecemaker/harness/citations.routes.js +16 -0
  733. package/dist-server/server/piecemaker/harness/citations.routes.js.map +1 -0
  734. package/dist-server/server/piecemaker/harness/citations.routes.test.js +68 -0
  735. package/dist-server/server/piecemaker/harness/citations.routes.test.js.map +1 -0
  736. package/dist-server/server/piecemaker/harness/decisions.cjs +298 -0
  737. package/dist-server/server/piecemaker/harness/decisions.cjs.map +1 -0
  738. package/dist-server/server/piecemaker/harness/flux-sse.cjs +108 -0
  739. package/dist-server/server/piecemaker/harness/flux-sse.cjs.map +1 -0
  740. package/dist-server/server/piecemaker/harness/harness.test.js +259 -0
  741. package/dist-server/server/piecemaker/harness/harness.test.js.map +1 -0
  742. package/dist-server/server/piecemaker/harness/index.cjs +100 -0
  743. package/dist-server/server/piecemaker/harness/index.cjs.map +1 -0
  744. package/dist-server/server/piecemaker/harness/proxy-harness.test.js +199 -0
  745. package/dist-server/server/piecemaker/harness/proxy-harness.test.js.map +1 -0
  746. package/dist-server/server/piecemaker/harness/source-passages.js +19 -0
  747. package/dist-server/server/piecemaker/harness/source-passages.js.map +1 -0
  748. package/dist-server/server/piecemaker/harness/verification.cjs +152 -0
  749. package/dist-server/server/piecemaker/harness/verification.cjs.map +1 -0
  750. package/dist-server/server/piecemaker/index.js +46 -0
  751. package/dist-server/server/piecemaker/index.js.map +1 -0
  752. package/dist-server/server/piecemaker/library/index.js +66 -0
  753. package/dist-server/server/piecemaker/library/index.js.map +1 -0
  754. package/dist-server/server/piecemaker/library/marketplace.js +204 -0
  755. package/dist-server/server/piecemaker/library/marketplace.js.map +1 -0
  756. package/dist-server/server/piecemaker/library/migrate-cli.js +13 -0
  757. package/dist-server/server/piecemaker/library/migrate-cli.js.map +1 -0
  758. package/dist-server/server/piecemaker/library/migrate.js +53 -0
  759. package/dist-server/server/piecemaker/library/migrate.js.map +1 -0
  760. package/dist-server/server/piecemaker/library/provider-skills.js +40 -0
  761. package/dist-server/server/piecemaker/library/provider-skills.js.map +1 -0
  762. package/dist-server/server/piecemaker/library/provider-skills.test.js +55 -0
  763. package/dist-server/server/piecemaker/library/provider-skills.test.js.map +1 -0
  764. package/dist-server/server/piecemaker/library/routes.js +56 -0
  765. package/dist-server/server/piecemaker/library/routes.js.map +1 -0
  766. package/dist-server/server/piecemaker/library/runtime.js +34 -0
  767. package/dist-server/server/piecemaker/library/runtime.js.map +1 -0
  768. package/dist-server/server/piecemaker/library/store.js +440 -0
  769. package/dist-server/server/piecemaker/library/store.js.map +1 -0
  770. package/dist-server/server/piecemaker/library/store.test.js +265 -0
  771. package/dist-server/server/piecemaker/library/store.test.js.map +1 -0
  772. package/dist-server/server/piecemaker/library/workspace-installation.js +70 -0
  773. package/dist-server/server/piecemaker/library/workspace-installation.js.map +1 -0
  774. package/dist-server/server/piecemaker/originals-cache.cjs +45 -0
  775. package/dist-server/server/piecemaker/originals-cache.cjs.map +1 -0
  776. package/dist-server/server/piecemaker/originals-cache.test.js +68 -0
  777. package/dist-server/server/piecemaker/originals-cache.test.js.map +1 -0
  778. package/dist-server/server/piecemaker/protection/bypass.cjs +66 -0
  779. package/dist-server/server/piecemaker/protection/bypass.cjs.map +1 -0
  780. package/dist-server/server/piecemaker/protection/bypass.test.js +117 -0
  781. package/dist-server/server/piecemaker/protection/bypass.test.js.map +1 -0
  782. package/dist-server/server/piecemaker/protection/routes.cjs +38 -0
  783. package/dist-server/server/piecemaker/protection/routes.cjs.map +1 -0
  784. package/dist-server/server/piecemaker/timesheet/index.js +9 -0
  785. package/dist-server/server/piecemaker/timesheet/index.js.map +1 -0
  786. package/dist-server/server/piecemaker/timesheet/routes.js +42 -0
  787. package/dist-server/server/piecemaker/timesheet/routes.js.map +1 -0
  788. package/dist-server/server/piecemaker/timesheet/service.js +241 -0
  789. package/dist-server/server/piecemaker/timesheet/service.js.map +1 -0
  790. package/dist-server/server/piecemaker/timesheet/service.test.js +124 -0
  791. package/dist-server/server/piecemaker/timesheet/service.test.js.map +1 -0
  792. package/dist-server/server/piecemaker/timesheet/store.js +126 -0
  793. package/dist-server/server/piecemaker/timesheet/store.js.map +1 -0
  794. package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/anonymization-state.cjs +147 -0
  795. package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/anonymization-state.cjs.map +1 -0
  796. package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/case-folder-structure.cjs +285 -0
  797. package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/case-folder-structure.cjs.map +1 -0
  798. package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/case-folders.cjs +87 -0
  799. package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/case-folders.cjs.map +1 -0
  800. package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/central-mapping.cjs +0 -0
  801. package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/central-mapping.cjs.map +1 -0
  802. package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/citation-sources.cjs +526 -0
  803. package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/citation-sources.cjs.map +1 -0
  804. package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/citations.cjs +304 -0
  805. package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/citations.cjs.map +1 -0
  806. package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/commits.cjs +1660 -0
  807. package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/commits.cjs.map +1 -0
  808. package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/institutional-terms.cjs +152 -0
  809. package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/institutional-terms.cjs.map +1 -0
  810. package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/legifrance-reads.cjs +84 -0
  811. package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/legifrance-reads.cjs.map +1 -0
  812. package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/mapping.cjs +332 -0
  813. package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/mapping.cjs.map +1 -0
  814. package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/mapping.test.js +64 -0
  815. package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/mapping.test.js.map +1 -0
  816. package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/protection.cjs +420 -0
  817. package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/protection.cjs.map +1 -0
  818. package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/session-timing.cjs +91 -0
  819. package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/session-timing.cjs.map +1 -0
  820. package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/substitution.cjs +304 -0
  821. package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/substitution.cjs.map +1 -0
  822. package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/verify-citations.cjs +293 -0
  823. package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/verify-citations.cjs.map +1 -0
  824. package/dist-server/server/piecemaker/vendor/websocket-server/case-folder-structure.cjs +5 -0
  825. package/dist-server/server/piecemaker/vendor/websocket-server/case-folder-structure.cjs.map +1 -0
  826. package/dist-server/server/piecemaker/vendor/websocket-server/legal-forms.cjs +91 -0
  827. package/dist-server/server/piecemaker/vendor/websocket-server/legal-forms.cjs.map +1 -0
  828. package/dist-server/server/piecemaker/vendor/websocket-server/legal-forms.test.js +11 -0
  829. package/dist-server/server/piecemaker/vendor/websocket-server/legal-forms.test.js.map +1 -0
  830. package/dist-server/server/piecemaker/vendor/websocket-server/originals-pipeline.cjs +955 -0
  831. package/dist-server/server/piecemaker/vendor/websocket-server/originals-pipeline.cjs.map +1 -0
  832. package/dist-server/server/shared/claude-cli-path.js +120 -0
  833. package/dist-server/server/shared/claude-cli-path.js.map +1 -0
  834. package/dist-server/server/shared/frontmatter.js +16 -0
  835. package/dist-server/server/shared/frontmatter.js.map +1 -0
  836. package/dist-server/server/shared/image-attachments.js +344 -0
  837. package/dist-server/server/shared/image-attachments.js.map +1 -0
  838. package/dist-server/server/shared/interfaces.js +2 -0
  839. package/dist-server/server/shared/interfaces.js.map +1 -0
  840. package/dist-server/server/shared/message-unification.js +376 -0
  841. package/dist-server/server/shared/message-unification.js.map +1 -0
  842. package/dist-server/server/shared/tests/application-data-root.test.js +33 -0
  843. package/dist-server/server/shared/tests/application-data-root.test.js.map +1 -0
  844. package/dist-server/server/shared/tests/claude-cli-path.test.js +89 -0
  845. package/dist-server/server/shared/tests/claude-cli-path.test.js.map +1 -0
  846. package/dist-server/server/shared/tests/image-attachments.test.js +262 -0
  847. package/dist-server/server/shared/tests/image-attachments.test.js.map +1 -0
  848. package/dist-server/server/shared/tests/message-unification.test.js +174 -0
  849. package/dist-server/server/shared/tests/message-unification.test.js.map +1 -0
  850. package/dist-server/server/shared/tests/slice-tail-page.test.js +35 -0
  851. package/dist-server/server/shared/tests/slice-tail-page.test.js.map +1 -0
  852. package/dist-server/server/shared/types.js +2 -0
  853. package/dist-server/server/shared/types.js.map +1 -0
  854. package/dist-server/server/shared/utils.js +1010 -0
  855. package/dist-server/server/shared/utils.js.map +1 -0
  856. package/dist-server/shared/networkHosts.js +20 -0
  857. package/dist-server/shared/networkHosts.js.map +1 -0
  858. package/dist-server/shared/product-config.mjs +79 -0
  859. package/dist-server/shared/product-config.mjs.map +1 -0
  860. package/package.json +271 -0
  861. package/product.config.json +17 -0
  862. package/public/api-docs.html +901 -0
  863. package/scripts/fix-node-pty.js +67 -0
  864. package/scripts/piecemaker/cli/install-command.mjs +130 -0
  865. package/scripts/piecemaker/cli/lib/composants.mjs +68 -0
  866. package/scripts/piecemaker/cli/lib/config.mjs +32 -0
  867. package/scripts/piecemaker/cli/lib/exec.mjs +79 -0
  868. package/scripts/piecemaker/cli/lib/node-runtime.mjs +76 -0
  869. package/scripts/piecemaker/cli/lib/ports.mjs +96 -0
  870. package/scripts/piecemaker/cli/lib/pwa.mjs +323 -0
  871. package/scripts/piecemaker/cli/lib/repos.mjs +140 -0
  872. package/scripts/piecemaker/cli/lib/services.mjs +71 -0
  873. package/scripts/piecemaker/cli/lib/ui.mjs +41 -0
  874. package/scripts/piecemaker/cli/piecemaker.mjs +159 -0
  875. package/scripts/piecemaker/cli/piecemaker.ps1 +63 -0
  876. package/scripts/piecemaker/cli/piecemaker.sh +60 -0
  877. package/scripts/piecemaker/generate-i18n-overrides.mjs +219 -0
  878. package/scripts/piecemaker/hooks/proxy-guard.mjs +286 -0
  879. package/scripts/promote-dist-server.mjs +50 -0
  880. package/scripts/release/build-server-bundle.js +178 -0
  881. package/scripts/release/package-piecemaker.sh +73 -0
  882. package/scripts/release/prepare-desktop-app.js +168 -0
  883. package/server/index.ts +410 -0
  884. package/server/load-env.ts +48 -0
  885. package/server/modules/agent/agent.module.ts +52 -0
  886. package/server/modules/agent/agent.routes.ts +1287 -0
  887. package/server/modules/agent/index.ts +2 -0
  888. package/server/modules/agent/tests/agent.routes.test.ts +205 -0
  889. package/server/modules/assets/assets.routes.ts +150 -0
  890. package/server/modules/assets/index.ts +3 -0
  891. package/server/modules/assets/services/image-assets.service.ts +127 -0
  892. package/server/modules/assets/tests/image-assets.service.test.ts +74 -0
  893. package/server/modules/auth/auth.middleware.ts +163 -0
  894. package/server/modules/auth/auth.module.ts +38 -0
  895. package/server/modules/auth/auth.routes.ts +57 -0
  896. package/server/modules/auth/auth.service.ts +155 -0
  897. package/server/modules/auth/index.ts +9 -0
  898. package/server/modules/auth/tests/auth.service.test.ts +95 -0
  899. package/server/modules/browser-use/browser-use-mcp.routes.ts +120 -0
  900. package/server/modules/browser-use/browser-use-mcp.ts +386 -0
  901. package/server/modules/browser-use/browser-use-runtime.ts +10 -0
  902. package/server/modules/browser-use/browser-use.routes.ts +96 -0
  903. package/server/modules/browser-use/browser-use.service.ts +835 -0
  904. package/server/modules/browser-use/index.ts +11 -0
  905. package/server/modules/browser-use/tests/browser-use.service.test.ts +10 -0
  906. package/server/modules/cli/cli.module.ts +110 -0
  907. package/server/modules/cli/cli.service.ts +266 -0
  908. package/server/modules/cli/cli.ts +19 -0
  909. package/server/modules/cli/index.ts +2 -0
  910. package/server/modules/cli/pwa-launcher.ts +131 -0
  911. package/server/modules/cli/sandbox.service.ts +413 -0
  912. package/server/modules/cli/tests/cli-environment-bootstrap.test.ts +186 -0
  913. package/server/modules/cli/tests/cli.service.test.ts +107 -0
  914. package/server/modules/cli/tests/sandbox.service.test.ts +61 -0
  915. package/server/modules/commands/commands.module.ts +22 -0
  916. package/server/modules/commands/commands.routes.ts +605 -0
  917. package/server/modules/commands/index.ts +2 -0
  918. package/server/modules/commands/tests/commands.test.ts +114 -0
  919. package/server/modules/database/connection.ts +144 -0
  920. package/server/modules/database/index.ts +27 -0
  921. package/server/modules/database/init-db.ts +17 -0
  922. package/server/modules/database/migrations.ts +552 -0
  923. package/server/modules/database/repositories/api-keys.ts +119 -0
  924. package/server/modules/database/repositories/app-config.ts +53 -0
  925. package/server/modules/database/repositories/credentials.ts +106 -0
  926. package/server/modules/database/repositories/github-tokens.ts +100 -0
  927. package/server/modules/database/repositories/notification-channel-endpoints.ts +153 -0
  928. package/server/modules/database/repositories/notification-preferences.ts +117 -0
  929. package/server/modules/database/repositories/projects.db.ts +196 -0
  930. package/server/modules/database/repositories/provider-models.ts +168 -0
  931. package/server/modules/database/repositories/push-subscriptions.ts +80 -0
  932. package/server/modules/database/repositories/scan-state.db.ts +42 -0
  933. package/server/modules/database/repositories/scheduled-messages.db.ts +127 -0
  934. package/server/modules/database/repositories/session-drafts.db.ts +171 -0
  935. package/server/modules/database/repositories/sessions.db.ts +714 -0
  936. package/server/modules/database/repositories/user-preferences.db.ts +80 -0
  937. package/server/modules/database/repositories/users.ts +140 -0
  938. package/server/modules/database/repositories/vapid-keys.ts +57 -0
  939. package/server/modules/database/schema.ts +306 -0
  940. package/server/modules/database/tests/projects.db.integration.test.ts +72 -0
  941. package/server/modules/database/tests/provider-models.db.integration.test.ts +125 -0
  942. package/server/modules/database/tests/sessions-provider-mapping.test.ts +109 -0
  943. package/server/modules/database/tests/sessions.db.integration.test.ts +116 -0
  944. package/server/modules/database/tests/user-preferences.db.integration.test.ts +135 -0
  945. package/server/modules/file-tree/file-tree.module.ts +116 -0
  946. package/server/modules/file-tree/file-tree.routes.ts +258 -0
  947. package/server/modules/file-tree/file-tree.service.ts +675 -0
  948. package/server/modules/file-tree/index.ts +2 -0
  949. package/server/modules/file-tree/tests/file-tree.routes.test.ts +157 -0
  950. package/server/modules/file-tree/tests/file-tree.service.test.ts +371 -0
  951. package/server/modules/git/git-branch.service.ts +43 -0
  952. package/server/modules/git/git-parsing.service.ts +66 -0
  953. package/server/modules/git/git.module.ts +22 -0
  954. package/server/modules/git/git.routes.ts +1600 -0
  955. package/server/modules/git/index.ts +2 -0
  956. package/server/modules/git/tests/git-branch.service.test.ts +60 -0
  957. package/server/modules/git/tests/git-init.routes.test.ts +135 -0
  958. package/server/modules/git/tests/git.test.ts +106 -0
  959. package/server/modules/notifications/index.ts +24 -0
  960. package/server/modules/notifications/notifications.routes.ts +127 -0
  961. package/server/modules/notifications/services/desktop-notification-clients.service.ts +124 -0
  962. package/server/modules/notifications/services/notification-orchestrator.service.js +310 -0
  963. package/server/modules/notifications/tests/notification-orchestrator.integration.test.ts +55 -0
  964. package/server/modules/notifications/vapid-keys.service.ts +38 -0
  965. package/server/modules/notifications/websocket/desktop-notifications-websocket.service.ts +109 -0
  966. package/server/modules/plugins/index.ts +9 -0
  967. package/server/modules/plugins/plugin-process.service.ts +217 -0
  968. package/server/modules/plugins/plugin-registry.service.ts +461 -0
  969. package/server/modules/plugins/plugins.module.ts +33 -0
  970. package/server/modules/plugins/plugins.routes.ts +66 -0
  971. package/server/modules/plugins/plugins.service.ts +147 -0
  972. package/server/modules/plugins/tests/plugins.service.test.ts +32 -0
  973. package/server/modules/projects/index.ts +11 -0
  974. package/server/modules/projects/projects.routes.ts +273 -0
  975. package/server/modules/projects/services/project-clone.service.ts +323 -0
  976. package/server/modules/projects/services/project-delete.service.ts +102 -0
  977. package/server/modules/projects/services/project-management.service.ts +144 -0
  978. package/server/modules/projects/services/project-star.service.ts +78 -0
  979. package/server/modules/projects/services/projects-has-taskmaster.service.ts +248 -0
  980. package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +314 -0
  981. package/server/modules/projects/tests/project-clone.service.test.ts +183 -0
  982. package/server/modules/projects/tests/project-management.service.test.ts +117 -0
  983. package/server/modules/projects/tests/project-star.service.test.ts +123 -0
  984. package/server/modules/projects/tests/projects-has-taskmaster.service.test.ts +105 -0
  985. package/server/modules/providers/README.md +380 -0
  986. package/server/modules/providers/index.ts +14 -0
  987. package/server/modules/providers/list/claude/claude-auth.provider.ts +180 -0
  988. package/server/modules/providers/list/claude/claude-fork.provider.ts +57 -0
  989. package/server/modules/providers/list/claude/claude-mcp.provider.ts +135 -0
  990. package/server/modules/providers/list/claude/claude-models.provider.ts +314 -0
  991. package/server/modules/providers/list/claude/claude-runtime.provider.js +1217 -0
  992. package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +203 -0
  993. package/server/modules/providers/list/claude/claude-sessions.provider.ts +1108 -0
  994. package/server/modules/providers/list/claude/claude-skills.provider.ts +265 -0
  995. package/server/modules/providers/list/claude/claude.provider.ts +33 -0
  996. package/server/modules/providers/list/codex/codex-app-server.client.ts +243 -0
  997. package/server/modules/providers/list/codex/codex-auth.provider.ts +100 -0
  998. package/server/modules/providers/list/codex/codex-fork.provider.ts +40 -0
  999. package/server/modules/providers/list/codex/codex-mcp.provider.ts +135 -0
  1000. package/server/modules/providers/list/codex/codex-models.provider.ts +123 -0
  1001. package/server/modules/providers/list/codex/codex-runtime.provider.js +572 -0
  1002. package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +212 -0
  1003. package/server/modules/providers/list/codex/codex-sessions.provider.ts +2267 -0
  1004. package/server/modules/providers/list/codex/codex-skills.provider.ts +73 -0
  1005. package/server/modules/providers/list/codex/codex.provider.ts +33 -0
  1006. package/server/modules/providers/list/cursor/cursor-auth.provider.ts +143 -0
  1007. package/server/modules/providers/list/cursor/cursor-mcp.provider.ts +108 -0
  1008. package/server/modules/providers/list/cursor/cursor-models.provider.ts +119 -0
  1009. package/server/modules/providers/list/cursor/cursor-runtime.provider.js +387 -0
  1010. package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +158 -0
  1011. package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +654 -0
  1012. package/server/modules/providers/list/cursor/cursor-skills.provider.ts +39 -0
  1013. package/server/modules/providers/list/cursor/cursor.provider.ts +30 -0
  1014. package/server/modules/providers/list/mistral/mistral-auth.provider.ts +181 -0
  1015. package/server/modules/providers/list/mistral/mistral-mcp.provider.ts +20 -0
  1016. package/server/modules/providers/list/mistral/mistral-models.provider.ts +76 -0
  1017. package/server/modules/providers/list/mistral/mistral-runtime.provider.ts +417 -0
  1018. package/server/modules/providers/list/mistral/mistral-session-synchronizer.provider.ts +15 -0
  1019. package/server/modules/providers/list/mistral/mistral-sessions.provider.ts +210 -0
  1020. package/server/modules/providers/list/mistral/mistral-skills.provider.ts +20 -0
  1021. package/server/modules/providers/list/mistral/mistral.provider.ts +35 -0
  1022. package/server/modules/providers/list/opencode/opencode-auth.provider.ts +110 -0
  1023. package/server/modules/providers/list/opencode/opencode-mcp.provider.ts +228 -0
  1024. package/server/modules/providers/list/opencode/opencode-models.provider.ts +304 -0
  1025. package/server/modules/providers/list/opencode/opencode-runtime.provider.js +434 -0
  1026. package/server/modules/providers/list/opencode/opencode-runtime.provider.test.js +261 -0
  1027. package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +179 -0
  1028. package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +500 -0
  1029. package/server/modules/providers/list/opencode/opencode-skills.provider.ts +78 -0
  1030. package/server/modules/providers/list/opencode/opencode.provider.ts +30 -0
  1031. package/server/modules/providers/provider.registry.ts +38 -0
  1032. package/server/modules/providers/provider.routes.ts +909 -0
  1033. package/server/modules/providers/services/mcp.service.ts +109 -0
  1034. package/server/modules/providers/services/provider-auth.service.ts +26 -0
  1035. package/server/modules/providers/services/provider-capabilities.service.ts +133 -0
  1036. package/server/modules/providers/services/provider-models.service.ts +401 -0
  1037. package/server/modules/providers/services/provider-runtime.service.ts +108 -0
  1038. package/server/modules/providers/services/provider-token-usage.service.ts +484 -0
  1039. package/server/modules/providers/services/session-conversations-search.service.ts +1321 -0
  1040. package/server/modules/providers/services/session-history-cache.service.ts +139 -0
  1041. package/server/modules/providers/services/session-synchronizer.service.ts +162 -0
  1042. package/server/modules/providers/services/sessions-watcher.service.ts +266 -0
  1043. package/server/modules/providers/services/sessions.service.ts +663 -0
  1044. package/server/modules/providers/services/skills.service.ts +39 -0
  1045. package/server/modules/providers/shared/base/abstract.provider.ts +32 -0
  1046. package/server/modules/providers/shared/mcp/mcp.provider.ts +151 -0
  1047. package/server/modules/providers/shared/skills/skills.provider.ts +287 -0
  1048. package/server/modules/providers/tests/claude-auth.test.ts +208 -0
  1049. package/server/modules/providers/tests/claude-sessions.test.ts +573 -0
  1050. package/server/modules/providers/tests/claude-subagent-echo.test.ts +25 -0
  1051. package/server/modules/providers/tests/claude-token-budget.test.ts +104 -0
  1052. package/server/modules/providers/tests/codex-message-editing.test.ts +419 -0
  1053. package/server/modules/providers/tests/codex-session-fork.test.ts +59 -0
  1054. package/server/modules/providers/tests/codex-sessions.test.ts +553 -0
  1055. package/server/modules/providers/tests/mcp.test.ts +349 -0
  1056. package/server/modules/providers/tests/opencode-models.test.ts +154 -0
  1057. package/server/modules/providers/tests/opencode-sessions.test.ts +522 -0
  1058. package/server/modules/providers/tests/provider-attachment-history.test.ts +247 -0
  1059. package/server/modules/providers/tests/provider-models.service.test.ts +353 -0
  1060. package/server/modules/providers/tests/provider-runtime.service.test.ts +138 -0
  1061. package/server/modules/providers/tests/provider-token-usage.service.test.ts +373 -0
  1062. package/server/modules/providers/tests/provider.routes.test.ts +244 -0
  1063. package/server/modules/providers/tests/session-fork.test.ts +173 -0
  1064. package/server/modules/providers/tests/session-history-cache.service.test.ts +143 -0
  1065. package/server/modules/providers/tests/session-orphan-prune.test.ts +107 -0
  1066. package/server/modules/providers/tests/session-synchronizer-coalescing.test.ts +136 -0
  1067. package/server/modules/providers/tests/sessions-details.test.ts +73 -0
  1068. package/server/modules/providers/tests/sessions.service.test.ts +193 -0
  1069. package/server/modules/providers/tests/skills.test.ts +692 -0
  1070. package/server/modules/scheduled-messages/index.ts +8 -0
  1071. package/server/modules/scheduled-messages/scheduled-messages.routes.ts +68 -0
  1072. package/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.ts +203 -0
  1073. package/server/modules/scheduled-messages/services/scheduled-messages.service.ts +121 -0
  1074. package/server/modules/scheduled-messages/tests/scheduled-messages.test.ts +310 -0
  1075. package/server/modules/settings/index.ts +2 -0
  1076. package/server/modules/settings/settings.module.ts +50 -0
  1077. package/server/modules/settings/settings.routes.ts +51 -0
  1078. package/server/modules/settings/settings.service.ts +186 -0
  1079. package/server/modules/settings/tests/settings.service.test.ts +54 -0
  1080. package/server/modules/system/index.ts +2 -0
  1081. package/server/modules/system/system.module.ts +62 -0
  1082. package/server/modules/system/system.routes.ts +21 -0
  1083. package/server/modules/system/system.service.ts +76 -0
  1084. package/server/modules/system/tests/system.service.test.ts +118 -0
  1085. package/server/modules/taskmaster/index.ts +2 -0
  1086. package/server/modules/taskmaster/taskmaster.module.ts +24 -0
  1087. package/server/modules/taskmaster/taskmaster.routes.ts +1472 -0
  1088. package/server/modules/taskmaster/taskmaster.service.ts +132 -0
  1089. package/server/modules/taskmaster/tests/taskmaster.routes.test.ts +201 -0
  1090. package/server/modules/taskmaster/tests/taskmaster.service.test.ts +97 -0
  1091. package/server/modules/user/index.ts +2 -0
  1092. package/server/modules/user/tests/user.service.test.ts +151 -0
  1093. package/server/modules/user/user.module.ts +67 -0
  1094. package/server/modules/user/user.routes.ts +94 -0
  1095. package/server/modules/user/user.service.ts +190 -0
  1096. package/server/modules/voice/index.ts +2 -0
  1097. package/server/modules/voice/tests/voice.service.test.ts +99 -0
  1098. package/server/modules/voice/voice.module.ts +47 -0
  1099. package/server/modules/voice/voice.routes.ts +113 -0
  1100. package/server/modules/voice/voice.service.ts +192 -0
  1101. package/server/modules/websocket/README.md +273 -0
  1102. package/server/modules/websocket/index.ts +11 -0
  1103. package/server/modules/websocket/services/chat-run-registry.service.ts +310 -0
  1104. package/server/modules/websocket/services/chat-session-writer.service.ts +175 -0
  1105. package/server/modules/websocket/services/chat-websocket.service.ts +654 -0
  1106. package/server/modules/websocket/services/plugin-websocket-proxy.service.ts +65 -0
  1107. package/server/modules/websocket/services/session-upsert-broadcast.service.ts +108 -0
  1108. package/server/modules/websocket/services/shell-websocket.service.ts +628 -0
  1109. package/server/modules/websocket/services/websocket-auth.service.ts +59 -0
  1110. package/server/modules/websocket/services/websocket-server.service.ts +126 -0
  1111. package/server/modules/websocket/services/websocket-state.service.ts +16 -0
  1112. package/server/modules/websocket/services/websocket-writer.service.ts +38 -0
  1113. package/server/modules/websocket/tests/chat-attachment-filter.test.ts +59 -0
  1114. package/server/modules/websocket/tests/chat-edit-send.test.ts +309 -0
  1115. package/server/modules/websocket/tests/chat-run-registry.test.ts +313 -0
  1116. package/server/modules/websocket/tests/session-upsert-broadcast.test.ts +138 -0
  1117. package/server/modules/websocket/tests/shell-websocket.service.test.ts +227 -0
  1118. package/server/modules/websocket/tests/websocket-heartbeat.service.test.ts +78 -0
  1119. package/server/modules/worktrees/index.ts +2 -0
  1120. package/server/modules/worktrees/services/worktree-create-and-open.service.ts +53 -0
  1121. package/server/modules/worktrees/services/worktree-create.service.ts +104 -0
  1122. package/server/modules/worktrees/services/worktree-git.service.ts +207 -0
  1123. package/server/modules/worktrees/services/worktree-list.service.ts +143 -0
  1124. package/server/modules/worktrees/services/worktree-merge.service.ts +157 -0
  1125. package/server/modules/worktrees/services/worktree-open.service.ts +85 -0
  1126. package/server/modules/worktrees/services/worktree-remove.service.ts +88 -0
  1127. package/server/modules/worktrees/tests/worktree-create-and-open.service.test.ts +68 -0
  1128. package/server/modules/worktrees/tests/worktree-create.service.test.ts +142 -0
  1129. package/server/modules/worktrees/tests/worktree-git.service.test.ts +84 -0
  1130. package/server/modules/worktrees/tests/worktree-list.service.test.ts +45 -0
  1131. package/server/modules/worktrees/tests/worktree-merge.service.test.ts +213 -0
  1132. package/server/modules/worktrees/tests/worktree-open.service.test.ts +122 -0
  1133. package/server/modules/worktrees/tests/worktree-remove.service.test.ts +227 -0
  1134. package/server/modules/worktrees/tests/worktrees.routes.test.ts +199 -0
  1135. package/server/modules/worktrees/worktrees.module.ts +113 -0
  1136. package/server/modules/worktrees/worktrees.routes.ts +116 -0
  1137. package/server/piecemaker/activation/claude-mcp.cjs +115 -0
  1138. package/server/piecemaker/activation/claude-plugins.cjs +65 -0
  1139. package/server/piecemaker/activation/codex-mcp.cjs +102 -0
  1140. package/server/piecemaker/activation/errors.cjs +15 -0
  1141. package/server/piecemaker/activation/index.cjs +5 -0
  1142. package/server/piecemaker/activation/library.cjs +284 -0
  1143. package/server/piecemaker/activation/library.cjs.bak +223 -0
  1144. package/server/piecemaker/activation/library.test.js +365 -0
  1145. package/server/piecemaker/activation/markdown.cjs +108 -0
  1146. package/server/piecemaker/activation/markdown.cjs.bak +78 -0
  1147. package/server/piecemaker/activation/paths.cjs +104 -0
  1148. package/server/piecemaker/activation/paths.test.js +158 -0
  1149. package/server/piecemaker/activation/routes.cjs +101 -0
  1150. package/server/piecemaker/activation/routes.test.js +344 -0
  1151. package/server/piecemaker/activation/snapshot.cjs +30 -0
  1152. package/server/piecemaker/anonymizer/anonymizer.test.js +320 -0
  1153. package/server/piecemaker/anonymizer/client-config.cjs +228 -0
  1154. package/server/piecemaker/anonymizer/codex-http.test.js +20 -0
  1155. package/server/piecemaker/anonymizer/dictionary.cjs +174 -0
  1156. package/server/piecemaker/anonymizer/lifecycle.test.ts +37 -0
  1157. package/server/piecemaker/anonymizer/lifecycle.ts +22 -0
  1158. package/server/piecemaker/anonymizer/providers.cjs +241 -0
  1159. package/server/piecemaker/anonymizer/proxy.cjs +365 -0
  1160. package/server/piecemaker/anonymizer/rewrite.cjs +251 -0
  1161. package/server/piecemaker/anonymizer/routes.cjs +43 -0
  1162. package/server/piecemaker/anonymizer/service.cjs +218 -0
  1163. package/server/piecemaker/harness/chat-harness.test.ts +131 -0
  1164. package/server/piecemaker/harness/chat-harness.ts +151 -0
  1165. package/server/piecemaker/harness/citation-instructions.ts +18 -0
  1166. package/server/piecemaker/harness/citation-store.ts +80 -0
  1167. package/server/piecemaker/harness/citation-turn.test.ts +155 -0
  1168. package/server/piecemaker/harness/citation-turn.ts +197 -0
  1169. package/server/piecemaker/harness/citations.routes.test.ts +68 -0
  1170. package/server/piecemaker/harness/citations.routes.ts +18 -0
  1171. package/server/piecemaker/harness/decisions.cjs +302 -0
  1172. package/server/piecemaker/harness/flux-sse.cjs +111 -0
  1173. package/server/piecemaker/harness/harness.test.js +296 -0
  1174. package/server/piecemaker/harness/index.cjs +103 -0
  1175. package/server/piecemaker/harness/proxy-harness.test.js +226 -0
  1176. package/server/piecemaker/harness/source-passages.ts +21 -0
  1177. package/server/piecemaker/harness/verification.cjs +159 -0
  1178. package/server/piecemaker/index.ts +59 -0
  1179. package/server/piecemaker/library/index.ts +54 -0
  1180. package/server/piecemaker/library/marketplace.ts +152 -0
  1181. package/server/piecemaker/library/migrate-cli.ts +11 -0
  1182. package/server/piecemaker/library/migrate.ts +49 -0
  1183. package/server/piecemaker/library/policy.cjs +9 -0
  1184. package/server/piecemaker/library/provider-skills.test.ts +61 -0
  1185. package/server/piecemaker/library/provider-skills.ts +48 -0
  1186. package/server/piecemaker/library/routes.ts +38 -0
  1187. package/server/piecemaker/library/runtime.ts +39 -0
  1188. package/server/piecemaker/library/store.test.ts +280 -0
  1189. package/server/piecemaker/library/store.ts +429 -0
  1190. package/server/piecemaker/library/workspace-installation.ts +61 -0
  1191. package/server/piecemaker/originals-cache.cjs +54 -0
  1192. package/server/piecemaker/originals-cache.test.js +77 -0
  1193. package/server/piecemaker/protection/bypass.cjs +73 -0
  1194. package/server/piecemaker/protection/bypass.test.js +140 -0
  1195. package/server/piecemaker/protection/routes.cjs +39 -0
  1196. package/server/piecemaker/router.cjs +109 -0
  1197. package/server/piecemaker/timesheet/index.ts +9 -0
  1198. package/server/piecemaker/timesheet/routes.ts +48 -0
  1199. package/server/piecemaker/timesheet/service.test.ts +142 -0
  1200. package/server/piecemaker/timesheet/service.ts +298 -0
  1201. package/server/piecemaker/timesheet/store.ts +167 -0
  1202. package/server/piecemaker/vendor/.env +2 -0
  1203. package/server/piecemaker/vendor/installer/assets/PieceMakerApp.swift +189 -0
  1204. package/server/piecemaker/vendor/installer/assets/claude-hooks/piecemaker-guard-secrets.mjs +230 -0
  1205. package/server/piecemaker/vendor/installer/assets/claude-hooks/piecemaker-secret-paths.default.json +1 -0
  1206. package/server/piecemaker/vendor/installer/assets/piecemaker.icns +0 -0
  1207. package/server/piecemaker/vendor/installer/assets/piecemaker.ico +0 -0
  1208. package/server/piecemaker/vendor/installer/bin/piecemaker.mjs +1018 -0
  1209. package/server/piecemaker/vendor/installer/lib/banner.mjs +35 -0
  1210. package/server/piecemaker/vendor/installer/lib/codex-skills.mjs +343 -0
  1211. package/server/piecemaker/vendor/installer/lib/commandes.mjs +13 -0
  1212. package/server/piecemaker/vendor/installer/lib/desktop-launcher.mjs +305 -0
  1213. package/server/piecemaker/vendor/installer/lib/legacy-legifrance.mjs +72 -0
  1214. package/server/piecemaker/vendor/installer/lib/platform.mjs +163 -0
  1215. package/server/piecemaker/vendor/installer/lib/prompt.mjs +138 -0
  1216. package/server/piecemaker/vendor/installer/lib/resume-steps.mjs +174 -0
  1217. package/server/piecemaker/vendor/installer/lib/secrets-guard.mjs +193 -0
  1218. package/server/piecemaker/vendor/installer/lib/service.mjs +467 -0
  1219. package/server/piecemaker/vendor/installer/lib/state.mjs +114 -0
  1220. package/server/piecemaker/vendor/installer/lib/ui.mjs +159 -0
  1221. package/server/piecemaker/vendor/installer/steps/00-identite.mjs +71 -0
  1222. package/server/piecemaker/vendor/installer/steps/01-prerequis.mjs +119 -0
  1223. package/server/piecemaker/vendor/installer/steps/02-dependances-node.mjs +127 -0
  1224. package/server/piecemaker/vendor/installer/steps/03-python-gliner.mjs +249 -0
  1225. package/server/piecemaker/vendor/installer/steps/03b-python-graphify.mjs +161 -0
  1226. package/server/piecemaker/vendor/installer/steps/04-conversion-md.mjs +147 -0
  1227. package/server/piecemaker/vendor/installer/steps/05-certificats.mjs +171 -0
  1228. package/server/piecemaker/vendor/installer/steps/06-hooks.mjs +266 -0
  1229. package/server/piecemaker/vendor/installer/steps/07-legifrance.mjs +279 -0
  1230. package/server/piecemaker/vendor/installer/steps/08-telegram.mjs +482 -0
  1231. package/server/piecemaker/vendor/installer/steps/09-claude-assets.mjs +151 -0
  1232. package/server/piecemaker/vendor/installer/steps/09-codex-plugin.mjs +142 -0
  1233. package/server/piecemaker/vendor/installer/steps/10-libreoffice.mjs +130 -0
  1234. package/server/piecemaker/vendor/installer/steps/10-pandoc.mjs +212 -0
  1235. package/server/piecemaker/vendor/installer/steps/11-docx-cli.mjs +144 -0
  1236. package/server/piecemaker/vendor/installer/steps/12-mcp-piecemaker.mjs +124 -0
  1237. package/server/piecemaker/vendor/installer/steps/13-garde-secrets.mjs +136 -0
  1238. package/server/piecemaker/vendor/installer/steps/14-mxc-sandbox.mjs +271 -0
  1239. package/server/piecemaker/vendor/installer/steps/15-pwa-desktop.mjs +64 -0
  1240. package/server/piecemaker/vendor/installer/templates/root-CLAUDE.md +79 -0
  1241. package/server/piecemaker/vendor/installer/templates/workspace-CLAUDE.md +111 -0
  1242. package/server/piecemaker/vendor/mcp/README.md +13 -0
  1243. package/server/piecemaker/vendor/mcp/piecemaker/server.mjs +222 -0
  1244. package/server/piecemaker/vendor/mike-defaults-fr/PROVENANCE.md +10 -0
  1245. package/server/piecemaker/vendor/mike-defaults-fr/assistant-workflows/compare-documents/SKILL.md +51 -0
  1246. package/server/piecemaker/vendor/mike-defaults-fr/assistant-workflows/draft-from-template/SKILL.md +27 -0
  1247. package/server/piecemaker/vendor/mike-defaults-fr/assistant-workflows/extract-key-terms/SKILL.md +43 -0
  1248. package/server/piecemaker/vendor/mike-defaults-fr/assistant-workflows/proofread/SKILL.md +98 -0
  1249. package/server/piecemaker/vendor/mike-defaults-fr/catalog.json +22 -0
  1250. package/server/piecemaker/vendor/mike-defaults-fr/tabular-review-workflows/commercial-agreement-tabular-review/SKILL.md +31 -0
  1251. package/server/piecemaker/vendor/mike-defaults-fr/tabular-review-workflows/commercial-agreement-tabular-review/table-columns.yaml +92 -0
  1252. package/server/piecemaker/vendor/package.json +7 -0
  1253. package/server/piecemaker/vendor/piecemaker-plugin/README.md +115 -0
  1254. package/server/piecemaker/vendor/piecemaker-plugin/agents/analyste-piece.md +38 -0
  1255. package/server/piecemaker/vendor/piecemaker-plugin/agents/jurisprudence-scanner.md +50 -0
  1256. package/server/piecemaker/vendor/piecemaker-plugin/hooks/hooks.json +84 -0
  1257. package/server/piecemaker/vendor/piecemaker-plugin/scripts/billing-track.mjs +194 -0
  1258. package/server/piecemaker/vendor/piecemaker-plugin/scripts/build-recherche-pdf.mjs +91 -0
  1259. package/server/piecemaker/vendor/piecemaker-plugin/scripts/classify-ai-documents.mjs +123 -0
  1260. package/server/piecemaker/vendor/piecemaker-plugin/scripts/commit-track.mjs +71 -0
  1261. package/server/piecemaker/vendor/piecemaker-plugin/scripts/compile-recherche.mjs +107 -0
  1262. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/anonymization-state.cjs +146 -0
  1263. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/case-folder-structure.cjs +303 -0
  1264. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/case-folders.cjs +86 -0
  1265. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/central-mapping.cjs +0 -0
  1266. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/citation-sources.cjs +519 -0
  1267. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/citations.cjs +292 -0
  1268. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/commits.cjs +1683 -0
  1269. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/docx-text-worker.cjs +43 -0
  1270. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/hook-io.mjs +249 -0
  1271. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/institutional-terms.cjs +160 -0
  1272. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/legifrance-reads.cjs +85 -0
  1273. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/mapping.cjs +346 -0
  1274. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/mapping.test.js +68 -0
  1275. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/protection.cjs +419 -0
  1276. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/recherche-report.cjs +177 -0
  1277. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/session-timing.cjs +80 -0
  1278. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/substitution.cjs +318 -0
  1279. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/verify-citations.cjs +318 -0
  1280. package/server/piecemaker/vendor/piecemaker-plugin/scripts/protect-originals.mjs +256 -0
  1281. package/server/piecemaker/vendor/piecemaker-plugin/scripts/session-commit-worker.mjs +260 -0
  1282. package/server/piecemaker/vendor/piecemaker-plugin/scripts/session-commit.mjs +83 -0
  1283. package/server/piecemaker/vendor/piecemaker-plugin/scripts/statusline.mjs +179 -0
  1284. package/server/piecemaker/vendor/piecemaker-plugin/scripts/track-legifrance-reads.mjs +46 -0
  1285. package/server/piecemaker/vendor/piecemaker-plugin/skills/ajout-de-fonctionnalites/SKILL.md +66 -0
  1286. package/server/piecemaker/vendor/piecemaker-plugin/skills/conversion-md/SKILL.md +60 -0
  1287. package/server/piecemaker/vendor/piecemaker-plugin/skills/graphe-juridique/SKILL.md +105 -0
  1288. package/server/piecemaker/vendor/piecemaker-plugin/skills/recherche-juridique/SKILL.md +234 -0
  1289. package/server/piecemaker/vendor/piecemaker-plugin/skills/redaction-juridique/SKILL.md +96 -0
  1290. package/server/piecemaker/vendor/piecemaker-plugin/skills/tamponnage/SKILL.md +148 -0
  1291. package/server/piecemaker/vendor/websocket-server/admin-routes.cjs +3195 -0
  1292. package/server/piecemaker/vendor/websocket-server/case-folder-structure.cjs +3 -0
  1293. package/server/piecemaker/vendor/websocket-server/case-instructions.cjs +114 -0
  1294. package/server/piecemaker/vendor/websocket-server/case-registry.cjs +93 -0
  1295. package/server/piecemaker/vendor/websocket-server/claude-assets.cjs +338 -0
  1296. package/server/piecemaker/vendor/websocket-server/claude-hooks.cjs +380 -0
  1297. package/server/piecemaker/vendor/websocket-server/document-index.cjs +682 -0
  1298. package/server/piecemaker/vendor/websocket-server/graphify-document-graph.cjs +456 -0
  1299. package/server/piecemaker/vendor/websocket-server/legal-chronology.cjs +502 -0
  1300. package/server/piecemaker/vendor/websocket-server/legal-forms.cjs +89 -0
  1301. package/server/piecemaker/vendor/websocket-server/legal-forms.test.js +10 -0
  1302. package/server/piecemaker/vendor/websocket-server/legal-graph-materializer.cjs +234 -0
  1303. package/server/piecemaker/vendor/websocket-server/legal-graph-prompt.txt +155 -0
  1304. package/server/piecemaker/vendor/websocket-server/legal-graph.cjs +2021 -0
  1305. package/server/piecemaker/vendor/websocket-server/legal-identity-boundary.cjs +247 -0
  1306. package/server/piecemaker/vendor/websocket-server/legal-party-registry.cjs +261 -0
  1307. package/server/piecemaker/vendor/websocket-server/lib/doc-generate.cjs +229 -0
  1308. package/server/piecemaker/vendor/websocket-server/lib/export-render.cjs +442 -0
  1309. package/server/piecemaker/vendor/websocket-server/lib/office-to-pdf.cjs +306 -0
  1310. package/server/piecemaker/vendor/websocket-server/lib/stamping.cjs +39 -0
  1311. package/server/piecemaker/vendor/websocket-server/originals-pipeline.cjs +965 -0
  1312. package/server/piecemaker/vendor/websocket-server/scripts/ADDRESS_ANONYMIZATION.md +216 -0
  1313. package/server/piecemaker/vendor/websocket-server/scripts/DEDUPLICATION_CHANGES.md +134 -0
  1314. package/server/piecemaker/vendor/websocket-server/scripts/GLINER2_README.md +138 -0
  1315. package/server/piecemaker/vendor/websocket-server/scripts/__pycache__/scan_utils.cpython-312.pyc +0 -0
  1316. package/server/piecemaker/vendor/websocket-server/scripts/convert_and_scan_pipeline.py +1984 -0
  1317. package/server/piecemaker/vendor/websocket-server/scripts/graphify-legal-sitecustomize.py +50 -0
  1318. package/server/piecemaker/vendor/websocket-server/scripts/mineru_improved_1.py +208 -0
  1319. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/AUDIT_PERF_QUALITE.md +246 -0
  1320. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/MODELE_COREML.md +62 -0
  1321. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/__pycache__/coreml_runtime.cpython-312.pyc +0 -0
  1322. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/__pycache__/model_config.cpython-312.pyc +0 -0
  1323. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/build_coreml.py +118 -0
  1324. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/coreml_runtime.py +164 -0
  1325. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/BACKENDS_MESURES.md +384 -0
  1326. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/CRITERES_QUALITE.md +81 -0
  1327. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/__pycache__/coreml_encoder.cpython-312.pyc +0 -0
  1328. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/bench_backend.py +151 -0
  1329. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/bench_batchsize.py +47 -0
  1330. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/bench_coreml_pipeline.py +138 -0
  1331. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/bench_gensight.py +124 -0
  1332. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/check_gold.py +66 -0
  1333. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/coreml_encoder.py +182 -0
  1334. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/encoder_backends.py +179 -0
  1335. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/evaluate.py +130 -0
  1336. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/gold.py +59 -0
  1337. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/profile_split.py +65 -0
  1338. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/slice_A.md +67 -0
  1339. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/slice_B.md +57 -0
  1340. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/slice_C.md +907 -0
  1341. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/sweep.py +115 -0
  1342. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/verify_substitution.cjs +116 -0
  1343. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/verify_whitespace.py +67 -0
  1344. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/verify_worker.py +106 -0
  1345. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/model_config.py +28 -0
  1346. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/presidio-gliner.py +471 -0
  1347. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/scanner_worker.py +668 -0
  1348. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/__init__.py +10 -0
  1349. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/__pycache__/__init__.cpython-312.pyc +0 -0
  1350. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/__pycache__/analysis_explanation.cpython-312.pyc +0 -0
  1351. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/__pycache__/entity_recognizer.cpython-312.pyc +0 -0
  1352. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/__pycache__/local_recognizer.cpython-312.pyc +0 -0
  1353. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/__pycache__/pattern.cpython-312.pyc +0 -0
  1354. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/__pycache__/pattern_recognizer.cpython-312.pyc +0 -0
  1355. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/__pycache__/recognizer_result.cpython-312.pyc +0 -0
  1356. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/analysis_explanation.py +65 -0
  1357. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/entity_recognizer.py +213 -0
  1358. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/input_validation/__init__.py +21 -0
  1359. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/input_validation/language_validation.py +18 -0
  1360. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/input_validation/schemas.py +132 -0
  1361. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/input_validation/yaml_recognizer_models.py +422 -0
  1362. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/local_recognizer.py +7 -0
  1363. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/__init__.py +15 -0
  1364. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/__pycache__/__init__.cpython-312.pyc +0 -0
  1365. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/__pycache__/device_detector.cpython-312.pyc +0 -0
  1366. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/__pycache__/ner_model_configuration.cpython-312.pyc +0 -0
  1367. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/__pycache__/nlp_artifacts.cpython-312.pyc +0 -0
  1368. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/__pycache__/nlp_engine.cpython-312.pyc +0 -0
  1369. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/__pycache__/spacy_nlp_engine.cpython-312.pyc +0 -0
  1370. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/device_detector.py +65 -0
  1371. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/ner_model_configuration.py +128 -0
  1372. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/nlp_artifacts.py +88 -0
  1373. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/nlp_engine.py +65 -0
  1374. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/nlp_engine_provider.py +114 -0
  1375. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/spacy_nlp_engine.py +290 -0
  1376. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/pattern.py +66 -0
  1377. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/pattern_recognizer.py +297 -0
  1378. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/__init__.py +4 -0
  1379. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/__pycache__/__init__.cpython-312.pyc +0 -0
  1380. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/__pycache__/gliner_recognizer.cpython-312.pyc +0 -0
  1381. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/__init__.py +0 -0
  1382. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/__pycache__/__init__.cpython-312.pyc +0 -0
  1383. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/__pycache__/credit_card_recognizer.cpython-312.pyc +0 -0
  1384. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/__pycache__/crypto_recognizer.cpython-312.pyc +0 -0
  1385. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/__pycache__/email_recognizer.cpython-312.pyc +0 -0
  1386. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/__pycache__/iban_patterns.cpython-312.pyc +0 -0
  1387. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/__pycache__/iban_recognizer.cpython-312.pyc +0 -0
  1388. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/__pycache__/ip_recognizer.cpython-312.pyc +0 -0
  1389. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/__pycache__/mac_recognizer.cpython-312.pyc +0 -0
  1390. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/__pycache__/url_recognizer.cpython-312.pyc +0 -0
  1391. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/credit_card_recognizer.py +82 -0
  1392. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/crypto_recognizer.py +141 -0
  1393. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/date_recognizer.py +102 -0
  1394. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/email_recognizer.py +48 -0
  1395. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/iban_patterns.py +173 -0
  1396. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/iban_recognizer.py +221 -0
  1397. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/ip_recognizer.py +65 -0
  1398. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/mac_recognizer.py +73 -0
  1399. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/url_recognizer.py +50 -0
  1400. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/gliner_recognizer.py +218 -0
  1401. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/spacy_recognizer.py +139 -0
  1402. package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/recognizer_result.py +188 -0
  1403. package/server/piecemaker/vendor/websocket-server/scripts/requirements.txt +29 -0
  1404. package/server/piecemaker/vendor/websocket-server/scripts/scan_utils.py +469 -0
  1405. package/server/piecemaker/vendor/websocket-server/scripts/smart_converter.py +356 -0
  1406. package/server/piecemaker/vendor/websocket-server/scripts/warmup.py +454 -0
  1407. package/server/piecemaker/vendor/websocket-server/stamping-routes.cjs +334 -0
  1408. package/server/piecemaker/vendor/websocket-server/telegram-admin.cjs +602 -0
  1409. package/server/piecemaker/vendor/websocket-server/workspace-paths.cjs +22 -0
  1410. package/server/shared/claude-cli-path.ts +157 -0
  1411. package/server/shared/frontmatter.ts +18 -0
  1412. package/server/shared/image-attachments.ts +437 -0
  1413. package/server/shared/interfaces.ts +241 -0
  1414. package/server/shared/message-unification.ts +449 -0
  1415. package/server/shared/tests/application-data-root.test.ts +29 -0
  1416. package/server/shared/tests/claude-cli-path.test.ts +118 -0
  1417. package/server/shared/tests/image-attachments.test.ts +353 -0
  1418. package/server/shared/tests/message-unification.test.ts +218 -0
  1419. package/server/shared/tests/slice-tail-page.test.ts +42 -0
  1420. package/server/shared/types.ts +1354 -0
  1421. package/server/shared/utils.ts +1212 -0
  1422. package/server/tsconfig.json +40 -0
  1423. package/shared/networkHosts.js +22 -0
  1424. package/shared/product-config.mjs +86 -0
@@ -0,0 +1,3195 @@
1
+ const fs = require('fs');
2
+ const os = require('os');
3
+ const path = require('path');
4
+ const crypto = require('node:crypto');
5
+ const { spawn, spawnSync } = require('child_process');
6
+ const { performance } = require('node:perf_hooks');
7
+ const {
8
+ listConfiguredCases,
9
+ readRegistryConfig,
10
+ registerCaseFolder,
11
+ resolveCaseReference,
12
+ validateSelectedCaseFolder,
13
+ } = require('./case-registry.cjs');
14
+ const { stampedPiecesDirectory } = require('./lib/stamping.cjs');
15
+ const {
16
+ COMMIT_USER_NAME_KEY,
17
+ caseOverview,
18
+ checkoutHistoryBranch,
19
+ createCommit,
20
+ createHistoryBranch,
21
+ historyBranches,
22
+ historyMonths,
23
+ isTechnicalCaseDirectoryName,
24
+ listCases,
25
+ listHistory,
26
+ listHistoryPeriod,
27
+ resolveCasesRoot,
28
+ resolveCommitIdentity,
29
+ restoreRevision,
30
+ revisionDetails,
31
+ safeCaseFiles,
32
+ worktreeDetails,
33
+ } = require('../piecemaker-plugin/scripts/lib/commits.cjs');
34
+ const {
35
+ cancelOriginalsJob,
36
+ getJob,
37
+ listOriginals,
38
+ readCaseMapping,
39
+ rebuildCaseMapping,
40
+ saveCaseMapping,
41
+ startOriginalsJob,
42
+ writeCaseMapping,
43
+ } = require('./originals-pipeline.cjs');
44
+ const { invalidateOriginals, listOriginalsCached } = require('../../originals-cache.cjs');
45
+ const {
46
+ documentKey,
47
+ readProtection,
48
+ writeProtection,
49
+ } = require('../piecemaker-plugin/scripts/lib/protection.cjs');
50
+ const { stateKey } = require('../piecemaker-plugin/scripts/lib/anonymization-state.cjs');
51
+ const {
52
+ applyDocumentIndexCorrection,
53
+ buildChronology,
54
+ documentIndexFile,
55
+ readDocumentIndex,
56
+ } = require('./document-index.cjs');
57
+ const { chronologyFromLegalGraph } = require('./legal-chronology.cjs');
58
+ const {
59
+ buildLegalGraph,
60
+ legalGraphStatus,
61
+ rematerializeDeterministicLegalGraph,
62
+ renderLegalGraphViewer,
63
+ } = require('./legal-graph.cjs');
64
+ const { renderChronologyHtml, renderHistoryHtml } = require('./lib/export-render.cjs');
65
+ const { outputExtension } = require('./lib/office-to-pdf.cjs');
66
+ const { generateDocument } = require('./lib/doc-generate.cjs');
67
+ const {
68
+ readInstitutionalTerms,
69
+ writeInstitutionalTerms,
70
+ } = require('../piecemaker-plugin/scripts/lib/institutional-terms.cjs');
71
+ const {
72
+ claudeAssetOf,
73
+ claudeAssetStatus,
74
+ registerClaudeAsset,
75
+ repositoryAssets,
76
+ syncClaudeAssets,
77
+ unregisterClaudeAsset,
78
+ } = require('./claude-assets.cjs');
79
+ const { claudeHooksStatus } = require('./claude-hooks.cjs');
80
+ const {
81
+ controlDossierBot,
82
+ controlTelegram,
83
+ getTelegramState,
84
+ saveDossierBot,
85
+ saveTelegramConfig,
86
+ } = require('./telegram-admin.cjs');
87
+ const {
88
+ DEFAULT_CASE_FOLDER_STRUCTURE,
89
+ configuredCaseFolderStructure,
90
+ ensureCaseFolderStructure,
91
+ normalizeCaseFolderStructure,
92
+ } = require('./case-folder-structure.cjs');
93
+
94
+ const MAX_MARKDOWN_BYTES = 1024 * 1024;
95
+ const SECRET_KEYS = new Set([
96
+ 'LEGIFRANCE_CLIENT_ID',
97
+ 'LEGIFRANCE_CLIENT_SECRET',
98
+ ]);
99
+ const ENV_KEYS = new Set([
100
+ COMMIT_USER_NAME_KEY,
101
+ 'LEGIFRANCE_CLIENT_ID',
102
+ 'LEGIFRANCE_CLIENT_SECRET',
103
+ 'LEGIFRANCE_ENV',
104
+ 'PYTHON_PATH',
105
+ 'SMART_CONVERTER_PATH',
106
+ ]);
107
+
108
+ function validateNewCaseName(value) {
109
+ const name = String(value || '').trim();
110
+ if (!name || name.length > 120 || name === '.' || name === '..' || path.basename(name) !== name
111
+ || name.startsWith('.') || /[\x00-\x1f\x7f]/.test(name) || isTechnicalCaseDirectoryName(name)) {
112
+ throw new Error('Nom de dossier juridique invalide.');
113
+ }
114
+ return name;
115
+ }
116
+
117
+ async function createLegalCase({ casesRoot, homeDir, name, config = {} }) {
118
+ const root = resolveCasesRoot(casesRoot);
119
+ const safeName = validateNewCaseName(name);
120
+ const directory = path.join(root, safeName);
121
+ if (fs.existsSync(directory)) throw new Error(`Le dossier juridique « ${safeName} » existe déjà.`);
122
+ fs.mkdirSync(directory);
123
+ try {
124
+ const structure = ensureCaseFolderStructure(directory, config);
125
+ writeProtection(directory, { unprotected: [] });
126
+ const mapping = writeCaseMapping(directory, { mapping: {}, reverse_mapping: {} });
127
+ await createCommit({
128
+ casesRoot: root,
129
+ caseName: safeName,
130
+ homeDir,
131
+ label: 'Création du dossier juridique',
132
+ event: 'admin-case-create',
133
+ // Le mapping vit dans le sous-dossier des fichiers produits : le commit doit
134
+ // viser son chemin relatif au dossier, pas seulement son nom de base.
135
+ paths: [path.relative(directory, mapping.file).split(path.sep).join('/')],
136
+ waitForLockMs: 10_000,
137
+ });
138
+ const folder = await caseOverview(root, homeDir, safeName);
139
+ folder.branches = await historyBranches(root, homeDir, safeName);
140
+ folder.structure = structure;
141
+ return folder;
142
+ } catch (error) {
143
+ fs.rmSync(directory, { recursive: true, force: true });
144
+ throw error;
145
+ }
146
+ }
147
+
148
+ /** Native folder-picker commands. Arguments stay separate from the shell. */
149
+ function folderPickerCommands(platform, initialFolder) {
150
+ const start = path.resolve(initialFolder || os.homedir());
151
+ if (platform === 'darwin') {
152
+ return [{
153
+ command: 'osascript',
154
+ args: [
155
+ '-e', 'on run argv',
156
+ '-e', 'set initialFolder to POSIX file (item 1 of argv)',
157
+ '-e', 'set selectedFolder to choose folder with prompt "Choisir un dossier juridique PieceMaker" default location initialFolder',
158
+ '-e', 'return POSIX path of selectedFolder',
159
+ '-e', 'end run',
160
+ start,
161
+ ],
162
+ }];
163
+ }
164
+ if (platform === 'win32') {
165
+ const script = [
166
+ 'Add-Type -AssemblyName System.Windows.Forms',
167
+ '$dialog = New-Object System.Windows.Forms.FolderBrowserDialog',
168
+ '$dialog.Description = "Choisir un dossier juridique PieceMaker"',
169
+ '$dialog.ShowNewFolderButton = $false',
170
+ 'if (Test-Path -LiteralPath $args[0]) { $dialog.SelectedPath = $args[0] }',
171
+ 'if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { [Console]::Write($dialog.SelectedPath) }',
172
+ ].join('; ');
173
+ return [{ command: 'powershell.exe', args: ['-NoProfile', '-STA', '-Command', script, start] }];
174
+ }
175
+ const withSlash = start.endsWith(path.sep) ? start : `${start}${path.sep}`;
176
+ return [
177
+ { command: 'zenity', args: ['--file-selection', '--directory', '--title=Choisir un dossier juridique PieceMaker', `--filename=${withSlash}`] },
178
+ { command: 'kdialog', args: ['--getexistingdirectory', start, '--title', 'Choisir un dossier juridique PieceMaker'] },
179
+ ];
180
+ }
181
+
182
+ function captureProcess(command, args, { cwd } = {}) {
183
+ return new Promise((resolve) => {
184
+ const child = spawn(command, args, { cwd, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] });
185
+ const stdout = [];
186
+ const stderr = [];
187
+ child.stdout.on('data', (chunk) => stdout.push(chunk));
188
+ child.stderr.on('data', (chunk) => stderr.push(chunk));
189
+ child.once('error', (error) => resolve({ code: null, stdout: '', stderr: '', error }));
190
+ child.once('close', (code) => resolve({
191
+ code,
192
+ stdout: Buffer.concat(stdout).toString('utf8').trim(),
193
+ stderr: Buffer.concat(stderr).toString('utf8').trim(),
194
+ error: null,
195
+ }));
196
+ });
197
+ }
198
+
199
+ async function selectLocalFolder(platform = process.platform, initialFolder = os.homedir()) {
200
+ let lastError = null;
201
+ for (const candidate of folderPickerCommands(platform, initialFolder)) {
202
+ const result = await captureProcess(candidate.command, candidate.args);
203
+ if (result.code === 0) return result.stdout ? validateSelectedCaseFolder(result.stdout) : null;
204
+ if (result.error?.code === 'ENOENT') {
205
+ lastError = result.error;
206
+ continue;
207
+ }
208
+ // Native dialogs use a non-zero status for an ordinary user cancellation.
209
+ if (!result.stdout && (result.code === 1 || /cancel|annul/i.test(result.stderr))) return null;
210
+ lastError = result.error || new Error(result.stderr || `Le sélecteur s’est arrêté avec le code ${result.code}.`);
211
+ }
212
+ throw new Error(`Aucun sélecteur de dossier n’est disponible sur ce poste (${lastError?.message || 'commande introuvable'}).`);
213
+ }
214
+
215
+ function installClaudeAssets(repoRoot, userHome, runCommand = captureCommand) {
216
+ if (!runCommand('claude', ['--version']).ok) {
217
+ return { installed: false, skipped: true, reason: 'Claude Code est introuvable.' };
218
+ }
219
+ const result = syncClaudeAssets(repoRoot, userHome);
220
+ return {
221
+ installed: result.conflicts.length === 0,
222
+ ...result,
223
+ };
224
+ }
225
+
226
+ async function registerLegalCase({
227
+ folder,
228
+ configFile,
229
+ repoRoot,
230
+ homeDir,
231
+ userHome = os.homedir(),
232
+ claudeAssetsInstaller = installClaudeAssets,
233
+ } = {}) {
234
+ const root = validateSelectedCaseFolder(folder);
235
+ const previous = readRegistryConfig(configFile);
236
+ const structure = ensureCaseFolderStructure(root, previous);
237
+ const claudeAssets = await claudeAssetsInstaller(repoRoot, userHome);
238
+ const protection = readProtection(root);
239
+ if (!protection.exists) writeProtection(root, { unprotected: [] });
240
+ const currentMapping = readCaseMapping(root);
241
+ const mapping = currentMapping.exists
242
+ ? currentMapping
243
+ : writeCaseMapping(root, { mapping: {}, reverse_mapping: {} });
244
+
245
+ const registered = registerCaseFolder(previous, root);
246
+ atomicWrite(configFile, `${JSON.stringify(registered.config, null, 2)}\n`);
247
+
248
+ const commit = await createCommit({
249
+ casesRoot: path.dirname(root),
250
+ caseName: path.basename(root),
251
+ homeDir,
252
+ label: 'Enregistrement du dossier juridique',
253
+ event: 'admin-case-register',
254
+ paths: [path.relative(root, mapping.file).split(path.sep).join('/')],
255
+ waitForLockMs: 10_000,
256
+ envFile: path.join(repoRoot, '.env'),
257
+ });
258
+ const folderOverview = await caseOverview(path.dirname(root), homeDir, path.basename(root));
259
+ folderOverview.path = registered.entry.id;
260
+ folderOverview.location = root;
261
+ folderOverview.registered = true;
262
+ folderOverview.branches = await historyBranches(path.dirname(root), homeDir, path.basename(root));
263
+ return {
264
+ folder: folderOverview,
265
+ installed: {
266
+ claudeAssets: Boolean(claudeAssets?.installed),
267
+ mapping: path.relative(root, mapping.file).split(path.sep).join('/'),
268
+ protection: path.relative(root, protection.file).split(path.sep).join('/'),
269
+ structure: structure.directories,
270
+ commit: commit.commit || null,
271
+ },
272
+ };
273
+ }
274
+
275
+ function defaultConfig(repoRoot, homeDir = path.join(os.homedir(), '.piecemaker')) {
276
+ return {
277
+ port: 43098,
278
+ pythonPath: null,
279
+ venvPath: path.join(homeDir, 'venv'),
280
+ adminTheme: 'light',
281
+ caseFolderStructure: { ...DEFAULT_CASE_FOLDER_STRUCTURE },
282
+ };
283
+ }
284
+
285
+ function validateAdminTheme(value) {
286
+ const theme = String(value || '');
287
+ if (theme !== 'light' && theme !== 'dark') throw new Error('Le thème doit être « light » ou « dark ».');
288
+ return theme;
289
+ }
290
+
291
+ function readJson(file, fallback) {
292
+ try {
293
+ return fs.existsSync(file) ? JSON.parse(fs.readFileSync(file, 'utf8')) : fallback;
294
+ } catch {
295
+ return fallback;
296
+ }
297
+ }
298
+
299
+ function atomicWrite(file, content, mode = undefined) {
300
+ fs.mkdirSync(path.dirname(file), { recursive: true });
301
+ const temp = path.join(path.dirname(file), `.${path.basename(file)}.${process.pid}.${Date.now()}.tmp`);
302
+ fs.writeFileSync(temp, content, { encoding: 'utf8', ...(mode ? { mode } : {}) });
303
+ fs.renameSync(temp, file);
304
+ if (mode) {
305
+ try {
306
+ fs.chmodSync(file, mode);
307
+ } catch {
308
+ // Windows does not implement POSIX file modes.
309
+ }
310
+ }
311
+ }
312
+
313
+ function readEnvFile(file) {
314
+ const values = {};
315
+ if (!fs.existsSync(file)) return values;
316
+ for (const line of fs.readFileSync(file, 'utf8').split(/\r?\n/)) {
317
+ const match = line.match(/^\s*([A-Z0-9_]+)\s*=(.*)$/);
318
+ if (match) values[match[1]] = match[2];
319
+ }
320
+ return values;
321
+ }
322
+
323
+ function updateEnvFile(file, updates, clearKeys = []) {
324
+ const clear = new Set(clearKeys.filter((key) => SECRET_KEYS.has(key)));
325
+ const cleanUpdates = new Map();
326
+ for (const [key, rawValue] of Object.entries(updates || {})) {
327
+ if (!ENV_KEYS.has(key)) continue;
328
+ const value = String(rawValue ?? '').trim();
329
+ if (!value || SECRET_KEYS.has(key) && value === '********') continue;
330
+ if (/\r|\n/.test(value)) throw new Error(`Valeur invalide pour ${key}`);
331
+ cleanUpdates.set(key, value);
332
+ }
333
+
334
+ if (cleanUpdates.has(COMMIT_USER_NAME_KEY)) {
335
+ resolveCommitIdentity({ identity: { name: cleanUpdates.get(COMMIT_USER_NAME_KEY) } });
336
+ }
337
+
338
+ const original = fs.existsSync(file) ? fs.readFileSync(file, 'utf8').split(/\r?\n/) : [];
339
+ const seen = new Set();
340
+ const lines = [];
341
+ for (const line of original) {
342
+ const match = line.match(/^\s*([A-Z0-9_]+)\s*=/);
343
+ if (!match) {
344
+ if (line || lines.length) lines.push(line);
345
+ continue;
346
+ }
347
+ const key = match[1];
348
+ if (clear.has(key)) continue;
349
+ if (cleanUpdates.has(key)) {
350
+ lines.push(`${key}=${cleanUpdates.get(key)}`);
351
+ cleanUpdates.delete(key);
352
+ seen.add(key);
353
+ } else {
354
+ lines.push(line);
355
+ seen.add(key);
356
+ }
357
+ }
358
+ for (const [key, value] of cleanUpdates) {
359
+ if (!seen.has(key)) lines.push(`${key}=${value}`);
360
+ }
361
+ while (lines.at(-1) === '') lines.pop();
362
+ atomicWrite(file, `${lines.join('\n')}\n`, 0o600);
363
+ }
364
+
365
+ function maskSecret(value) {
366
+ if (!value) return { configured: false, hint: '' };
367
+ const suffix = value.length > 4 ? value.slice(-4) : '';
368
+ return { configured: true, hint: suffix ? `••••${suffix}` : 'configurée' };
369
+ }
370
+
371
+ function captureCommand(command, args = [], timeout = 3000) {
372
+ const executable = process.platform === 'win32' && !command.endsWith('.exe')
373
+ ? `${command}.cmd`
374
+ : command;
375
+ const result = spawnSync(executable, args, {
376
+ encoding: 'utf8',
377
+ timeout,
378
+ windowsHide: true,
379
+ // Windows cannot execute a .cmd shim directly. The command and arguments
380
+ // are constants owned by PieceMaker, never values supplied by the browser.
381
+ shell: process.platform === 'win32',
382
+ });
383
+ return {
384
+ ok: result.status === 0 && !result.error,
385
+ output: String(result.stdout || result.stderr || '').trim(),
386
+ };
387
+ }
388
+
389
+ // Une ancienne installation PieceMaker par marketplace peut encore être
390
+ // présente sur un poste mis à jour. On la masque des catalogues Anthropic
391
+ // pour ne pas dupliquer les composants désormais enregistrés directement.
392
+ const LEGACY_PIECEMAKER_MARKETPLACE_NAME = 'piecemaker';
393
+
394
+ function parseJsonOutput(output) {
395
+ try {
396
+ return JSON.parse(output);
397
+ } catch {
398
+ return null;
399
+ }
400
+ }
401
+
402
+ /**
403
+ * Enregistre directement les composants PieceMaker dans ~/.claude. Le nom de
404
+ * cette fonction est conservé pour la compatibilité de l'API d'administration,
405
+ * mais aucune commande `claude plugin` ni aucun manifest n'est utilisé.
406
+ */
407
+ async function ensureClaudePluginActive({
408
+ repoRoot,
409
+ userHome = os.homedir(),
410
+ runCommand = captureCommand,
411
+ syncAssets = syncClaudeAssets,
412
+ } = {}) {
413
+ if (!runCommand('claude', ['--version']).ok) {
414
+ return {
415
+ ok: false,
416
+ action: 'skipped',
417
+ installed: false,
418
+ reason: 'Claude Code est introuvable.',
419
+ };
420
+ }
421
+ const result = syncAssets(repoRoot, userHome);
422
+ return {
423
+ ok: result.conflicts.length === 0,
424
+ action: 'registered',
425
+ installed: true,
426
+ ...result,
427
+ };
428
+ }
429
+
430
+ // -----------------------------------------------------------------------
431
+ // Marketplace officiel Claude Code — onglet « Marketplace officiel » du
432
+ // même pop-up. Constat d'investigation (voir docs/plugin-marketplace, sinon
433
+ // le rapport de cette fonctionnalité) : la CLI `claude` n'expose AUCUNE
434
+ // recherche distante — `claude plugin --help` n'a pas de sous-commande
435
+ // `search`, et ni `plugin list` ni `plugin marketplace list` n'acceptent de
436
+ // requête. Le seul catalogue énumérable programmatiquement est
437
+ // `claude plugin list --available --json`, qui renvoie les plugins NON
438
+ // installés des marketplaces déjà enregistrées localement (nom,
439
+ // description, marketplace, popularité `installCount`) à côté de la liste
440
+ // des plugins installés (`claude plugin list --json`, avec leur état
441
+ // enabled/disabled). Rien n'est donc inventé ici : la recherche du pop-up
442
+ // filtre côté client cette même liste (voir admin/app.js). Le seul
443
+ // marketplace enregistrable en un clic depuis ce pop-up est le marketplace
444
+ // premier-parti d'Anthropic (`claude-plugins-official`, dépôt
445
+ // anthropics/claude-plugins-official). Tout autre marketplace se déclare via
446
+ // `claude plugin marketplace add` en dehors de ce pop-up.
447
+ // -----------------------------------------------------------------------
448
+ const OFFICIAL_MARKETPLACE_NAME = 'claude-plugins-official';
449
+ const OFFICIAL_MARKETPLACE_SLUG = 'anthropics/claude-plugins-official';
450
+ // Marketplace « Claude for Legal » d'Anthropic (dépôt anthropics/claude-for-legal),
451
+ // suite de plugins juridiques par domaine (commercial-legal, litigation-legal,
452
+ // ip-legal…). C'est le catalogue de l'onglet « Plugin legal Claude » du pop-up —
453
+ // distinct du marketplace généraliste claude-plugins-official (onglet
454
+ // « Marketplace officiel »). Même mécanique CLI que l'officiel : add / list /
455
+ // install / enable / disable, scoping par nom de marketplace.
456
+ const LEGAL_MARKETPLACE_NAME = 'claude-for-legal';
457
+ const LEGAL_MARKETPLACE_SLUG = 'anthropics/claude-for-legal';
458
+
459
+ // Les deux catalogues énumérables depuis le pop-up, indexés par le `scope`
460
+ // que le frontend passe (onglet legal vs officiel). Tout scope inconnu retombe
461
+ // sur l'officiel — le comportement historique.
462
+ const MARKETPLACE_SCOPES = {
463
+ legal: { name: LEGAL_MARKETPLACE_NAME, slug: LEGAL_MARKETPLACE_SLUG },
464
+ official: { name: OFFICIAL_MARKETPLACE_NAME, slug: OFFICIAL_MARKETPLACE_SLUG },
465
+ };
466
+ function marketplaceForScope(scope) {
467
+ return MARKETPLACE_SCOPES[scope] || MARKETPLACE_SCOPES.official;
468
+ }
469
+
470
+ /** Marketplaces Claude Code enregistrées sur ce poste, hors la nôtre. */
471
+ function listRegisteredMarketplaces(runCommand = captureCommand) {
472
+ const result = runCommand('claude', ['plugin', 'marketplace', 'list', '--json']);
473
+ if (!result.ok) return [];
474
+ const parsed = parseJsonOutput(result.output);
475
+ if (!Array.isArray(parsed)) return [];
476
+ return parsed
477
+ .filter((entry) => entry?.name && entry.name !== LEGACY_PIECEMAKER_MARKETPLACE_NAME)
478
+ .map((entry) => ({
479
+ name: entry.name,
480
+ repo: entry.repo || null,
481
+ source: entry.source || null,
482
+ // Heuristique locale, pas un champ renvoyé par la CLI : seul le
483
+ // marketplace historique publié par Anthropic sous ce dépôt exact est
484
+ // marqué « officiel » — voir le commentaire ci-dessus.
485
+ official: entry.source === 'github' && entry.repo === OFFICIAL_MARKETPLACE_SLUG,
486
+ }));
487
+ }
488
+
489
+ // Le catalogue officiel dépasse aujourd'hui ~300 plugins et `claude plugin
490
+ // list --available --json` rafraîchit les marketplaces par le réseau : l'appel
491
+ // prend couramment 10–16 s. On lui laisse donc un délai large (il ne doit
492
+ // jamais être tué en cours de route, sinon les onglets se vident) et on met le
493
+ // résultat en cache un court instant : ré-ouvrir un onglet, basculer legal ↔
494
+ // officiel ou paginer ne relance pas la commande lente. Le cache n'est actif
495
+ // que pour le vrai `captureCommand` — les tests injectent leur runCommand et
496
+ // doivent voir chaque appel.
497
+ const AVAILABLE_TTL_MS = 5 * 60 * 1000;
498
+ const AVAILABLE_TIMEOUT_MS = 60000;
499
+ let availableCache = null; // { at: number, data }
500
+
501
+ function invalidateMarketplaceCache() {
502
+ availableCache = null;
503
+ }
504
+
505
+ function fetchAvailablePlugins(runCommand = captureCommand) {
506
+ const useCache = runCommand === captureCommand;
507
+ if (useCache && availableCache && Date.now() - availableCache.at < AVAILABLE_TTL_MS) {
508
+ return availableCache.data;
509
+ }
510
+ const result = runCommand('claude', ['plugin', 'list', '--available', '--json'], AVAILABLE_TIMEOUT_MS);
511
+ if (!result.ok) {
512
+ return { ok: false, reason: result.output || 'Échec de l’appel « claude plugin list --available --json ».' };
513
+ }
514
+ const parsed = parseJsonOutput(result.output) || {};
515
+ const data = {
516
+ ok: true,
517
+ installed: Array.isArray(parsed.installed) ? parsed.installed : [],
518
+ available: Array.isArray(parsed.available) ? parsed.available : [],
519
+ };
520
+ if (useCache) availableCache = { at: Date.now(), data };
521
+ return data;
522
+ }
523
+
524
+ /**
525
+ * Plugins-connecteurs des marketplaces déjà enregistrées (hors piecemaker),
526
+ * fusion de `plugin list --json` (installés, avec enabled/scope) et
527
+ * `plugin list --available --json` (catalogue non installé, avec
528
+ * description/popularité) — seule source de données énumérable, voir le
529
+ * commentaire au-dessus de OFFICIAL_MARKETPLACE_NAME. `runCommand`
530
+ * injectable pour les tests.
531
+ */
532
+ function listMarketplaceConnectors(runCommand = captureCommand, options = {}) {
533
+ // `marketplaceName` restreint le catalogue à un seul marketplace (onglet
534
+ // legal ↔ claude-for-legal, onglet officiel ↔ claude-plugins-official) ;
535
+ // absent, on garde le comportement historique (tous les marketplaces hors
536
+ // piecemaker). Un plugin d'un autre marketplace n'apparaît alors jamais dans
537
+ // l'onglet, et `applyMarketplaceSelection` n'y touche pas.
538
+ const scopeName = options.marketplaceName || null;
539
+ const inScope = (marketplace) => !scopeName || marketplace === scopeName;
540
+ const marketplaces = listRegisteredMarketplaces(runCommand);
541
+ // `registered` : le marketplace de l'onglet est-il déjà déclaré sur ce poste ?
542
+ // (scopé sur son nom, sinon sur l'officiel — comme avant.)
543
+ const registered = marketplaces.some((entry) => entry.name === (scopeName || OFFICIAL_MARKETPLACE_NAME));
544
+ const fetched = fetchAvailablePlugins(runCommand);
545
+ if (!fetched.ok) {
546
+ return {
547
+ marketplaces,
548
+ officialRegistered: registered,
549
+ registered,
550
+ plugins: [],
551
+ reason: fetched.reason,
552
+ };
553
+ }
554
+ const installed = fetched.installed;
555
+ const available = fetched.available;
556
+
557
+ const plugins = [];
558
+ const seen = new Set();
559
+ for (const entry of available) {
560
+ if (!entry?.pluginId || entry.marketplaceName === LEGACY_PIECEMAKER_MARKETPLACE_NAME || seen.has(entry.pluginId)) continue;
561
+ if (!inScope(entry.marketplaceName)) continue;
562
+ seen.add(entry.pluginId);
563
+ plugins.push({
564
+ id: entry.pluginId,
565
+ name: entry.displayName || entry.name || entry.pluginId,
566
+ description: entry.description || '',
567
+ marketplace: entry.marketplaceName || '',
568
+ installCount: Number.isFinite(entry.installCount) ? entry.installCount : null,
569
+ installed: false,
570
+ enabled: false,
571
+ });
572
+ }
573
+ // `plugin list --json` ne fournit pas de description — seul le catalogue
574
+ // « available » en a une. Un plugin déjà installé apparaît donc ici avec
575
+ // une description vide (limite de la CLI, pas une omission de notre part).
576
+ for (const entry of installed) {
577
+ if (!entry?.id || entry.id.endsWith(`@${LEGACY_PIECEMAKER_MARKETPLACE_NAME}`) || seen.has(entry.id)) continue;
578
+ const [name, marketplace] = entry.id.split('@');
579
+ if (!inScope(marketplace)) continue;
580
+ seen.add(entry.id);
581
+ plugins.push({
582
+ id: entry.id,
583
+ name,
584
+ description: '',
585
+ marketplace: marketplace || '',
586
+ installCount: null,
587
+ installed: true,
588
+ enabled: Boolean(entry.enabled),
589
+ });
590
+ }
591
+
592
+ return { marketplaces, officialRegistered: registered, registered, plugins };
593
+ }
594
+
595
+ /**
596
+ * Enregistre (ou rafraîchit s'il l'est déjà) un marketplace Anthropic —
597
+ * bouton « Découvrir le marketplace… » des onglets du pop-up. `marketplace`
598
+ * ({ name, slug }) désigne le catalogue voulu : par défaut le marketplace
599
+ * officiel généraliste, ou celui du scope (legal → claude-for-legal), pour un
600
+ * poste qui n'a encore que le marketplace piecemaker.
601
+ */
602
+ function registerOfficialMarketplace(runCommand = captureCommand, marketplace = MARKETPLACE_SCOPES.official) {
603
+ const alreadyRegistered = listRegisteredMarketplaces(runCommand)
604
+ .some((entry) => entry.name === marketplace.name);
605
+ const action = alreadyRegistered
606
+ ? runCommand('claude', ['plugin', 'marketplace', 'update', marketplace.name], 8000)
607
+ : runCommand('claude', ['plugin', 'marketplace', 'add', marketplace.slug], 15000);
608
+ if (!action.ok) {
609
+ return { ok: false, alreadyRegistered, reason: action.output || `Échec de l’enregistrement du marketplace « ${marketplace.name} ».` };
610
+ }
611
+ // Un nouveau marketplace change le catalogue « available » : purger le cache.
612
+ invalidateMarketplaceCache();
613
+ return { ok: true, alreadyRegistered };
614
+ }
615
+
616
+ /**
617
+ * Applique une sélection de connecteurs marketplace « voulus actifs » —
618
+ * même sémantique que `applyPluginComponentSelection` (coché = actif),
619
+ * étendue à trois actions CLI selon l'état courant : `install` (jamais
620
+ * installé), `enable` (installé mais désactivé), `disable` (actif et
621
+ * décoché). Jamais `uninstall` : décocher désactive, ne supprime rien —
622
+ * choix délibéré, réversible, cohérent avec le reste de PieceMaker qui ne
623
+ * retire jamais un composant tiers de façon destructive. Idempotent : un
624
+ * connecteur déjà dans l'état voulu n'appelle aucune commande. Chaque appel
625
+ * est best-effort — un échec individuel n'empêche pas les suivants.
626
+ */
627
+ function applyMarketplaceSelection(selection, runCommand = captureCommand, options = {}) {
628
+ const wanted = new Set((Array.isArray(selection) ? selection : []).map(String));
629
+ // Scopé au marketplace de l'onglet : décocher un plugin ne peut jamais
630
+ // désactiver un connecteur d'un autre marketplace (l'univers considéré est
631
+ // exactement celui listé dans l'onglet).
632
+ const { plugins } = listMarketplaceConnectors(runCommand, options);
633
+ const results = plugins.map((plugin) => {
634
+ const shouldBeActive = wanted.has(plugin.id);
635
+ const isActive = plugin.installed && plugin.enabled;
636
+ if (shouldBeActive === isActive) return { id: plugin.id, action: 'none', ok: true };
637
+ if (shouldBeActive) {
638
+ const command = plugin.installed
639
+ ? runCommand('claude', ['plugin', 'enable', plugin.id], 5000)
640
+ : runCommand('claude', ['plugin', 'install', plugin.id, '-y'], 20000);
641
+ return {
642
+ id: plugin.id,
643
+ action: plugin.installed ? 'enable' : 'install',
644
+ ok: command.ok,
645
+ reason: command.ok ? '' : command.output || 'Échec de la commande claude.',
646
+ };
647
+ }
648
+ const command = runCommand('claude', ['plugin', 'disable', plugin.id], 5000);
649
+ return { id: plugin.id, action: 'disable', ok: command.ok, reason: command.ok ? '' : command.output || 'Échec de la commande claude.' };
650
+ });
651
+ // L'état installé/activé a changé : le prochain chargement doit repartir de zéro.
652
+ if (results.some((entry) => entry.action !== 'none')) invalidateMarketplaceCache();
653
+ return {
654
+ results,
655
+ installed: results.filter((entry) => entry.action === 'install' && entry.ok).length,
656
+ enabled: results.filter((entry) => entry.action === 'enable' && entry.ok).length,
657
+ disabled: results.filter((entry) => entry.action === 'disable' && entry.ok).length,
658
+ failed: results.filter((entry) => !entry.ok),
659
+ };
660
+ }
661
+
662
+ async function fetchWithTimeout(fetchImpl, url, options = {}, timeout = 3000) {
663
+ const controller = new AbortController();
664
+ const timer = setTimeout(() => controller.abort(), timeout);
665
+ try {
666
+ return await fetchImpl(url, { ...options, signal: controller.signal });
667
+ } finally {
668
+ clearTimeout(timer);
669
+ }
670
+ }
671
+
672
+ async function ollamaState(fetchImpl = global.fetch) {
673
+ if (typeof fetchImpl !== 'function') return { installed: false, version: '', models: [] };
674
+ try {
675
+ const [versionResponse, tagsResponse] = await Promise.all([
676
+ fetchWithTimeout(fetchImpl, 'http://127.0.0.1:11434/api/version', {}, 1800).catch(() => null),
677
+ fetchWithTimeout(fetchImpl, 'http://127.0.0.1:11434/api/tags', {}, 2500),
678
+ ]);
679
+ if (!tagsResponse?.ok) return { installed: false, version: '', models: [] };
680
+ const data = await tagsResponse.json();
681
+ const versionData = versionResponse?.ok ? await versionResponse.json().catch(() => ({})) : {};
682
+ const models = Array.isArray(data.models) ? data.models.map((model) => ({
683
+ name: String(model.name || model.model || ''),
684
+ digest: String(model.digest || ''),
685
+ size: Number(model.size) || 0,
686
+ modifiedAt: model.modified_at || '',
687
+ family: model.details?.family || '',
688
+ parameterSize: model.details?.parameter_size || '',
689
+ quantization: model.details?.quantization_level || '',
690
+ })).filter((model) => model.name) : [];
691
+ return { installed: true, version: String(versionData.version || ''), models };
692
+ } catch {
693
+ return { installed: false, version: '', models: [] };
694
+ }
695
+ }
696
+
697
+ function ollamaRegistryReference(modelName) {
698
+ const value = String(modelName || '').trim();
699
+ if (!value || value.length > 240 || !/^[A-Za-z0-9._/-]+(?::[A-Za-z0-9._-]+)?$/.test(value)) {
700
+ throw new Error('Nom de modèle Ollama invalide.');
701
+ }
702
+ const lastSlash = value.lastIndexOf('/');
703
+ const tagSeparator = value.indexOf(':', lastSlash + 1);
704
+ const repositoryName = tagSeparator >= 0 ? value.slice(0, tagSeparator) : value;
705
+ const tag = tagSeparator >= 0 ? value.slice(tagSeparator + 1) : 'latest';
706
+ // Names beginning with a registry host (notably hf.co/...) are imports, not
707
+ // models published in Ollama's registry, so their update cannot be inferred.
708
+ if (/^[^/]+\.[^/]+\//.test(repositoryName)) return null;
709
+ const repository = repositoryName.includes('/') ? repositoryName : `library/${repositoryName}`;
710
+ return { repository, tag };
711
+ }
712
+
713
+ async function checkOllamaModelUpdate(modelName, fetchImpl = global.fetch) {
714
+ const reference = ollamaRegistryReference(modelName);
715
+ if (!reference) {
716
+ return { status: 'unknown', message: 'Ce modèle provient d’un registre externe ; vérification automatique indisponible.' };
717
+ }
718
+ const local = await ollamaState(fetchImpl);
719
+ const model = local.models.find((entry) => entry.name === modelName);
720
+ if (!model) throw new Error('Ce modèle local n’est plus disponible dans Ollama.');
721
+ const repositoryPath = reference.repository.split('/').map(encodeURIComponent).join('/');
722
+ const url = `https://registry.ollama.ai/v2/${repositoryPath}/manifests/${encodeURIComponent(reference.tag)}`;
723
+ let response;
724
+ try {
725
+ response = await fetchWithTimeout(fetchImpl, url, {
726
+ headers: { Accept: 'application/vnd.docker.distribution.manifest.v2+json' },
727
+ }, 8000);
728
+ } catch (error) {
729
+ return { status: 'unknown', message: `Registre Ollama injoignable (${error.message}).` };
730
+ }
731
+ if (response.status === 404) {
732
+ return { status: 'unknown', message: 'Modèle local ou privé : aucune version publique correspondante.' };
733
+ }
734
+ if (!response.ok) {
735
+ return { status: 'unknown', message: `Vérification impossible (registre HTTP ${response.status}).` };
736
+ }
737
+ const manifest = Buffer.from(await response.arrayBuffer());
738
+ const remoteDigest = crypto.createHash('sha256').update(manifest).digest('hex');
739
+ const updateAvailable = Boolean(model.digest) && remoteDigest !== model.digest.replace(/^sha256:/, '');
740
+ return updateAvailable
741
+ ? { status: 'update', message: 'Une mise à jour est disponible dans le registre Ollama.' }
742
+ : { status: 'current', message: 'Le modèle local est à jour.' };
743
+ }
744
+
745
+ async function configurationOverview({ repoRoot, homeDir, userHome, getRuntimeStatus, fetchImpl }) {
746
+ const env = readEnvFile(path.join(repoRoot, '.env'));
747
+ const installer = readJson(path.join(homeDir, 'state.json'), { steps: {} });
748
+ const steps = installer.steps || {};
749
+ const runtime = getRuntimeStatus();
750
+ const claude = captureCommand('claude', ['--version']);
751
+ const codex = captureCommand('codex', ['--version']);
752
+ const clients = [
753
+ claude.ok && { name: 'Claude Code', version: claude.output.replace(/\s*\(Claude Code\)\s*$/i, '') },
754
+ codex.ok && { name: 'Codex', version: codex.output.replace(/^codex(?:-cli)?\s*/i, '') },
755
+ ].filter(Boolean);
756
+ const hookFiles = [
757
+ 'protect-originals.mjs',
758
+ 'commit-track.mjs',
759
+ 'billing-track.mjs',
760
+ ].map((name) => path.join(repoRoot, 'piecemaker-plugin', 'scripts', name));
761
+ const hooksReady = hookFiles.every((file) => fs.existsSync(file))
762
+ && fs.existsSync(path.join(repoRoot, 'piecemaker-plugin', 'hooks', 'hooks.json'));
763
+ const claudeAssets = repositoryAssets(repoRoot);
764
+ const claudeAssetsReady = claudeAssets.length > 0 && claudeAssets.every((asset) =>
765
+ ['linked', 'copied'].includes(claudeAssetStatus(repoRoot, userHome, asset)?.state));
766
+ const hooksRegistered = claudeHooksStatus(repoRoot, userHome).ok;
767
+ const claudeSettings = readJson(path.join(userHome, '.claude', 'settings.json'), {});
768
+ const legifrancePluginEnabled = claudeSettings.enabledPlugins?.['piecemaker@mcp-legifrance'] === true;
769
+ let nodePtyReady = false;
770
+ try { nodePtyReady = Boolean(require.resolve('node-pty')); } catch { /* dépendance optionnelle */ }
771
+
772
+ const mcpItems = [
773
+ {
774
+ name: 'Légifrance',
775
+ installed: legifrancePluginEnabled,
776
+ configured: Boolean(env.LEGIFRANCE_CLIENT_ID && env.LEGIFRANCE_CLIENT_SECRET),
777
+ detail: 'Plugin autonome PieceMaker-Legal/mcp-legifrance via les API officielles PISTE.',
778
+ },
779
+ ];
780
+ const models = await ollamaState(fetchImpl);
781
+ const telegram = getTelegramState({ repoRoot, homeDir, userHome });
782
+
783
+ // Moteurs Python locaux du venv — détection par présence de paquet, sans import
784
+ // (importer gliner2/mineru chargerait des centaines de Mo à chaque appel).
785
+ const config = readJson(path.join(homeDir, 'config.json'), {});
786
+ const venvDir = config.venvPath || path.join(homeDir, 'venv');
787
+ const sitePackages = (() => {
788
+ if (process.platform === 'win32') return path.join(venvDir, 'Lib', 'site-packages');
789
+ const libDir = path.join(venvDir, 'lib');
790
+ try {
791
+ const py = fs.readdirSync(libDir).find((name) => name.startsWith('python'));
792
+ return py ? path.join(libDir, py, 'site-packages') : '';
793
+ } catch { return ''; }
794
+ })();
795
+ const hasPackage = (name) => Boolean(sitePackages) && fs.existsSync(path.join(sitePackages, name));
796
+ const glinerDir = path.join(repoRoot, 'websocket-server', 'scripts', 'presidio-gliner');
797
+ const glinerModelId = 'fastino/gliner2.5-multi-v1';
798
+ const huggingFaceHubCache = process.env.HF_HUB_CACHE
799
+ || path.join(process.env.HF_HOME || path.join(userHome, '.cache', 'huggingface'), 'hub');
800
+ const glinerModelCache = path.join(
801
+ huggingFaceHubCache,
802
+ `models--${glinerModelId.replace('/', '--')}`,
803
+ 'snapshots',
804
+ );
805
+ const glinerModelReady = (() => {
806
+ try {
807
+ return fs.readdirSync(glinerModelCache)
808
+ .some((revision) => ['config.json', 'model.safetensors']
809
+ .every((name) => fs.existsSync(path.join(glinerModelCache, revision, name))));
810
+ } catch { return false; }
811
+ })();
812
+ const glinerReady = fs.existsSync(path.join(glinerDir, 'presidio-gliner.py'))
813
+ && hasPackage('gliner2')
814
+ && glinerModelReady;
815
+ const coremlDefault = path.join(
816
+ glinerDir, 'models', 'gliner2.5-multi-v1-encoder_b1_832.mlmodelc',
817
+ );
818
+ const coreml = [process.env.PIECEMAKER_COREML_MODEL, coremlDefault]
819
+ .filter(Boolean)
820
+ .some((candidate) => fs.existsSync(candidate));
821
+ const mineruReady = hasPackage('mineru') || hasPackage('magic_pdf')
822
+ || fs.existsSync(path.join(venvDir, 'bin', 'mineru'));
823
+ const folders = listConfiguredCases(readRegistryConfig(path.join(homeDir, 'config.json'))).map((entry) => {
824
+ const bot = telegram.dossiers.find((candidate) => {
825
+ try { return path.resolve(candidate.workdir) === path.resolve(entry.root); } catch { return false; }
826
+ });
827
+ return {
828
+ id: entry.id,
829
+ name: entry.name,
830
+ path: entry.root,
831
+ bot: bot ? {
832
+ id: bot.id,
833
+ name: bot.name,
834
+ configured: Boolean(bot.linked),
835
+ running: Boolean(bot.running),
836
+ } : null,
837
+ };
838
+ });
839
+
840
+ return {
841
+ components: {
842
+ client: {
843
+ name: clients.map((client) => client.name).join(' + ') || 'Client IA',
844
+ installed: clients.length > 0,
845
+ summary: clients.length
846
+ ? clients.map((client) => `${client.name}${client.version ? ` ${client.version}` : ''}`).join(' · ')
847
+ : 'Aucun client en ligne de commande détecté',
848
+ clients,
849
+ pluginInstalled: claude.ok && claudeAssetsReady,
850
+ pluginVersion: '',
851
+ },
852
+ terminal: {
853
+ name: 'Terminal intégré',
854
+ installed: runtime.terminalReady ?? nodePtyReady,
855
+ summary: runtime.terminalReady ?? nodePtyReady
856
+ ? `PTY local · ${os.userInfo().shell || process.env.COMSPEC || 'shell système'}`
857
+ : 'Le pont PTY optionnel est absent',
858
+ shell: os.userInfo().shell || process.env.COMSPEC || '',
859
+ },
860
+ hooks: {
861
+ name: 'Hooks PieceMaker',
862
+ installed: hooksReady && hooksRegistered,
863
+ summary: `${hookFiles.filter((file) => fs.existsSync(file)).length}/${hookFiles.length} garde-fous disponibles`,
864
+ count: hookFiles.filter((file) => fs.existsSync(file)).length,
865
+ installerStatus: steps['06-hooks']?.status || '',
866
+ },
867
+ mcp: {
868
+ name: 'MCP locaux',
869
+ installed: mcpItems.every((item) => item.installed),
870
+ configured: mcpItems.every((item) => item.installed && item.configured),
871
+ summary: `${mcpItems.filter((item) => item.installed).length}/${mcpItems.length} serveurs présents`,
872
+ items: mcpItems,
873
+ },
874
+ gliner: {
875
+ name: 'GLiNER2.5 · PII',
876
+ installed: glinerReady,
877
+ summary: glinerReady
878
+ ? coreml ? 'Détection locale · GPU CoreML' : 'Détection locale · CPU'
879
+ : 'Modèle d’anonymisation absent',
880
+ coreml,
881
+ model: glinerModelId,
882
+ engine: coreml ? 'CoreML (GPU)' : 'torch (CPU)',
883
+ },
884
+ mineru: {
885
+ name: 'MinerU · OCR',
886
+ installed: mineruReady,
887
+ optional: true,
888
+ summary: mineruReady
889
+ ? 'OCR local · PDF scannés et images'
890
+ : 'Optionnel · non installé',
891
+ },
892
+ telegram: {
893
+ name: 'Telegram',
894
+ installed: true,
895
+ optional: true,
896
+ configured: Boolean(telegram.assistant.token.configured || telegram.monitor.token.configured),
897
+ summary: (() => {
898
+ const running = [telegram.assistant.running && 'Assistant', telegram.monitor.running && 'Surveillance'].filter(Boolean);
899
+ if (running.length) return `${running.join(' + ')} en ligne`;
900
+ if (telegram.assistant.token.configured || telegram.monitor.token.configured) return 'Configuré · arrêté';
901
+ return 'Aucun bot configuré';
902
+ })(),
903
+ bots: [
904
+ { name: telegram.assistant.name, role: 'Assistant', configured: Boolean(telegram.assistant.token.configured), running: Boolean(telegram.assistant.running) },
905
+ { name: telegram.monitor.name, role: 'Surveillance', configured: Boolean(telegram.monitor.token.configured), running: Boolean(telegram.monitor.running) },
906
+ ],
907
+ },
908
+ },
909
+ models: {
910
+ provider: 'Ollama',
911
+ installed: models.installed,
912
+ version: models.version,
913
+ items: models.models,
914
+ },
915
+ folders,
916
+ };
917
+ }
918
+
919
+ function normalizeManagedPath(relativePath) {
920
+ return String(relativePath || '').replaceAll('\\', '/').replace(/^\.\//, '');
921
+ }
922
+
923
+ function managedFileKind(relativePath) {
924
+ const normalized = normalizeManagedPath(relativePath);
925
+ if (normalized === 'AGENTS.md' || normalized === 'CLAUDE.md') return 'instructions';
926
+ if (/^piecemaker-plugin\/agents\/[^/]+\.md$/.test(normalized)) return 'agent';
927
+ if (/^piecemaker-plugin\/skills\/[^/]+\/SKILL\.md$/.test(normalized)) return 'skill';
928
+ if (/^billing\/synthese\/[^/]+\.md$/.test(normalized)) return 'billing';
929
+ if (/^billing\/\d{4}-\d{2}\.jsonl$/.test(normalized)) return 'billing';
930
+ return null;
931
+ }
932
+
933
+ function nearestExistingPath(value) {
934
+ let current = value;
935
+ while (!fs.existsSync(current) && path.dirname(current) !== current) current = path.dirname(current);
936
+ return current;
937
+ }
938
+
939
+ function managedAbsolutePath(repoRoot, relativePath, homeDir = path.join(os.homedir(), '.piecemaker')) {
940
+ const normalized = normalizeManagedPath(relativePath);
941
+ const kind = managedFileKind(normalized);
942
+ if (!kind) throw new Error('Ce fichier ne fait pas partie des fichiers administrables.');
943
+ const synthesis = normalized.startsWith('billing/synthese/');
944
+ const root = kind === 'billing'
945
+ ? path.resolve(homeDir, 'billing', ...(synthesis ? ['synthese'] : []))
946
+ : path.resolve(repoRoot);
947
+ const relative = kind === 'billing'
948
+ ? normalized.replace(/^billing\/(?:synthese\/)?/, '')
949
+ : normalized;
950
+ const absolute = path.resolve(root, ...relative.split('/'));
951
+ if (absolute !== root && !absolute.startsWith(`${root}${path.sep}`)) {
952
+ throw new Error('Chemin de fichier invalide.');
953
+ }
954
+ const existing = nearestExistingPath(absolute);
955
+ const realRoot = fs.realpathSync(nearestExistingPath(root));
956
+ const realExisting = fs.realpathSync(existing);
957
+ if (realExisting !== realRoot && !realExisting.startsWith(`${realRoot}${path.sep}`)) {
958
+ throw new Error('Le fichier résout vers un emplacement extérieur au dépôt.');
959
+ }
960
+ if (kind === 'billing' && !fs.existsSync(absolute)) throw new Error('Rapport de facturation introuvable.');
961
+ return { normalized, absolute };
962
+ }
963
+
964
+ // Préfixe des chemins synthétiques d'un skill fourni par un plugin de
965
+ // marketplace installé (onglet « Skills et agents », groupe « Skills
966
+ // officiels »). Ce ne sont pas des fichiers du dépôt : ils vivent dans le cache
967
+ // Claude Code (~/.claude/plugins/cache/…), en lecture seule. Le chemin encode
968
+ // l'id du plugin et le slug du skill pour être re-résolu à la lecture sans
969
+ // stocker de chemin de version volatil.
970
+ const OFFICIAL_SKILL_PREFIX = 'official-skill:';
971
+
972
+ // Skills exposés par les plugins de marketplace installés ET activés (hors
973
+ // notre propre plugin piecemaker, dont les skills sont déjà listés depuis le
974
+ // dépôt). Source : `claude plugin list --json`, qui donne l'`installPath` exact
975
+ // (dossier de version inclus) et l'état `enabled` de chaque plugin ; on y lit
976
+ // alors les `skills/<slug>/SKILL.md`. `runCommand` injectable pour les tests.
977
+ function installedPluginSkills(runCommand = captureCommand) {
978
+ const result = runCommand('claude', ['plugin', 'list', '--json'], 5000);
979
+ if (!result.ok) return [];
980
+ const plugins = parseJsonOutput(result.output);
981
+ if (!Array.isArray(plugins)) return [];
982
+ const skills = [];
983
+ const seen = new Set();
984
+ for (const plugin of plugins) {
985
+ if (!plugin?.id || plugin.enabled === false) continue;
986
+ // Nos propres skills sont déjà listés depuis le dépôt — ne pas les répéter.
987
+ if (plugin.id.endsWith(`@${LEGACY_PIECEMAKER_MARKETPLACE_NAME}`)) continue;
988
+ const installPath = typeof plugin.installPath === 'string' ? plugin.installPath : '';
989
+ if (!installPath) continue;
990
+ const skillsDir = path.join(installPath, 'skills');
991
+ let entries;
992
+ try {
993
+ entries = fs.readdirSync(skillsDir);
994
+ } catch {
995
+ continue; // pas de dossier skills/ (plugin d'agents seuls, MCP, etc.)
996
+ }
997
+ const [pluginName, marketplace] = plugin.id.split('@');
998
+ for (const name of entries.sort()) {
999
+ const skillFile = path.join(skillsDir, name, 'SKILL.md');
1000
+ if (!fs.existsSync(skillFile)) continue;
1001
+ const key = `${plugin.id}/${name}`;
1002
+ if (seen.has(key)) continue; // même plugin installé en scope user ET project
1003
+ seen.add(key);
1004
+ const metadata = pluginComponentFrontMatter(skillFile);
1005
+ skills.push({
1006
+ path: `${OFFICIAL_SKILL_PREFIX}${plugin.id}/${name}`,
1007
+ pluginId: plugin.id,
1008
+ plugin: pluginName,
1009
+ marketplace: marketplace || '',
1010
+ slug: name,
1011
+ name: metadata.name || name,
1012
+ description: metadata.description || '',
1013
+ absolute: skillFile,
1014
+ });
1015
+ }
1016
+ }
1017
+ return skills.sort((a, b) => a.name.localeCompare(b.name));
1018
+ }
1019
+
1020
+ // Registre persistant des skills officiels supprimés par l'utilisateur. Un
1021
+ // skill de plugin de marketplace vit dans le cache Claude Code, que
1022
+ // `claude plugin install`/`update` restaure : sans ce registre, la suppression
1023
+ // serait annulée à la première mise à jour. On mémorise donc le chemin
1024
+ // synthétique retiré et on réapplique le retrait au démarrage du serveur et à
1025
+ // chaque listing (voir `reapplyDeletedOfficialSkills`).
1026
+ function deletedOfficialSkillsFile(homeDir) {
1027
+ return path.join(homeDir, 'deleted-official-skills.json');
1028
+ }
1029
+
1030
+ function readDeletedOfficialSkills(homeDir) {
1031
+ const data = readJson(deletedOfficialSkillsFile(homeDir), null);
1032
+ const paths = Array.isArray(data?.paths) ? data.paths : [];
1033
+ return new Set(paths.filter((value) => typeof value === 'string'));
1034
+ }
1035
+
1036
+ function recordDeletedOfficialSkill(homeDir, pathValue) {
1037
+ const set = readDeletedOfficialSkills(homeDir);
1038
+ set.add(pathValue);
1039
+ atomicWrite(
1040
+ deletedOfficialSkillsFile(homeDir),
1041
+ `${JSON.stringify({ version: 1, paths: [...set].sort() }, null, 2)}\n`,
1042
+ );
1043
+ }
1044
+
1045
+ // Réapplique le registre : retire du cache Claude Code tout dossier de skill
1046
+ // officiel réapparu après une mise à jour/réinstallation de plugin. Best-effort
1047
+ // et idempotent — un skill déjà absent n'est pas une erreur. Renvoie le nombre
1048
+ // de dossiers effectivement retirés, pour la journalisation au démarrage.
1049
+ function reapplyDeletedOfficialSkills(homeDir = path.join(os.homedir(), '.piecemaker'), runCommand = captureCommand) {
1050
+ const deleted = readDeletedOfficialSkills(homeDir);
1051
+ if (!deleted.size) return 0;
1052
+ let removed = 0;
1053
+ for (const skill of installedPluginSkills(runCommand)) {
1054
+ if (!deleted.has(skill.path) || !skill.absolute || !fs.existsSync(skill.absolute)) continue;
1055
+ try {
1056
+ fs.rmSync(path.dirname(skill.absolute), { recursive: true, force: true });
1057
+ removed += 1;
1058
+ } catch {
1059
+ // Cache verrouillé ou déjà retiré : on n'interrompt pas le balayage.
1060
+ }
1061
+ }
1062
+ return removed;
1063
+ }
1064
+
1065
+ // Nombre maximal d'annexes listées pour un skill (panneau « Skills et
1066
+ // agents ») — borne défensive contre un dossier de skill pathologique.
1067
+ const MAX_SKILL_ASSETS = 200;
1068
+
1069
+ // Les fichiers annexes (assets/scripts) d'un skill, pour l'affichage
1070
+ // indenté sous son bouton dans le panneau « Skills et agents ». `skillRelPath`
1071
+ // est le chemin relatif de son SKILL.md ; on recurse dans les sous-dossiers
1072
+ // (ex. `scripts/`) mais `name` reste le seul basename — jamais le chemin — et
1073
+ // `path` est le chemin relatif au dépôt utilisé pour la suppression. Les
1074
+ // fichiers cachés et les dossiers cachés sont ignorés, SKILL.md n'est jamais
1075
+ // listé comme une annexe, et la liste est triée par nom puis plafonnée.
1076
+ function listSkillAssets(repoRoot, skillRelPath) {
1077
+ const normalized = normalizeManagedPath(skillRelPath);
1078
+ if (!/^piecemaker-plugin\/skills\/[^/]+\/SKILL\.md$/.test(normalized)) return [];
1079
+ const skillDirRel = normalized.replace(/\/SKILL\.md$/, '');
1080
+ const skillDir = path.resolve(repoRoot, ...skillDirRel.split('/'));
1081
+ const assets = [];
1082
+ const walk = (dir, relDir) => {
1083
+ if (assets.length >= MAX_SKILL_ASSETS) return;
1084
+ let entries;
1085
+ try {
1086
+ entries = fs.readdirSync(dir, { withFileTypes: true });
1087
+ } catch {
1088
+ return;
1089
+ }
1090
+ for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
1091
+ if (assets.length >= MAX_SKILL_ASSETS) return;
1092
+ if (entry.name.startsWith('.')) continue;
1093
+ const entryRel = relDir ? `${relDir}/${entry.name}` : entry.name;
1094
+ if (entry.isDirectory()) {
1095
+ walk(path.join(dir, entry.name), entryRel);
1096
+ } else if (entry.isFile()) {
1097
+ if (relDir === '' && entry.name === 'SKILL.md') continue;
1098
+ assets.push({ name: entry.name, path: `${skillDirRel}/${entryRel}` });
1099
+ }
1100
+ }
1101
+ };
1102
+ walk(skillDir, '');
1103
+ assets.sort((a, b) => a.name.localeCompare(b.name));
1104
+ return assets.slice(0, MAX_SKILL_ASSETS);
1105
+ }
1106
+
1107
+ // Les instructions, skills et agents du dépôt, plus les skills des plugins de
1108
+ // marketplace installés (groupe « Skills officiels », lecture seule) : les
1109
+ // aperçus de facturation vivent dans ~/.piecemaker/billing, une hiérarchie
1110
+ // distincte du dépôt, et ne sont pas listés dans l'éditeur « Skills et agents ».
1111
+ // `options.installedSkills` permet aux tests d'injecter la liste des skills de
1112
+ // plugins (sinon elle est lue via le CLI claude, non hermétique).
1113
+ function listManagedFiles(repoRoot, homeDir = path.join(os.homedir(), '.piecemaker'), userHome = os.homedir(), options = {}) {
1114
+ const candidates = ['AGENTS.md', 'CLAUDE.md'];
1115
+ const agentsDir = path.join(repoRoot, 'piecemaker-plugin', 'agents');
1116
+ if (fs.existsSync(agentsDir)) {
1117
+ for (const name of fs.readdirSync(agentsDir).filter((entry) => entry.endsWith('.md')).sort()) {
1118
+ candidates.push(`piecemaker-plugin/agents/${name}`);
1119
+ }
1120
+ }
1121
+ const skillsDir = path.join(repoRoot, 'piecemaker-plugin', 'skills');
1122
+ if (fs.existsSync(skillsDir)) {
1123
+ for (const name of fs.readdirSync(skillsDir).sort()) {
1124
+ const relative = `piecemaker-plugin/skills/${name}/SKILL.md`;
1125
+ if (fs.existsSync(path.join(repoRoot, ...relative.split('/')))) candidates.push(relative);
1126
+ }
1127
+ }
1128
+
1129
+ const files = candidates.map((relativePath) => {
1130
+ const { absolute, normalized } = managedAbsolutePath(repoRoot, relativePath, homeDir);
1131
+ const kind = managedFileKind(normalized);
1132
+ return {
1133
+ path: normalized,
1134
+ name: normalized === 'AGENTS.md' || normalized === 'CLAUDE.md'
1135
+ ? normalized
1136
+ : path.basename(path.dirname(normalized)) === 'agents'
1137
+ ? path.basename(normalized, '.md')
1138
+ : path.basename(path.dirname(normalized)),
1139
+ kind,
1140
+ exists: fs.existsSync(absolute),
1141
+ readonly: false,
1142
+ // Visibilité côté Claude Code (voir claude-assets.cjs) — null pour les
1143
+ // fichiers qui ne sont pas des composants de plugin.
1144
+ claudeCode: claudeAssetStatus(repoRoot, userHome, normalized),
1145
+ // Fichiers annexes (assets/scripts) — seuls les skills en ont un
1146
+ // dossier propre ; un agent n'a jamais d'annexes.
1147
+ assets: kind === 'skill' ? listSkillAssets(repoRoot, normalized) : [],
1148
+ };
1149
+ });
1150
+
1151
+ // Skills des plugins de marketplace installés — lecture seule, groupe
1152
+ // « Skills officiels ». Un plugin installé depuis les onglets marketplace du
1153
+ // pop-up apparaît ainsi dans la liste dès qu'il expose des skills.
1154
+ const installedSkills = options.installedSkills || installedPluginSkills();
1155
+ const deletedOfficial = readDeletedOfficialSkills(homeDir);
1156
+ for (const skill of installedSkills) {
1157
+ // Skill officiel supprimé mais réapparu (mise à jour/réinstallation du
1158
+ // plugin) : on le re-retire du cache et on ne le liste pas — la suppression
1159
+ // reste effective par-delà les mises à jour.
1160
+ if (deletedOfficial.has(skill.path)) {
1161
+ if (skill.absolute && fs.existsSync(skill.absolute)) {
1162
+ try { fs.rmSync(path.dirname(skill.absolute), { recursive: true, force: true }); } catch { /* best-effort */ }
1163
+ }
1164
+ continue;
1165
+ }
1166
+ files.push({
1167
+ path: skill.path,
1168
+ name: skill.name,
1169
+ kind: 'official-skill',
1170
+ exists: true,
1171
+ readonly: true,
1172
+ plugin: skill.plugin,
1173
+ marketplace: skill.marketplace,
1174
+ description: skill.description,
1175
+ claudeCode: null,
1176
+ });
1177
+ }
1178
+ return files;
1179
+ }
1180
+
1181
+ // Lecture minimale du front matter (name/description) d'un skill/agent, pour
1182
+ // le pop-up « Ajouter le plugin legal Claude ». Même grammaire que
1183
+ // admin/markdown.mjs#parseMetadata (module navigateur, non réutilisable
1184
+ // ici — server.cjs ne charge pas de modules ES du dossier admin/), réduite
1185
+ // aux deux clés affichées.
1186
+ function parseFrontMatter(raw) {
1187
+ const match = String(raw || '').match(/^---\n([\s\S]*?)\n---(?:\n|$)/);
1188
+ if (!match) return {};
1189
+ const values = {};
1190
+ for (const line of match[1].split('\n')) {
1191
+ const lineMatch = line.match(/^([A-Za-z][A-Za-z0-9_-]*):\s*(.*)$/);
1192
+ if (!lineMatch) continue;
1193
+ let value = lineMatch[2];
1194
+ if (value.startsWith('"')) {
1195
+ try { value = JSON.parse(value); } catch { value = value.replace(/^"|"$/g, ''); }
1196
+ } else if (value.startsWith("'") && value.endsWith("'")) {
1197
+ value = value.slice(1, -1).replaceAll("''", "'");
1198
+ }
1199
+ values[lineMatch[1]] = value;
1200
+ }
1201
+ return values;
1202
+ }
1203
+
1204
+ function pluginComponentFrontMatter(absolutePath) {
1205
+ try {
1206
+ return parseFrontMatter(fs.readFileSync(absolutePath, 'utf8'));
1207
+ } catch {
1208
+ return {};
1209
+ }
1210
+ }
1211
+
1212
+ /**
1213
+ * Les skills et agents PieceMaker, avec leur état d'enregistrement
1214
+ * auprès de Claude Code — support du pop-up de sélection de
1215
+ * « Ajouter le plugin legal Claude ». S'appuie sur `repositoryAssets` /
1216
+ * `claudeAssetStatus` (claude-assets.cjs), la même source que la liste de
1217
+ * fichiers et `files/sync`, pour que les deux vues restent en accord.
1218
+ */
1219
+ function listPluginComponents(repoRoot, userHome = os.homedir()) {
1220
+ return repositoryAssets(repoRoot).map((relativePath) => {
1221
+ const asset = claudeAssetOf(repoRoot, userHome, relativePath);
1222
+ const status = claudeAssetStatus(repoRoot, userHome, relativePath);
1223
+ const sourceFile = asset.type === 'dir' ? path.join(asset.source, 'SKILL.md') : asset.source;
1224
+ const metadata = pluginComponentFrontMatter(sourceFile);
1225
+ return {
1226
+ path: relativePath,
1227
+ kind: asset.kind,
1228
+ slug: asset.slug,
1229
+ name: metadata.name || asset.slug,
1230
+ description: metadata.description || '',
1231
+ state: status.state,
1232
+ // Coché par défaut dans le pop-up : un enregistrement déjà présent,
1233
+ // même périmé (« stale » — autre clone du dépôt), compte comme actif.
1234
+ registered: ['linked', 'copied', 'stale'].includes(status.state),
1235
+ note: status.state === 'conflict'
1236
+ ? `Un ${asset.kind === 'skill' ? 'skill' : 'agent'} personnel du même nom existe déjà dans ~/.claude — non modifiable ici.`
1237
+ : '',
1238
+ };
1239
+ });
1240
+ }
1241
+
1242
+ /**
1243
+ * Applique une sélection de composants (chemins relatifs voulus « actifs »)
1244
+ * en enregistrant ceux cochés et en retirant ceux décochés, via
1245
+ * `registerClaudeAsset`/`unregisterClaudeAsset` — jamais de substitution
1246
+ * maison. Un composant en conflit (fichier personnel homonyme) n'est jamais
1247
+ * touché, coché ou non. Idempotent : ré-appliquer la même sélection ne
1248
+ * change rien à un état déjà atteint.
1249
+ */
1250
+ function applyPluginComponentSelection(repoRoot, userHome, selection) {
1251
+ const wanted = new Set((Array.isArray(selection) ? selection : []).map(String));
1252
+ const assets = repositoryAssets(repoRoot).map((relativePath) => {
1253
+ const current = claudeAssetStatus(repoRoot, userHome, relativePath);
1254
+ if (current.state === 'conflict') return { path: relativePath, ...current };
1255
+ const result = wanted.has(relativePath)
1256
+ ? registerClaudeAsset(repoRoot, userHome, relativePath)
1257
+ : unregisterClaudeAsset(repoRoot, userHome, relativePath);
1258
+ return { path: relativePath, ...result };
1259
+ });
1260
+ return {
1261
+ assets,
1262
+ registered: assets.filter((asset) => asset.state === 'linked' || asset.state === 'copied').length,
1263
+ removed: assets.filter((asset) => asset.state === 'missing' && !wanted.has(asset.path)).length,
1264
+ conflicts: assets.filter((asset) => asset.state === 'conflict'),
1265
+ };
1266
+ }
1267
+
1268
+ function billingLedgerToMarkdown(file) {
1269
+ const month = path.basename(file, '.jsonl');
1270
+ const entries = fs.readFileSync(file, 'utf8').split(/\r?\n/).filter(Boolean).flatMap((line) => {
1271
+ try { return [JSON.parse(line)]; } catch { return []; }
1272
+ });
1273
+ const clean = (value) => String(value ?? '—').replace(/[\r\n|]+/g, ' ').trim() || '—';
1274
+ const duration = (ms) => Number.isFinite(ms)
1275
+ ? `${Math.floor(ms / 60000)} min ${Math.round((ms % 60000) / 1000)} s`
1276
+ : '—';
1277
+ const rows = entries.map((entry) => {
1278
+ const date = entry.timestamp ? new Date(entry.timestamp).toLocaleString('fr-FR') : '—';
1279
+ const tools = Object.entries(entry.tool_counts || {}).map(([name, count]) => `${name} (${count})`).join(', ') || '—';
1280
+ return `| ${clean(date)} | ${clean(entry.event)} | ${clean(entry.task_label)} | ${clean(entry.dossier)} | ${duration(entry.duration_ms)} | ${clean(tools)} |`;
1281
+ });
1282
+ return [
1283
+ `# Suivi de facturation — ${month}`,
1284
+ '',
1285
+ `${entries.length} événement(s) enregistré(s). Aperçu généré depuis le journal mensuel en lecture seule.`,
1286
+ '',
1287
+ '| Date | Événement | Tâche | Dossier | Durée | Outils |',
1288
+ '| --- | --- | --- | --- | --- | --- |',
1289
+ ...rows,
1290
+ '',
1291
+ ].join('\n');
1292
+ }
1293
+
1294
+ // Lecture (seule) d'un skill de plugin de marketplace : re-résolution du chemin
1295
+ // synthétique `official-skill:<pluginId>/<slug>` vers son SKILL.md dans le cache
1296
+ // Claude Code, via `installedPluginSkills`. Jamais d'écriture — saveManagedFile
1297
+ // refuse ce préfixe (managedAbsolutePath ne le reconnaît pas comme administrable).
1298
+ function readOfficialSkill(pathValue, runCommand = captureCommand) {
1299
+ const skill = installedPluginSkills(runCommand).find((entry) => entry.path === pathValue);
1300
+ if (!skill || !fs.existsSync(skill.absolute)) throw new Error('Skill de plugin introuvable ou plugin désactivé.');
1301
+ return {
1302
+ path: pathValue,
1303
+ kind: 'official-skill',
1304
+ exists: true,
1305
+ content: fs.readFileSync(skill.absolute, 'utf8'),
1306
+ readonly: true,
1307
+ sourceType: 'markdown',
1308
+ };
1309
+ }
1310
+
1311
+ function readManagedFile(repoRoot, relativePath, homeDir = path.join(os.homedir(), '.piecemaker')) {
1312
+ if (typeof relativePath === 'string' && relativePath.startsWith(OFFICIAL_SKILL_PREFIX)) {
1313
+ return readOfficialSkill(relativePath);
1314
+ }
1315
+ const { absolute, normalized } = managedAbsolutePath(repoRoot, relativePath, homeDir);
1316
+ const exists = fs.existsSync(absolute);
1317
+ const kind = managedFileKind(normalized);
1318
+ return {
1319
+ path: normalized,
1320
+ kind,
1321
+ exists,
1322
+ content: exists
1323
+ ? normalized.endsWith('.jsonl') ? billingLedgerToMarkdown(absolute) : fs.readFileSync(absolute, 'utf8')
1324
+ : '',
1325
+ readonly: kind === 'billing',
1326
+ sourceType: normalized.endsWith('.jsonl') ? 'billing-ledger' : 'markdown',
1327
+ };
1328
+ }
1329
+
1330
+ function backupFile(source, relativePath, homeDir) {
1331
+ if (!fs.existsSync(source)) return null;
1332
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-');
1333
+ const target = path.join(homeDir, 'backups', stamp, ...normalizeManagedPath(relativePath).split('/'));
1334
+ fs.mkdirSync(path.dirname(target), { recursive: true });
1335
+ fs.copyFileSync(source, target);
1336
+ return target;
1337
+ }
1338
+
1339
+ function saveManagedFile(repoRoot, homeDir, relativePath, content) {
1340
+ if (typeof content !== 'string') throw new Error('Le contenu Markdown est requis.');
1341
+ if (Buffer.byteLength(content, 'utf8') > MAX_MARKDOWN_BYTES) {
1342
+ throw new Error('Le fichier dépasse la limite de 1 Mo.');
1343
+ }
1344
+ if (managedFileKind(relativePath) === 'billing') throw new Error('Les rapports de facturation sont en lecture seule.');
1345
+ const { absolute, normalized } = managedAbsolutePath(repoRoot, relativePath, homeDir);
1346
+ const backup = backupFile(absolute, normalized, homeDir);
1347
+ atomicWrite(absolute, content.endsWith('\n') ? content : `${content}\n`);
1348
+ return { path: normalized, backup, savedAt: new Date().toISOString() };
1349
+ }
1350
+
1351
+ // Modèles acceptés dans le front matter d'un agent Claude Code. « inherit »
1352
+ // reprend le modèle de la session appelante.
1353
+ const AGENT_MODELS = ['inherit', 'haiku', 'sonnet', 'opus'];
1354
+ const DEFAULT_AGENT_TOOLS = 'Read, Grep, Glob';
1355
+
1356
+ /**
1357
+ * Un agent n'a pas le même front matter qu'un skill : il déclare en plus les
1358
+ * outils auxquels il a droit et le modèle qui l'exécute. On valide donc ces
1359
+ * deux champs ici plutôt que de recopier le gabarit d'un skill.
1360
+ * Une liste d'outils vide signifie « hérite de tous les outils » : la clé est
1361
+ * alors omise, ce que Claude Code interprète ainsi.
1362
+ */
1363
+ function normalizeAgentTools(value) {
1364
+ if (value === undefined || value === null) return DEFAULT_AGENT_TOOLS;
1365
+ const tools = String(value)
1366
+ .split(',')
1367
+ .map((tool) => tool.trim())
1368
+ .filter(Boolean);
1369
+ const seen = [];
1370
+ for (const tool of tools) {
1371
+ // « Bash(git status:*) » : nom d'outil, éventuellement suivi d'un motif.
1372
+ if (!/^[A-Za-z][A-Za-z0-9_-]*(\([^()\n]*\))?$/.test(tool)) {
1373
+ throw new Error(`Outil invalide : « ${tool} ». Utilisez des noms comme Read, Grep, Glob ou Bash(git *).`);
1374
+ }
1375
+ if (!seen.includes(tool)) seen.push(tool);
1376
+ }
1377
+ if (seen.length > 30) throw new Error('Trop d\u2019outils déclarés pour un agent (30 maximum).');
1378
+ return seen.join(', ');
1379
+ }
1380
+
1381
+ function normalizeAgentModel(value) {
1382
+ const model = String(value ?? 'sonnet').trim().toLowerCase() || 'sonnet';
1383
+ if (!AGENT_MODELS.includes(model)) {
1384
+ throw new Error(`Modèle inconnu : « ${model} ». Choisissez ${AGENT_MODELS.join(', ')}.`);
1385
+ }
1386
+ return model;
1387
+ }
1388
+
1389
+ function agentTemplate(slug, title, summary, tools, model) {
1390
+ const metadata = [
1391
+ `name: ${slug}`,
1392
+ `description: ${JSON.stringify(summary)}`,
1393
+ ...(tools ? [`tools: ${tools}`] : []),
1394
+ `model: ${model}`,
1395
+ ].join('\n');
1396
+ const toolLine = tools
1397
+ ? `Outils autorisés : ${tools}.`
1398
+ : 'Aucune restriction d\u2019outils : l\u2019agent hérite de ceux de la session.';
1399
+ return `---\n${metadata}\n---\n\n# ${title}\n\n`
1400
+ + `Vous êtes un sous-agent PieceMaker lancé pour une tâche précise, dans sa\n`
1401
+ + `propre fenêtre de contexte. Décrivez ci-dessous votre rôle et vos règles.\n\n`
1402
+ + `## Mission\n\n${summary}\n\n`
1403
+ + `## Déroulé attendu\n\n`
1404
+ + `1. Rassemblez le contexte nécessaire (documents, mapping, dossier).\n`
1405
+ + `2. Effectuez l\u2019analyse ou la production demandée.\n`
1406
+ + `3. Renvoyez un rapport final autonome : l\u2019agent appelant ne voit pas vos étapes intermédiaires.\n\n`
1407
+ + `## Contraintes\n\n`
1408
+ + `- ${toolLine}\n`
1409
+ + `- Ne levez jamais une anonymisation existante : travaillez sur les codes tels quels.\n`
1410
+ + `- Ne citez aucun texte de loi ni jurisprudence de mémoire.\n`;
1411
+ }
1412
+
1413
+ function skillTemplate(slug, title, summary) {
1414
+ const metadata = `name: ${slug}\ndescription: ${JSON.stringify(summary)}`;
1415
+ return `---\n${metadata}\n---\n\n# ${title}\n\n`
1416
+ + `${summary}\n\n`
1417
+ + `## Quand utiliser ce skill\n\nDécrivez les situations qui doivent déclencher ce skill.\n\n`
1418
+ + `## Déroulé\n\n1. Première étape.\n2. Deuxième étape.\n\n`
1419
+ + `## Points de vigilance\n\n- Ce qu\u2019il ne faut jamais faire.\n`;
1420
+ }
1421
+
1422
+ function createManagedFile(repoRoot, homeDir, { kind, slug, name, description, tools, model } = {}) {
1423
+ if (!['skill', 'agent'].includes(kind)) throw new Error('Choisissez « skill » ou « agent ».');
1424
+ const safeSlug = String(slug || '').trim().toLowerCase();
1425
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(safeSlug)) {
1426
+ throw new Error('L\u2019identifiant doit contenir uniquement des minuscules, chiffres et tirets.');
1427
+ }
1428
+ const title = String(name || safeSlug).replace(/[\r\n]+/g, ' ').trim().slice(0, 80) || safeSlug;
1429
+ const summary = String(description || '').replace(/[\r\n]+/g, ' ').trim().slice(0, 1000);
1430
+ if (!summary) throw new Error('Une description est requise pour que l\u2019agent sache quand utiliser ce fichier.');
1431
+ const relativePath = kind === 'skill'
1432
+ ? `piecemaker-plugin/skills/${safeSlug}/SKILL.md`
1433
+ : `piecemaker-plugin/agents/${safeSlug}.md`;
1434
+ const { absolute } = managedAbsolutePath(repoRoot, relativePath, homeDir);
1435
+ if (fs.existsSync(absolute)) throw new Error('Un fichier portant cet identifiant existe déjà.');
1436
+ const content = kind === 'skill'
1437
+ ? skillTemplate(safeSlug, title, summary)
1438
+ : agentTemplate(safeSlug, title, summary, normalizeAgentTools(tools), normalizeAgentModel(model));
1439
+ saveManagedFile(repoRoot, homeDir, relativePath, content);
1440
+ return readManagedFile(repoRoot, relativePath, homeDir);
1441
+ }
1442
+
1443
+ /**
1444
+ * Écrit un fichier annexe (asset ou script) dans le dossier d'un skill —
1445
+ * `piecemaker-plugin/skills/<slug>/`. Seuls les skills ont un dossier propre :
1446
+ * un agent est un fichier `.md` isolé, sans annexes. Le nom est réduit à son
1447
+ * basename (aucune remontée de dossier), `SKILL.md` ne peut pas être écrasé, et
1448
+ * la cible est revérifiée à l'intérieur du dossier du skill avant écriture.
1449
+ * Renvoie le nom retenu et son chemin relatif au dépôt.
1450
+ */
1451
+ function saveManagedAsset(repoRoot, homeDir, skillRelPath, filename, buffer) {
1452
+ const { absolute, normalized } = managedAbsolutePath(repoRoot, skillRelPath, homeDir);
1453
+ if (managedFileKind(normalized) !== 'skill') {
1454
+ throw new Error('Un fichier annexe ne peut être ajouté qu’à un skill.');
1455
+ }
1456
+ const safeName = path.basename(String(filename || '').trim());
1457
+ if (!safeName || safeName.startsWith('.')) throw new Error('Nom de fichier invalide.');
1458
+ if (safeName === 'SKILL.md') throw new Error('SKILL.md ne peut pas être remplacé ainsi.');
1459
+ if (!Buffer.isBuffer(buffer) || buffer.length === 0) throw new Error('Fichier vide.');
1460
+ const skillDir = path.dirname(absolute);
1461
+ const target = path.resolve(skillDir, safeName);
1462
+ if (target !== path.join(skillDir, safeName)) throw new Error('Chemin de fichier invalide.');
1463
+ fs.mkdirSync(skillDir, { recursive: true });
1464
+ const temp = path.join(skillDir, `.${safeName}.${process.pid}.${Date.now()}.tmp`);
1465
+ fs.writeFileSync(temp, buffer);
1466
+ fs.renameSync(temp, target);
1467
+ const slugDir = normalized.replace(/\/SKILL\.md$/, '');
1468
+ return { name: safeName, path: `${slugDir}/${safeName}` };
1469
+ }
1470
+
1471
+ /**
1472
+ * Supprime un unique fichier annexe (asset ou script) d'un skill, sans toucher
1473
+ * au reste du dossier. `managedAbsolutePath` ne reconnaît pas un chemin
1474
+ * d'annexe (`managedFileKind` renvoie `null` dessus), donc on ne peut pas le
1475
+ * réutiliser tel quel : cette fonction reprend la même vérification de
1476
+ * confinement (chemin résolu sous le dépôt, puis realpath contre un lien
1477
+ * symbolique) directement contre `piecemaker-plugin/skills/<slug>/`.
1478
+ * `SKILL.md` n'est jamais une « annexe » — sa suppression passe par
1479
+ * `deleteManagedFile` (suppression du skill entier).
1480
+ */
1481
+ function deleteManagedAsset(repoRoot, homeDir, assetRelPath) {
1482
+ const normalized = normalizeManagedPath(assetRelPath);
1483
+ if (!/^piecemaker-plugin\/skills\/[^/]+\/.+/.test(normalized) || normalized.endsWith('/SKILL.md')) {
1484
+ throw new Error('Ce chemin ne correspond pas à un fichier annexe de skill.');
1485
+ }
1486
+ const root = path.resolve(repoRoot);
1487
+ const absolute = path.resolve(root, ...normalized.split('/'));
1488
+ if (absolute !== root && !absolute.startsWith(`${root}${path.sep}`)) {
1489
+ throw new Error('Chemin de fichier invalide.');
1490
+ }
1491
+ const existing = nearestExistingPath(absolute);
1492
+ const realRoot = fs.realpathSync(nearestExistingPath(root));
1493
+ const realExisting = fs.realpathSync(existing);
1494
+ if (realExisting !== realRoot && !realExisting.startsWith(`${realRoot}${path.sep}`)) {
1495
+ throw new Error('Le fichier résout vers un emplacement extérieur au dépôt.');
1496
+ }
1497
+ if (!fs.existsSync(absolute) || !fs.statSync(absolute).isFile()) {
1498
+ throw new Error('Fichier annexe introuvable.');
1499
+ }
1500
+ backupFile(absolute, normalized, homeDir);
1501
+ fs.rmSync(absolute);
1502
+ return { path: normalized, deletedAt: new Date().toISOString() };
1503
+ }
1504
+
1505
+ /**
1506
+ * Supprime un skill ou un agent du dépôt : seuls ces deux types sont
1507
+ * effaçables ici — instructions (CLAUDE.md/AGENTS.md), skills officiels et
1508
+ * aperçus de facturation ne le sont jamais. Le contenu est sauvegardé avant
1509
+ * retrait, puis l'enregistrement Claude Code (lien ou copie) est nettoyé par
1510
+ * l'appelant via `unregisterClaudeAsset`. Pour un skill on retire le dossier
1511
+ * `piecemaker-plugin/skills/<slug>/` entier (SKILL.md et ses annexes) ; pour un
1512
+ * agent, le seul fichier `.md`.
1513
+ */
1514
+ function deleteManagedFile(repoRoot, homeDir, relativePath) {
1515
+ // Skill de plugin de marketplace : on retire son dossier directement dans le
1516
+ // cache Claude Code — voir `deleteOfficialSkill`.
1517
+ if (typeof relativePath === 'string' && relativePath.startsWith(OFFICIAL_SKILL_PREFIX)) {
1518
+ return deleteOfficialSkill(relativePath, homeDir);
1519
+ }
1520
+ const { absolute, normalized } = managedAbsolutePath(repoRoot, relativePath, homeDir);
1521
+ const kind = managedFileKind(normalized);
1522
+ if (!['skill', 'agent'].includes(kind)) {
1523
+ throw new Error('Seuls un skill ou un agent peuvent être supprimés.');
1524
+ }
1525
+ if (!fs.existsSync(absolute)) throw new Error('Fichier introuvable.');
1526
+ backupFile(absolute, normalized, homeDir);
1527
+ // Un skill vit dans son propre dossier — on supprime le dossier, pas juste
1528
+ // le SKILL.md, pour ne pas laisser un dossier vide derrière.
1529
+ const target = kind === 'skill' ? path.dirname(absolute) : absolute;
1530
+ fs.rmSync(target, { recursive: true, force: true });
1531
+ return { path: normalized, kind, deletedAt: new Date().toISOString() };
1532
+ }
1533
+
1534
+ /**
1535
+ * Supprime le dossier d'un skill de plugin de marketplace directement dans le
1536
+ * cache Claude Code (~/.claude/plugins/cache/…) : le chemin synthétique
1537
+ * `official-skill:<pluginId>/<slug>` est re-résolu vers son SKILL.md via
1538
+ * `installedPluginSkills`, puis le dossier `skills/<slug>/` entier est retiré.
1539
+ * Le retrait est mémorisé dans le registre `deleted-official-skills.json` puis
1540
+ * réappliqué (`reapplyDeletedOfficialSkills`) au démarrage du serveur et à
1541
+ * chaque listing : la suppression survit donc à une réinstallation ou mise à
1542
+ * jour du plugin, qui restaurerait sinon le dossier. C'est une suppression
1543
+ * chirurgicale dans un cache géré, pas une désinstallation, et sans sauvegarde.
1544
+ */
1545
+ function deleteOfficialSkill(pathValue, homeDir = path.join(os.homedir(), '.piecemaker'), runCommand = captureCommand) {
1546
+ const skill = installedPluginSkills(runCommand).find((entry) => entry.path === pathValue);
1547
+ if (!skill || !fs.existsSync(skill.absolute)) throw new Error('Skill de plugin introuvable ou plugin désactivé.');
1548
+ // Enregistrer avant de retirer : même si le rm échoue, une mise à jour
1549
+ // ultérieure ne doit pas ressusciter un skill que l'utilisateur a supprimé.
1550
+ recordDeletedOfficialSkill(homeDir, pathValue);
1551
+ fs.rmSync(path.dirname(skill.absolute), { recursive: true, force: true });
1552
+ return { path: pathValue, kind: 'official-skill', deletedAt: new Date().toISOString() };
1553
+ }
1554
+
1555
+ // Le slug qui identifie un skill/agent sur le disque : le dossier pour un
1556
+ // skill (`piecemaker-plugin/skills/<slug>/SKILL.md`), le basename du `.md` pour
1557
+ // un agent (`piecemaker-plugin/agents/<slug>.md`).
1558
+ function managedSlug(normalized) {
1559
+ const kind = managedFileKind(normalized);
1560
+ if (kind === 'skill') return normalized.split('/')[2];
1561
+ if (kind === 'agent') return path.basename(normalized, '.md');
1562
+ return null;
1563
+ }
1564
+
1565
+ /**
1566
+ * Renomme un skill/agent quand le champ `name:` de son front matter ne
1567
+ * correspond plus au slug de son emplacement. Ce nom sert de nom de dossier
1568
+ * (skill) ou de fichier (agent) et doit rester en accord avec ce que Claude
1569
+ * Code découvre. Pour un skill on déplace le dossier
1570
+ * `piecemaker-plugin/skills/<slug>/` entier (SKILL.md et ses annexes suivent) ;
1571
+ * pour un agent, le seul fichier `.md`. Renvoie `{ renamed, path, previous }`,
1572
+ * ou `{ renamed: false }` si le nom est inchangé.
1573
+ */
1574
+ function renameManagedFile(repoRoot, homeDir, relativePath, newSlug) {
1575
+ const { absolute, normalized } = managedAbsolutePath(repoRoot, relativePath, homeDir);
1576
+ const kind = managedFileKind(normalized);
1577
+ if (!['skill', 'agent'].includes(kind)) {
1578
+ throw new Error('Seuls un skill ou un agent peuvent être renommés.');
1579
+ }
1580
+ const slug = String(newSlug || '').trim();
1581
+ if (!slug || slug === managedSlug(normalized)) return { renamed: false, path: normalized };
1582
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug)) {
1583
+ throw new Error('Le nom sert de nom de dossier : uniquement minuscules, chiffres et tirets.');
1584
+ }
1585
+ const targetRelative = kind === 'skill'
1586
+ ? `piecemaker-plugin/skills/${slug}/SKILL.md`
1587
+ : `piecemaker-plugin/agents/${slug}.md`;
1588
+ const { absolute: targetAbsolute, normalized: targetNormalized } = managedAbsolutePath(repoRoot, targetRelative, homeDir);
1589
+ const source = kind === 'skill' ? path.dirname(absolute) : absolute;
1590
+ const destination = kind === 'skill' ? path.dirname(targetAbsolute) : targetAbsolute;
1591
+ if (!fs.existsSync(source)) throw new Error('Fichier introuvable — enregistrez-le avant de le renommer.');
1592
+ if (fs.existsSync(destination)) {
1593
+ throw new Error(`Un ${kind === 'skill' ? 'skill' : 'agent'} nommé « ${slug} » existe déjà.`);
1594
+ }
1595
+ fs.renameSync(source, destination);
1596
+ return { renamed: true, path: targetNormalized, previous: normalized };
1597
+ }
1598
+
1599
+ function isLocalOrigin(origin) {
1600
+ if (!origin) return true;
1601
+ try {
1602
+ const hostname = new URL(origin).hostname;
1603
+ return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '[::1]' || hostname === '::1';
1604
+ } catch {
1605
+ return false;
1606
+ }
1607
+ }
1608
+
1609
+ function finishAdminTiming(res, metric, startedAt, details = {}) {
1610
+ const durationMs = Number((performance.now() - startedAt).toFixed(1));
1611
+ res.set('Server-Timing', `${metric};dur=${durationMs}`);
1612
+ if (process.env.PIECEMAKER_PERF_LOG === '1' || durationMs >= 250) {
1613
+ const write = durationMs >= 250 ? console.warn : console.log;
1614
+ write(`[PM-PERF] admin.${metric}`, { durationMs, ...details });
1615
+ }
1616
+ }
1617
+
1618
+ /**
1619
+ * Dossiers tamponnables : un par `compilation_dossier_<documentId>.json` écrit
1620
+ * dans le dossier de sortie (voir server.cjs). `folder` est le dossier de
1621
+ * travail (celui du document Word), où les pièces tamponnées sont écrites dans
1622
+ * le sous-dossier « Pièces tamponnées ». Seules les métadonnées utiles au bordereau sont
1623
+ * renvoyées — jamais `texte_integral`, qui contient les pièces en clair.
1624
+ */
1625
+ // Les pièces à tamponner ne viennent plus d'un `compilation_dossier_*.json`
1626
+ // (produit par l'ancien chargement de dossier depuis Word) : la logique s'appuie
1627
+ // désormais sur les dossiers juridiques enregistrés, quelle que soit leur place
1628
+ // dans l'arborescence. Chaque dossier expose ses pièces originales — tout fichier
1629
+ // qui n'est ni `.md` ni `.json` —, identifiées par leur chemin relatif au dossier.
1630
+ async function listDossiers(repoRoot, homeDir) {
1631
+ const config = readRegistryConfig(path.join(homeDir, 'config.json'));
1632
+ const dossiers = [];
1633
+ for (const entry of listConfiguredCases(config)) {
1634
+ let originals;
1635
+ try {
1636
+ originals = await listOriginals(entry.root);
1637
+ } catch {
1638
+ // Dossier déplacé/illisible pendant l'inventaire : on l'omet simplement.
1639
+ continue;
1640
+ }
1641
+ if (!originals.length) continue;
1642
+ dossiers.push({
1643
+ documentId: entry.id,
1644
+ informations: { intitule: entry.name },
1645
+ folder: entry.root,
1646
+ stampedDir: stampedPiecesDirectory(entry.root),
1647
+ documents: originals.map((file) => ({
1648
+ id: file.path,
1649
+ filename: file.path,
1650
+ type_document: (file.extension || '').replace(/^\./, '').toUpperCase(),
1651
+ date_document: '',
1652
+ })),
1653
+ });
1654
+ }
1655
+ return dossiers;
1656
+ }
1657
+
1658
+ const REVEAL_TARGETS = new Set(['files', 'terminal']);
1659
+
1660
+ /**
1661
+ * Résout un chemin reçu du navigateur (relatif à la racine d'un dossier
1662
+ * juridique) vers un chemin absolu, sans jamais remonter hors de cette
1663
+ * racine. Même garde que `startOriginalsJob` (originals-pipeline.cjs) pour
1664
+ * les pièces sélectionnées : `path.resolve` puis vérification du préfixe.
1665
+ */
1666
+ function resolveCasePath(caseRoot, relativePath) {
1667
+ const relative = String(relativePath || '').replaceAll('\\', '/').replace(/^\.\//, '');
1668
+ if (!relative) throw new Error('Chemin de pièce manquant.');
1669
+ const absolute = path.resolve(caseRoot, ...relative.split('/'));
1670
+ if (absolute !== caseRoot && !absolute.startsWith(`${caseRoot}${path.sep}`)) {
1671
+ throw new Error('Pièce hors du dossier juridique.');
1672
+ }
1673
+ if (!fs.existsSync(absolute)) throw new Error('Pièce introuvable.');
1674
+ return absolute;
1675
+ }
1676
+
1677
+ /**
1678
+ * Le Markdown converti d'une pièce originale (chemin relatif à la racine du
1679
+ * dossier). La chronologie ne connaît une pièce que par le chemin de son
1680
+ * ORIGINAL (`document-index.cjs`, sur `originalFilesOverview`) ; retrouver son
1681
+ * `.md` reprend la même clé de correspondance que `originalFilesOverview`
1682
+ * (`documentKey` du basename), sans supposer d'emplacement fixe — le
1683
+ * converti vit sous `Fichiers convertis PieceMaker/`, mais un `.md` migré
1684
+ * depuis une version antérieure peut encore traîner à la racine.
1685
+ */
1686
+ async function convertedMarkdownRelativePath(caseRoot, originalRelativePath) {
1687
+ const key = documentKey(path.basename(String(originalRelativePath || '')));
1688
+ const files = await safeCaseFiles(caseRoot);
1689
+ return files.find((file) => path.extname(file).toLowerCase() === '.md' && documentKey(file) === key) || null;
1690
+ }
1691
+
1692
+ /**
1693
+ * Commandes candidates pour montrer un dossier du poste de travail, essayées
1694
+ * dans l'ordre jusqu'à ce que l'une démarre. Fonction pure : `platform` suit la
1695
+ * convention de `process.platform`, aucun chemin n'est concaténé dans une
1696
+ * chaîne de shell (les arguments partent tels quels à `spawn`, sans `shell`).
1697
+ */
1698
+ function revealCommands(platform, target, absolutePath) {
1699
+ if (!REVEAL_TARGETS.has(target)) throw new Error('Action de dossier inconnue.');
1700
+ const isAbsolute = platform === 'win32' ? path.win32.isAbsolute : path.posix.isAbsolute;
1701
+ if (!isAbsolute(String(absolutePath || ''))) throw new Error('Chemin de dossier invalide.');
1702
+ if (platform === 'darwin') {
1703
+ return target === 'terminal'
1704
+ ? [{ command: 'open', args: ['-a', 'Terminal', absolutePath] }]
1705
+ // `-R` révèle le dossier dans sa fenêtre parente, comme « Afficher dans le Finder ».
1706
+ : [{ command: 'open', args: ['-R', absolutePath] }];
1707
+ }
1708
+ if (platform === 'win32') {
1709
+ return target === 'terminal'
1710
+ ? [
1711
+ { command: 'wt.exe', args: ['-d', absolutePath] },
1712
+ // Repli sans Windows Terminal : PowerShell ouvre l'invite de commandes
1713
+ // dans le dossier, `-WorkingDirectory` évite tout `cd` à échapper.
1714
+ { command: 'powershell.exe', args: ['-NoProfile', '-Command', `Start-Process -FilePath cmd.exe -WorkingDirectory '${absolutePath.replace(/'/g, "''")}'`] },
1715
+ ]
1716
+ : [{ command: 'explorer.exe', args: [`/select,${absolutePath}`] }];
1717
+ }
1718
+ return target === 'terminal'
1719
+ ? [
1720
+ { command: 'x-terminal-emulator', args: [`--working-directory=${absolutePath}`] },
1721
+ { command: 'gnome-terminal', args: [`--working-directory=${absolutePath}`] },
1722
+ { command: 'konsole', args: ['--workdir', absolutePath] },
1723
+ { command: 'xterm', args: ['-e', 'sh', '-c', 'cd "$0" && exec "${SHELL:-/bin/sh}"', absolutePath] },
1724
+ ]
1725
+ : [{ command: 'xdg-open', args: [absolutePath] }];
1726
+ }
1727
+
1728
+ function spawnDetached({ command, args }) {
1729
+ return new Promise((resolve, reject) => {
1730
+ const child = spawn(command, args, { detached: true, stdio: 'ignore' });
1731
+ child.once('error', reject);
1732
+ // `spawn` confirme le démarrage : le processus survit ensuite à PieceMaker.
1733
+ child.once('spawn', () => {
1734
+ child.unref();
1735
+ resolve(command);
1736
+ });
1737
+ });
1738
+ }
1739
+
1740
+ async function revealLocalFolder(platform, target, absolutePath) {
1741
+ const candidates = revealCommands(platform, target, absolutePath);
1742
+ let lastError = null;
1743
+ for (const candidate of candidates) {
1744
+ try {
1745
+ return await spawnDetached(candidate);
1746
+ } catch (error) {
1747
+ lastError = error;
1748
+ }
1749
+ }
1750
+ throw new Error(target === 'terminal'
1751
+ ? `Aucun terminal n’a pu être ouvert sur ce poste (${lastError?.message || 'commande introuvable'}).`
1752
+ : `Le gestionnaire de fichiers n’a pas pu être ouvert (${lastError?.message || 'commande introuvable'}).`);
1753
+ }
1754
+
1755
+ // Installation à la demande des moteurs locaux depuis le tiroir de la carte de
1756
+ // configuration : un clic sur « Installer » relance l'étape d'installateur
1757
+ // correspondante en arrière-plan (non interactif), au lieu d'obliger l'utilisateur
1758
+ // à ouvrir un terminal. Chaque composant est mappé sur son étape.
1759
+ const INSTALL_STEP_IDS = {
1760
+ gliner: '03-python-gliner',
1761
+ mineru: '04-conversion-md',
1762
+ };
1763
+ const installJobs = new Map();
1764
+ let activeInstallComponent = null;
1765
+
1766
+ // Nettoie une ligne de sortie de l'installateur (codes ANSI, spinner) pour n'en
1767
+ // garder qu'un texte de progression lisible ; les lignes JSON de warmup.py
1768
+ // portent un champ `message` plus parlant que la ligne brute.
1769
+ function cleanInstallProgressLine(raw) {
1770
+ // eslint-disable-next-line no-control-regex
1771
+ const stripped = String(raw).replace(/\u001b\[[0-9;]*[A-Za-z]/g, '').replace(/[\r\t]+/g, ' ').trim();
1772
+ if (!stripped) return '';
1773
+ try {
1774
+ const parsed = JSON.parse(stripped);
1775
+ if (parsed && typeof parsed.message === 'string') return parsed.message.trim();
1776
+ } catch { /* ligne texte ordinaire */ }
1777
+ return stripped;
1778
+ }
1779
+
1780
+ function startInstallJob(repoRoot, component) {
1781
+ const stepId = INSTALL_STEP_IDS[component];
1782
+ if (!stepId) throw new Error(`Composant non installable : ${component}`);
1783
+ if (activeInstallComponent) {
1784
+ throw Object.assign(new Error(`Une installation est déjà en cours (${activeInstallComponent}).`), { conflict: true });
1785
+ }
1786
+ const installer = path.join(repoRoot, 'installer', 'bin', 'piecemaker.mjs');
1787
+ if (!fs.existsSync(installer)) throw new Error('Installateur PieceMaker introuvable.');
1788
+ const id = crypto.randomUUID();
1789
+ const job = { id, component, state: 'running', progress: 'Démarrage de l’installation…', startedAt: Date.now(), finishedAt: null, error: '' };
1790
+ installJobs.set(id, job);
1791
+ activeInstallComponent = component;
1792
+
1793
+ const child = spawn(process.execPath, [installer, '--step', stepId], {
1794
+ cwd: repoRoot,
1795
+ windowsHide: true,
1796
+ stdio: ['ignore', 'pipe', 'pipe'],
1797
+ // PIECEMAKER_YES=1 : accepte les valeurs par défaut (MinerU inclus) sans TTY.
1798
+ env: { ...process.env, PIECEMAKER_YES: '1', NO_COLOR: '1' },
1799
+ });
1800
+ const tailStderr = [];
1801
+ const onLine = (chunk) => {
1802
+ for (const part of String(chunk).split('\n')) {
1803
+ const line = cleanInstallProgressLine(part);
1804
+ if (line) job.progress = line;
1805
+ }
1806
+ };
1807
+ child.stdout.on('data', onLine);
1808
+ child.stderr.on('data', (chunk) => {
1809
+ onLine(chunk);
1810
+ tailStderr.push(String(chunk));
1811
+ while (tailStderr.join('').length > 4000) tailStderr.shift();
1812
+ });
1813
+ child.once('error', (error) => {
1814
+ job.state = 'failed';
1815
+ job.error = error.message;
1816
+ job.finishedAt = Date.now();
1817
+ activeInstallComponent = null;
1818
+ });
1819
+ child.once('close', (code) => {
1820
+ if (job.state === 'running') {
1821
+ job.state = code === 0 ? 'done' : 'failed';
1822
+ if (code !== 0) job.error = cleanInstallProgressLine(tailStderr.join('')) || `L’installateur s’est arrêté avec le code ${code}.`;
1823
+ if (code === 0) job.progress = 'Installation terminée.';
1824
+ job.finishedAt = Date.now();
1825
+ }
1826
+ activeInstallComponent = null;
1827
+ // Purge tardive : garde le job interrogeable un moment après la fin.
1828
+ setTimeout(() => installJobs.delete(id), 5 * 60 * 1000).unref?.();
1829
+ });
1830
+ return job;
1831
+ }
1832
+
1833
+ function publicInstallJob(job) {
1834
+ if (!job) return null;
1835
+ return { id: job.id, component: job.component, state: job.state, progress: job.progress, error: job.error };
1836
+ }
1837
+
1838
+ // `AAAA-MM` : seule forme acceptée pour le mois de l'export d'historique. Cette
1839
+ // valeur part ensuite dans des arguments `git log` (`--since`/`--until`) et
1840
+ // dans un nom de fichier — on la valide donc avant tout usage.
1841
+ const HISTORY_MONTH_RE = /^\d{4}-(0[1-9]|1[0-2])$/;
1842
+
1843
+ // Formats acceptés par les routes d'export « papier » (chronologie, historique).
1844
+ const EXPORT_FORMATS = new Set(['pdf', 'docx']);
1845
+ const EXPORT_CONTENT_TYPES = {
1846
+ pdf: 'application/pdf',
1847
+ docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
1848
+ };
1849
+
1850
+ function validateExportFormat(format) {
1851
+ if (!EXPORT_FORMATS.has(format)) throw new Error('Format d’export invalide (pdf ou docx).');
1852
+ return format;
1853
+ }
1854
+
1855
+ /**
1856
+ * Repli ASCII d'un nom de fichier pour l'en-tête `Content-Disposition` : les
1857
+ * vieux clients HTTP ignorent le paramètre `filename*` et n'affichent que
1858
+ * `filename`, qui doit rester dans le jeu de caractères historique de l'en-tête
1859
+ * (RFC 6266 §4.3). Nos noms contiennent accents, tiret cadratin et guillemets
1860
+ * français (« Chronologie — Dossier Martin ») : on translittère les accents,
1861
+ * puis on réduit le reste du hors-ASCII (tirets, guillemets…) à un tiret
1862
+ * simple, et on retire pour finir tout guillemet droit ou caractère de
1863
+ * contrôle — les deux interdits dans la valeur de l'en-tête.
1864
+ */
1865
+ function asciiFallbackFilename(name) {
1866
+ const withoutAccents = String(name).normalize('NFKD').replace(/[\u0300-\u036f]/g, '');
1867
+ return withoutAccents
1868
+ .replace(/[^\x20-\x7E]/g, '-')
1869
+ .replace(/["\x00-\x1F\x7F]/g, '')
1870
+ .trim() || 'export';
1871
+ }
1872
+
1873
+ /**
1874
+ * Génère un document imprimable (PDF/DOCX) à partir d'un HTML déjà rendu, via
1875
+ * pandoc (`doc-generate.cjs`) — avec repli intégral sur LibreOffice
1876
+ * (`office-to-pdf.cjs`) si pandoc est absent du poste —, et l'envoie en
1877
+ * téléchargement. Factorisée entre les deux routes d'export : seule leur
1878
+ * mise en forme HTML diffère.
1879
+ *
1880
+ * Le dossier temporaire contient le HTML source PUIS le document produit —
1881
+ * potentiellement en clair, avec les vrais noms des clients (vue cabinet
1882
+ * ré-identifiée). Il est donc systématiquement détruit après l'envoi, y
1883
+ * compris si la conversion échoue : aucun fichier ne doit survivre dans /tmp.
1884
+ *
1885
+ * Les en-têtes ne sont posés qu'une fois le document produit : tant que
1886
+ * `generateDocument` n'a pas résolu, l'appelant peut encore répondre en JSON
1887
+ * en cas d'erreur (voir les routes `/repository/chronology/export` et
1888
+ * `/history/export`).
1889
+ */
1890
+ async function sendGeneratedDocument(res, { html, filename, format }) {
1891
+ validateExportFormat(format);
1892
+
1893
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'piecemaker-export-'));
1894
+ const cleanup = () => {
1895
+ try {
1896
+ fs.rmSync(dir, { recursive: true, force: true });
1897
+ } catch {
1898
+ // Dossier temporaire : un échec de nettoyage n'a aucune conséquence
1899
+ // fonctionnelle, seulement un déchet dans /tmp — on ne le remonte pas.
1900
+ }
1901
+ };
1902
+
1903
+ let producedPath;
1904
+ try {
1905
+ const { path: generatedPath, engine } = await generateDocument(html, dir, { format });
1906
+ producedPath = generatedPath;
1907
+ // Trace serveur uniquement (jamais en en-tête ni en corps de réponse) :
1908
+ // permet de diagnostiquer, a posteriori, quel moteur a réellement produit
1909
+ // le document sur ce poste (pandoc+typst, pandoc+libreoffice, ou le
1910
+ // repli libreoffice pur si pandoc est absent).
1911
+ console.log(`[export] ${filename}.${outputExtension(format)} généré via ${engine}`);
1912
+ } catch (error) {
1913
+ cleanup();
1914
+ throw error;
1915
+ }
1916
+
1917
+ const ext = outputExtension(format);
1918
+ const fullName = `${filename}.${ext}`;
1919
+ res.setHeader('Content-Type', EXPORT_CONTENT_TYPES[format]);
1920
+ res.setHeader(
1921
+ 'Content-Disposition',
1922
+ `attachment; filename="${asciiFallbackFilename(fullName)}"; filename*=UTF-8''${encodeURIComponent(fullName)}`,
1923
+ );
1924
+
1925
+ const stream = fs.createReadStream(producedPath);
1926
+ // Le nettoyage doit survenir quoi qu'il arrive une fois l'envoi entamé : fin
1927
+ // normale (`close`, émis par un ReadStream après `end` comme après un
1928
+ // `destroy` sur erreur) ou coupure côté client (`close` sur la réponse).
1929
+ // `fs.rmSync` ci-dessus est idempotent : peu importe lequel des deux
1930
+ // déclenche `cleanup()` en premier.
1931
+ stream.once('close', cleanup);
1932
+ res.once('close', cleanup);
1933
+ // Sans écouteur, un `error` sur le flux remonterait en exception non
1934
+ // interceptée et tuerait le serveur — or il sert aussi l'administration et les
1935
+ // WebSocket. Les en-têtes sont déjà partis : on ne peut que couper.
1936
+ stream.once('error', (error) => {
1937
+ console.warn(`[export] Lecture du document généré interrompue: ${error.message}`);
1938
+ cleanup();
1939
+ res.destroy();
1940
+ });
1941
+ stream.pipe(res);
1942
+ }
1943
+
1944
+ // Une sauvegarde touche successivement l'index, le graphe matérialisé et
1945
+ // l'historique local. Cette file par dossier empêche deux requêtes admin de
1946
+ // produire un graphe correspondant à une révision intermédiaire.
1947
+ const documentMetaMutationQueues = new Map();
1948
+
1949
+ async function serializeDocumentMetaMutation(caseRoot, callback) {
1950
+ const key = path.resolve(caseRoot);
1951
+ const previous = documentMetaMutationQueues.get(key) || Promise.resolve();
1952
+ const current = previous.catch(() => {}).then(callback);
1953
+ documentMetaMutationQueues.set(key, current);
1954
+ try {
1955
+ return await current;
1956
+ } finally {
1957
+ if (documentMetaMutationQueues.get(key) === current) documentMetaMutationQueues.delete(key);
1958
+ }
1959
+ }
1960
+
1961
+ /**
1962
+ * Point de mutation testable de la chronologie. Il ne lance jamais Graphify :
1963
+ * la couche sémantique existante est relue puis la couche déterministe est
1964
+ * rematérialisée avant que la réponse puisse annoncer le succès.
1965
+ */
1966
+ async function applyDocumentMetaMutation({
1967
+ legalCase,
1968
+ relativePath,
1969
+ correction,
1970
+ homeDir,
1971
+ envFile,
1972
+ applyCorrection = applyDocumentIndexCorrection,
1973
+ rematerialize = rematerializeDeterministicLegalGraph,
1974
+ createHistoryCommit = createCommit,
1975
+ }) {
1976
+ return serializeDocumentMetaMutation(legalCase.root, async () => {
1977
+ const mutation = applyCorrection(legalCase.root, relativePath, correction);
1978
+ const graph = await rematerialize(legalCase.root, {
1979
+ semanticStaleReasons: mutation.semanticStaleReasons,
1980
+ });
1981
+ let history;
1982
+ try {
1983
+ history = await createHistoryCommit({
1984
+ casesRoot: legalCase.casesRoot,
1985
+ caseName: legalCase.caseName,
1986
+ homeDir,
1987
+ envFile,
1988
+ label: 'Correction manuelle de la chronologie',
1989
+ description: `Mise à jour déterministe de la pièce ${mutation.documentKey.slice(0, 12).toUpperCase()}.`,
1990
+ event: 'admin-document-meta-edit',
1991
+ paths: [path.relative(legalCase.root, documentIndexFile(legalCase.root)).split(path.sep).join('/')],
1992
+ waitForLockMs: 10_000,
1993
+ });
1994
+ } catch (error) {
1995
+ // La correction et le graphe sont déjà cohérents : une indisponibilité
1996
+ // de l'historique ne doit ni les annuler ni provoquer une seconde saisie.
1997
+ history = { created: false, error: error.message };
1998
+ }
1999
+ return {
2000
+ mutation,
2001
+ graph: {
2002
+ graphFile: graph.graphFile,
2003
+ staticState: 'current',
2004
+ staticRevision: graph.staticRevision,
2005
+ semanticState: graph.semanticState,
2006
+ semanticStaleReasons: graph.semanticStaleReasons,
2007
+ },
2008
+ history: {
2009
+ created: Boolean(history?.created),
2010
+ hash: history?.commit || null,
2011
+ skipped: history?.skipped || null,
2012
+ error: history?.error || null,
2013
+ },
2014
+ };
2015
+ });
2016
+ }
2017
+
2018
+ function graphStatusFromSynchronization(initialStatus, synchronized) {
2019
+ return {
2020
+ ...initialStatus,
2021
+ exists: true,
2022
+ stale: synchronized.semanticState !== 'current' || synchronized.semanticQuarantined,
2023
+ staticState: synchronized.staticState,
2024
+ semanticState: synchronized.semanticState,
2025
+ staticRevision: synchronized.staticRevision,
2026
+ semanticBaseRevision: synchronized.semanticBaseRevision,
2027
+ semanticStaleReasons: synchronized.semanticStaleReasons || [],
2028
+ semanticQuarantined: Boolean(synchronized.semanticQuarantined),
2029
+ graphFile: synchronized.graphFile,
2030
+ generatedAt: synchronized.generatedAt,
2031
+ stats: synchronized.graph?.piecemaker || null,
2032
+ registry: synchronized.registry || initialStatus.registry,
2033
+ registryStatus: synchronized.registry?.status || initialStatus.registryStatus,
2034
+ };
2035
+ }
2036
+
2037
+ /**
2038
+ * Vue admin unifiée : synchronise exclusivement la couche déterministe,
2039
+ * projette la frise depuis ce graphe, puis ré-identifie en mémoire. Toutes les
2040
+ * dépendances coûteuses sont injectables pour prouver que ce chemin de lecture
2041
+ * ne peut jamais lancer `buildLegalGraph` ni un LLM.
2042
+ */
2043
+ async function loadAdminLegalChronology({
2044
+ caseRoot,
2045
+ buildLocalChronology = buildChronology,
2046
+ readMapping = readCaseMapping,
2047
+ readGraph = (file) => readJson(file, null),
2048
+ readStatus = legalGraphStatus,
2049
+ rematerialize = rematerializeDeterministicLegalGraph,
2050
+ renderViewer = renderLegalGraphViewer,
2051
+ project = chronologyFromLegalGraph,
2052
+ }) {
2053
+ let status = await readStatus(caseRoot);
2054
+ // Au premier affichage il n'existe encore aucun manifeste. Créer la couche
2055
+ // documentaire ici reste une opération locale et déterministe ; Graphify
2056
+ // profond n'est accessible que par la route POST d'actualisation explicite.
2057
+ if (!status.exists || !readGraph(status.graphFile)) {
2058
+ const synchronized = await rematerialize(caseRoot);
2059
+ status = graphStatusFromSynchronization(status, synchronized);
2060
+ }
2061
+ const graph = readGraph(status.graphFile);
2062
+ if (!graph) throw new Error('Le graphe documentaire du dossier n’a pas pu être matérialisé.');
2063
+ const [localChronology, mappingDocument] = await Promise.all([
2064
+ buildLocalChronology(caseRoot, {
2065
+ deanonymizeLabels: true,
2066
+ includeManualDecisions: true,
2067
+ }),
2068
+ Promise.resolve(readMapping(caseRoot)),
2069
+ ]);
2070
+ const chronology = project(graph, localChronology, mappingDocument, {
2071
+ deanonymize: true,
2072
+ graphRevision: status.staticRevision,
2073
+ graphStatus: status,
2074
+ generatedAt: status.generatedAt,
2075
+ });
2076
+ if (typeof renderViewer === 'function') {
2077
+ try {
2078
+ chronology.graph.viewerHtml = await renderViewer(chronology.graph);
2079
+ } catch (error) {
2080
+ // Le renderer est un enrichissement local. La frise et les états du
2081
+ // graphe restent disponibles, sans jamais requalifier l'analyse en ready.
2082
+ chronology.graph.viewerError = String(error?.message || error);
2083
+ }
2084
+ }
2085
+ return chronology;
2086
+ }
2087
+
2088
+ /** Construction sémantique réservée à l'action admin explicite. */
2089
+ async function refreshAdminLegalGraph({
2090
+ caseRoot,
2091
+ build = buildLegalGraph,
2092
+ readStatus = legalGraphStatus,
2093
+ }) {
2094
+ await build(caseRoot, { refreshSemantic: true, force: true });
2095
+ return readStatus(caseRoot);
2096
+ }
2097
+
2098
+ function createAdminRouter({
2099
+ repoRoot = path.resolve(__dirname, '..'),
2100
+ homeDir = path.join(os.homedir(), '.piecemaker'),
2101
+ userHome = os.homedir(),
2102
+ getRuntimeStatus = () => ({}),
2103
+ fetchImpl = global.fetch,
2104
+ pickFolder = selectLocalFolder,
2105
+ claudeAssetsInstaller = installClaudeAssets,
2106
+ // Adaptation PieceMaker/CloudCLI : monté sur le serveur CloudCLI, le routeur
2107
+ // est déjà protégé par `authenticateToken`, et l'origine peut être une app
2108
+ // Electron (`file://`) ou un hôte LAN. L'appelant peut donc fournir sa propre
2109
+ // règle ; par défaut on garde la restriction locale d'origine.
2110
+ isOriginAllowed = isLocalOrigin,
2111
+ } = {}) {
2112
+ // Lazy import keeps the pure filesystem/Git helpers testable before npm
2113
+ // dependencies are installed. The running server already depends on Express.
2114
+ const express = require('express');
2115
+ const router = express.Router();
2116
+ const configFile = path.join(homeDir, 'config.json');
2117
+ const envFile = path.join(repoRoot, '.env');
2118
+ const registryConfig = () => readRegistryConfig(configFile);
2119
+ const selectedCase = (reference) => {
2120
+ const config = registryConfig();
2121
+ const legalCase = resolveCaseReference(config, reference);
2122
+ // Migration idempotente des dossiers enregistrés avant l'introduction de
2123
+ // l'arborescence métier. Le manifeste fige les noms utilisés par ce dossier.
2124
+ ensureCaseFolderStructure(legalCase.root, config);
2125
+ return legalCase;
2126
+ };
2127
+
2128
+ // Migration : l'identité de commit ne vivait que dans le `.env` du clone
2129
+ // runtime, invisible au hook d'édition qui tourne depuis le cache du plugin.
2130
+ // On la recopie une fois dans config.json, lu par resolveCommitIdentity.
2131
+ try {
2132
+ const bootConfig = readJson(configFile, {});
2133
+ const bootName = String(readEnvFile(envFile)[COMMIT_USER_NAME_KEY] || '').trim();
2134
+ if (bootName && !bootConfig?.commits?.userName) {
2135
+ bootConfig.commits = { ...(bootConfig.commits || {}), userName: bootName };
2136
+ atomicWrite(configFile, `${JSON.stringify(bootConfig, null, 2)}\n`);
2137
+ }
2138
+ } catch {
2139
+ // La migration est opportuniste ; son échec ne doit pas empêcher l'admin.
2140
+ }
2141
+
2142
+ // Réapplique au démarrage les suppressions de skills officiels : une mise à
2143
+ // jour de plugin (`claude plugin update`) survenue serveur éteint aurait pu
2144
+ // restaurer leur dossier dans le cache Claude Code.
2145
+ try {
2146
+ reapplyDeletedOfficialSkills(homeDir);
2147
+ } catch {
2148
+ // Best-effort : le balayage ne doit jamais empêcher l'admin de démarrer.
2149
+ }
2150
+
2151
+ router.use((req, res, next) => {
2152
+ res.set('Cache-Control', 'no-store');
2153
+ if (!isOriginAllowed(req.get('Origin'))) {
2154
+ return res.status(403).json({ error: 'L’administration PieceMaker est réservée à une page locale.' });
2155
+ }
2156
+ next();
2157
+ });
2158
+
2159
+ router.get('/status', (req, res) => {
2160
+ const pkg = readJson(path.join(repoRoot, 'package.json'), {});
2161
+ const files = listManagedFiles(repoRoot, homeDir, userHome);
2162
+ res.json({
2163
+ ok: true,
2164
+ version: pkg.version || 'inconnue',
2165
+ repoRoot,
2166
+ certificatesReady:
2167
+ fs.existsSync(path.join(repoRoot, 'websocket-server', 'localhost.crt')) &&
2168
+ fs.existsSync(path.join(repoRoot, 'websocket-server', 'localhost.key')),
2169
+ files: {
2170
+ skills: files.filter((file) => file.kind === 'skill').length,
2171
+ agents: files.filter((file) => file.kind === 'agent').length,
2172
+ registered: files.filter((file) => ['linked', 'copied'].includes(file.claudeCode?.state)).length,
2173
+ },
2174
+ ...getRuntimeStatus(),
2175
+ });
2176
+ });
2177
+
2178
+ router.get('/configuration', async (req, res) => {
2179
+ try {
2180
+ res.json(await configurationOverview({
2181
+ repoRoot,
2182
+ homeDir,
2183
+ userHome,
2184
+ getRuntimeStatus,
2185
+ fetchImpl,
2186
+ }));
2187
+ } catch (error) {
2188
+ res.status(500).json({ error: error.message });
2189
+ }
2190
+ });
2191
+
2192
+ router.post('/configuration/models/check', async (req, res) => {
2193
+ try {
2194
+ res.json({
2195
+ ok: true,
2196
+ model: String(req.body?.model || ''),
2197
+ checkedAt: new Date().toISOString(),
2198
+ ...await checkOllamaModelUpdate(req.body?.model, fetchImpl),
2199
+ });
2200
+ } catch (error) {
2201
+ res.status(400).json({ error: error.message });
2202
+ }
2203
+ });
2204
+
2205
+ // Installe un composant absent (GLiNER, MinerU) en relançant son étape
2206
+ // d'installateur en arrière-plan ; le tiroir suit ensuite l'avancement en
2207
+ // interrogeant GET /configuration/install?id=.
2208
+ router.post('/configuration/install', (req, res) => {
2209
+ try {
2210
+ const component = String(req.body?.component || '');
2211
+ const job = startInstallJob(repoRoot, component);
2212
+ res.json({ ok: true, job: publicInstallJob(job) });
2213
+ } catch (error) {
2214
+ res.status(error.conflict ? 409 : 400).json({ error: error.message });
2215
+ }
2216
+ });
2217
+
2218
+ router.get('/configuration/install', (req, res) => {
2219
+ const job = installJobs.get(String(req.query?.id || ''));
2220
+ if (!job) return res.status(404).json({ error: 'Installation inconnue ou expirée.' });
2221
+ res.json({ ok: true, job: publicInstallJob(job) });
2222
+ });
2223
+
2224
+ router.get('/settings', (req, res) => {
2225
+ const config = { ...defaultConfig(repoRoot, homeDir), ...readJson(configFile, {}) };
2226
+ config.caseFolderStructure = configuredCaseFolderStructure(config);
2227
+ const env = readEnvFile(envFile);
2228
+ const publicEnv = {};
2229
+ const secrets = {};
2230
+ for (const key of ENV_KEYS) {
2231
+ if (SECRET_KEYS.has(key)) secrets[key] = maskSecret(env[key]);
2232
+ else publicEnv[key] = env[key] || '';
2233
+ }
2234
+ res.json({ config, env: publicEnv, secrets });
2235
+ });
2236
+
2237
+ router.put('/settings', (req, res) => {
2238
+ try {
2239
+ const current = { ...defaultConfig(repoRoot, homeDir), ...readJson(configFile, {}) };
2240
+ current.caseFolderStructure = configuredCaseFolderStructure(current);
2241
+ const patch = req.body?.config || {};
2242
+ const next = { ...current };
2243
+
2244
+ if (patch.port !== undefined) {
2245
+ const port = Number(patch.port);
2246
+ if (!Number.isInteger(port) || port < 1024 || port > 65535) throw new Error('Le port doit être compris entre 1024 et 65535.');
2247
+ next.port = port;
2248
+ }
2249
+ if (patch.pythonPath !== undefined) next.pythonPath = String(patch.pythonPath || '').trim() || null;
2250
+ if (patch.adminTheme !== undefined) next.adminTheme = validateAdminTheme(patch.adminTheme);
2251
+ if (patch.caseFolderStructure !== undefined) {
2252
+ next.caseFolderStructure = normalizeCaseFolderStructure({
2253
+ ...current.caseFolderStructure,
2254
+ ...(patch.caseFolderStructure && typeof patch.caseFolderStructure === 'object'
2255
+ ? patch.caseFolderStructure
2256
+ : {}),
2257
+ });
2258
+ }
2259
+
2260
+ // L'identité de commit est aussi mémorisée dans config.json : le hook
2261
+ // d'édition (lancé depuis le cache du plugin) ne peut pas lire le `.env`
2262
+ // du clone runtime, mais lit config.json.
2263
+ const rawName = req.body?.env?.[COMMIT_USER_NAME_KEY];
2264
+ if (rawName !== undefined) {
2265
+ const requested = String(rawName || '').trim();
2266
+ if (requested && requested !== '********') {
2267
+ const { name } = resolveCommitIdentity({ identity: { name: requested } });
2268
+ next.commits = { ...(next.commits || {}), userName: name };
2269
+ }
2270
+ }
2271
+
2272
+ atomicWrite(configFile, `${JSON.stringify(next, null, 2)}\n`);
2273
+ updateEnvFile(envFile, req.body?.env || {}, req.body?.clearSecrets || []);
2274
+ res.json({
2275
+ ok: true,
2276
+ restartRequired: patch.port !== undefined || patch.pythonPath !== undefined,
2277
+ });
2278
+ } catch (error) {
2279
+ res.status(400).json({ error: error.message });
2280
+ }
2281
+ });
2282
+
2283
+ // Entités institutionnelles à ne jamais anonymiser — liste globale, éditable.
2284
+ // La détection GLiNER n'est pas touchée : ces termes sont écartés au moment de
2285
+ // bâtir le mapping (voir mapping.cjs / institutional-terms.cjs).
2286
+ router.get('/institutional-terms', (req, res) => {
2287
+ try {
2288
+ res.json(readInstitutionalTerms());
2289
+ } catch (error) {
2290
+ res.status(500).json({ error: error.message });
2291
+ }
2292
+ });
2293
+
2294
+ router.put('/institutional-terms', (req, res) => {
2295
+ try {
2296
+ const terms = req.body?.terms;
2297
+ if (!Array.isArray(terms)) throw new Error('Le corps doit contenir un tableau « terms ».');
2298
+ if (terms.length > 1000) throw new Error('Liste trop longue (1000 termes maximum).');
2299
+ res.json({ ok: true, ...writeInstitutionalTerms(terms) });
2300
+ } catch (error) {
2301
+ res.status(400).json({ error: error.message });
2302
+ }
2303
+ });
2304
+
2305
+ router.get('/files', (req, res) => {
2306
+ res.json({ files: listManagedFiles(repoRoot, homeDir, userHome) });
2307
+ });
2308
+
2309
+ router.post('/files', (req, res) => {
2310
+ try {
2311
+ const file = createManagedFile(repoRoot, homeDir, req.body);
2312
+ // Enregistrement immédiat auprès de Claude Code, sans manifest ni cache.
2313
+ const claudeCode = registerClaudeAsset(repoRoot, userHome, file.path);
2314
+ res.status(201).json({ ok: true, file: { ...file, claudeCode } });
2315
+ } catch (error) {
2316
+ res.status(400).json({ error: error.message });
2317
+ }
2318
+ });
2319
+
2320
+ router.post('/files/sync', (req, res) => {
2321
+ try {
2322
+ res.json({ ok: true, ...syncClaudeAssets(repoRoot, userHome) });
2323
+ } catch (error) {
2324
+ res.status(500).json({ error: error.message });
2325
+ }
2326
+ });
2327
+
2328
+ // Support du bouton « Ajouter le plugin legal Claude » (onglet Skills et
2329
+ // agents) : liste les composants PieceMaker avec leur état
2330
+ // d'enregistrement, pour le pop-up de sélection.
2331
+ router.get('/plugin/components', (req, res) => {
2332
+ try {
2333
+ const components = listPluginComponents(repoRoot, userHome);
2334
+ res.json({
2335
+ plugin: { installed: components.some((component) => component.registered), version: '' },
2336
+ components,
2337
+ });
2338
+ } catch (error) {
2339
+ res.status(500).json({ error: error.message });
2340
+ }
2341
+ });
2342
+
2343
+ // Enregistre directement dans ~/.claude
2344
+ // uniquement les composants cochés dans le pop-up — désenregistre ceux
2345
+ // décochés. `components` : chemins relatifs (piecemaker-plugin/agents/...
2346
+ // ou piecemaker-plugin/skills/.../SKILL.md) tels que renvoyés par
2347
+ // GET /plugin/components.
2348
+ router.post('/plugin/install', async (req, res) => {
2349
+ try {
2350
+ const selection = Array.isArray(req.body?.components) ? req.body.components : [];
2351
+ const plugin = await ensureClaudePluginActive({ repoRoot, userHome });
2352
+ const result = applyPluginComponentSelection(repoRoot, userHome, selection);
2353
+ res.json({ ok: true, plugin, ...result });
2354
+ } catch (error) {
2355
+ res.status(400).json({ error: error.message });
2356
+ }
2357
+ });
2358
+
2359
+ // Onglets marketplace du pop-up : liste les plugins-connecteurs d'un
2360
+ // marketplace Anthropic. `scope=legal` cible claude-for-legal (onglet
2361
+ // « Plugin legal Claude »), `scope=official` (défaut) claude-plugins-official
2362
+ // (onglet « Marketplace officiel »). Voir le commentaire de
2363
+ // listMarketplaceConnectors pour ce que la CLI expose (aucune recherche
2364
+ // distante).
2365
+ router.get('/plugin/marketplace', (req, res) => {
2366
+ try {
2367
+ const marketplace = marketplaceForScope(String(req.query?.scope || 'official'));
2368
+ res.json({ scope: req.query?.scope || 'official', marketplaceName: marketplace.name, ...listMarketplaceConnectors(captureCommand, { marketplaceName: marketplace.name }) });
2369
+ } catch (error) {
2370
+ res.status(500).json({ error: error.message });
2371
+ }
2372
+ });
2373
+
2374
+ // Enregistre (ou rafraîchit) le marketplace du scope quand le poste ne
2375
+ // l'a pas encore — bouton « Découvrir… » dédié dans chaque onglet.
2376
+ router.post('/plugin/marketplace/register', (req, res) => {
2377
+ try {
2378
+ const marketplace = marketplaceForScope(String(req.body?.scope || 'official'));
2379
+ const result = registerOfficialMarketplace(captureCommand, marketplace);
2380
+ res.json({ ok: result.ok, ...result });
2381
+ } catch (error) {
2382
+ res.status(400).json({ error: error.message });
2383
+ }
2384
+ });
2385
+
2386
+ // Installe/active/désactive les connecteurs cochés — `plugins` : ids
2387
+ // `nom@marketplace` tels que renvoyés par GET /plugin/marketplace ; `scope`
2388
+ // borne l'action au seul marketplace de l'onglet.
2389
+ router.post('/plugin/marketplace/install', (req, res) => {
2390
+ try {
2391
+ const selection = Array.isArray(req.body?.plugins) ? req.body.plugins : [];
2392
+ const marketplace = marketplaceForScope(String(req.body?.scope || 'official'));
2393
+ res.json({ ok: true, ...applyMarketplaceSelection(selection, captureCommand, { marketplaceName: marketplace.name }) });
2394
+ } catch (error) {
2395
+ res.status(400).json({ error: error.message });
2396
+ }
2397
+ });
2398
+
2399
+ router.get('/file', (req, res) => {
2400
+ try {
2401
+ res.json(readManagedFile(repoRoot, req.query.path, homeDir));
2402
+ } catch (error) {
2403
+ res.status(400).json({ error: error.message });
2404
+ }
2405
+ });
2406
+
2407
+ router.put('/file', (req, res) => {
2408
+ try {
2409
+ const relativePath = req.body?.path;
2410
+ const content = req.body?.content;
2411
+ const normalized = normalizeManagedPath(relativePath || '');
2412
+ const kind = managedFileKind(normalized);
2413
+ let effectivePath = relativePath;
2414
+ let previousPath = null;
2415
+ // Le champ `name:` du front matter sert de nom de dossier (skill) ou de
2416
+ // fichier (agent) : s'il change sur un fichier déjà présent, on renomme
2417
+ // sur le disque pour que Claude Code retrouve l'asset sous son nouveau
2418
+ // nom (un fichier pas encore créé garde le chemin de son slug initial).
2419
+ if (['skill', 'agent'].includes(kind) && typeof content === 'string') {
2420
+ const { absolute } = managedAbsolutePath(repoRoot, normalized, homeDir);
2421
+ if (fs.existsSync(absolute)) {
2422
+ const rename = renameManagedFile(repoRoot, homeDir, normalized, parseFrontMatter(content).name);
2423
+ if (rename.renamed) {
2424
+ effectivePath = rename.path;
2425
+ previousPath = rename.previous;
2426
+ }
2427
+ }
2428
+ }
2429
+ const saved = saveManagedFile(repoRoot, homeDir, effectivePath, content);
2430
+ // Un renommage laisse l'ancien lien Claude Code orphelin : on le retire
2431
+ // avant d'enregistrer le nouveau.
2432
+ if (previousPath) unregisterClaudeAsset(repoRoot, userHome, previousPath);
2433
+ res.json({
2434
+ ok: true,
2435
+ ...saved,
2436
+ renamedFrom: previousPath || undefined,
2437
+ claudeCode: registerClaudeAsset(repoRoot, userHome, saved.path),
2438
+ });
2439
+ } catch (error) {
2440
+ res.status(400).json({ error: error.message });
2441
+ }
2442
+ });
2443
+
2444
+ // Supprime un skill/agent du dépôt et retire son enregistrement Claude Code.
2445
+ // Le chemin arrive en query (`?path=`) ou dans le corps.
2446
+ router.delete('/file', (req, res) => {
2447
+ try {
2448
+ const relativePath = req.query?.path || req.body?.path;
2449
+ const deleted = deleteManagedFile(repoRoot, homeDir, relativePath);
2450
+ const claudeCode = unregisterClaudeAsset(repoRoot, userHome, deleted.path);
2451
+ res.json({ ok: true, ...deleted, claudeCode });
2452
+ } catch (error) {
2453
+ res.status(400).json({ error: error.message });
2454
+ }
2455
+ });
2456
+
2457
+ // Ajoute un fichier annexe (asset ou script) au dossier du skill sélectionné
2458
+ // et renvoie son nom, que l'éditeur insère comme lien Markdown relatif. Corps
2459
+ // brut (octet-stream) ; nom dans l'en-tête X-Filename (encodeURIComponent),
2460
+ // SKILL.md cible dans X-Skill-Path.
2461
+ router.post('/asset', express.raw({ type: 'application/octet-stream', limit: '100mb' }), (req, res) => {
2462
+ try {
2463
+ const skillPath = req.get('X-Skill-Path');
2464
+ const filename = decodeURIComponent(req.get('X-Filename') || '');
2465
+ const saved = saveManagedAsset(repoRoot, homeDir, skillPath, filename, req.body);
2466
+ res.status(201).json({ ok: true, ...saved });
2467
+ } catch (error) {
2468
+ res.status(400).json({ error: error.message });
2469
+ }
2470
+ });
2471
+
2472
+ // Supprime un unique fichier annexe d'un skill (pas le SKILL.md lui-même —
2473
+ // voir DELETE /file pour supprimer le skill entier). Chemin en query
2474
+ // (`?path=`) ou dans le corps.
2475
+ router.delete('/asset', (req, res) => {
2476
+ try {
2477
+ const relativePath = req.query?.path || req.body?.path;
2478
+ const deleted = deleteManagedAsset(repoRoot, homeDir, relativePath);
2479
+ res.json({ ok: true, ...deleted });
2480
+ } catch (error) {
2481
+ res.status(400).json({ error: error.message });
2482
+ }
2483
+ });
2484
+
2485
+ router.get('/dossiers', async (req, res) => {
2486
+ try {
2487
+ res.json({
2488
+ dossiers: await listDossiers(repoRoot, homeDir),
2489
+ tamponConfigured: fs.existsSync(path.join(homeDir, 'tampon.png')),
2490
+ });
2491
+ } catch (error) {
2492
+ res.status(500).json({ error: error.message });
2493
+ }
2494
+ });
2495
+
2496
+ router.get('/repository', async (req, res) => {
2497
+ const startedAt = performance.now();
2498
+ try {
2499
+ const config = registryConfig();
2500
+ const entries = listConfiguredCases(config);
2501
+ const overview = {
2502
+ name: 'PieceMaker',
2503
+ root: '',
2504
+ branch: 'Dossiers enregistrés',
2505
+ head: '',
2506
+ shortHead: '',
2507
+ folders: entries.map((entry) => ({
2508
+ name: entry.name,
2509
+ path: entry.id,
2510
+ location: entry.root,
2511
+ registered: entry.registered,
2512
+ })),
2513
+ changes: [],
2514
+ };
2515
+ finishAdminTiming(res, 'repository', startedAt, { folders: overview.folders.length });
2516
+ res.json(overview);
2517
+ } catch (error) {
2518
+ res.status(503).json({ error: error.message });
2519
+ }
2520
+ });
2521
+
2522
+ router.post('/repository/cases', async (req, res) => {
2523
+ try {
2524
+ const selected = await pickFolder(process.platform, userHome);
2525
+ if (!selected) return res.json({ ok: true, cancelled: true });
2526
+ const result = await registerLegalCase({
2527
+ folder: selected,
2528
+ configFile,
2529
+ repoRoot,
2530
+ homeDir,
2531
+ userHome,
2532
+ claudeAssetsInstaller,
2533
+ });
2534
+ res.status(201).json({ ok: true, ...result });
2535
+ } catch (error) {
2536
+ res.status(400).json({ error: error.message });
2537
+ }
2538
+ });
2539
+
2540
+ router.get('/repository/case', async (req, res) => {
2541
+ const startedAt = performance.now();
2542
+ try {
2543
+ const legalCase = selectedCase(req.query.case);
2544
+ const folder = await caseOverview(legalCase.casesRoot, homeDir, legalCase.caseName);
2545
+ folder.path = legalCase.id;
2546
+ folder.location = legalCase.root;
2547
+ folder.registered = legalCase.registered;
2548
+ // Les Markdown convertis vivent déjà dans l'historique ; ce cadre ne
2549
+ // présente que les pièces originales et un résumé non sensible du mapping.
2550
+ folder.originals = await listOriginalsCached(legalCase.root);
2551
+ const mapping = readCaseMapping(legalCase.root);
2552
+ folder.mapping = {
2553
+ exists: mapping.exists,
2554
+ name: path.basename(mapping.file),
2555
+ entries: Object.keys(mapping.mapping).length,
2556
+ };
2557
+ folder.branches = await historyBranches(legalCase.casesRoot, homeDir, legalCase.caseName);
2558
+ finishAdminTiming(res, 'case', startedAt, {
2559
+ changes: folder.changes,
2560
+ originals: folder.originals.length,
2561
+ });
2562
+ res.json({ folder });
2563
+ } catch (error) {
2564
+ res.status(400).json({ error: error.message });
2565
+ }
2566
+ });
2567
+
2568
+ router.get('/repository/chronology', async (req, res) => {
2569
+ const startedAt = performance.now();
2570
+ try {
2571
+ const legalCase = selectedCase(req.query.case);
2572
+ // Cette route est une vue cabinet et n'expose plus de branche publique
2573
+ // pseudonymisée. `deanonymize=0` est volontairement ignoré : les données
2574
+ // destinées au modèle empruntent les chemins internes dédiés.
2575
+ const chronology = await loadAdminLegalChronology({ caseRoot: legalCase.root });
2576
+ chronology.case = { path: legalCase.id, name: legalCase.caseName, location: legalCase.root };
2577
+ finishAdminTiming(res, 'chronology', startedAt, {
2578
+ documents: chronology.stats.documents,
2579
+ entities: chronology.stats.entities,
2580
+ graphEdges: chronology.graph.edges.length,
2581
+ });
2582
+ res.json(chronology);
2583
+ } catch (error) {
2584
+ res.status(400).json({ error: error.message });
2585
+ }
2586
+ });
2587
+
2588
+ // Seule cette action explicite peut appeler Graphify profond/LLM depuis
2589
+ // l'administration. Le manifeste passe à `building` avant l'extraction et à
2590
+ // `failed` en cas d'erreur ; un GET concurrent ne présente donc jamais
2591
+ // l'ancien fragment comme prêt.
2592
+ router.post('/repository/legal-graph/refresh', async (req, res) => {
2593
+ try {
2594
+ const legalCase = selectedCase(req.body?.case);
2595
+ const status = await refreshAdminLegalGraph({ caseRoot: legalCase.root });
2596
+ res.json({ ok: true, graph: status });
2597
+ } catch (error) {
2598
+ res.status(503).json({ error: error.message });
2599
+ }
2600
+ });
2601
+
2602
+ // Export « papier » (PDF/DOCX) de la même projection matérialisée que la
2603
+ // frise admin, ré-identifiée en mémoire pour le cabinet. Le renderer HTML du
2604
+ // graphe n'a pas d'équivalent papier et n'est donc jamais appelé ici.
2605
+ router.get('/repository/chronology/export', async (req, res) => {
2606
+ const startedAt = performance.now();
2607
+ try {
2608
+ const format = validateExportFormat(String(req.query.format || ''));
2609
+ const legalCase = selectedCase(req.query.case);
2610
+ const chronology = await loadAdminLegalChronology({
2611
+ caseRoot: legalCase.root,
2612
+ renderViewer: null,
2613
+ });
2614
+ const html = renderChronologyHtml(chronology, { caseName: legalCase.caseName });
2615
+ finishAdminTiming(res, 'chronology-export', startedAt, {
2616
+ documents: chronology.stats.documents,
2617
+ format,
2618
+ });
2619
+ await sendGeneratedDocument(res, {
2620
+ html,
2621
+ filename: `Chronologie — ${legalCase.caseName}`,
2622
+ format,
2623
+ });
2624
+ } catch (error) {
2625
+ // Une fois l'envoi du document entamé, les en-têtes sont partis : on ne
2626
+ // peut plus répondre en JSON, seulement couper la connexion.
2627
+ if (res.headersSent) return res.destroy();
2628
+ res.status(400).json({ error: error.message });
2629
+ }
2630
+ });
2631
+
2632
+ // Correction manuelle des métadonnées d'une pièce dans la chronologie —
2633
+ // nature (type), date, lieu (juridiction) et champs libres ajoutés/retirés par
2634
+ // le cabinet. Stocké avec sa provenance dans l'index documentaire que le
2635
+ // pipeline relit en préservant décisions et révisions : un re-scan rafraîchit
2636
+ // la détection sans écraser la correction.
2637
+ // `path` désigne la pièce ORIGINALE (comme le reste de la chronologie) et sert
2638
+ // seulement à calculer la clé de hachage — il n'est jamais lu. Envoyer une
2639
+ // correction entièrement vide efface l'entrée (retour aux valeurs détectées).
2640
+ router.put('/repository/document-meta', async (req, res) => {
2641
+ try {
2642
+ const legalCase = selectedCase(req.body?.case);
2643
+ const relativePath = String(req.body?.path || '').trim();
2644
+ if (!relativePath) throw new Error('Chemin de pièce manquant.');
2645
+ // Validation d'appartenance au dossier (même règle que le reste) ; on ne
2646
+ // lit pas la pièce, on refuse seulement un chemin hors racine.
2647
+ resolveCasePath(legalCase.root, relativePath);
2648
+ const body = req.body || {};
2649
+ const result = await applyDocumentMetaMutation({
2650
+ legalCase,
2651
+ relativePath,
2652
+ homeDir,
2653
+ envFile,
2654
+ correction: {
2655
+ nature: body.nature,
2656
+ dateIso: body.dateIso,
2657
+ localisation: body.localisation,
2658
+ fields: body.fields,
2659
+ reason: body.reason,
2660
+ ...(Object.hasOwn(body, 'entityDecisions')
2661
+ ? { entityDecisions: body.entityDecisions }
2662
+ : {}),
2663
+ },
2664
+ });
2665
+ res.json({
2666
+ ok: true,
2667
+ override: result.mutation.override,
2668
+ entityDecisions: result.mutation.entityDecisions,
2669
+ editRevision: result.mutation.editRevision,
2670
+ revisions: result.mutation.revisions,
2671
+ graph: result.graph,
2672
+ commit: result.history,
2673
+ });
2674
+ } catch (error) {
2675
+ res.status(400).json({ error: error.message });
2676
+ }
2677
+ });
2678
+
2679
+ // Décisions d'entité propres à une pièce. Contrairement à l'éditeur de
2680
+ // mapping global, cette route préserve l'override de métadonnées existant et
2681
+ // ne supprime ni ne renomme aucun code dans le reste du dossier.
2682
+ router.put('/repository/document-entities', async (req, res) => {
2683
+ try {
2684
+ const legalCase = selectedCase(req.body?.case);
2685
+ const relativePath = String(req.body?.path || '').trim();
2686
+ if (!relativePath) throw new Error('Chemin de pièce manquant.');
2687
+ resolveCasePath(legalCase.root, relativePath);
2688
+ const index = readDocumentIndex(legalCase.root);
2689
+ const key = stateKey(relativePath);
2690
+ const existingOverride = index.overrides?.[key] || {};
2691
+ const result = await applyDocumentMetaMutation({
2692
+ legalCase,
2693
+ relativePath,
2694
+ homeDir,
2695
+ envFile,
2696
+ correction: {
2697
+ ...existingOverride,
2698
+ reason: req.body?.reason,
2699
+ entityDecisions: req.body?.entityDecisions,
2700
+ },
2701
+ });
2702
+ res.json({
2703
+ ok: true,
2704
+ entityDecisions: result.mutation.entityDecisions,
2705
+ editRevision: result.mutation.editRevision,
2706
+ revisions: result.mutation.revisions,
2707
+ graph: result.graph,
2708
+ commit: result.history,
2709
+ });
2710
+ } catch (error) {
2711
+ res.status(400).json({ error: error.message });
2712
+ }
2713
+ });
2714
+
2715
+ // Contenu en lecture seule du Markdown converti d'une pièce — la seule
2716
+ // surface sûre : `path` est le chemin de la pièce ORIGINALE (comme partout
2717
+ // ailleurs dans la chronologie/l'aperçu), jamais lu directement. Sert
2718
+ // l'aperçu de la chronologie et le panneau droit de la correction d'entités ;
2719
+ // le fichier original (PDF/DOCX/scan) n'est jamais renvoyé par cette route.
2720
+ router.get('/repository/document', async (req, res) => {
2721
+ try {
2722
+ const legalCase = selectedCase(req.query.case);
2723
+ const mdPath = await convertedMarkdownRelativePath(legalCase.root, req.query.path);
2724
+ if (!mdPath) throw new Error('Cette pièce n’a pas encore de Markdown converti.');
2725
+ const absolute = resolveCasePath(legalCase.root, mdPath);
2726
+ const content = fs.readFileSync(absolute, 'utf8');
2727
+ res.json({
2728
+ path: mdPath,
2729
+ content: Buffer.byteLength(content, 'utf8') > MAX_MARKDOWN_BYTES
2730
+ ? `${content.slice(0, MAX_MARKDOWN_BYTES)}\n\n… (aperçu tronqué à 1 Mo)`
2731
+ : content,
2732
+ });
2733
+ } catch (error) {
2734
+ res.status(400).json({ error: error.message });
2735
+ }
2736
+ });
2737
+
2738
+ router.post('/branches', async (req, res) => {
2739
+ try {
2740
+ const legalCase = selectedCase(req.body?.case);
2741
+ const branches = await createHistoryBranch({
2742
+ casesRoot: legalCase.casesRoot,
2743
+ caseName: legalCase.caseName,
2744
+ homeDir,
2745
+ name: req.body?.name,
2746
+ });
2747
+ if (branches.skipped === 'busy') throw new Error('L’historique est occupé. Réessayez dans quelques secondes.');
2748
+ res.status(201).json({ ok: true, ...branches });
2749
+ } catch (error) {
2750
+ res.status(400).json({ error: error.message });
2751
+ }
2752
+ });
2753
+
2754
+ router.put('/branches/current', async (req, res) => {
2755
+ try {
2756
+ const legalCase = selectedCase(req.body?.case);
2757
+ const branches = await checkoutHistoryBranch({
2758
+ casesRoot: legalCase.casesRoot,
2759
+ caseName: legalCase.caseName,
2760
+ homeDir,
2761
+ name: req.body?.name,
2762
+ });
2763
+ if (branches.skipped === 'busy') throw new Error('L’historique est occupé. Réessayez dans quelques secondes.');
2764
+ res.json({ ok: true, ...branches });
2765
+ } catch (error) {
2766
+ res.status(400).json({ error: error.message });
2767
+ }
2768
+ });
2769
+
2770
+ // Ouvre le dossier juridique sélectionné (ou, à défaut, le dossier personnel)
2771
+ // dans le gestionnaire de fichiers ou le terminal du poste. Le chemin est
2772
+ // toujours résolu localement, jamais reçu tel quel du navigateur : un `path`
2773
+ // optionnel (target: 'files' seulement) désigne une pièce précise, relative à
2774
+ // la racine du dossier — `resolveCasePath` refuse toute sortie de racine.
2775
+ // Révéler dans le Finder n'affiche jamais de contenu dans le navigateur : sûr
2776
+ // même pour une pièce protégée.
2777
+ router.post('/reveal', async (req, res) => {
2778
+ try {
2779
+ const target = String(req.body?.target || '');
2780
+ if (!REVEAL_TARGETS.has(target)) throw new Error('Action de dossier inconnue.');
2781
+ const caseName = String(req.body?.case || '').trim();
2782
+ const relativePath = target === 'files' ? String(req.body?.path || '').trim() : '';
2783
+ let absolute;
2784
+ if (caseName && relativePath) {
2785
+ absolute = resolveCasePath(selectedCase(caseName).root, relativePath);
2786
+ } else {
2787
+ absolute = caseName ? selectedCase(caseName).root : userHome;
2788
+ }
2789
+ await revealLocalFolder(process.platform, target, absolute);
2790
+ res.json({ ok: true, target, path: absolute });
2791
+ } catch (error) {
2792
+ res.status(400).json({ error: error.message });
2793
+ }
2794
+ });
2795
+
2796
+ // ── Pièces originales : conversion Markdown et pipeline d'anonymisation ──
2797
+ // Les deux traitements sont longs (OCR, modèles NER) : la route rend la main
2798
+ // avec un identifiant de travail que l'administration interroge ensuite.
2799
+
2800
+ router.post('/originals/pipeline', async (req, res) => {
2801
+ try {
2802
+ const legalCase = selectedCase(req.body?.case);
2803
+ const job = await startOriginalsJob({
2804
+ casesRoot: legalCase.casesRoot,
2805
+ caseName: legalCase.caseName,
2806
+ homeDir,
2807
+ action: String(req.body?.action || ''),
2808
+ files: Array.isArray(req.body?.files) ? req.body.files : [],
2809
+ options: {
2810
+ // Sans `force`, un travail sans sélection ne refait que les pièces
2811
+ // dont le Markdown ou le scan PII manque.
2812
+ force: req.body?.force === true,
2813
+ engine: String(req.body?.engine || '').trim() || undefined,
2814
+ mode: String(req.body?.mode || '').trim() || undefined,
2815
+ lang: String(req.body?.lang || '').trim() || undefined,
2816
+ },
2817
+ });
2818
+ job.reference = legalCase.id;
2819
+ res.status(202).json({ ok: true, job });
2820
+ } catch (error) {
2821
+ res.status(400).json({ error: error.message });
2822
+ }
2823
+ });
2824
+
2825
+ router.get('/originals/job', (req, res) => {
2826
+ const job = getJob(req.query.id);
2827
+ if (!job) return res.status(404).json({ error: 'Travail inconnu ou expiré.' });
2828
+ res.json({ job });
2829
+ });
2830
+
2831
+ router.delete('/originals/job', (req, res) => {
2832
+ const job = cancelOriginalsJob(req.query.id);
2833
+ if (!job) return res.status(404).json({ error: 'Aucun traitement en cours pour cet identifiant.' });
2834
+ res.json({ ok: true, job });
2835
+ });
2836
+
2837
+ // ── Protection des pièces ────────────────────────────────────────────────
2838
+ // La protection est une propriété du fichier, pas de son emplacement : c'est
2839
+ // ici qu'on la décide, et les hooks Claude Code l'appliquent
2840
+ // (`piecemaker-plugin/scripts/protect-originals.mjs`). Tout est protégé par
2841
+ // défaut ; `protection.json` n'enregistre que les exceptions.
2842
+
2843
+ router.get('/protection', async (req, res) => {
2844
+ const startedAt = performance.now();
2845
+ try {
2846
+ const legalCase = selectedCase(req.query.case);
2847
+ const files = await listOriginalsCached(legalCase.root);
2848
+ finishAdminTiming(res, 'protection', startedAt, { files: files.length });
2849
+ res.json({
2850
+ case: legalCase.id,
2851
+ files,
2852
+ protectedCount: files.filter((file) => file.protected).length,
2853
+ resourceCount: files.filter((file) => file.resource).length,
2854
+ truncated: Boolean(files.truncated),
2855
+ });
2856
+ } catch (error) {
2857
+ res.status(400).json({ error: error.message });
2858
+ }
2859
+ });
2860
+
2861
+ router.put('/protection', (req, res) => {
2862
+ try {
2863
+ if (!Array.isArray(req.body?.unprotected)) {
2864
+ throw new Error('« unprotected » doit être la liste des pièces laissées accessibles à l’IA.');
2865
+ }
2866
+ if (req.body?.resources !== undefined && !Array.isArray(req.body.resources)) {
2867
+ throw new Error('« resources » doit être la liste des pièces marquées comme ressources.');
2868
+ }
2869
+ const legalCase = selectedCase(req.body?.case);
2870
+ const saved = writeProtection(legalCase.root, {
2871
+ unprotected: req.body.unprotected,
2872
+ resources: req.body.resources,
2873
+ });
2874
+ invalidateOriginals(legalCase.root);
2875
+ res.json({
2876
+ ok: true,
2877
+ case: legalCase.id,
2878
+ unprotected: [...saved.unprotected],
2879
+ resources: [...saved.resources],
2880
+ });
2881
+ } catch (error) {
2882
+ res.status(400).json({ error: error.message });
2883
+ }
2884
+ });
2885
+
2886
+ // Le mapping du dossier est le seul fichier de l'administration qui contient
2887
+ // des données personnelles en clair : il n'est jamais journalisé, seulement
2888
+ // rendu au navigateur local qui l'a demandé.
2889
+ router.get('/mapping', (req, res) => {
2890
+ try {
2891
+ const legalCase = selectedCase(req.query.case);
2892
+ const mapping = readCaseMapping(legalCase.root);
2893
+ res.json({
2894
+ case: legalCase.id,
2895
+ name: path.basename(mapping.file),
2896
+ exists: mapping.exists,
2897
+ mapping: mapping.mapping,
2898
+ reverse_mapping: mapping.reverse_mapping,
2899
+ informations_dossier: mapping.informations_dossier,
2900
+ });
2901
+ } catch (error) {
2902
+ res.status(400).json({ error: error.message });
2903
+ }
2904
+ });
2905
+
2906
+ router.put('/mapping', async (req, res) => {
2907
+ try {
2908
+ if (!req.body?.mapping || typeof req.body.mapping !== 'object' || Array.isArray(req.body.mapping)) {
2909
+ throw new Error('Le mapping doit être un objet { entité: code }.');
2910
+ }
2911
+ const legalCase = selectedCase(req.body?.case);
2912
+ const saved = saveCaseMapping(legalCase.root, {
2913
+ mapping: req.body.mapping,
2914
+ reverse_mapping: req.body.reverse_mapping,
2915
+ ...(req.body.informations_dossier !== undefined
2916
+ ? { informations_dossier: req.body.informations_dossier }
2917
+ : {}),
2918
+ });
2919
+ // Un renommage garde les codes stables ; une suppression peut en revanche
2920
+ // modifier les parties ou le corpus. Le synchroniseur compare les
2921
+ // signatures et met en quarantaine l'ancien fragment si nécessaire,
2922
+ // toujours sans lancer le LLM.
2923
+ const graph = await rematerializeDeterministicLegalGraph(legalCase.root);
2924
+ const commit = await createCommit({
2925
+ casesRoot: legalCase.casesRoot,
2926
+ caseName: legalCase.caseName,
2927
+ homeDir,
2928
+ label: 'Modification du mapping d’anonymisation',
2929
+ event: 'admin-mapping-edit',
2930
+ paths: [path.relative(legalCase.root, saved.file).split(path.sep).join('/')],
2931
+ waitForLockMs: 10_000,
2932
+ });
2933
+ if (commit.skipped === 'busy') throw new Error('Mapping enregistré, mais historique occupé : commit automatique non créé.');
2934
+ res.json({
2935
+ ok: true,
2936
+ case: legalCase.id,
2937
+ name: path.basename(saved.file),
2938
+ exists: true,
2939
+ mapping: saved.mapping,
2940
+ reverse_mapping: saved.reverse_mapping,
2941
+ informations_dossier: saved.informations_dossier,
2942
+ graph: {
2943
+ staticState: graph.staticState,
2944
+ staticRevision: graph.staticRevision,
2945
+ semanticState: graph.semanticState,
2946
+ semanticStaleReasons: graph.semanticStaleReasons,
2947
+ semanticQuarantined: graph.semanticQuarantined,
2948
+ },
2949
+ commit: { created: commit.created, hash: commit.commit || null },
2950
+ });
2951
+ } catch (error) {
2952
+ res.status(400).json({ error: error.message });
2953
+ }
2954
+ });
2955
+
2956
+ router.post('/mapping/rebuild', async (req, res) => {
2957
+ try {
2958
+ const legalCase = selectedCase(req.body?.case);
2959
+ const rebuilt = await rebuildCaseMapping(legalCase.root);
2960
+ const commit = await createCommit({
2961
+ casesRoot: legalCase.casesRoot,
2962
+ caseName: legalCase.caseName,
2963
+ homeDir,
2964
+ label: 'Régénération du mapping d’anonymisation',
2965
+ event: 'admin-mapping-rebuild',
2966
+ paths: [path.relative(legalCase.root, rebuilt.file).split(path.sep).join('/')],
2967
+ waitForLockMs: 10_000,
2968
+ });
2969
+ if (commit.skipped === 'busy') throw new Error('Mapping régénéré, mais historique occupé : commit automatique non créé.');
2970
+ res.json({
2971
+ ok: true,
2972
+ case: legalCase.id,
2973
+ name: path.basename(rebuilt.file),
2974
+ added: rebuilt.added,
2975
+ total: rebuilt.total,
2976
+ mapping: rebuilt.mapping,
2977
+ reverse_mapping: rebuilt.reverse_mapping,
2978
+ informations_dossier: rebuilt.informations_dossier,
2979
+ commit: { created: commit.created, hash: commit.commit || null },
2980
+ });
2981
+ } catch (error) {
2982
+ res.status(400).json({ error: error.message });
2983
+ }
2984
+ });
2985
+
2986
+ router.get('/history', async (req, res) => {
2987
+ const startedAt = performance.now();
2988
+ try {
2989
+ const limit = Math.min(Math.max(Number(req.query.limit) || 100, 1), 250);
2990
+ const legalCase = selectedCase(req.query.case);
2991
+ const history = await listHistory(legalCase.casesRoot, homeDir, { limit, caseName: legalCase.caseName });
2992
+ finishAdminTiming(res, 'history', startedAt, { commits: history.length, limit });
2993
+ res.json({ history });
2994
+ } catch (error) {
2995
+ res.status(400).json({ error: error.message });
2996
+ }
2997
+ });
2998
+
2999
+ // Export « papier » (PDF/DOCX) de la feuille de temps d'un mois donné.
3000
+ router.get('/history/export', async (req, res) => {
3001
+ const startedAt = performance.now();
3002
+ try {
3003
+ const format = validateExportFormat(String(req.query.format || ''));
3004
+ const month = String(req.query.month || '');
3005
+ if (!HISTORY_MONTH_RE.test(month)) throw new Error('Mois invalide (attendu AAAA-MM).');
3006
+ const legalCase = selectedCase(req.query.case);
3007
+
3008
+ // Fenêtre du mois, bornes incluses. `--until` de git est INCLUSIF :
3009
+ // prendre le 1er du mois suivant compterait deux fois un commit tombant
3010
+ // exactement à minuit, à cheval entre les exports de deux mois
3011
+ // consécutifs. On borne donc au dernier instant du mois lui-même, le
3012
+ // dernier jour se déduisant du jour 0 du mois suivant.
3013
+ const [year, monthNum] = month.split('-').map(Number);
3014
+ const lastDay = new Date(Date.UTC(year, monthNum, 0)).getUTCDate();
3015
+ const since = `${month}-01`;
3016
+ const until = `${month}-${String(lastDay).padStart(2, '0')}T23:59:59`;
3017
+
3018
+ const entries = await listHistoryPeriod(legalCase.casesRoot, homeDir, {
3019
+ caseName: legalCase.caseName,
3020
+ since,
3021
+ until,
3022
+ });
3023
+ const html = renderHistoryHtml(entries, { caseName: legalCase.caseName, month });
3024
+ finishAdminTiming(res, 'history-export', startedAt, { commits: entries.length, format });
3025
+ await sendGeneratedDocument(res, {
3026
+ html,
3027
+ filename: `Feuille de temps ${month} — ${legalCase.caseName}`,
3028
+ format,
3029
+ });
3030
+ } catch (error) {
3031
+ if (res.headersSent) return res.destroy();
3032
+ res.status(400).json({ error: error.message });
3033
+ }
3034
+ });
3035
+
3036
+ // Mois disposant d'au moins un commit, du plus récent au plus ancien —
3037
+ // alimente le menu déroulant de l'export mensuel côté admin.
3038
+ router.get('/history/months', async (req, res) => {
3039
+ try {
3040
+ const legalCase = selectedCase(req.query.case);
3041
+ const months = await historyMonths(legalCase.casesRoot, homeDir, { caseName: legalCase.caseName });
3042
+ res.json({ months });
3043
+ } catch (error) {
3044
+ res.status(400).json({ error: error.message });
3045
+ }
3046
+ });
3047
+
3048
+ router.get('/revision', async (req, res) => {
3049
+ const startedAt = performance.now();
3050
+ try {
3051
+ const hash = String(req.query.hash || '');
3052
+ const filePath = String(req.query.path || '');
3053
+ const snapshot = String(req.query.snapshot || '');
3054
+ const legalCase = selectedCase(req.query.case);
3055
+ const revision = hash === 'WORKTREE'
3056
+ ? await worktreeDetails(legalCase.casesRoot, homeDir, legalCase.caseName, filePath, snapshot)
3057
+ : await revisionDetails(legalCase.casesRoot, homeDir, legalCase.caseName, hash, filePath);
3058
+ finishAdminTiming(res, 'revision', startedAt, {
3059
+ files: revision.filesCount,
3060
+ patchBytes: Buffer.byteLength(revision.patch || '', 'utf8'),
3061
+ truncated: revision.truncated,
3062
+ });
3063
+ res.json(revision);
3064
+ } catch (error) {
3065
+ res.status(400).json({ error: error.message });
3066
+ }
3067
+ });
3068
+
3069
+ router.post('/commits', async (req, res) => {
3070
+ try {
3071
+ const legalCase = selectedCase(req.body?.case);
3072
+ const label = String(req.body?.label || 'Commit manuel').trim().slice(0, 140);
3073
+ const description = String(req.body?.description || '').trim().slice(0, 4000);
3074
+ // Sélection de fichiers optionnelle : `paths` absent, vide ou non-tableau
3075
+ // laisse `createCommit` sans cette clé, donc le comportement inchangé
3076
+ // (commit du dossier entier). Quand une sélection est fournie, chaque
3077
+ // chemin est validé par `validateRelativeSafePath` (déjà appelée en
3078
+ // interne par `createCommit`/`buildSelectedTree`, commits.cjs) : toute
3079
+ // évasion du dossier ou fichier protégé y lève une erreur explicite,
3080
+ // remontée en 400 par le catch ci-dessous — pas de revalidation ici.
3081
+ const rawPaths = req.body?.paths;
3082
+ const paths = Array.isArray(rawPaths) && rawPaths.length ? rawPaths : null;
3083
+ const result = await createCommit({
3084
+ casesRoot: legalCase.casesRoot,
3085
+ caseName: legalCase.caseName,
3086
+ homeDir,
3087
+ label,
3088
+ description,
3089
+ event: 'manual',
3090
+ waitForLockMs: 10_000,
3091
+ ...(paths ? { paths } : {}),
3092
+ });
3093
+ if (result.skipped === 'busy') throw new Error('L’historique est occupé. Réessayez dans quelques secondes.');
3094
+ res.status(result.created ? 201 : 200).json({ ok: true, ...result });
3095
+ } catch (error) {
3096
+ res.status(400).json({ error: error.message });
3097
+ }
3098
+ });
3099
+
3100
+ router.post('/restore', async (req, res) => {
3101
+ try {
3102
+ if (req.body?.confirm !== true) {
3103
+ return res.status(400).json({ error: 'La confirmation explicite de la restauration est requise.' });
3104
+ }
3105
+ const legalCase = selectedCase(req.body?.case);
3106
+ const result = await restoreRevision({
3107
+ casesRoot: legalCase.casesRoot,
3108
+ caseName: legalCase.caseName,
3109
+ homeDir,
3110
+ hash: req.body?.hash,
3111
+ });
3112
+ res.json({ ok: true, ...result });
3113
+ } catch (error) {
3114
+ res.status(400).json({ error: error.message });
3115
+ }
3116
+ });
3117
+
3118
+ router.get('/telegram', (req, res) => {
3119
+ res.json(getTelegramState({ repoRoot, homeDir, userHome }));
3120
+ });
3121
+
3122
+ router.put('/telegram', (req, res) => {
3123
+ try {
3124
+ res.json({ ok: true, ...saveTelegramConfig({ repoRoot, homeDir, userHome }, req.body) });
3125
+ } catch (error) {
3126
+ res.status(400).json({ error: error.message });
3127
+ }
3128
+ });
3129
+
3130
+ router.post('/telegram/:role/:action', (req, res) => {
3131
+ try {
3132
+ res.json(controlTelegram({ repoRoot, homeDir, userHome }, req.params.role, req.params.action));
3133
+ } catch (error) {
3134
+ res.status(400).json({ error: error.message });
3135
+ }
3136
+ });
3137
+
3138
+ router.put('/telegram/dossiers/:id', (req, res) => {
3139
+ try {
3140
+ res.json({ ok: true, dossier: saveDossierBot({ repoRoot, homeDir, userHome }, req.params.id, req.body) });
3141
+ } catch (error) {
3142
+ res.status(400).json({ error: error.message });
3143
+ }
3144
+ });
3145
+
3146
+ router.post('/telegram/dossiers/:id/:action', (req, res) => {
3147
+ try {
3148
+ res.json(controlDossierBot({ repoRoot, homeDir, userHome }, req.params.id, req.params.action));
3149
+ } catch (error) {
3150
+ res.status(400).json({ error: error.message });
3151
+ }
3152
+ });
3153
+
3154
+ return router;
3155
+ }
3156
+
3157
+ module.exports = {
3158
+ loadAdminLegalChronology,
3159
+ applyDocumentMetaMutation,
3160
+ applyMarketplaceSelection,
3161
+ applyPluginComponentSelection,
3162
+ checkOllamaModelUpdate,
3163
+ configurationOverview,
3164
+ createLegalCase,
3165
+ createAdminRouter,
3166
+ createManagedFile,
3167
+ deleteManagedAsset,
3168
+ deleteManagedFile,
3169
+ ensureClaudePluginActive,
3170
+ folderPickerCommands,
3171
+ installedPluginSkills,
3172
+ reapplyDeletedOfficialSkills,
3173
+ installClaudeAssets,
3174
+ isLocalOrigin,
3175
+ listDossiers,
3176
+ listManagedFiles,
3177
+ listMarketplaceConnectors,
3178
+ listPluginComponents,
3179
+ listRegisteredMarketplaces,
3180
+ listSkillAssets,
3181
+ managedFileKind,
3182
+ normalizeAgentModel,
3183
+ normalizeAgentTools,
3184
+ readManagedFile,
3185
+ refreshAdminLegalGraph,
3186
+ registerLegalCase,
3187
+ registerOfficialMarketplace,
3188
+ renameManagedFile,
3189
+ revealCommands,
3190
+ saveManagedAsset,
3191
+ saveManagedFile,
3192
+ selectLocalFolder,
3193
+ updateEnvFile,
3194
+ validateAdminTheme,
3195
+ };