@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.
- package/LICENSE +718 -0
- package/README.md +80 -0
- package/dist/Mistral-Icon-Gradient-RGB.svg +12 -0
- package/dist/api-docs.html +901 -0
- package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/abnfDiagram-N423BO3Z-DjEx6DRO.js +1 -0
- package/dist/assets/arc-DYt0oU_6.js +1 -0
- package/dist/assets/architectureDiagram-T3A2C74G-Dn6DhYDe.js +36 -0
- package/dist/assets/blockDiagram-VBNYF7ZC-CsvhTLZi.js +132 -0
- package/dist/assets/c4Diagram-5PPSVZJV-BuPnjHm6.js +10 -0
- package/dist/assets/channel-Czt0N91A.js +1 -0
- package/dist/assets/chunk-2GRJ4B5K-BZJJc1-J.js +1 -0
- package/dist/assets/chunk-2Q5K7J3B-6stdrDbe.js +1 -0
- package/dist/assets/chunk-5RXB4S5H-D5UwN5cg.js +231 -0
- package/dist/assets/chunk-5VM5RSS4-BDB8AQ_I.js +15 -0
- package/dist/assets/chunk-6Q2QTUOP-RhjQ-NDz.js +88 -0
- package/dist/assets/chunk-GF5L2VYU-CJuiA7R4.js +206 -0
- package/dist/assets/chunk-JWPE2WC7-DjFkIZqh.js +1 -0
- package/dist/assets/chunk-KBJHAD2P-BeSQsoqv.js +1 -0
- package/dist/assets/chunk-RYQCIY6F-CPgwsb0o.js +1 -0
- package/dist/assets/chunk-XXDRQBXY-CMMVEsX3.js +1 -0
- package/dist/assets/classDiagram-JCYQIIEL-BrnQyrwR.js +1 -0
- package/dist/assets/classDiagram-v2-OCEON4UE-BrnQyrwR.js +1 -0
- package/dist/assets/cose-bilkent-JH36ORCC-XAdYMfi5.js +1 -0
- package/dist/assets/cynefin-VYW2F7L2-d2GpDBn-.js +178 -0
- package/dist/assets/cynefinDiagram-MW4NZA55-CGtqUrU1.js +62 -0
- package/dist/assets/cytoscape.esm-C10L9Icf.js +331 -0
- package/dist/assets/dagre-VZM6K2ZE-BxujKIsf.js +4 -0
- package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/assets/diagram-7IWD3JNH-CJn9hobL.js +30 -0
- package/dist/assets/diagram-B4RE2ZJO-DF0Cnjzz.js +3 -0
- package/dist/assets/diagram-LBJQPF4R-Bq4d3NfR.js +24 -0
- package/dist/assets/diagram-Q27KOJAE-DDdfoXt-.js +24 -0
- package/dist/assets/diagram-UB23O5K3-TSFk7FuJ.js +41 -0
- package/dist/assets/ebnfDiagram-BXEA7PRR-DRvm2Wy9.js +1 -0
- package/dist/assets/erDiagram-JOGREHBK-BmdN66-A.js +85 -0
- package/dist/assets/flowDiagram-UKHOOZJN-Dsp5LwJz.js +156 -0
- package/dist/assets/ganttDiagram-PKOTCBZU-CYfCQ4Yi.js +292 -0
- package/dist/assets/gitGraphDiagram-DS77QQ5N-DEzmuv8v.js +106 -0
- package/dist/assets/graph-Cx-EqisW.js +1 -0
- package/dist/assets/index-CqFxV_pT.css +32 -0
- package/dist/assets/index-dWwHeqcq.js +4989 -0
- package/dist/assets/infoDiagram-6WML65LV-B1EfV5Bm.js +2 -0
- package/dist/assets/init-Gi6I4Gst.js +1 -0
- package/dist/assets/ishikawaDiagram-WSZJBQD7-OuQJfUii.js +70 -0
- package/dist/assets/journeyDiagram-NVQOT4AX-DHIilb_4.js +139 -0
- package/dist/assets/kanban-definition-27J2QSJJ-D4CvKWBP.js +89 -0
- package/dist/assets/layout-D6BstSzF.js +1 -0
- package/dist/assets/linear-i_Nzyzap.js +1 -0
- package/dist/assets/map-CV9uif0g.js +1 -0
- package/dist/assets/mermaid.core-DbSHejzG.js +312 -0
- package/dist/assets/mindmap-definition-FAOFIHXS-DqhC-BPZ.js +96 -0
- package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/assets/pegDiagram-VL7TDLO6-pBqvOEvm.js +1 -0
- package/dist/assets/pieDiagram-7S7Q4E2Y-BH53iuaz.js +39 -0
- package/dist/assets/quadrantDiagram-CIZ2JOQS-CvW9ldJo.js +7 -0
- package/dist/assets/railroadDiagram-AXF67PYL-CERi66oR.js +1 -0
- package/dist/assets/requirementDiagram-LRYGKXZP-DbcP4MGI.js +84 -0
- package/dist/assets/sankeyDiagram-W5VNT64P-DO9bPHWO.js +40 -0
- package/dist/assets/sequenceDiagram-SI44F4Z6-D3zd2eJS.js +162 -0
- package/dist/assets/server.browser-CtX3Hdl5.js +27 -0
- package/dist/assets/sizeCapture-X5ZJPWSS-mcEXJiem.js +1 -0
- package/dist/assets/stateDiagram-OKZ733FA-DMqQgpiD.js +1 -0
- package/dist/assets/stateDiagram-v2-UEYNNEHI-DprF_P9y.js +1 -0
- package/dist/assets/swimlanes-SLNWSIFB-Cb8rIhB1.js +2 -0
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-C6NVYMho.js +8 -0
- package/dist/assets/timeline-definition-Z64GVDOM-BQ4r_QsK.js +120 -0
- package/dist/assets/vendor-codemirror-CYrSt7-U.js +41 -0
- package/dist/assets/vendor-react-BGZc9oRE.js +59 -0
- package/dist/assets/vendor-xterm-CS4rQ5Mr.js +66 -0
- package/dist/assets/vennDiagram-T6HMQDX7-BEkKGTum.js +34 -0
- package/dist/assets/wardleyDiagram-T6FBY63Y-kJwmgH-h.js +78 -0
- package/dist/assets/xychartDiagram-ELKLHX3M-CSJRTKCr.js +7 -0
- package/dist/clear-cache.html +85 -0
- package/dist/convert-icons.md +53 -0
- package/dist/favicon.png +0 -0
- package/dist/favicon.svg +9 -0
- package/dist/generate-icons.js +49 -0
- package/dist/icons/claude-ai-icon.svg +1 -0
- package/dist/icons/codex-white.svg +3 -0
- package/dist/icons/codex.svg +3 -0
- package/dist/icons/cursor-white.svg +12 -0
- package/dist/icons/cursor.svg +1 -0
- package/dist/icons/icon-128x128.png +0 -0
- package/dist/icons/icon-128x128.svg +12 -0
- package/dist/icons/icon-144x144.png +0 -0
- package/dist/icons/icon-144x144.svg +12 -0
- package/dist/icons/icon-152x152.png +0 -0
- package/dist/icons/icon-152x152.svg +12 -0
- package/dist/icons/icon-192x192.png +0 -0
- package/dist/icons/icon-192x192.svg +12 -0
- package/dist/icons/icon-384x384.png +0 -0
- package/dist/icons/icon-384x384.svg +12 -0
- package/dist/icons/icon-512x512.png +0 -0
- package/dist/icons/icon-512x512.svg +12 -0
- package/dist/icons/icon-72x72.png +0 -0
- package/dist/icons/icon-72x72.svg +12 -0
- package/dist/icons/icon-96x96.png +0 -0
- package/dist/icons/icon-96x96.svg +12 -0
- package/dist/icons/icon-template.svg +12 -0
- package/dist/index.html +60 -0
- package/dist/logo-128.png +0 -0
- package/dist/logo-256.png +0 -0
- package/dist/logo-32.png +0 -0
- package/dist/logo-512.png +0 -0
- package/dist/logo-64.png +0 -0
- package/dist/logo.svg +17 -0
- package/dist/manifest.json +61 -0
- package/dist/screenshots/cli-selection.png +0 -0
- package/dist/screenshots/desktop-main.png +0 -0
- package/dist/screenshots/mobile-chat.png +0 -0
- package/dist/screenshots/tools-modal.png +0 -0
- package/dist/sw.js +125 -0
- package/dist-server/server/index.js +345 -0
- package/dist-server/server/index.js.map +1 -0
- package/dist-server/server/load-env.js +46 -0
- package/dist-server/server/load-env.js.map +1 -0
- package/dist-server/server/modules/agent/agent.module.js +38 -0
- package/dist-server/server/modules/agent/agent.module.js.map +1 -0
- package/dist-server/server/modules/agent/agent.routes.js +1161 -0
- package/dist-server/server/modules/agent/agent.routes.js.map +1 -0
- package/dist-server/server/modules/agent/index.js +3 -0
- package/dist-server/server/modules/agent/index.js.map +1 -0
- package/dist-server/server/modules/agent/tests/agent.routes.test.js +176 -0
- package/dist-server/server/modules/agent/tests/agent.routes.test.js.map +1 -0
- package/dist-server/server/modules/assets/assets.routes.js +130 -0
- package/dist-server/server/modules/assets/assets.routes.js.map +1 -0
- package/dist-server/server/modules/assets/index.js +4 -0
- package/dist-server/server/modules/assets/index.js.map +1 -0
- package/dist-server/server/modules/assets/services/image-assets.service.js +95 -0
- package/dist-server/server/modules/assets/services/image-assets.service.js.map +1 -0
- package/dist-server/server/modules/assets/tests/image-assets.service.test.js +56 -0
- package/dist-server/server/modules/assets/tests/image-assets.service.test.js.map +1 -0
- package/dist-server/server/modules/auth/auth.middleware.js +133 -0
- package/dist-server/server/modules/auth/auth.middleware.js.map +1 -0
- package/dist-server/server/modules/auth/auth.module.js +29 -0
- package/dist-server/server/modules/auth/auth.module.js.map +1 -0
- package/dist-server/server/modules/auth/auth.routes.js +45 -0
- package/dist-server/server/modules/auth/auth.routes.js.map +1 -0
- package/dist-server/server/modules/auth/auth.service.js +113 -0
- package/dist-server/server/modules/auth/auth.service.js.map +1 -0
- package/dist-server/server/modules/auth/index.js +9 -0
- package/dist-server/server/modules/auth/index.js.map +1 -0
- package/dist-server/server/modules/auth/tests/auth.service.test.js +80 -0
- package/dist-server/server/modules/auth/tests/auth.service.test.js.map +1 -0
- package/dist-server/server/modules/browser-use/browser-use-mcp.js +352 -0
- package/dist-server/server/modules/browser-use/browser-use-mcp.js.map +1 -0
- package/dist-server/server/modules/browser-use/browser-use-mcp.routes.js +107 -0
- package/dist-server/server/modules/browser-use/browser-use-mcp.routes.js.map +1 -0
- package/dist-server/server/modules/browser-use/browser-use-runtime.js +10 -0
- package/dist-server/server/modules/browser-use/browser-use-runtime.js.map +1 -0
- package/dist-server/server/modules/browser-use/browser-use.routes.js +93 -0
- package/dist-server/server/modules/browser-use/browser-use.routes.js.map +1 -0
- package/dist-server/server/modules/browser-use/browser-use.service.js +686 -0
- package/dist-server/server/modules/browser-use/browser-use.service.js.map +1 -0
- package/dist-server/server/modules/browser-use/index.js +11 -0
- package/dist-server/server/modules/browser-use/index.js.map +1 -0
- package/dist-server/server/modules/browser-use/tests/browser-use.service.test.js +8 -0
- package/dist-server/server/modules/browser-use/tests/browser-use.service.test.js.map +1 -0
- package/dist-server/server/modules/cli/cli.js +17 -0
- package/dist-server/server/modules/cli/cli.js.map +1 -0
- package/dist-server/server/modules/cli/cli.module.js +93 -0
- package/dist-server/server/modules/cli/cli.module.js.map +1 -0
- package/dist-server/server/modules/cli/cli.service.js +230 -0
- package/dist-server/server/modules/cli/cli.service.js.map +1 -0
- package/dist-server/server/modules/cli/index.js +3 -0
- package/dist-server/server/modules/cli/index.js.map +1 -0
- package/dist-server/server/modules/cli/pwa-launcher.js +116 -0
- package/dist-server/server/modules/cli/pwa-launcher.js.map +1 -0
- package/dist-server/server/modules/cli/sandbox.service.js +332 -0
- package/dist-server/server/modules/cli/sandbox.service.js.map +1 -0
- package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js +154 -0
- package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js.map +1 -0
- package/dist-server/server/modules/cli/tests/cli.service.test.js +91 -0
- package/dist-server/server/modules/cli/tests/cli.service.test.js.map +1 -0
- package/dist-server/server/modules/cli/tests/sandbox.service.test.js +55 -0
- package/dist-server/server/modules/cli/tests/sandbox.service.test.js.map +1 -0
- package/dist-server/server/modules/commands/commands.module.js +20 -0
- package/dist-server/server/modules/commands/commands.module.js.map +1 -0
- package/dist-server/server/modules/commands/commands.routes.js +498 -0
- package/dist-server/server/modules/commands/commands.routes.js.map +1 -0
- package/dist-server/server/modules/commands/index.js +3 -0
- package/dist-server/server/modules/commands/index.js.map +1 -0
- package/dist-server/server/modules/commands/tests/commands.test.js +91 -0
- package/dist-server/server/modules/commands/tests/commands.test.js.map +1 -0
- package/dist-server/server/modules/database/connection.js +125 -0
- package/dist-server/server/modules/database/connection.js.map +1 -0
- package/dist-server/server/modules/database/index.js +23 -0
- package/dist-server/server/modules/database/index.js.map +1 -0
- package/dist-server/server/modules/database/init-db.js +18 -0
- package/dist-server/server/modules/database/init-db.js.map +1 -0
- package/dist-server/server/modules/database/migrations.js +465 -0
- package/dist-server/server/modules/database/migrations.js.map +1 -0
- package/dist-server/server/modules/database/repositories/api-keys.js +72 -0
- package/dist-server/server/modules/database/repositories/api-keys.js.map +1 -0
- package/dist-server/server/modules/database/repositories/app-config.js +47 -0
- package/dist-server/server/modules/database/repositories/app-config.js.map +1 -0
- package/dist-server/server/modules/database/repositories/credentials.js +68 -0
- package/dist-server/server/modules/database/repositories/credentials.js.map +1 -0
- package/dist-server/server/modules/database/repositories/github-tokens.js +54 -0
- package/dist-server/server/modules/database/repositories/github-tokens.js.map +1 -0
- package/dist-server/server/modules/database/repositories/notification-channel-endpoints.js +100 -0
- package/dist-server/server/modules/database/repositories/notification-channel-endpoints.js.map +1 -0
- package/dist-server/server/modules/database/repositories/notification-preferences.js +82 -0
- package/dist-server/server/modules/database/repositories/notification-preferences.js.map +1 -0
- package/dist-server/server/modules/database/repositories/projects.db.js +172 -0
- package/dist-server/server/modules/database/repositories/projects.db.js.map +1 -0
- package/dist-server/server/modules/database/repositories/provider-models.js +112 -0
- package/dist-server/server/modules/database/repositories/provider-models.js.map +1 -0
- package/dist-server/server/modules/database/repositories/push-subscriptions.js +49 -0
- package/dist-server/server/modules/database/repositories/push-subscriptions.js.map +1 -0
- package/dist-server/server/modules/database/repositories/scan-state.db.js +31 -0
- package/dist-server/server/modules/database/repositories/scan-state.db.js.map +1 -0
- package/dist-server/server/modules/database/repositories/scheduled-messages.db.js +71 -0
- package/dist-server/server/modules/database/repositories/scheduled-messages.db.js.map +1 -0
- package/dist-server/server/modules/database/repositories/session-drafts.db.js +105 -0
- package/dist-server/server/modules/database/repositories/session-drafts.db.js.map +1 -0
- package/dist-server/server/modules/database/repositories/sessions.db.js +507 -0
- package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -0
- package/dist-server/server/modules/database/repositories/user-preferences.db.js +65 -0
- package/dist-server/server/modules/database/repositories/user-preferences.db.js.map +1 -0
- package/dist-server/server/modules/database/repositories/users.js +88 -0
- package/dist-server/server/modules/database/repositories/users.js.map +1 -0
- package/dist-server/server/modules/database/repositories/vapid-keys.js +38 -0
- package/dist-server/server/modules/database/repositories/vapid-keys.js.map +1 -0
- package/dist-server/server/modules/database/schema.js +291 -0
- package/dist-server/server/modules/database/schema.js.map +1 -0
- package/dist-server/server/modules/database/tests/projects.db.integration.test.js +64 -0
- package/dist-server/server/modules/database/tests/projects.db.integration.test.js.map +1 -0
- package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js +102 -0
- package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js.map +1 -0
- package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js +79 -0
- package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js.map +1 -0
- package/dist-server/server/modules/database/tests/sessions.db.integration.test.js +93 -0
- package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -0
- package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js +105 -0
- package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js.map +1 -0
- package/dist-server/server/modules/file-tree/file-tree.module.js +95 -0
- package/dist-server/server/modules/file-tree/file-tree.module.js.map +1 -0
- package/dist-server/server/modules/file-tree/file-tree.routes.js +208 -0
- package/dist-server/server/modules/file-tree/file-tree.routes.js.map +1 -0
- package/dist-server/server/modules/file-tree/file-tree.service.js +584 -0
- package/dist-server/server/modules/file-tree/file-tree.service.js.map +1 -0
- package/dist-server/server/modules/file-tree/index.js +3 -0
- package/dist-server/server/modules/file-tree/index.js.map +1 -0
- package/dist-server/server/modules/file-tree/tests/file-tree.routes.test.js +123 -0
- package/dist-server/server/modules/file-tree/tests/file-tree.routes.test.js.map +1 -0
- package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js +311 -0
- package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js.map +1 -0
- package/dist-server/server/modules/git/git-branch.service.js +15 -0
- package/dist-server/server/modules/git/git-branch.service.js.map +1 -0
- package/dist-server/server/modules/git/git-parsing.service.js +61 -0
- package/dist-server/server/modules/git/git-parsing.service.js.map +1 -0
- package/dist-server/server/modules/git/git.module.js +14 -0
- package/dist-server/server/modules/git/git.module.js.map +1 -0
- package/dist-server/server/modules/git/git.routes.js +1347 -0
- package/dist-server/server/modules/git/git.routes.js.map +1 -0
- package/dist-server/server/modules/git/index.js +3 -0
- package/dist-server/server/modules/git/index.js.map +1 -0
- package/dist-server/server/modules/git/tests/git-branch.service.test.js +52 -0
- package/dist-server/server/modules/git/tests/git-branch.service.test.js.map +1 -0
- package/dist-server/server/modules/git/tests/git-init.routes.test.js +132 -0
- package/dist-server/server/modules/git/tests/git-init.routes.test.js.map +1 -0
- package/dist-server/server/modules/git/tests/git.test.js +80 -0
- package/dist-server/server/modules/git/tests/git.test.js.map +1 -0
- package/dist-server/server/modules/notifications/index.js +20 -0
- package/dist-server/server/modules/notifications/index.js.map +1 -0
- package/dist-server/server/modules/notifications/notifications.routes.js +115 -0
- package/dist-server/server/modules/notifications/notifications.routes.js.map +1 -0
- package/dist-server/server/modules/notifications/services/desktop-notification-clients.service.js +97 -0
- package/dist-server/server/modules/notifications/services/desktop-notification-clients.service.js.map +1 -0
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js +249 -0
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js.map +1 -0
- package/dist-server/server/modules/notifications/tests/notification-orchestrator.integration.test.js +44 -0
- package/dist-server/server/modules/notifications/tests/notification-orchestrator.integration.test.js.map +1 -0
- package/dist-server/server/modules/notifications/vapid-keys.service.js +28 -0
- package/dist-server/server/modules/notifications/vapid-keys.service.js.map +1 -0
- package/dist-server/server/modules/notifications/websocket/desktop-notifications-websocket.service.js +79 -0
- package/dist-server/server/modules/notifications/websocket/desktop-notifications-websocket.service.js.map +1 -0
- package/dist-server/server/modules/plugins/index.js +9 -0
- package/dist-server/server/modules/plugins/index.js.map +1 -0
- package/dist-server/server/modules/plugins/plugin-process.service.js +191 -0
- package/dist-server/server/modules/plugins/plugin-process.service.js.map +1 -0
- package/dist-server/server/modules/plugins/plugin-registry.service.js +415 -0
- package/dist-server/server/modules/plugins/plugin-registry.service.js.map +1 -0
- package/dist-server/server/modules/plugins/plugins.module.js +30 -0
- package/dist-server/server/modules/plugins/plugins.module.js.map +1 -0
- package/dist-server/server/modules/plugins/plugins.routes.js +70 -0
- package/dist-server/server/modules/plugins/plugins.routes.js.map +1 -0
- package/dist-server/server/modules/plugins/plugins.service.js +123 -0
- package/dist-server/server/modules/plugins/plugins.service.js.map +1 -0
- package/dist-server/server/modules/plugins/tests/plugins.service.test.js +28 -0
- package/dist-server/server/modules/plugins/tests/plugins.service.test.js.map +1 -0
- package/dist-server/server/modules/projects/index.js +9 -0
- package/dist-server/server/modules/projects/index.js.map +1 -0
- package/dist-server/server/modules/projects/projects.routes.js +205 -0
- package/dist-server/server/modules/projects/projects.routes.js.map +1 -0
- package/dist-server/server/modules/projects/services/project-clone.service.js +220 -0
- package/dist-server/server/modules/projects/services/project-clone.service.js.map +1 -0
- package/dist-server/server/modules/projects/services/project-delete.service.js +89 -0
- package/dist-server/server/modules/projects/services/project-delete.service.js.map +1 -0
- package/dist-server/server/modules/projects/services/project-management.service.js +90 -0
- package/dist-server/server/modules/projects/services/project-management.service.js.map +1 -0
- package/dist-server/server/modules/projects/services/project-star.service.js +60 -0
- package/dist-server/server/modules/projects/services/project-star.service.js.map +1 -0
- package/dist-server/server/modules/projects/services/projects-has-taskmaster.service.js +167 -0
- package/dist-server/server/modules/projects/services/projects-has-taskmaster.service.js.map +1 -0
- package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js +188 -0
- package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js.map +1 -0
- package/dist-server/server/modules/projects/tests/project-clone.service.test.js +126 -0
- package/dist-server/server/modules/projects/tests/project-clone.service.test.js.map +1 -0
- package/dist-server/server/modules/projects/tests/project-management.service.test.js +85 -0
- package/dist-server/server/modules/projects/tests/project-management.service.test.js.map +1 -0
- package/dist-server/server/modules/projects/tests/project-star.service.test.js +95 -0
- package/dist-server/server/modules/projects/tests/project-star.service.test.js.map +1 -0
- package/dist-server/server/modules/projects/tests/projects-has-taskmaster.service.test.js +87 -0
- package/dist-server/server/modules/projects/tests/projects-has-taskmaster.service.test.js.map +1 -0
- package/dist-server/server/modules/providers/index.js +12 -0
- package/dist-server/server/modules/providers/index.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +151 -0
- package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js +46 -0
- package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude-mcp.provider.js +103 -0
- package/dist-server/server/modules/providers/list/claude/claude-mcp.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +267 -0
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js +1058 -0
- package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js +141 -0
- package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +924 -0
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js +205 -0
- package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude.provider.js +23 -0
- package/dist-server/server/modules/providers/list/claude/claude.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js +204 -0
- package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-auth.provider.js +84 -0
- package/dist-server/server/modules/providers/list/codex/codex-auth.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js +32 -0
- package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-mcp.provider.js +107 -0
- package/dist-server/server/modules/providers/list/codex/codex-mcp.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +109 -0
- package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js +498 -0
- package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +153 -0
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +1955 -0
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js +62 -0
- package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex.provider.js +23 -0
- package/dist-server/server/modules/providers/list/codex/codex.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-auth.provider.js +118 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-auth.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-mcp.provider.js +80 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-mcp.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js +100 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js +305 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js +111 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js +566 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js +35 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/cursor/cursor.provider.js +21 -0
- package/dist-server/server/modules/providers/list/cursor/cursor.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/mistral/mistral-auth.provider.js +147 -0
- package/dist-server/server/modules/providers/list/mistral/mistral-auth.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/mistral/mistral-mcp.provider.js +15 -0
- package/dist-server/server/modules/providers/list/mistral/mistral-mcp.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/mistral/mistral-models.provider.js +69 -0
- package/dist-server/server/modules/providers/list/mistral/mistral-models.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/mistral/mistral-runtime.provider.js +326 -0
- package/dist-server/server/modules/providers/list/mistral/mistral-runtime.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/mistral/mistral-session-synchronizer.provider.js +12 -0
- package/dist-server/server/modules/providers/list/mistral/mistral-session-synchronizer.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/mistral/mistral-sessions.provider.js +180 -0
- package/dist-server/server/modules/providers/list/mistral/mistral-sessions.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/mistral/mistral-skills.provider.js +15 -0
- package/dist-server/server/modules/providers/list/mistral/mistral-skills.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/mistral/mistral.provider.js +25 -0
- package/dist-server/server/modules/providers/list/mistral/mistral.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-auth.provider.js +91 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-auth.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-mcp.provider.js +181 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-mcp.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +257 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.js +368 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.test.js +225 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.test.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +136 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js +404 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-skills.provider.js +62 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-skills.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode.provider.js +21 -0
- package/dist-server/server/modules/providers/list/opencode/opencode.provider.js.map +1 -0
- package/dist-server/server/modules/providers/provider.registry.js +33 -0
- package/dist-server/server/modules/providers/provider.registry.js.map +1 -0
- package/dist-server/server/modules/providers/provider.routes.js +676 -0
- package/dist-server/server/modules/providers/provider.routes.js.map +1 -0
- package/dist-server/server/modules/providers/services/mcp.service.js +84 -0
- package/dist-server/server/modules/providers/services/mcp.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/provider-auth.service.js +25 -0
- package/dist-server/server/modules/providers/services/provider-auth.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/provider-capabilities.service.js +94 -0
- package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/provider-models.service.js +289 -0
- package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/provider-runtime.service.js +66 -0
- package/dist-server/server/modules/providers/services/provider-runtime.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/provider-token-usage.service.js +372 -0
- package/dist-server/server/modules/providers/services/provider-token-usage.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/session-conversations-search.service.js +974 -0
- package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/session-history-cache.service.js +87 -0
- package/dist-server/server/modules/providers/services/session-history-cache.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/session-synchronizer.service.js +130 -0
- package/dist-server/server/modules/providers/services/session-synchronizer.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js +214 -0
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/sessions.service.js +519 -0
- package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/skills.service.js +22 -0
- package/dist-server/server/modules/providers/services/skills.service.js.map +1 -0
- package/dist-server/server/modules/providers/shared/base/abstract.provider.js +13 -0
- package/dist-server/server/modules/providers/shared/base/abstract.provider.js.map +1 -0
- package/dist-server/server/modules/providers/shared/mcp/mcp.provider.js +102 -0
- package/dist-server/server/modules/providers/shared/mcp/mcp.provider.js.map +1 -0
- package/dist-server/server/modules/providers/shared/skills/skills.provider.js +207 -0
- package/dist-server/server/modules/providers/shared/skills/skills.provider.js.map +1 -0
- package/dist-server/server/modules/providers/tests/claude-auth.test.js +174 -0
- package/dist-server/server/modules/providers/tests/claude-auth.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/claude-sessions.test.js +470 -0
- package/dist-server/server/modules/providers/tests/claude-sessions.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js +16 -0
- package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/claude-token-budget.test.js +85 -0
- package/dist-server/server/modules/providers/tests/claude-token-budget.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/codex-message-editing.test.js +338 -0
- package/dist-server/server/modules/providers/tests/codex-message-editing.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/codex-session-fork.test.js +54 -0
- package/dist-server/server/modules/providers/tests/codex-session-fork.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/codex-sessions.test.js +491 -0
- package/dist-server/server/modules/providers/tests/codex-sessions.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/mcp.test.js +291 -0
- package/dist-server/server/modules/providers/tests/mcp.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/opencode-models.test.js +103 -0
- package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +424 -0
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/provider-attachment-history.test.js +205 -0
- package/dist-server/server/modules/providers/tests/provider-attachment-history.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/provider-models.service.test.js +268 -0
- package/dist-server/server/modules/providers/tests/provider-models.service.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/provider-runtime.service.test.js +126 -0
- package/dist-server/server/modules/providers/tests/provider-runtime.service.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js +329 -0
- package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/provider.routes.test.js +161 -0
- package/dist-server/server/modules/providers/tests/provider.routes.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/session-fork.test.js +113 -0
- package/dist-server/server/modules/providers/tests/session-fork.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js +120 -0
- package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js +90 -0
- package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js +122 -0
- package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/sessions-details.test.js +62 -0
- package/dist-server/server/modules/providers/tests/sessions-details.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/sessions.service.test.js +131 -0
- package/dist-server/server/modules/providers/tests/sessions.service.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/skills.test.js +398 -0
- package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -0
- package/dist-server/server/modules/scheduled-messages/index.js +5 -0
- package/dist-server/server/modules/scheduled-messages/index.js.map +1 -0
- package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js +47 -0
- package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js.map +1 -0
- package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js +163 -0
- package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js.map +1 -0
- package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js +95 -0
- package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js.map +1 -0
- package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js +255 -0
- package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js.map +1 -0
- package/dist-server/server/modules/settings/index.js +3 -0
- package/dist-server/server/modules/settings/index.js.map +1 -0
- package/dist-server/server/modules/settings/settings.module.js +35 -0
- package/dist-server/server/modules/settings/settings.module.js.map +1 -0
- package/dist-server/server/modules/settings/settings.routes.js +34 -0
- package/dist-server/server/modules/settings/settings.routes.js.map +1 -0
- package/dist-server/server/modules/settings/settings.service.js +116 -0
- package/dist-server/server/modules/settings/settings.service.js.map +1 -0
- package/dist-server/server/modules/settings/tests/settings.service.test.js +48 -0
- package/dist-server/server/modules/settings/tests/settings.service.test.js.map +1 -0
- package/dist-server/server/modules/system/index.js +3 -0
- package/dist-server/server/modules/system/index.js.map +1 -0
- package/dist-server/server/modules/system/system.module.js +44 -0
- package/dist-server/server/modules/system/system.module.js.map +1 -0
- package/dist-server/server/modules/system/system.routes.js +16 -0
- package/dist-server/server/modules/system/system.routes.js.map +1 -0
- package/dist-server/server/modules/system/system.service.js +45 -0
- package/dist-server/server/modules/system/system.service.js.map +1 -0
- package/dist-server/server/modules/system/tests/system.service.test.js +100 -0
- package/dist-server/server/modules/system/tests/system.service.test.js.map +1 -0
- package/dist-server/server/modules/taskmaster/index.js +3 -0
- package/dist-server/server/modules/taskmaster/index.js.map +1 -0
- package/dist-server/server/modules/taskmaster/taskmaster.module.js +20 -0
- package/dist-server/server/modules/taskmaster/taskmaster.module.js.map +1 -0
- package/dist-server/server/modules/taskmaster/taskmaster.routes.js +1372 -0
- package/dist-server/server/modules/taskmaster/taskmaster.routes.js.map +1 -0
- package/dist-server/server/modules/taskmaster/taskmaster.service.js +107 -0
- package/dist-server/server/modules/taskmaster/taskmaster.service.js.map +1 -0
- package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js +180 -0
- package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js.map +1 -0
- package/dist-server/server/modules/taskmaster/tests/taskmaster.service.test.js +85 -0
- package/dist-server/server/modules/taskmaster/tests/taskmaster.service.test.js.map +1 -0
- package/dist-server/server/modules/user/index.js +3 -0
- package/dist-server/server/modules/user/index.js.map +1 -0
- package/dist-server/server/modules/user/tests/user.service.test.js +123 -0
- package/dist-server/server/modules/user/tests/user.service.test.js.map +1 -0
- package/dist-server/server/modules/user/user.module.js +56 -0
- package/dist-server/server/modules/user/user.module.js.map +1 -0
- package/dist-server/server/modules/user/user.routes.js +88 -0
- package/dist-server/server/modules/user/user.routes.js.map +1 -0
- package/dist-server/server/modules/user/user.service.js +134 -0
- package/dist-server/server/modules/user/user.service.js.map +1 -0
- package/dist-server/server/modules/voice/index.js +3 -0
- package/dist-server/server/modules/voice/index.js.map +1 -0
- package/dist-server/server/modules/voice/tests/voice.service.test.js +87 -0
- package/dist-server/server/modules/voice/tests/voice.service.test.js.map +1 -0
- package/dist-server/server/modules/voice/voice.module.js +44 -0
- package/dist-server/server/modules/voice/voice.module.js.map +1 -0
- package/dist-server/server/modules/voice/voice.routes.js +87 -0
- package/dist-server/server/modules/voice/voice.routes.js.map +1 -0
- package/dist-server/server/modules/voice/voice.service.js +148 -0
- package/dist-server/server/modules/voice/voice.service.js.map +1 -0
- package/dist-server/server/modules/websocket/index.js +11 -0
- package/dist-server/server/modules/websocket/index.js.map +1 -0
- package/dist-server/server/modules/websocket/services/chat-run-registry.service.js +232 -0
- package/dist-server/server/modules/websocket/services/chat-run-registry.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/chat-session-writer.service.js +135 -0
- package/dist-server/server/modules/websocket/services/chat-session-writer.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js +467 -0
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/plugin-websocket-proxy.service.js +52 -0
- package/dist-server/server/modules/websocket/services/plugin-websocket-proxy.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js +94 -0
- package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js +472 -0
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/websocket-auth.service.js +36 -0
- package/dist-server/server/modules/websocket/services/websocket-auth.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/websocket-server.service.js +90 -0
- package/dist-server/server/modules/websocket/services/websocket-server.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/websocket-state.service.js +14 -0
- package/dist-server/server/modules/websocket/services/websocket-state.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/websocket-writer.service.js +32 -0
- package/dist-server/server/modules/websocket/services/websocket-writer.service.js.map +1 -0
- package/dist-server/server/modules/websocket/tests/chat-attachment-filter.test.js +40 -0
- package/dist-server/server/modules/websocket/tests/chat-attachment-filter.test.js.map +1 -0
- package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js +263 -0
- package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js.map +1 -0
- package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js +275 -0
- package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js.map +1 -0
- package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js +106 -0
- package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js.map +1 -0
- package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js +177 -0
- package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js.map +1 -0
- package/dist-server/server/modules/websocket/tests/websocket-heartbeat.service.test.js +61 -0
- package/dist-server/server/modules/websocket/tests/websocket-heartbeat.service.test.js.map +1 -0
- package/dist-server/server/modules/worktrees/index.js +3 -0
- package/dist-server/server/modules/worktrees/index.js.map +1 -0
- package/dist-server/server/modules/worktrees/services/worktree-create-and-open.service.js +37 -0
- package/dist-server/server/modules/worktrees/services/worktree-create-and-open.service.js.map +1 -0
- package/dist-server/server/modules/worktrees/services/worktree-create.service.js +67 -0
- package/dist-server/server/modules/worktrees/services/worktree-create.service.js.map +1 -0
- package/dist-server/server/modules/worktrees/services/worktree-git.service.js +172 -0
- package/dist-server/server/modules/worktrees/services/worktree-git.service.js.map +1 -0
- package/dist-server/server/modules/worktrees/services/worktree-list.service.js +91 -0
- package/dist-server/server/modules/worktrees/services/worktree-list.service.js.map +1 -0
- package/dist-server/server/modules/worktrees/services/worktree-merge.service.js +118 -0
- package/dist-server/server/modules/worktrees/services/worktree-merge.service.js.map +1 -0
- package/dist-server/server/modules/worktrees/services/worktree-open.service.js +58 -0
- package/dist-server/server/modules/worktrees/services/worktree-open.service.js.map +1 -0
- package/dist-server/server/modules/worktrees/services/worktree-remove.service.js +65 -0
- package/dist-server/server/modules/worktrees/services/worktree-remove.service.js.map +1 -0
- package/dist-server/server/modules/worktrees/tests/worktree-create-and-open.service.test.js +58 -0
- package/dist-server/server/modules/worktrees/tests/worktree-create-and-open.service.test.js.map +1 -0
- package/dist-server/server/modules/worktrees/tests/worktree-create.service.test.js +85 -0
- package/dist-server/server/modules/worktrees/tests/worktree-create.service.test.js.map +1 -0
- package/dist-server/server/modules/worktrees/tests/worktree-git.service.test.js +58 -0
- package/dist-server/server/modules/worktrees/tests/worktree-git.service.test.js.map +1 -0
- package/dist-server/server/modules/worktrees/tests/worktree-list.service.test.js +34 -0
- package/dist-server/server/modules/worktrees/tests/worktree-list.service.test.js.map +1 -0
- package/dist-server/server/modules/worktrees/tests/worktree-merge.service.test.js +137 -0
- package/dist-server/server/modules/worktrees/tests/worktree-merge.service.test.js.map +1 -0
- package/dist-server/server/modules/worktrees/tests/worktree-open.service.test.js +99 -0
- package/dist-server/server/modules/worktrees/tests/worktree-open.service.test.js.map +1 -0
- package/dist-server/server/modules/worktrees/tests/worktree-remove.service.test.js +157 -0
- package/dist-server/server/modules/worktrees/tests/worktree-remove.service.test.js.map +1 -0
- package/dist-server/server/modules/worktrees/tests/worktrees.routes.test.js +167 -0
- package/dist-server/server/modules/worktrees/tests/worktrees.routes.test.js.map +1 -0
- package/dist-server/server/modules/worktrees/worktrees.module.js +97 -0
- package/dist-server/server/modules/worktrees/worktrees.module.js.map +1 -0
- package/dist-server/server/modules/worktrees/worktrees.routes.js +84 -0
- package/dist-server/server/modules/worktrees/worktrees.routes.js.map +1 -0
- package/dist-server/server/piecemaker/activation/claude-mcp.cjs +109 -0
- package/dist-server/server/piecemaker/activation/claude-mcp.cjs.map +1 -0
- package/dist-server/server/piecemaker/activation/claude-plugins.cjs +60 -0
- package/dist-server/server/piecemaker/activation/claude-plugins.cjs.map +1 -0
- package/dist-server/server/piecemaker/activation/codex-mcp.cjs +93 -0
- package/dist-server/server/piecemaker/activation/codex-mcp.cjs.map +1 -0
- package/dist-server/server/piecemaker/activation/errors.cjs +14 -0
- package/dist-server/server/piecemaker/activation/errors.cjs.map +1 -0
- package/dist-server/server/piecemaker/activation/library.cjs +281 -0
- package/dist-server/server/piecemaker/activation/library.cjs.map +1 -0
- package/dist-server/server/piecemaker/activation/library.test.js +281 -0
- package/dist-server/server/piecemaker/activation/library.test.js.map +1 -0
- package/dist-server/server/piecemaker/activation/markdown.cjs +103 -0
- package/dist-server/server/piecemaker/activation/markdown.cjs.map +1 -0
- package/dist-server/server/piecemaker/activation/paths.cjs +103 -0
- package/dist-server/server/piecemaker/activation/paths.cjs.map +1 -0
- package/dist-server/server/piecemaker/activation/paths.test.js +91 -0
- package/dist-server/server/piecemaker/activation/paths.test.js.map +1 -0
- package/dist-server/server/piecemaker/activation/routes.cjs +100 -0
- package/dist-server/server/piecemaker/activation/routes.cjs.map +1 -0
- package/dist-server/server/piecemaker/activation/routes.test.js +307 -0
- package/dist-server/server/piecemaker/activation/routes.test.js.map +1 -0
- package/dist-server/server/piecemaker/activation/snapshot.cjs +28 -0
- package/dist-server/server/piecemaker/activation/snapshot.cjs.map +1 -0
- package/dist-server/server/piecemaker/anonymizer/anonymizer.test.js +282 -0
- package/dist-server/server/piecemaker/anonymizer/anonymizer.test.js.map +1 -0
- package/dist-server/server/piecemaker/anonymizer/client-config.cjs +224 -0
- package/dist-server/server/piecemaker/anonymizer/client-config.cjs.map +1 -0
- package/dist-server/server/piecemaker/anonymizer/codex-http.test.js +19 -0
- package/dist-server/server/piecemaker/anonymizer/codex-http.test.js.map +1 -0
- package/dist-server/server/piecemaker/anonymizer/dictionary.cjs +163 -0
- package/dist-server/server/piecemaker/anonymizer/dictionary.cjs.map +1 -0
- package/dist-server/server/piecemaker/anonymizer/lifecycle.js +20 -0
- package/dist-server/server/piecemaker/anonymizer/lifecycle.js.map +1 -0
- package/dist-server/server/piecemaker/anonymizer/lifecycle.test.js +35 -0
- package/dist-server/server/piecemaker/anonymizer/lifecycle.test.js.map +1 -0
- package/dist-server/server/piecemaker/anonymizer/providers.cjs +236 -0
- package/dist-server/server/piecemaker/anonymizer/providers.cjs.map +1 -0
- package/dist-server/server/piecemaker/anonymizer/proxy.cjs +338 -0
- package/dist-server/server/piecemaker/anonymizer/proxy.cjs.map +1 -0
- package/dist-server/server/piecemaker/anonymizer/rewrite.cjs +249 -0
- package/dist-server/server/piecemaker/anonymizer/rewrite.cjs.map +1 -0
- package/dist-server/server/piecemaker/anonymizer/service.cjs +217 -0
- package/dist-server/server/piecemaker/anonymizer/service.cjs.map +1 -0
- package/dist-server/server/piecemaker/harness/chat-harness.js +157 -0
- package/dist-server/server/piecemaker/harness/chat-harness.js.map +1 -0
- package/dist-server/server/piecemaker/harness/chat-harness.test.js +120 -0
- package/dist-server/server/piecemaker/harness/chat-harness.test.js.map +1 -0
- package/dist-server/server/piecemaker/harness/citation-instructions.js +17 -0
- package/dist-server/server/piecemaker/harness/citation-instructions.js.map +1 -0
- package/dist-server/server/piecemaker/harness/citation-store.js +72 -0
- package/dist-server/server/piecemaker/harness/citation-store.js.map +1 -0
- package/dist-server/server/piecemaker/harness/citation-turn.js +211 -0
- package/dist-server/server/piecemaker/harness/citation-turn.js.map +1 -0
- package/dist-server/server/piecemaker/harness/citation-turn.test.js +144 -0
- package/dist-server/server/piecemaker/harness/citation-turn.test.js.map +1 -0
- package/dist-server/server/piecemaker/harness/citations.routes.js +16 -0
- package/dist-server/server/piecemaker/harness/citations.routes.js.map +1 -0
- package/dist-server/server/piecemaker/harness/citations.routes.test.js +68 -0
- package/dist-server/server/piecemaker/harness/citations.routes.test.js.map +1 -0
- package/dist-server/server/piecemaker/harness/decisions.cjs +298 -0
- package/dist-server/server/piecemaker/harness/decisions.cjs.map +1 -0
- package/dist-server/server/piecemaker/harness/flux-sse.cjs +108 -0
- package/dist-server/server/piecemaker/harness/flux-sse.cjs.map +1 -0
- package/dist-server/server/piecemaker/harness/harness.test.js +259 -0
- package/dist-server/server/piecemaker/harness/harness.test.js.map +1 -0
- package/dist-server/server/piecemaker/harness/index.cjs +100 -0
- package/dist-server/server/piecemaker/harness/index.cjs.map +1 -0
- package/dist-server/server/piecemaker/harness/proxy-harness.test.js +199 -0
- package/dist-server/server/piecemaker/harness/proxy-harness.test.js.map +1 -0
- package/dist-server/server/piecemaker/harness/source-passages.js +19 -0
- package/dist-server/server/piecemaker/harness/source-passages.js.map +1 -0
- package/dist-server/server/piecemaker/harness/verification.cjs +152 -0
- package/dist-server/server/piecemaker/harness/verification.cjs.map +1 -0
- package/dist-server/server/piecemaker/index.js +46 -0
- package/dist-server/server/piecemaker/index.js.map +1 -0
- package/dist-server/server/piecemaker/library/index.js +66 -0
- package/dist-server/server/piecemaker/library/index.js.map +1 -0
- package/dist-server/server/piecemaker/library/marketplace.js +204 -0
- package/dist-server/server/piecemaker/library/marketplace.js.map +1 -0
- package/dist-server/server/piecemaker/library/migrate-cli.js +13 -0
- package/dist-server/server/piecemaker/library/migrate-cli.js.map +1 -0
- package/dist-server/server/piecemaker/library/migrate.js +53 -0
- package/dist-server/server/piecemaker/library/migrate.js.map +1 -0
- package/dist-server/server/piecemaker/library/provider-skills.js +40 -0
- package/dist-server/server/piecemaker/library/provider-skills.js.map +1 -0
- package/dist-server/server/piecemaker/library/provider-skills.test.js +55 -0
- package/dist-server/server/piecemaker/library/provider-skills.test.js.map +1 -0
- package/dist-server/server/piecemaker/library/routes.js +56 -0
- package/dist-server/server/piecemaker/library/routes.js.map +1 -0
- package/dist-server/server/piecemaker/library/runtime.js +34 -0
- package/dist-server/server/piecemaker/library/runtime.js.map +1 -0
- package/dist-server/server/piecemaker/library/store.js +440 -0
- package/dist-server/server/piecemaker/library/store.js.map +1 -0
- package/dist-server/server/piecemaker/library/store.test.js +265 -0
- package/dist-server/server/piecemaker/library/store.test.js.map +1 -0
- package/dist-server/server/piecemaker/library/workspace-installation.js +70 -0
- package/dist-server/server/piecemaker/library/workspace-installation.js.map +1 -0
- package/dist-server/server/piecemaker/originals-cache.cjs +45 -0
- package/dist-server/server/piecemaker/originals-cache.cjs.map +1 -0
- package/dist-server/server/piecemaker/originals-cache.test.js +68 -0
- package/dist-server/server/piecemaker/originals-cache.test.js.map +1 -0
- package/dist-server/server/piecemaker/protection/bypass.cjs +66 -0
- package/dist-server/server/piecemaker/protection/bypass.cjs.map +1 -0
- package/dist-server/server/piecemaker/protection/bypass.test.js +117 -0
- package/dist-server/server/piecemaker/protection/bypass.test.js.map +1 -0
- package/dist-server/server/piecemaker/protection/routes.cjs +38 -0
- package/dist-server/server/piecemaker/protection/routes.cjs.map +1 -0
- package/dist-server/server/piecemaker/timesheet/index.js +9 -0
- package/dist-server/server/piecemaker/timesheet/index.js.map +1 -0
- package/dist-server/server/piecemaker/timesheet/routes.js +42 -0
- package/dist-server/server/piecemaker/timesheet/routes.js.map +1 -0
- package/dist-server/server/piecemaker/timesheet/service.js +241 -0
- package/dist-server/server/piecemaker/timesheet/service.js.map +1 -0
- package/dist-server/server/piecemaker/timesheet/service.test.js +124 -0
- package/dist-server/server/piecemaker/timesheet/service.test.js.map +1 -0
- package/dist-server/server/piecemaker/timesheet/store.js +126 -0
- package/dist-server/server/piecemaker/timesheet/store.js.map +1 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/anonymization-state.cjs +147 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/anonymization-state.cjs.map +1 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/case-folder-structure.cjs +285 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/case-folder-structure.cjs.map +1 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/case-folders.cjs +87 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/case-folders.cjs.map +1 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/central-mapping.cjs +0 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/central-mapping.cjs.map +1 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/citation-sources.cjs +526 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/citation-sources.cjs.map +1 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/citations.cjs +304 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/citations.cjs.map +1 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/commits.cjs +1660 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/commits.cjs.map +1 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/institutional-terms.cjs +152 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/institutional-terms.cjs.map +1 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/legifrance-reads.cjs +84 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/legifrance-reads.cjs.map +1 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/mapping.cjs +332 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/mapping.cjs.map +1 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/mapping.test.js +64 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/mapping.test.js.map +1 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/protection.cjs +420 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/protection.cjs.map +1 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/session-timing.cjs +91 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/session-timing.cjs.map +1 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/substitution.cjs +304 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/substitution.cjs.map +1 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/verify-citations.cjs +293 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/verify-citations.cjs.map +1 -0
- package/dist-server/server/piecemaker/vendor/websocket-server/case-folder-structure.cjs +5 -0
- package/dist-server/server/piecemaker/vendor/websocket-server/case-folder-structure.cjs.map +1 -0
- package/dist-server/server/piecemaker/vendor/websocket-server/legal-forms.cjs +91 -0
- package/dist-server/server/piecemaker/vendor/websocket-server/legal-forms.cjs.map +1 -0
- package/dist-server/server/piecemaker/vendor/websocket-server/legal-forms.test.js +11 -0
- package/dist-server/server/piecemaker/vendor/websocket-server/legal-forms.test.js.map +1 -0
- package/dist-server/server/piecemaker/vendor/websocket-server/originals-pipeline.cjs +955 -0
- package/dist-server/server/piecemaker/vendor/websocket-server/originals-pipeline.cjs.map +1 -0
- package/dist-server/server/shared/claude-cli-path.js +120 -0
- package/dist-server/server/shared/claude-cli-path.js.map +1 -0
- package/dist-server/server/shared/frontmatter.js +16 -0
- package/dist-server/server/shared/frontmatter.js.map +1 -0
- package/dist-server/server/shared/image-attachments.js +344 -0
- package/dist-server/server/shared/image-attachments.js.map +1 -0
- package/dist-server/server/shared/interfaces.js +2 -0
- package/dist-server/server/shared/interfaces.js.map +1 -0
- package/dist-server/server/shared/message-unification.js +376 -0
- package/dist-server/server/shared/message-unification.js.map +1 -0
- package/dist-server/server/shared/tests/application-data-root.test.js +33 -0
- package/dist-server/server/shared/tests/application-data-root.test.js.map +1 -0
- package/dist-server/server/shared/tests/claude-cli-path.test.js +89 -0
- package/dist-server/server/shared/tests/claude-cli-path.test.js.map +1 -0
- package/dist-server/server/shared/tests/image-attachments.test.js +262 -0
- package/dist-server/server/shared/tests/image-attachments.test.js.map +1 -0
- package/dist-server/server/shared/tests/message-unification.test.js +174 -0
- package/dist-server/server/shared/tests/message-unification.test.js.map +1 -0
- package/dist-server/server/shared/tests/slice-tail-page.test.js +35 -0
- package/dist-server/server/shared/tests/slice-tail-page.test.js.map +1 -0
- package/dist-server/server/shared/types.js +2 -0
- package/dist-server/server/shared/types.js.map +1 -0
- package/dist-server/server/shared/utils.js +1010 -0
- package/dist-server/server/shared/utils.js.map +1 -0
- package/dist-server/shared/networkHosts.js +20 -0
- package/dist-server/shared/networkHosts.js.map +1 -0
- package/dist-server/shared/product-config.mjs +79 -0
- package/dist-server/shared/product-config.mjs.map +1 -0
- package/package.json +271 -0
- package/product.config.json +17 -0
- package/public/api-docs.html +901 -0
- package/scripts/fix-node-pty.js +67 -0
- package/scripts/piecemaker/cli/install-command.mjs +130 -0
- package/scripts/piecemaker/cli/lib/composants.mjs +68 -0
- package/scripts/piecemaker/cli/lib/config.mjs +32 -0
- package/scripts/piecemaker/cli/lib/exec.mjs +79 -0
- package/scripts/piecemaker/cli/lib/node-runtime.mjs +76 -0
- package/scripts/piecemaker/cli/lib/ports.mjs +96 -0
- package/scripts/piecemaker/cli/lib/pwa.mjs +323 -0
- package/scripts/piecemaker/cli/lib/repos.mjs +140 -0
- package/scripts/piecemaker/cli/lib/services.mjs +71 -0
- package/scripts/piecemaker/cli/lib/ui.mjs +41 -0
- package/scripts/piecemaker/cli/piecemaker.mjs +159 -0
- package/scripts/piecemaker/cli/piecemaker.ps1 +63 -0
- package/scripts/piecemaker/cli/piecemaker.sh +60 -0
- package/scripts/piecemaker/generate-i18n-overrides.mjs +219 -0
- package/scripts/piecemaker/hooks/proxy-guard.mjs +286 -0
- package/scripts/promote-dist-server.mjs +50 -0
- package/scripts/release/build-server-bundle.js +178 -0
- package/scripts/release/package-piecemaker.sh +73 -0
- package/scripts/release/prepare-desktop-app.js +168 -0
- package/server/index.ts +410 -0
- package/server/load-env.ts +48 -0
- package/server/modules/agent/agent.module.ts +52 -0
- package/server/modules/agent/agent.routes.ts +1287 -0
- package/server/modules/agent/index.ts +2 -0
- package/server/modules/agent/tests/agent.routes.test.ts +205 -0
- package/server/modules/assets/assets.routes.ts +150 -0
- package/server/modules/assets/index.ts +3 -0
- package/server/modules/assets/services/image-assets.service.ts +127 -0
- package/server/modules/assets/tests/image-assets.service.test.ts +74 -0
- package/server/modules/auth/auth.middleware.ts +163 -0
- package/server/modules/auth/auth.module.ts +38 -0
- package/server/modules/auth/auth.routes.ts +57 -0
- package/server/modules/auth/auth.service.ts +155 -0
- package/server/modules/auth/index.ts +9 -0
- package/server/modules/auth/tests/auth.service.test.ts +95 -0
- package/server/modules/browser-use/browser-use-mcp.routes.ts +120 -0
- package/server/modules/browser-use/browser-use-mcp.ts +386 -0
- package/server/modules/browser-use/browser-use-runtime.ts +10 -0
- package/server/modules/browser-use/browser-use.routes.ts +96 -0
- package/server/modules/browser-use/browser-use.service.ts +835 -0
- package/server/modules/browser-use/index.ts +11 -0
- package/server/modules/browser-use/tests/browser-use.service.test.ts +10 -0
- package/server/modules/cli/cli.module.ts +110 -0
- package/server/modules/cli/cli.service.ts +266 -0
- package/server/modules/cli/cli.ts +19 -0
- package/server/modules/cli/index.ts +2 -0
- package/server/modules/cli/pwa-launcher.ts +131 -0
- package/server/modules/cli/sandbox.service.ts +413 -0
- package/server/modules/cli/tests/cli-environment-bootstrap.test.ts +186 -0
- package/server/modules/cli/tests/cli.service.test.ts +107 -0
- package/server/modules/cli/tests/sandbox.service.test.ts +61 -0
- package/server/modules/commands/commands.module.ts +22 -0
- package/server/modules/commands/commands.routes.ts +605 -0
- package/server/modules/commands/index.ts +2 -0
- package/server/modules/commands/tests/commands.test.ts +114 -0
- package/server/modules/database/connection.ts +144 -0
- package/server/modules/database/index.ts +27 -0
- package/server/modules/database/init-db.ts +17 -0
- package/server/modules/database/migrations.ts +552 -0
- package/server/modules/database/repositories/api-keys.ts +119 -0
- package/server/modules/database/repositories/app-config.ts +53 -0
- package/server/modules/database/repositories/credentials.ts +106 -0
- package/server/modules/database/repositories/github-tokens.ts +100 -0
- package/server/modules/database/repositories/notification-channel-endpoints.ts +153 -0
- package/server/modules/database/repositories/notification-preferences.ts +117 -0
- package/server/modules/database/repositories/projects.db.ts +196 -0
- package/server/modules/database/repositories/provider-models.ts +168 -0
- package/server/modules/database/repositories/push-subscriptions.ts +80 -0
- package/server/modules/database/repositories/scan-state.db.ts +42 -0
- package/server/modules/database/repositories/scheduled-messages.db.ts +127 -0
- package/server/modules/database/repositories/session-drafts.db.ts +171 -0
- package/server/modules/database/repositories/sessions.db.ts +714 -0
- package/server/modules/database/repositories/user-preferences.db.ts +80 -0
- package/server/modules/database/repositories/users.ts +140 -0
- package/server/modules/database/repositories/vapid-keys.ts +57 -0
- package/server/modules/database/schema.ts +306 -0
- package/server/modules/database/tests/projects.db.integration.test.ts +72 -0
- package/server/modules/database/tests/provider-models.db.integration.test.ts +125 -0
- package/server/modules/database/tests/sessions-provider-mapping.test.ts +109 -0
- package/server/modules/database/tests/sessions.db.integration.test.ts +116 -0
- package/server/modules/database/tests/user-preferences.db.integration.test.ts +135 -0
- package/server/modules/file-tree/file-tree.module.ts +116 -0
- package/server/modules/file-tree/file-tree.routes.ts +258 -0
- package/server/modules/file-tree/file-tree.service.ts +675 -0
- package/server/modules/file-tree/index.ts +2 -0
- package/server/modules/file-tree/tests/file-tree.routes.test.ts +157 -0
- package/server/modules/file-tree/tests/file-tree.service.test.ts +371 -0
- package/server/modules/git/git-branch.service.ts +43 -0
- package/server/modules/git/git-parsing.service.ts +66 -0
- package/server/modules/git/git.module.ts +22 -0
- package/server/modules/git/git.routes.ts +1600 -0
- package/server/modules/git/index.ts +2 -0
- package/server/modules/git/tests/git-branch.service.test.ts +60 -0
- package/server/modules/git/tests/git-init.routes.test.ts +135 -0
- package/server/modules/git/tests/git.test.ts +106 -0
- package/server/modules/notifications/index.ts +24 -0
- package/server/modules/notifications/notifications.routes.ts +127 -0
- package/server/modules/notifications/services/desktop-notification-clients.service.ts +124 -0
- package/server/modules/notifications/services/notification-orchestrator.service.js +310 -0
- package/server/modules/notifications/tests/notification-orchestrator.integration.test.ts +55 -0
- package/server/modules/notifications/vapid-keys.service.ts +38 -0
- package/server/modules/notifications/websocket/desktop-notifications-websocket.service.ts +109 -0
- package/server/modules/plugins/index.ts +9 -0
- package/server/modules/plugins/plugin-process.service.ts +217 -0
- package/server/modules/plugins/plugin-registry.service.ts +461 -0
- package/server/modules/plugins/plugins.module.ts +33 -0
- package/server/modules/plugins/plugins.routes.ts +66 -0
- package/server/modules/plugins/plugins.service.ts +147 -0
- package/server/modules/plugins/tests/plugins.service.test.ts +32 -0
- package/server/modules/projects/index.ts +11 -0
- package/server/modules/projects/projects.routes.ts +273 -0
- package/server/modules/projects/services/project-clone.service.ts +323 -0
- package/server/modules/projects/services/project-delete.service.ts +102 -0
- package/server/modules/projects/services/project-management.service.ts +144 -0
- package/server/modules/projects/services/project-star.service.ts +78 -0
- package/server/modules/projects/services/projects-has-taskmaster.service.ts +248 -0
- package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +314 -0
- package/server/modules/projects/tests/project-clone.service.test.ts +183 -0
- package/server/modules/projects/tests/project-management.service.test.ts +117 -0
- package/server/modules/projects/tests/project-star.service.test.ts +123 -0
- package/server/modules/projects/tests/projects-has-taskmaster.service.test.ts +105 -0
- package/server/modules/providers/README.md +380 -0
- package/server/modules/providers/index.ts +14 -0
- package/server/modules/providers/list/claude/claude-auth.provider.ts +180 -0
- package/server/modules/providers/list/claude/claude-fork.provider.ts +57 -0
- package/server/modules/providers/list/claude/claude-mcp.provider.ts +135 -0
- package/server/modules/providers/list/claude/claude-models.provider.ts +314 -0
- package/server/modules/providers/list/claude/claude-runtime.provider.js +1217 -0
- package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +203 -0
- package/server/modules/providers/list/claude/claude-sessions.provider.ts +1108 -0
- package/server/modules/providers/list/claude/claude-skills.provider.ts +265 -0
- package/server/modules/providers/list/claude/claude.provider.ts +33 -0
- package/server/modules/providers/list/codex/codex-app-server.client.ts +243 -0
- package/server/modules/providers/list/codex/codex-auth.provider.ts +100 -0
- package/server/modules/providers/list/codex/codex-fork.provider.ts +40 -0
- package/server/modules/providers/list/codex/codex-mcp.provider.ts +135 -0
- package/server/modules/providers/list/codex/codex-models.provider.ts +123 -0
- package/server/modules/providers/list/codex/codex-runtime.provider.js +572 -0
- package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +212 -0
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +2267 -0
- package/server/modules/providers/list/codex/codex-skills.provider.ts +73 -0
- package/server/modules/providers/list/codex/codex.provider.ts +33 -0
- package/server/modules/providers/list/cursor/cursor-auth.provider.ts +143 -0
- package/server/modules/providers/list/cursor/cursor-mcp.provider.ts +108 -0
- package/server/modules/providers/list/cursor/cursor-models.provider.ts +119 -0
- package/server/modules/providers/list/cursor/cursor-runtime.provider.js +387 -0
- package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +158 -0
- package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +654 -0
- package/server/modules/providers/list/cursor/cursor-skills.provider.ts +39 -0
- package/server/modules/providers/list/cursor/cursor.provider.ts +30 -0
- package/server/modules/providers/list/mistral/mistral-auth.provider.ts +181 -0
- package/server/modules/providers/list/mistral/mistral-mcp.provider.ts +20 -0
- package/server/modules/providers/list/mistral/mistral-models.provider.ts +76 -0
- package/server/modules/providers/list/mistral/mistral-runtime.provider.ts +417 -0
- package/server/modules/providers/list/mistral/mistral-session-synchronizer.provider.ts +15 -0
- package/server/modules/providers/list/mistral/mistral-sessions.provider.ts +210 -0
- package/server/modules/providers/list/mistral/mistral-skills.provider.ts +20 -0
- package/server/modules/providers/list/mistral/mistral.provider.ts +35 -0
- package/server/modules/providers/list/opencode/opencode-auth.provider.ts +110 -0
- package/server/modules/providers/list/opencode/opencode-mcp.provider.ts +228 -0
- package/server/modules/providers/list/opencode/opencode-models.provider.ts +304 -0
- package/server/modules/providers/list/opencode/opencode-runtime.provider.js +434 -0
- package/server/modules/providers/list/opencode/opencode-runtime.provider.test.js +261 -0
- package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +179 -0
- package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +500 -0
- package/server/modules/providers/list/opencode/opencode-skills.provider.ts +78 -0
- package/server/modules/providers/list/opencode/opencode.provider.ts +30 -0
- package/server/modules/providers/provider.registry.ts +38 -0
- package/server/modules/providers/provider.routes.ts +909 -0
- package/server/modules/providers/services/mcp.service.ts +109 -0
- package/server/modules/providers/services/provider-auth.service.ts +26 -0
- package/server/modules/providers/services/provider-capabilities.service.ts +133 -0
- package/server/modules/providers/services/provider-models.service.ts +401 -0
- package/server/modules/providers/services/provider-runtime.service.ts +108 -0
- package/server/modules/providers/services/provider-token-usage.service.ts +484 -0
- package/server/modules/providers/services/session-conversations-search.service.ts +1321 -0
- package/server/modules/providers/services/session-history-cache.service.ts +139 -0
- package/server/modules/providers/services/session-synchronizer.service.ts +162 -0
- package/server/modules/providers/services/sessions-watcher.service.ts +266 -0
- package/server/modules/providers/services/sessions.service.ts +663 -0
- package/server/modules/providers/services/skills.service.ts +39 -0
- package/server/modules/providers/shared/base/abstract.provider.ts +32 -0
- package/server/modules/providers/shared/mcp/mcp.provider.ts +151 -0
- package/server/modules/providers/shared/skills/skills.provider.ts +287 -0
- package/server/modules/providers/tests/claude-auth.test.ts +208 -0
- package/server/modules/providers/tests/claude-sessions.test.ts +573 -0
- package/server/modules/providers/tests/claude-subagent-echo.test.ts +25 -0
- package/server/modules/providers/tests/claude-token-budget.test.ts +104 -0
- package/server/modules/providers/tests/codex-message-editing.test.ts +419 -0
- package/server/modules/providers/tests/codex-session-fork.test.ts +59 -0
- package/server/modules/providers/tests/codex-sessions.test.ts +553 -0
- package/server/modules/providers/tests/mcp.test.ts +349 -0
- package/server/modules/providers/tests/opencode-models.test.ts +154 -0
- package/server/modules/providers/tests/opencode-sessions.test.ts +522 -0
- package/server/modules/providers/tests/provider-attachment-history.test.ts +247 -0
- package/server/modules/providers/tests/provider-models.service.test.ts +353 -0
- package/server/modules/providers/tests/provider-runtime.service.test.ts +138 -0
- package/server/modules/providers/tests/provider-token-usage.service.test.ts +373 -0
- package/server/modules/providers/tests/provider.routes.test.ts +244 -0
- package/server/modules/providers/tests/session-fork.test.ts +173 -0
- package/server/modules/providers/tests/session-history-cache.service.test.ts +143 -0
- package/server/modules/providers/tests/session-orphan-prune.test.ts +107 -0
- package/server/modules/providers/tests/session-synchronizer-coalescing.test.ts +136 -0
- package/server/modules/providers/tests/sessions-details.test.ts +73 -0
- package/server/modules/providers/tests/sessions.service.test.ts +193 -0
- package/server/modules/providers/tests/skills.test.ts +692 -0
- package/server/modules/scheduled-messages/index.ts +8 -0
- package/server/modules/scheduled-messages/scheduled-messages.routes.ts +68 -0
- package/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.ts +203 -0
- package/server/modules/scheduled-messages/services/scheduled-messages.service.ts +121 -0
- package/server/modules/scheduled-messages/tests/scheduled-messages.test.ts +310 -0
- package/server/modules/settings/index.ts +2 -0
- package/server/modules/settings/settings.module.ts +50 -0
- package/server/modules/settings/settings.routes.ts +51 -0
- package/server/modules/settings/settings.service.ts +186 -0
- package/server/modules/settings/tests/settings.service.test.ts +54 -0
- package/server/modules/system/index.ts +2 -0
- package/server/modules/system/system.module.ts +62 -0
- package/server/modules/system/system.routes.ts +21 -0
- package/server/modules/system/system.service.ts +76 -0
- package/server/modules/system/tests/system.service.test.ts +118 -0
- package/server/modules/taskmaster/index.ts +2 -0
- package/server/modules/taskmaster/taskmaster.module.ts +24 -0
- package/server/modules/taskmaster/taskmaster.routes.ts +1472 -0
- package/server/modules/taskmaster/taskmaster.service.ts +132 -0
- package/server/modules/taskmaster/tests/taskmaster.routes.test.ts +201 -0
- package/server/modules/taskmaster/tests/taskmaster.service.test.ts +97 -0
- package/server/modules/user/index.ts +2 -0
- package/server/modules/user/tests/user.service.test.ts +151 -0
- package/server/modules/user/user.module.ts +67 -0
- package/server/modules/user/user.routes.ts +94 -0
- package/server/modules/user/user.service.ts +190 -0
- package/server/modules/voice/index.ts +2 -0
- package/server/modules/voice/tests/voice.service.test.ts +99 -0
- package/server/modules/voice/voice.module.ts +47 -0
- package/server/modules/voice/voice.routes.ts +113 -0
- package/server/modules/voice/voice.service.ts +192 -0
- package/server/modules/websocket/README.md +273 -0
- package/server/modules/websocket/index.ts +11 -0
- package/server/modules/websocket/services/chat-run-registry.service.ts +310 -0
- package/server/modules/websocket/services/chat-session-writer.service.ts +175 -0
- package/server/modules/websocket/services/chat-websocket.service.ts +654 -0
- package/server/modules/websocket/services/plugin-websocket-proxy.service.ts +65 -0
- package/server/modules/websocket/services/session-upsert-broadcast.service.ts +108 -0
- package/server/modules/websocket/services/shell-websocket.service.ts +628 -0
- package/server/modules/websocket/services/websocket-auth.service.ts +59 -0
- package/server/modules/websocket/services/websocket-server.service.ts +126 -0
- package/server/modules/websocket/services/websocket-state.service.ts +16 -0
- package/server/modules/websocket/services/websocket-writer.service.ts +38 -0
- package/server/modules/websocket/tests/chat-attachment-filter.test.ts +59 -0
- package/server/modules/websocket/tests/chat-edit-send.test.ts +309 -0
- package/server/modules/websocket/tests/chat-run-registry.test.ts +313 -0
- package/server/modules/websocket/tests/session-upsert-broadcast.test.ts +138 -0
- package/server/modules/websocket/tests/shell-websocket.service.test.ts +227 -0
- package/server/modules/websocket/tests/websocket-heartbeat.service.test.ts +78 -0
- package/server/modules/worktrees/index.ts +2 -0
- package/server/modules/worktrees/services/worktree-create-and-open.service.ts +53 -0
- package/server/modules/worktrees/services/worktree-create.service.ts +104 -0
- package/server/modules/worktrees/services/worktree-git.service.ts +207 -0
- package/server/modules/worktrees/services/worktree-list.service.ts +143 -0
- package/server/modules/worktrees/services/worktree-merge.service.ts +157 -0
- package/server/modules/worktrees/services/worktree-open.service.ts +85 -0
- package/server/modules/worktrees/services/worktree-remove.service.ts +88 -0
- package/server/modules/worktrees/tests/worktree-create-and-open.service.test.ts +68 -0
- package/server/modules/worktrees/tests/worktree-create.service.test.ts +142 -0
- package/server/modules/worktrees/tests/worktree-git.service.test.ts +84 -0
- package/server/modules/worktrees/tests/worktree-list.service.test.ts +45 -0
- package/server/modules/worktrees/tests/worktree-merge.service.test.ts +213 -0
- package/server/modules/worktrees/tests/worktree-open.service.test.ts +122 -0
- package/server/modules/worktrees/tests/worktree-remove.service.test.ts +227 -0
- package/server/modules/worktrees/tests/worktrees.routes.test.ts +199 -0
- package/server/modules/worktrees/worktrees.module.ts +113 -0
- package/server/modules/worktrees/worktrees.routes.ts +116 -0
- package/server/piecemaker/activation/claude-mcp.cjs +115 -0
- package/server/piecemaker/activation/claude-plugins.cjs +65 -0
- package/server/piecemaker/activation/codex-mcp.cjs +102 -0
- package/server/piecemaker/activation/errors.cjs +15 -0
- package/server/piecemaker/activation/index.cjs +5 -0
- package/server/piecemaker/activation/library.cjs +284 -0
- package/server/piecemaker/activation/library.cjs.bak +223 -0
- package/server/piecemaker/activation/library.test.js +365 -0
- package/server/piecemaker/activation/markdown.cjs +108 -0
- package/server/piecemaker/activation/markdown.cjs.bak +78 -0
- package/server/piecemaker/activation/paths.cjs +104 -0
- package/server/piecemaker/activation/paths.test.js +158 -0
- package/server/piecemaker/activation/routes.cjs +101 -0
- package/server/piecemaker/activation/routes.test.js +344 -0
- package/server/piecemaker/activation/snapshot.cjs +30 -0
- package/server/piecemaker/anonymizer/anonymizer.test.js +320 -0
- package/server/piecemaker/anonymizer/client-config.cjs +228 -0
- package/server/piecemaker/anonymizer/codex-http.test.js +20 -0
- package/server/piecemaker/anonymizer/dictionary.cjs +174 -0
- package/server/piecemaker/anonymizer/lifecycle.test.ts +37 -0
- package/server/piecemaker/anonymizer/lifecycle.ts +22 -0
- package/server/piecemaker/anonymizer/providers.cjs +241 -0
- package/server/piecemaker/anonymizer/proxy.cjs +365 -0
- package/server/piecemaker/anonymizer/rewrite.cjs +251 -0
- package/server/piecemaker/anonymizer/routes.cjs +43 -0
- package/server/piecemaker/anonymizer/service.cjs +218 -0
- package/server/piecemaker/harness/chat-harness.test.ts +131 -0
- package/server/piecemaker/harness/chat-harness.ts +151 -0
- package/server/piecemaker/harness/citation-instructions.ts +18 -0
- package/server/piecemaker/harness/citation-store.ts +80 -0
- package/server/piecemaker/harness/citation-turn.test.ts +155 -0
- package/server/piecemaker/harness/citation-turn.ts +197 -0
- package/server/piecemaker/harness/citations.routes.test.ts +68 -0
- package/server/piecemaker/harness/citations.routes.ts +18 -0
- package/server/piecemaker/harness/decisions.cjs +302 -0
- package/server/piecemaker/harness/flux-sse.cjs +111 -0
- package/server/piecemaker/harness/harness.test.js +296 -0
- package/server/piecemaker/harness/index.cjs +103 -0
- package/server/piecemaker/harness/proxy-harness.test.js +226 -0
- package/server/piecemaker/harness/source-passages.ts +21 -0
- package/server/piecemaker/harness/verification.cjs +159 -0
- package/server/piecemaker/index.ts +59 -0
- package/server/piecemaker/library/index.ts +54 -0
- package/server/piecemaker/library/marketplace.ts +152 -0
- package/server/piecemaker/library/migrate-cli.ts +11 -0
- package/server/piecemaker/library/migrate.ts +49 -0
- package/server/piecemaker/library/policy.cjs +9 -0
- package/server/piecemaker/library/provider-skills.test.ts +61 -0
- package/server/piecemaker/library/provider-skills.ts +48 -0
- package/server/piecemaker/library/routes.ts +38 -0
- package/server/piecemaker/library/runtime.ts +39 -0
- package/server/piecemaker/library/store.test.ts +280 -0
- package/server/piecemaker/library/store.ts +429 -0
- package/server/piecemaker/library/workspace-installation.ts +61 -0
- package/server/piecemaker/originals-cache.cjs +54 -0
- package/server/piecemaker/originals-cache.test.js +77 -0
- package/server/piecemaker/protection/bypass.cjs +73 -0
- package/server/piecemaker/protection/bypass.test.js +140 -0
- package/server/piecemaker/protection/routes.cjs +39 -0
- package/server/piecemaker/router.cjs +109 -0
- package/server/piecemaker/timesheet/index.ts +9 -0
- package/server/piecemaker/timesheet/routes.ts +48 -0
- package/server/piecemaker/timesheet/service.test.ts +142 -0
- package/server/piecemaker/timesheet/service.ts +298 -0
- package/server/piecemaker/timesheet/store.ts +167 -0
- package/server/piecemaker/vendor/.env +2 -0
- package/server/piecemaker/vendor/installer/assets/PieceMakerApp.swift +189 -0
- package/server/piecemaker/vendor/installer/assets/claude-hooks/piecemaker-guard-secrets.mjs +230 -0
- package/server/piecemaker/vendor/installer/assets/claude-hooks/piecemaker-secret-paths.default.json +1 -0
- package/server/piecemaker/vendor/installer/assets/piecemaker.icns +0 -0
- package/server/piecemaker/vendor/installer/assets/piecemaker.ico +0 -0
- package/server/piecemaker/vendor/installer/bin/piecemaker.mjs +1018 -0
- package/server/piecemaker/vendor/installer/lib/banner.mjs +35 -0
- package/server/piecemaker/vendor/installer/lib/codex-skills.mjs +343 -0
- package/server/piecemaker/vendor/installer/lib/commandes.mjs +13 -0
- package/server/piecemaker/vendor/installer/lib/desktop-launcher.mjs +305 -0
- package/server/piecemaker/vendor/installer/lib/legacy-legifrance.mjs +72 -0
- package/server/piecemaker/vendor/installer/lib/platform.mjs +163 -0
- package/server/piecemaker/vendor/installer/lib/prompt.mjs +138 -0
- package/server/piecemaker/vendor/installer/lib/resume-steps.mjs +174 -0
- package/server/piecemaker/vendor/installer/lib/secrets-guard.mjs +193 -0
- package/server/piecemaker/vendor/installer/lib/service.mjs +467 -0
- package/server/piecemaker/vendor/installer/lib/state.mjs +114 -0
- package/server/piecemaker/vendor/installer/lib/ui.mjs +159 -0
- package/server/piecemaker/vendor/installer/steps/00-identite.mjs +71 -0
- package/server/piecemaker/vendor/installer/steps/01-prerequis.mjs +119 -0
- package/server/piecemaker/vendor/installer/steps/02-dependances-node.mjs +127 -0
- package/server/piecemaker/vendor/installer/steps/03-python-gliner.mjs +249 -0
- package/server/piecemaker/vendor/installer/steps/03b-python-graphify.mjs +161 -0
- package/server/piecemaker/vendor/installer/steps/04-conversion-md.mjs +147 -0
- package/server/piecemaker/vendor/installer/steps/05-certificats.mjs +171 -0
- package/server/piecemaker/vendor/installer/steps/06-hooks.mjs +266 -0
- package/server/piecemaker/vendor/installer/steps/07-legifrance.mjs +279 -0
- package/server/piecemaker/vendor/installer/steps/08-telegram.mjs +482 -0
- package/server/piecemaker/vendor/installer/steps/09-claude-assets.mjs +151 -0
- package/server/piecemaker/vendor/installer/steps/09-codex-plugin.mjs +142 -0
- package/server/piecemaker/vendor/installer/steps/10-libreoffice.mjs +130 -0
- package/server/piecemaker/vendor/installer/steps/10-pandoc.mjs +212 -0
- package/server/piecemaker/vendor/installer/steps/11-docx-cli.mjs +144 -0
- package/server/piecemaker/vendor/installer/steps/12-mcp-piecemaker.mjs +124 -0
- package/server/piecemaker/vendor/installer/steps/13-garde-secrets.mjs +136 -0
- package/server/piecemaker/vendor/installer/steps/14-mxc-sandbox.mjs +271 -0
- package/server/piecemaker/vendor/installer/steps/15-pwa-desktop.mjs +64 -0
- package/server/piecemaker/vendor/installer/templates/root-CLAUDE.md +79 -0
- package/server/piecemaker/vendor/installer/templates/workspace-CLAUDE.md +111 -0
- package/server/piecemaker/vendor/mcp/README.md +13 -0
- package/server/piecemaker/vendor/mcp/piecemaker/server.mjs +222 -0
- package/server/piecemaker/vendor/mike-defaults-fr/PROVENANCE.md +10 -0
- package/server/piecemaker/vendor/mike-defaults-fr/assistant-workflows/compare-documents/SKILL.md +51 -0
- package/server/piecemaker/vendor/mike-defaults-fr/assistant-workflows/draft-from-template/SKILL.md +27 -0
- package/server/piecemaker/vendor/mike-defaults-fr/assistant-workflows/extract-key-terms/SKILL.md +43 -0
- package/server/piecemaker/vendor/mike-defaults-fr/assistant-workflows/proofread/SKILL.md +98 -0
- package/server/piecemaker/vendor/mike-defaults-fr/catalog.json +22 -0
- package/server/piecemaker/vendor/mike-defaults-fr/tabular-review-workflows/commercial-agreement-tabular-review/SKILL.md +31 -0
- package/server/piecemaker/vendor/mike-defaults-fr/tabular-review-workflows/commercial-agreement-tabular-review/table-columns.yaml +92 -0
- package/server/piecemaker/vendor/package.json +7 -0
- package/server/piecemaker/vendor/piecemaker-plugin/README.md +115 -0
- package/server/piecemaker/vendor/piecemaker-plugin/agents/analyste-piece.md +38 -0
- package/server/piecemaker/vendor/piecemaker-plugin/agents/jurisprudence-scanner.md +50 -0
- package/server/piecemaker/vendor/piecemaker-plugin/hooks/hooks.json +84 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/billing-track.mjs +194 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/build-recherche-pdf.mjs +91 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/classify-ai-documents.mjs +123 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/commit-track.mjs +71 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/compile-recherche.mjs +107 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/anonymization-state.cjs +146 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/case-folder-structure.cjs +303 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/case-folders.cjs +86 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/central-mapping.cjs +0 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/citation-sources.cjs +519 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/citations.cjs +292 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/commits.cjs +1683 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/docx-text-worker.cjs +43 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/hook-io.mjs +249 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/institutional-terms.cjs +160 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/legifrance-reads.cjs +85 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/mapping.cjs +346 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/mapping.test.js +68 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/protection.cjs +419 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/recherche-report.cjs +177 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/session-timing.cjs +80 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/substitution.cjs +318 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/verify-citations.cjs +318 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/protect-originals.mjs +256 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/session-commit-worker.mjs +260 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/session-commit.mjs +83 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/statusline.mjs +179 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/track-legifrance-reads.mjs +46 -0
- package/server/piecemaker/vendor/piecemaker-plugin/skills/ajout-de-fonctionnalites/SKILL.md +66 -0
- package/server/piecemaker/vendor/piecemaker-plugin/skills/conversion-md/SKILL.md +60 -0
- package/server/piecemaker/vendor/piecemaker-plugin/skills/graphe-juridique/SKILL.md +105 -0
- package/server/piecemaker/vendor/piecemaker-plugin/skills/recherche-juridique/SKILL.md +234 -0
- package/server/piecemaker/vendor/piecemaker-plugin/skills/redaction-juridique/SKILL.md +96 -0
- package/server/piecemaker/vendor/piecemaker-plugin/skills/tamponnage/SKILL.md +148 -0
- package/server/piecemaker/vendor/websocket-server/admin-routes.cjs +3195 -0
- package/server/piecemaker/vendor/websocket-server/case-folder-structure.cjs +3 -0
- package/server/piecemaker/vendor/websocket-server/case-instructions.cjs +114 -0
- package/server/piecemaker/vendor/websocket-server/case-registry.cjs +93 -0
- package/server/piecemaker/vendor/websocket-server/claude-assets.cjs +338 -0
- package/server/piecemaker/vendor/websocket-server/claude-hooks.cjs +380 -0
- package/server/piecemaker/vendor/websocket-server/document-index.cjs +682 -0
- package/server/piecemaker/vendor/websocket-server/graphify-document-graph.cjs +456 -0
- package/server/piecemaker/vendor/websocket-server/legal-chronology.cjs +502 -0
- package/server/piecemaker/vendor/websocket-server/legal-forms.cjs +89 -0
- package/server/piecemaker/vendor/websocket-server/legal-forms.test.js +10 -0
- package/server/piecemaker/vendor/websocket-server/legal-graph-materializer.cjs +234 -0
- package/server/piecemaker/vendor/websocket-server/legal-graph-prompt.txt +155 -0
- package/server/piecemaker/vendor/websocket-server/legal-graph.cjs +2021 -0
- package/server/piecemaker/vendor/websocket-server/legal-identity-boundary.cjs +247 -0
- package/server/piecemaker/vendor/websocket-server/legal-party-registry.cjs +261 -0
- package/server/piecemaker/vendor/websocket-server/lib/doc-generate.cjs +229 -0
- package/server/piecemaker/vendor/websocket-server/lib/export-render.cjs +442 -0
- package/server/piecemaker/vendor/websocket-server/lib/office-to-pdf.cjs +306 -0
- package/server/piecemaker/vendor/websocket-server/lib/stamping.cjs +39 -0
- package/server/piecemaker/vendor/websocket-server/originals-pipeline.cjs +965 -0
- package/server/piecemaker/vendor/websocket-server/scripts/ADDRESS_ANONYMIZATION.md +216 -0
- package/server/piecemaker/vendor/websocket-server/scripts/DEDUPLICATION_CHANGES.md +134 -0
- package/server/piecemaker/vendor/websocket-server/scripts/GLINER2_README.md +138 -0
- package/server/piecemaker/vendor/websocket-server/scripts/__pycache__/scan_utils.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/convert_and_scan_pipeline.py +1984 -0
- package/server/piecemaker/vendor/websocket-server/scripts/graphify-legal-sitecustomize.py +50 -0
- package/server/piecemaker/vendor/websocket-server/scripts/mineru_improved_1.py +208 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/AUDIT_PERF_QUALITE.md +246 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/MODELE_COREML.md +62 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/__pycache__/coreml_runtime.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/__pycache__/model_config.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/build_coreml.py +118 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/coreml_runtime.py +164 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/BACKENDS_MESURES.md +384 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/CRITERES_QUALITE.md +81 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/__pycache__/coreml_encoder.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/bench_backend.py +151 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/bench_batchsize.py +47 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/bench_coreml_pipeline.py +138 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/bench_gensight.py +124 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/check_gold.py +66 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/coreml_encoder.py +182 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/encoder_backends.py +179 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/evaluate.py +130 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/gold.py +59 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/profile_split.py +65 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/slice_A.md +67 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/slice_B.md +57 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/slice_C.md +907 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/sweep.py +115 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/verify_substitution.cjs +116 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/verify_whitespace.py +67 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/verify_worker.py +106 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/model_config.py +28 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/presidio-gliner.py +471 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/scanner_worker.py +668 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/__init__.py +10 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/__pycache__/__init__.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/__pycache__/analysis_explanation.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/__pycache__/entity_recognizer.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/__pycache__/local_recognizer.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/__pycache__/pattern.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/__pycache__/pattern_recognizer.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/__pycache__/recognizer_result.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/analysis_explanation.py +65 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/entity_recognizer.py +213 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/input_validation/__init__.py +21 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/input_validation/language_validation.py +18 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/input_validation/schemas.py +132 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/input_validation/yaml_recognizer_models.py +422 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/local_recognizer.py +7 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/__init__.py +15 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/__pycache__/__init__.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/__pycache__/device_detector.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/__pycache__/ner_model_configuration.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/__pycache__/nlp_artifacts.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/__pycache__/nlp_engine.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/__pycache__/spacy_nlp_engine.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/device_detector.py +65 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/ner_model_configuration.py +128 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/nlp_artifacts.py +88 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/nlp_engine.py +65 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/nlp_engine_provider.py +114 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/spacy_nlp_engine.py +290 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/pattern.py +66 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/pattern_recognizer.py +297 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/__init__.py +4 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/__pycache__/__init__.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/__pycache__/gliner_recognizer.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/__init__.py +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/__pycache__/__init__.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/__pycache__/credit_card_recognizer.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/__pycache__/crypto_recognizer.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/__pycache__/email_recognizer.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/__pycache__/iban_patterns.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/__pycache__/iban_recognizer.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/__pycache__/ip_recognizer.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/__pycache__/mac_recognizer.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/__pycache__/url_recognizer.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/credit_card_recognizer.py +82 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/crypto_recognizer.py +141 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/date_recognizer.py +102 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/email_recognizer.py +48 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/iban_patterns.py +173 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/iban_recognizer.py +221 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/ip_recognizer.py +65 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/mac_recognizer.py +73 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/url_recognizer.py +50 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/gliner_recognizer.py +218 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/spacy_recognizer.py +139 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/recognizer_result.py +188 -0
- package/server/piecemaker/vendor/websocket-server/scripts/requirements.txt +29 -0
- package/server/piecemaker/vendor/websocket-server/scripts/scan_utils.py +469 -0
- package/server/piecemaker/vendor/websocket-server/scripts/smart_converter.py +356 -0
- package/server/piecemaker/vendor/websocket-server/scripts/warmup.py +454 -0
- package/server/piecemaker/vendor/websocket-server/stamping-routes.cjs +334 -0
- package/server/piecemaker/vendor/websocket-server/telegram-admin.cjs +602 -0
- package/server/piecemaker/vendor/websocket-server/workspace-paths.cjs +22 -0
- package/server/shared/claude-cli-path.ts +157 -0
- package/server/shared/frontmatter.ts +18 -0
- package/server/shared/image-attachments.ts +437 -0
- package/server/shared/interfaces.ts +241 -0
- package/server/shared/message-unification.ts +449 -0
- package/server/shared/tests/application-data-root.test.ts +29 -0
- package/server/shared/tests/claude-cli-path.test.ts +118 -0
- package/server/shared/tests/image-attachments.test.ts +353 -0
- package/server/shared/tests/message-unification.test.ts +218 -0
- package/server/shared/tests/slice-tail-page.test.ts +42 -0
- package/server/shared/types.ts +1354 -0
- package/server/shared/utils.ts +1212 -0
- package/server/tsconfig.json +40 -0
- package/shared/networkHosts.js +22 -0
- 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
|
+
};
|