@piecemaker-legal/piecemaker 2.0.8 → 2.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/dist/assets/FindReplaceDialog-KU5TEIBJ-CQ76sPaJ.js +1 -0
  2. package/dist/assets/FootnotePropertiesDialog-W3YHM4BV-BpgYQvvd.js +1 -0
  3. package/dist/assets/HyperlinkDialog-ZYH7ZHRF-qIPBOssF.css +1 -0
  4. package/dist/assets/HyperlinkDialog-ZYH7ZHRF-z08UbYbL.js +144 -0
  5. package/dist/assets/ImagePositionDialog-HEWYR6Q3-CFzOnzAt.js +1 -0
  6. package/dist/assets/ImagePropertiesDialog-RAB47C6M-D-ThGU-b.js +1 -0
  7. package/dist/assets/PageSetupDialog-AIGIXO3E-_PYIO8eb.js +1 -0
  8. package/dist/assets/SplitCellDialog-IFBKYCD6-Cb9uQOB8.js +1 -0
  9. package/dist/assets/TablePropertiesDialog-TMLKGOOW-12vmjb6B.js +1 -0
  10. package/dist/assets/WatermarkDialog-KRL7WMIS-C-d8rzt2.js +1 -0
  11. package/dist/assets/{abnfDiagram-VCTEODGH-XuFdW3jA.js → abnfDiagram-VCTEODGH-B4OsX7eF.js} +1 -1
  12. package/dist/assets/arc-BK1oUIk7.js +1 -0
  13. package/dist/assets/architectureDiagram-5GKGNRK7-CfaFI6L7.js +36 -0
  14. package/dist/assets/{blockDiagram-I7D4REHJ-CsgOSDLB.js → blockDiagram-I7D4REHJ-DQEI2azY.js} +1 -1
  15. package/dist/assets/{c4Diagram-7LVT6UL2-awKtsGCB.js → c4Diagram-7LVT6UL2-DbravpVi.js} +1 -1
  16. package/dist/assets/channel-DVyNr2Yg.js +1 -0
  17. package/dist/assets/{chunk-2Q5K7J3B-DA5F8ig-.js → chunk-2Q5K7J3B-BLpZ9huC.js} +1 -1
  18. package/dist/assets/{chunk-5VM5RSS4-DawRFEGW.js → chunk-5VM5RSS4-BLlEdmX9.js} +1 -1
  19. package/dist/assets/{chunk-F27PBJKO-BO6B_U1p.js → chunk-F27PBJKO-D-ZNhI6q.js} +1 -1
  20. package/dist/assets/{chunk-IMKFNOWR-Dhrf_k6W.js → chunk-IMKFNOWR-vzSiKnhh.js} +1 -1
  21. package/dist/assets/{chunk-JWPE2WC7-BTGX4CJ7.js → chunk-JWPE2WC7-DfF0k4nb.js} +1 -1
  22. package/dist/assets/{chunk-POPQ4Y6H-ByKLZ0wa.js → chunk-POPQ4Y6H-BnwCYsPP.js} +1 -1
  23. package/dist/assets/{chunk-SVP7TREG-DNCtUeow.js → chunk-SVP7TREG-DRxc8fhq.js} +1 -1
  24. package/dist/assets/{chunk-TICWLB2K-bgcAtBKw.js → chunk-TICWLB2K-Dd2Q-md-.js} +1 -1
  25. package/dist/assets/{chunk-XXDRQBXY-AD-pwqOj.js → chunk-XXDRQBXY-DBjkEUp_.js} +1 -1
  26. package/dist/assets/classDiagram-ZZMXUADV-2pGPHl5H.js +1 -0
  27. package/dist/assets/classDiagram-v2-VYDZK3BY-2pGPHl5H.js +1 -0
  28. package/dist/assets/{cose-bilkent-JH36ORCC-D23VMTjy.js → cose-bilkent-JH36ORCC-By6PS4oe.js} +1 -1
  29. package/dist/assets/{cynefin-OW5HDTMX-thDLtHBg.js → cynefin-OW5HDTMX-YxWpjc5W.js} +1 -1
  30. package/dist/assets/{cynefinDiagram-5FMLGOSQ-BTG5qDGi.js → cynefinDiagram-5FMLGOSQ-DrtXzz3p.js} +1 -1
  31. package/dist/assets/{dagre-GXQ25YYZ-yXAeLV32.js → dagre-GXQ25YYZ-BDpTLKqZ.js} +1 -1
  32. package/dist/assets/{diagram-S7CK7UJ4-v1-YDY3c.js → diagram-S7CK7UJ4-C4LvqeCs.js} +1 -1
  33. package/dist/assets/{diagram-UQ7AKVKN-DlO9hQ63.js → diagram-UQ7AKVKN-BTpnMeHp.js} +1 -1
  34. package/dist/assets/{diagram-VSXAHHWV-B3Bz2C6Y.js → diagram-VSXAHHWV-DucHHWev.js} +1 -1
  35. package/dist/assets/{diagram-VX7I27RA-BosIgZRa.js → diagram-VX7I27RA-DYHZNmmH.js} +1 -1
  36. package/dist/assets/{diagram-Z3DM3KII-C1MFbIji.js → diagram-Z3DM3KII-BHIH2BGN.js} +1 -1
  37. package/dist/assets/{ebnfDiagram-PWID7BFC-B6g9aRAb.js → ebnfDiagram-PWID7BFC-CGiEkwj0.js} +1 -1
  38. package/dist/assets/{erDiagram-RLTQ6QDP-BuV5RUQ-.js → erDiagram-RLTQ6QDP-AJMHi2Us.js} +1 -1
  39. package/dist/assets/{flowDiagram-HODETNUW-OFcS8Psg.js → flowDiagram-HODETNUW-BqzANBh2.js} +1 -1
  40. package/dist/assets/{ganttDiagram-EL5Y4UJY-CxCIs-y4.js → ganttDiagram-EL5Y4UJY-B0xPDY2_.js} +1 -1
  41. package/dist/assets/{gitGraphDiagram-WWUBYQGX-DQHfgTRq.js → gitGraphDiagram-WWUBYQGX-CzPX5CNO.js} +1 -1
  42. package/dist/assets/{index-BPmxqMd5.js → index-Bk2iXKnJ.js} +140 -140
  43. package/dist/assets/index-Dc9wNuS4.css +1 -0
  44. package/dist/assets/{infoDiagram-27XIBGKW-UvDaFHGE.js → infoDiagram-27XIBGKW-Cjc44H0O.js} +1 -1
  45. package/dist/assets/{ishikawaDiagram-5VMMS53U-B04u_588.js → ishikawaDiagram-5VMMS53U-BWhr2v7r.js} +1 -1
  46. package/dist/assets/{journeyDiagram-3NMN7TZE-BYvBbjXl.js → journeyDiagram-3NMN7TZE-Bl4r6RrP.js} +1 -1
  47. package/dist/assets/{kanban-definition-UXKFOSKX-DxAJd2LW.js → kanban-definition-UXKFOSKX-FV-T0Vy1.js} +6 -6
  48. package/dist/assets/layout-CTfb1sAF.js +1 -0
  49. package/dist/assets/{linear-CmWY0F-m.js → linear-Dfgi6vlo.js} +1 -1
  50. package/dist/assets/{mermaid.core-ClUcKNvb.js → mermaid.core-DOCGsdlo.js} +7 -7
  51. package/dist/assets/{mindmap-definition-YA3MSWOX-DZ2GO-J-.js → mindmap-definition-YA3MSWOX-CymQ86ir.js} +1 -1
  52. package/dist/assets/{pegDiagram-XKGWAZYB-DF4i_o7r.js → pegDiagram-XKGWAZYB-1VMq_vi0.js} +1 -1
  53. package/dist/assets/{pieDiagram-E7YTZNPT-G20Z0KTO.js → pieDiagram-E7YTZNPT-C5GgXtJU.js} +1 -1
  54. package/dist/assets/processTemplate-GPFDLQMX-B1sVV2O0.js +22 -0
  55. package/dist/assets/{quadrantDiagram-AXDQQJYC-5MRlN0BO.js → quadrantDiagram-AXDQQJYC-C6zpryYf.js} +1 -1
  56. package/dist/assets/{railroadDiagram-O6MQD6OU-B4Fp-guo.js → railroadDiagram-O6MQD6OU-D-sv4SWi.js} +1 -1
  57. package/dist/assets/{requirementDiagram-BXWQKSXE-DFL0eww_.js → requirementDiagram-BXWQKSXE-CkjY-z4B.js} +1 -1
  58. package/dist/assets/sankeyDiagram-P5KCCOFB-De-d8Q6L.js +40 -0
  59. package/dist/assets/{sequenceDiagram-WJ2MYXX4-iP9OUxwi.js → sequenceDiagram-WJ2MYXX4-DCsGSJa9.js} +1 -1
  60. package/dist/assets/{server.browser-BzI1jrZw.js → server.browser-BY3bL-IX.js} +1 -1
  61. package/dist/assets/{sizeCapture-INFHLROL-C95kgcA-.js → sizeCapture-INFHLROL-xINZhgER.js} +1 -1
  62. package/dist/assets/stateDiagram-D77RDMKH-CuLUTWSL.js +1 -0
  63. package/dist/assets/stateDiagram-v2-MP3YSRHH-Cnbs-M92.js +1 -0
  64. package/dist/assets/{swimlanes-42K2YHIH-BbMY7ECY.js → swimlanes-42K2YHIH-BwxARD7N.js} +1 -1
  65. package/dist/assets/swimlanesDiagram-VR7AAH4N-D7p8qZI2.js +8 -0
  66. package/dist/assets/{timeline-definition-24CTP7MA-BQs8wPer.js → timeline-definition-24CTP7MA-BIOvnn-l.js} +3 -3
  67. package/dist/assets/{vendor-codemirror-CjW0lHR5.js → vendor-codemirror-CN-sJePf.js} +2 -2
  68. package/dist/assets/{vendor-react-D809iwXR.js → vendor-react-B22wqN3J.js} +5 -5
  69. package/dist/assets/{vennDiagram-4TSXK5OY-Imm0EPbt.js → vennDiagram-4TSXK5OY-BSD6Pfmt.js} +4 -4
  70. package/dist/assets/{wardleyDiagram-VM6X3IG4-DpMMDayn.js → wardleyDiagram-VM6X3IG4-7Zz2iRkF.js} +1 -1
  71. package/dist/assets/{xychartDiagram-S5SC5T6Z-DdLp1sYL.js → xychartDiagram-S5SC5T6Z-Ctx17Y1D.js} +1 -1
  72. package/dist/index.html +4 -4
  73. package/dist/sw.js +1 -1
  74. package/dist-server/server/modules/providers/list/mistral/mistral-models.provider.js +4 -53
  75. package/dist-server/server/modules/providers/list/mistral/mistral-models.provider.js.map +1 -1
  76. package/dist-server/server/modules/providers/list/mistral/mistral-runtime.provider.js +2 -3
  77. package/dist-server/server/modules/providers/list/mistral/mistral-runtime.provider.js.map +1 -1
  78. package/dist-server/server/modules/providers/list/mistral/mistral-vibe-models.js +30 -0
  79. package/dist-server/server/modules/providers/list/mistral/mistral-vibe-models.js.map +1 -0
  80. package/dist-server/server/modules/providers/tests/mistral-vibe-models.test.js +24 -0
  81. package/dist-server/server/modules/providers/tests/mistral-vibe-models.test.js.map +1 -0
  82. package/dist-server/server/piecemaker/anonymizer/lifecycle.js +2 -3
  83. package/dist-server/server/piecemaker/anonymizer/lifecycle.js.map +1 -1
  84. package/dist-server/server/piecemaker/desktop-update/index.js +4 -3
  85. package/dist-server/server/piecemaker/desktop-update/index.js.map +1 -1
  86. package/dist-server/server/piecemaker/docx-document.js +50 -0
  87. package/dist-server/server/piecemaker/docx-document.js.map +1 -0
  88. package/dist-server/server/piecemaker/index.js +9 -2
  89. package/dist-server/server/piecemaker/index.js.map +1 -1
  90. package/dist-server/server/piecemaker/model-discovery/codex-catalog.js +46 -0
  91. package/dist-server/server/piecemaker/model-discovery/codex-catalog.js.map +1 -0
  92. package/dist-server/server/piecemaker/model-discovery/index.js +42 -0
  93. package/dist-server/server/piecemaker/model-discovery/index.js.map +1 -0
  94. package/dist-server/server/piecemaker/model-discovery/mistral-catalog.js +19 -0
  95. package/dist-server/server/piecemaker/model-discovery/mistral-catalog.js.map +1 -0
  96. package/dist-server/server/piecemaker/model-discovery/stdio-rpc.js +61 -0
  97. package/dist-server/server/piecemaker/model-discovery/stdio-rpc.js.map +1 -0
  98. package/dist-server/server/piecemaker/project-registry.js +29 -0
  99. package/dist-server/server/piecemaker/project-registry.js.map +1 -0
  100. package/package.json +12 -1
  101. package/scripts/piecemaker/cli/disable-anonymizer.mjs +19 -0
  102. package/scripts/piecemaker/cli/lib/config.mjs +0 -1
  103. package/scripts/piecemaker/cli/lib/services.mjs +1 -10
  104. package/scripts/piecemaker/cli/piecemaker.mjs +0 -3
  105. package/server/modules/providers/list/mistral/mistral-models.provider.ts +4 -53
  106. package/server/modules/providers/list/mistral/mistral-runtime.provider.ts +2 -3
  107. package/server/modules/providers/list/mistral/mistral-vibe-models.ts +43 -0
  108. package/server/modules/providers/tests/mistral-vibe-models.test.ts +27 -0
  109. package/server/piecemaker/anonymizer/client-config.cjs +92 -92
  110. package/server/piecemaker/anonymizer/hudsucker-fixture.cjs +9 -16
  111. package/server/piecemaker/anonymizer/hudsucker-proxy/Cargo.toml +1 -1
  112. package/server/piecemaker/anonymizer/hudsucker-proxy/src/main.rs +251 -268
  113. package/server/piecemaker/anonymizer/lifecycle.ts +2 -3
  114. package/server/piecemaker/anonymizer/rewriter-bridge.cjs +6 -0
  115. package/server/piecemaker/anonymizer/service.cjs +117 -175
  116. package/server/piecemaker/desktop-update/index.ts +4 -3
  117. package/server/piecemaker/docx-document.ts +54 -0
  118. package/server/piecemaker/harness/decisions.cjs +1 -1
  119. package/server/piecemaker/harness/index.cjs +2 -3
  120. package/server/piecemaker/harness/verification.cjs +1 -1
  121. package/server/piecemaker/index.ts +11 -3
  122. package/server/piecemaker/model-discovery/codex-catalog.ts +67 -0
  123. package/server/piecemaker/model-discovery/index.ts +69 -0
  124. package/server/piecemaker/model-discovery/mistral-catalog.ts +36 -0
  125. package/server/piecemaker/model-discovery/stdio-rpc.ts +79 -0
  126. package/server/piecemaker/project-registry.ts +34 -0
  127. package/server/piecemaker/router.cjs +6 -7
  128. package/server/piecemaker/vendor/installer/bin/piecemaker.mjs +31 -77
  129. package/server/piecemaker/vendor/installer/lib/codex-skills.mjs +2 -84
  130. package/server/piecemaker/vendor/installer/lib/service.mjs +4 -24
  131. package/server/piecemaker/vendor/installer/steps/06-hooks.mjs +10 -21
  132. package/server/piecemaker/vendor/installer/steps/09-claude-assets.mjs +0 -11
  133. package/server/piecemaker/vendor/installer/steps/09-codex-plugin.mjs +6 -36
  134. package/server/piecemaker/vendor/installer/steps/13-garde-secrets.mjs +30 -12
  135. package/server/piecemaker/vendor/piecemaker-plugin/README.md +2 -9
  136. package/server/piecemaker/vendor/piecemaker-plugin/scripts/billing-track.mjs +5 -5
  137. package/server/piecemaker/vendor/piecemaker-plugin/scripts/classify-ai-documents.mjs +8 -8
  138. package/server/piecemaker/vendor/piecemaker-plugin/scripts/commit-track.mjs +2 -2
  139. package/server/piecemaker/vendor/piecemaker-plugin/scripts/compile-recherche.mjs +4 -6
  140. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/case-folders.cjs +71 -38
  141. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/mapping.cjs +8 -38
  142. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/protection.cjs +82 -61
  143. package/server/piecemaker/vendor/piecemaker-plugin/scripts/protect-originals.mjs +4 -55
  144. package/server/piecemaker/vendor/piecemaker-plugin/scripts/statusline.mjs +24 -117
  145. package/server/piecemaker/vendor/piecemaker-plugin/skills/tamponnage/SKILL.md +2 -2
  146. package/server/piecemaker/vendor/websocket-server/admin-routes.cjs +15 -121
  147. package/server/piecemaker/vendor/websocket-server/case-registry.cjs +16 -32
  148. package/server/piecemaker/vendor/websocket-server/claude-hooks.cjs +0 -73
  149. package/server/piecemaker/vendor/websocket-server/originals-pipeline.cjs +0 -7
  150. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/scanner_worker.py +2 -0
  151. package/server/piecemaker/vendor/websocket-server/stamping-routes.cjs +3 -12
  152. package/server/piecemaker/vendor/websocket-server/workspace-paths.cjs +6 -6
  153. package/dist/assets/arc-CsCtfL20.js +0 -1
  154. package/dist/assets/architectureDiagram-5GKGNRK7-DGxozBGS.js +0 -36
  155. package/dist/assets/channel-DnynWwYa.js +0 -1
  156. package/dist/assets/classDiagram-ZZMXUADV-CHIsGj2B.js +0 -1
  157. package/dist/assets/classDiagram-v2-VYDZK3BY-CHIsGj2B.js +0 -1
  158. package/dist/assets/index-WwknP0l4.css +0 -1
  159. package/dist/assets/layout-R7w2kLxO.js +0 -1
  160. package/dist/assets/sankeyDiagram-P5KCCOFB-CpOdmqdy.js +0 -40
  161. package/dist/assets/stateDiagram-D77RDMKH-D9WHRvlY.js +0 -1
  162. package/dist/assets/stateDiagram-v2-MP3YSRHH-D9Qlflkb.js +0 -1
  163. package/dist/assets/swimlanesDiagram-VR7AAH4N-CA64dwpM.js +0 -8
  164. package/scripts/piecemaker/cli/lib/piecemaker-anonymizer.mjs +0 -78
  165. package/scripts/piecemaker/hooks/proxy-guard.mjs +0 -251
  166. package/server/piecemaker/anonymizer/providers.cjs +0 -120
  167. package/server/piecemaker/vendor/installer/steps/08-telegram.mjs +0 -482
  168. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/central-mapping.cjs +0 -0
  169. package/server/piecemaker/vendor/websocket-server/telegram-admin.cjs +0 -602
@@ -1,15 +1,14 @@
1
1
  type AnonymizerService = {
2
2
  start(): Promise<unknown>;
3
3
  stop(): Promise<unknown>;
4
- status(): { enabled: boolean; listening: boolean; coverage: Record<string, { state: string }> };
4
+ status(): { enabled: boolean; listening: boolean; reason?: string };
5
5
  };
6
6
 
7
7
  export async function startRequiredAnonymizer(service: AnonymizerService) {
8
8
  const ensureReady = () => {
9
9
  const status = service.status();
10
+ if (status.reason === 'disabled') return;
10
11
  if (!status.enabled || !status.listening) throw new Error('PieceMaker : le proxy PII est indisponible. Démarrage IA refusé.');
11
- const missing = ['claude', 'codex'].filter((provider) => status.coverage[provider]?.state !== 'filtered');
12
- if (missing.length) throw new Error(`PieceMaker : routage PII non configuré pour ${missing.join(', ')}. Démarrage IA refusé.`);
13
12
  };
14
13
  try {
15
14
  await service.start();
@@ -85,6 +85,12 @@ function startRewriterBridge({ dictionary, harness = null }) {
85
85
  const provider = header(request, 'x-piecemaker-provider') || 'claude';
86
86
  const current = () => dictionary.get();
87
87
 
88
+ if (request.method === 'GET' && request.url === '/health') {
89
+ response.writeHead(200);
90
+ response.end('ok');
91
+ return;
92
+ }
93
+
88
94
  if (request.method === 'POST' && request.url === '/v1/request') {
89
95
  readBody(request).then((body) => {
90
96
  stats.requests += 1;
@@ -1,40 +1,27 @@
1
- /**
2
- * Cycle de vie du proxy PII et point d'injection dans CloudCLI.
3
- *
4
- * Deux leviers, aucun ne touchant un fichier upstream.
5
- *
6
- * **L'environnement du serveur.** Les quatre chemins de lancement de CloudCLI —
7
- * chat Claude, Codex, Cursor, opencode — étalent `process.env` dans le
8
- * sous-processus qu'ils créent, tout comme le terminal intégré. Poser des
9
- * variables ici les atteint donc tous les cinq sans ajouter une ligne ailleurs.
10
- *
11
- * **Les configurations utilisateur des clients.** Codex ne lit pas de variable
12
- * de base d'URL, opencode préfère sa configuration ; `providers.cjs` s'en
13
- * charge, et pose le garde-fou de Cursor, seul client non interceptable.
14
- *
15
- * Le proxy vit et meurt avec le serveur : à l'arrêt, les bases écrites sont
16
- * retirées pour ne pas laisser les clients pointer un port fermé.
17
- */
18
1
  const { spawn } = require('node:child_process');
19
2
  const fs = require('node:fs');
20
3
  const os = require('node:os');
21
4
  const path = require('node:path');
5
+ const tls = require('node:tls');
22
6
 
23
7
  const { createSqliteDictionaryLoader } = require('./sqlite-dictionary.cjs');
24
8
  const { startRewriterBridge } = require('./rewriter-bridge.cjs');
25
- const { bypassProviders, configureProviders } = require('./providers.cjs');
9
+ const { removeLegacyProxyConfig } = require('./client-config.cjs');
26
10
  const { createHarnessJuridique } = require('../harness/index.cjs');
27
11
 
28
- const ENV_VAR = 'ANTHROPIC_BASE_URL';
29
- const OPENAI_ENV_VAR = 'OPENAI_BASE_URL';
30
12
  const DEFAULT_UPSTREAM = 'https://api.anthropic.com';
31
- const DEFAULT_PORT = 4111;
32
- const PROXY_ENV_KEYS = [
33
- 'HTTPS_PROXY', 'HTTP_PROXY', 'ALL_PROXY', 'NO_PROXY', 'NODE_USE_ENV_PROXY',
34
- 'NODE_EXTRA_CA_CERTS', 'SSL_CERT_FILE', 'CODEX_CA_CERTIFICATE', 'REQUESTS_CA_BUNDLE',
35
- ];
13
+ const INTERCEPTED_HOSTS = ['api.anthropic.com', 'api.openai.com', 'chatgpt.com'];
14
+ const COVERAGE = {
15
+ claude: { state: 'filtered', detail: null, file: null },
16
+ codex: { state: 'filtered', detail: null, file: null },
17
+ opencode: { state: 'filtered', detail: null, file: null },
18
+ cursor: { state: 'blocked', detail: 'not-interceptable', file: null },
19
+ };
20
+
21
+ const CURSOR_REFUSAL = 'PieceMaker : cursor-agent ne peut pas être filtré par le proxy d’anonymisation '
22
+ + '(son protocole n’accepte aucune base d’URL). Tant qu’un mapping existe, il est bloqué pour éviter '
23
+ + 'que des noms de parties quittent la machine en clair. Utilisez Claude, Codex ou opencode.';
36
24
 
37
- /** Interrupteur : `PIECEMAKER_ANONYMIZER=off`, ou `anonymizer.enabled: false` dans `config.json`. */
38
25
  function isDisabled(homeDir) {
39
26
  const flag = String(process.env.PIECEMAKER_ANONYMIZER || '').toLowerCase();
40
27
  if (flag === 'off' || flag === '0' || flag === 'false') return true;
@@ -46,13 +33,6 @@ function isDisabled(homeDir) {
46
33
  }
47
34
  }
48
35
 
49
- /**
50
- * Cible du relais Anthropic. Une base distante déjà configurée (passerelle
51
- * d'entreprise, point de terminaison alternatif) est respectée : on se chaîne
52
- * derrière elle. Une base en boucle locale ne peut être qu'une instance
53
- * précédente de ce proxy, et serait un relais mort : on l'ignore au profit de
54
- * l'API officielle.
55
- */
56
36
  function resolveUpstream(configured) {
57
37
  if (!configured) return DEFAULT_UPSTREAM;
58
38
  try {
@@ -64,35 +44,15 @@ function resolveUpstream(configured) {
64
44
  }
65
45
  }
66
46
 
67
- /**
68
- * Lecture du rapport de `configureProviders` en une couverture par client, la
69
- * seule chose que l'interface a besoin de montrer.
70
- *
71
- * `filtered` : les échanges passent par le proxy et sont anonymisés.
72
- * `blocked` : le client ne peut pas être filtré et est donc empêché de partir.
73
- * `unconfigured` : le câblage a échoué ou est en conflit — le client peut
74
- * encore parler en direct, et c'est précisément ce que le statut doit crier.
75
- */
76
- function summarizeCoverage(report) {
77
- const coverage = {};
78
- for (const [name, result] of Object.entries(report || {})) {
79
- if (result?.blocked) coverage[name] = { state: 'blocked', detail: result.reason, file: result.file || null };
80
- else if (result?.configured) coverage[name] = { state: 'filtered', detail: null, file: result.file || null };
81
- else coverage[name] = { state: 'unconfigured', detail: result?.reason || 'unknown', file: result?.file || null };
82
- }
83
- return coverage;
84
- }
85
-
86
47
  function hudsuckerBinary() {
87
48
  if (process.env.PIECEMAKER_HUDSUCKER_BIN) return process.env.PIECEMAKER_HUDSUCKER_BIN;
88
49
  const name = process.platform === 'win32' ? 'piecemaker-hudsucker.exe' : 'piecemaker-hudsucker';
89
- const packaged = path.join(__dirname, 'bin', name);
90
- const release = path.join(__dirname, 'hudsucker-proxy', 'target', 'release', name);
91
- const debug = path.join(__dirname, 'hudsucker-proxy', 'target', 'debug', name);
92
- if (fs.existsSync(packaged)) return packaged;
93
- if (fs.existsSync(release)) return release;
94
- if (fs.existsSync(debug)) return debug;
95
- return packaged;
50
+ const candidates = [
51
+ path.join(__dirname, 'bin', name),
52
+ path.join(__dirname, 'hudsucker-proxy', 'target', 'release', name),
53
+ path.join(__dirname, 'hudsucker-proxy', 'target', 'debug', name),
54
+ ];
55
+ return candidates.find((candidate) => fs.existsSync(candidate)) || candidates[0];
96
56
  }
97
57
 
98
58
  function certificatePaths() {
@@ -100,75 +60,95 @@ function certificatePaths() {
100
60
  return {
101
61
  cert: path.join(directory, 'piecemaker-ca.crt'),
102
62
  key: path.join(directory, 'piecemaker-ca.key'),
63
+ trustBundle: path.join(directory, 'piecemaker-trust-bundle.pem'),
103
64
  };
104
65
  }
105
66
 
106
- function waitForOutput(child, needle, timeoutMs) {
67
+ function writeTrustBundle(caFile, bundleFile) {
68
+ const content = [...tls.rootCertificates, fs.readFileSync(caFile, 'utf8').trim()].join('\n');
69
+ const temporary = `${bundleFile}.piecemaker-${process.pid}-${Date.now()}`;
70
+ fs.writeFileSync(temporary, `${content}\n`, { encoding: 'utf8', mode: 0o644 });
71
+ fs.renameSync(temporary, bundleFile);
72
+ return bundleFile;
73
+ }
74
+
75
+ function removeLegacyCentralMapping(homeDir) {
76
+ let entries = [];
77
+ try {
78
+ entries = fs.readdirSync(homeDir);
79
+ } catch {
80
+ return;
81
+ }
82
+ for (const name of entries) {
83
+ if (name.startsWith('central-mapping.json')) fs.rmSync(path.join(homeDir, name), { force: true });
84
+ }
85
+ }
86
+
87
+ function installCursorGuard({ binDir, mappingFile }) {
88
+ fs.mkdirSync(binDir, { recursive: true });
89
+ const file = path.join(binDir, 'cursor-agent');
90
+ const script = `#!/bin/sh
91
+ if [ -s ${JSON.stringify(mappingFile)} ]; then
92
+ printf '%s\\n' ${JSON.stringify(CURSOR_REFUSAL)} >&2
93
+ exit 78
94
+ fi
95
+ PATH=$(printf '%s' "$PATH" | sed -e "s|^${binDir.replace(/[|\\]/g, '\\$&')}:||")
96
+ export PATH
97
+ exec cursor-agent "$@"
98
+ `;
99
+ fs.writeFileSync(file, script, { encoding: 'utf8', mode: 0o755 });
100
+ return { file };
101
+ }
102
+
103
+ function waitForListening(child, timeoutMs) {
107
104
  return new Promise((resolve, reject) => {
108
105
  let buffer = '';
109
- let settled = false;
110
- const finish = (failed, value) => {
111
- if (settled) return;
112
- settled = true;
106
+ const finish = (error, port) => {
113
107
  clearTimeout(timer);
114
- child.stdout?.off('data', take);
115
- child.stderr?.off('data', take);
108
+ child.stdout.off('data', take);
116
109
  child.off('exit', onExit);
117
- if (failed) reject(value);
118
- else resolve(value);
110
+ if (error) reject(error);
111
+ else resolve(port);
119
112
  };
120
- const timer = setTimeout(() => finish(true, new Error(`hudsucker n'a pas écouté : ${buffer}`)), timeoutMs);
113
+ const timer = setTimeout(() => finish(new Error(`hudsucker n'a pas écouté : ${buffer}`)), timeoutMs);
121
114
  const take = (chunk) => {
122
115
  buffer += chunk.toString();
123
- if (buffer.includes(needle)) finish(false, buffer);
116
+ const match = buffer.match(/listening 127\.0\.0\.1:(\d+)/);
117
+ if (match) finish(null, Number(match[1]));
124
118
  };
125
- const onExit = (code) => finish(true, new Error(`hudsucker sorti (${code}) : ${buffer}`));
126
- child.stdout?.on('data', take);
127
- child.stderr?.on('data', take);
119
+ const onExit = (code) => finish(new Error(`hudsucker sorti (${code}) : ${buffer}`));
120
+ child.stdout.on('data', take);
128
121
  child.once('exit', onExit);
129
122
  });
130
123
  }
131
124
 
132
- function proxyEnvironment(proxyUrl, caFile) {
125
+ function proxyEnvironment(proxyUrl, caFile, trustBundle) {
133
126
  return {
134
127
  HTTPS_PROXY: proxyUrl,
135
- HTTP_PROXY: proxyUrl,
136
- ALL_PROXY: proxyUrl,
128
+ https_proxy: proxyUrl,
137
129
  NO_PROXY: 'localhost,127.0.0.1,::1',
138
130
  NODE_USE_ENV_PROXY: '1',
139
131
  NODE_EXTRA_CA_CERTS: caFile,
140
- SSL_CERT_FILE: caFile,
132
+ SSL_CERT_FILE: trustBundle,
141
133
  CODEX_CA_CERTIFICATE: caFile,
142
- REQUESTS_CA_BUNDLE: caFile,
134
+ REQUESTS_CA_BUNDLE: trustBundle,
143
135
  };
144
136
  }
145
137
 
146
- function createAnonymizerService({ homeDir, userHome = os.homedir(), logger = console, required = false }) {
138
+ function createAnonymizerService({ homeDir, userHome = os.homedir(), logger = console }) {
147
139
  const dictionary = createSqliteDictionaryLoader({ databasePath: process.env.DATABASE_PATH || path.join(homeDir, 'auth.db') });
148
- const legacyMappingFile = path.join(homeDir, 'central-mapping.json');
149
- // Harnais de citations vérifiées (décisions Légifrance + bloc <CITATIONS>) :
150
- // son propre interrupteur (`PIECEMAKER_CITATIONS=off`) est géré à
151
- // l'intérieur, pas ici.
152
140
  const harness = createHarnessJuridique({ homeDir, verifyResponses: false });
153
- // Le shim Cursor vit sous le répertoire de données PieceMaker : il n'a rien à
154
- // faire dans un répertoire appartenant à un fournisseur.
155
141
  const binDir = path.join(homeDir, 'bin');
156
- const state = {
157
- enabled: false,
158
- reason: 'not_started',
159
- origin: null,
160
- upstream: null,
161
- startedAt: null,
162
- routes: [],
163
- coverage: {},
164
- };
142
+ const state = { enabled: false, reason: 'not_started', origin: null, upstream: null, coverage: {} };
165
143
  let proxy = null;
166
144
  let bridge = null;
167
145
  let previousEnv = null;
146
+ const killProxy = () => proxy?.kill('SIGKILL');
168
147
 
169
148
  async function start() {
170
149
  if (proxy) return state;
171
- if (!required && isDisabled(homeDir)) {
150
+ removeLegacyProxyConfig({ userHome, homeDir });
151
+ if (isDisabled(homeDir)) {
172
152
  state.reason = 'disabled';
173
153
  return state;
174
154
  }
@@ -181,14 +161,8 @@ function createAnonymizerService({ homeDir, userHome = os.homedir(), logger = co
181
161
  return state;
182
162
  }
183
163
 
184
- const claudeUpstream = resolveUpstream(process.env[ENV_VAR]);
185
- const hosts = ['api.anthropic.com', 'api.openai.com', 'chatgpt.com'];
186
- try {
187
- const extra = new URL(claudeUpstream).hostname;
188
- if (extra && !hosts.includes(extra)) hosts.push(extra);
189
- } catch {
190
- // l'amont par défaut reste la liste fixe
191
- }
164
+ const upstream = resolveUpstream(process.env.ANTHROPIC_BASE_URL);
165
+ const hosts = [...new Set([...INTERCEPTED_HOSTS, new URL(upstream).hostname])];
192
166
 
193
167
  try {
194
168
  bridge = await startRewriterBridge({ dictionary, harness });
@@ -198,33 +172,30 @@ function createAnonymizerService({ homeDir, userHome = os.homedir(), logger = co
198
172
  return state;
199
173
  }
200
174
 
201
- const listenedPort = DEFAULT_PORT;
202
- const origin = `http://127.0.0.1:${listenedPort}`;
203
175
  const child = spawn(binary, [
204
- '--listen', `127.0.0.1:${listenedPort}`,
176
+ '--listen', '127.0.0.1:0',
205
177
  '--rewriter', `http://127.0.0.1:${bridge.port}`,
206
178
  '--ca-cert', certificates.cert,
207
179
  '--ca-key', certificates.key,
208
180
  '--hosts', hosts.join(','),
209
181
  ], {
210
- stdio: ['ignore', 'pipe', 'pipe'],
211
- env: {
212
- ...process.env,
213
- HTTPS_PROXY: '',
214
- HTTP_PROXY: '',
215
- ALL_PROXY: '',
216
- https_proxy: '',
217
- http_proxy: '',
218
- all_proxy: '',
219
- },
182
+ stdio: ['pipe', 'pipe', 'pipe'],
183
+ env: { ...process.env, HTTPS_PROXY: '', HTTP_PROXY: '', ALL_PROXY: '', https_proxy: '', http_proxy: '', all_proxy: '' },
220
184
  });
221
185
  proxy = child;
222
- child.stdout?.on('data', (chunk) => logger.log?.(String(chunk).trim()));
223
- child.stderr?.on('data', (chunk) => logger.warn?.(`[piecemaker] hudsucker : ${String(chunk).trim()}`));
186
+ process.once('exit', killProxy);
187
+ child.stderr.on('data', (chunk) => logger.warn?.(`[piecemaker] hudsucker : ${String(chunk).trim()}`));
188
+ child.once('exit', (code, signal) => {
189
+ if (proxy === child && state.enabled) logger.warn?.(`[piecemaker] hudsucker arrêté (${code ?? signal}) : requêtes IA refusées`);
190
+ });
191
+
192
+ let port;
224
193
  try {
225
- await waitForOutput(child, `listening 127.0.0.1:${listenedPort}`, 15000);
194
+ port = await waitForListening(child, 15000);
195
+ child.stdout.resume();
226
196
  } catch (error) {
227
197
  child.kill('SIGKILL');
198
+ process.off('exit', killProxy);
228
199
  proxy = null;
229
200
  await bridge.close();
230
201
  bridge = null;
@@ -233,70 +204,44 @@ function createAnonymizerService({ homeDir, userHome = os.homedir(), logger = co
233
204
  return state;
234
205
  }
235
206
 
236
- // Publie le port dans la config partagée pour que les consommateurs
237
- // indépendants du process serveur puissent le retrouver.
238
- try {
239
- const configFile = path.join(homeDir, 'config.json');
240
- const current = JSON.parse(fs.readFileSync(configFile, 'utf8'));
241
- if (current.mikePiiPort !== listenedPort) {
242
- fs.writeFileSync(configFile, `${JSON.stringify({ ...current, mikePiiPort: listenedPort }, null, 2)}\n`, 'utf8');
243
- }
244
- } catch (error) {
245
- logger.warn?.(`[piecemaker] impossible de publier mikePiiPort dans config.json : ${error.message}`);
246
- }
247
-
248
- previousEnv = {
249
- [ENV_VAR]: process.env[ENV_VAR],
250
- [OPENAI_ENV_VAR]: process.env[OPENAI_ENV_VAR],
251
- PATH: process.env.PATH,
252
- };
253
- for (const key of PROXY_ENV_KEYS) previousEnv[key] = process.env[key];
254
- Object.assign(process.env, proxyEnvironment(origin, certificates.cert));
207
+ const origin = `http://127.0.0.1:${port}`;
208
+ const environment = proxyEnvironment(origin, certificates.cert, writeTrustBundle(certificates.cert, certificates.trustBundle));
209
+ previousEnv = Object.fromEntries(['PATH', 'HTTP_PROXY', 'http_proxy', 'ALL_PROXY', 'all_proxy', ...Object.keys(environment)]
210
+ .map((key) => [key, process.env[key]]));
211
+ for (const key of ['HTTP_PROXY', 'http_proxy', 'ALL_PROXY', 'all_proxy']) delete process.env[key];
212
+ Object.assign(process.env, environment);
213
+ removeLegacyCentralMapping(homeDir);
214
+ installCursorGuard({ binDir, mappingFile: path.join(homeDir, 'central-mapping.json') });
255
215
  process.env.PATH = `${binDir}${path.delimiter}${process.env.PATH || ''}`;
256
216
 
257
- const report = await configureProviders({
258
- origin,
259
- caFile: certificates.cert,
260
- userHome,
261
- binDir,
262
- mappingFile: legacyMappingFile,
263
- });
264
217
  Object.assign(state, {
265
218
  enabled: true,
266
219
  reason: 'running',
267
220
  origin,
268
- upstream: claudeUpstream,
269
- startedAt: new Date().toISOString(),
270
- routes: hosts.map((host) => ({ provider: host, upstream: `https://${host}` })),
271
- coverage: summarizeCoverage(report),
221
+ upstream,
222
+ coverage: COVERAGE,
272
223
  });
273
-
274
- const uncovered = Object.entries(state.coverage)
275
- .filter(([, value]) => value.state === 'unconfigured')
276
- .map(([name]) => name);
277
224
  logger.log?.(`[piecemaker] anonymisation active sur ${origin} (claude, codex, opencode ; cursor bloqué)`);
278
- if (uncovered.length) {
279
- logger.warn?.(`[piecemaker] clients non filtrés, à vérifier : ${uncovered.join(', ')}`);
280
- }
281
225
  return state;
282
226
  }
283
227
 
284
228
  async function stop() {
285
- if (!proxy && !bridge) {
286
- dictionary.close();
287
- return;
288
- }
289
- if (proxy && proxy.exitCode === null) {
290
- proxy.kill('SIGTERM');
291
- await Promise.race([
292
- new Promise((resolve) => proxy.once('exit', resolve)),
293
- new Promise((resolve) => setTimeout(resolve, 2000)),
294
- ]);
295
- if (proxy.exitCode === null) proxy.kill('SIGKILL');
229
+ if (proxy) {
230
+ const child = proxy;
231
+ proxy = null;
232
+ process.off('exit', killProxy);
233
+ if (child.exitCode === null) {
234
+ child.kill('SIGTERM');
235
+ await Promise.race([
236
+ new Promise((resolve) => child.once('exit', resolve)),
237
+ new Promise((resolve) => setTimeout(resolve, 2000)),
238
+ ]);
239
+ if (child.exitCode === null) child.kill('SIGKILL');
240
+ }
296
241
  }
297
242
  if (bridge) await bridge.close();
298
243
  bridge = null;
299
- await bypassProviders({ userHome, binDir });
244
+ fs.rmSync(path.join(binDir, 'cursor-agent'), { force: true });
300
245
  if (previousEnv) {
301
246
  for (const [key, value] of Object.entries(previousEnv)) {
302
247
  if (value === undefined) delete process.env[key];
@@ -304,16 +249,15 @@ function createAnonymizerService({ homeDir, userHome = os.homedir(), logger = co
304
249
  }
305
250
  previousEnv = null;
306
251
  }
307
- proxy = null;
308
252
  dictionary.close();
309
- Object.assign(state, { enabled: false, reason: 'stopped', origin: null, routes: [], coverage: {} });
253
+ Object.assign(state, { enabled: false, reason: 'stopped', origin: null, upstream: null, coverage: {} });
310
254
  }
311
255
 
312
256
  function status() {
313
257
  const current = dictionary.get();
314
258
  return {
315
259
  ...state,
316
- listening: Boolean(proxy && proxy.exitCode === null && !proxy.killed),
260
+ listening: Boolean(proxy && proxy.exitCode === null && proxy.signalCode === null),
317
261
  dictionary: {
318
262
  file: dictionary.file,
319
263
  exists: dictionary.exists(),
@@ -330,10 +274,8 @@ function createAnonymizerService({ homeDir, userHome = os.homedir(), logger = co
330
274
  }
331
275
 
332
276
  module.exports = {
333
- ENV_VAR,
334
- OPENAI_ENV_VAR,
335
277
  createAnonymizerService,
336
- isDisabled,
278
+ installCursorGuard,
337
279
  resolveUpstream,
338
- summarizeCoverage,
280
+ writeTrustBundle,
339
281
  };
@@ -1,4 +1,5 @@
1
1
  import { spawn } from 'node:child_process';
2
+ import path from 'node:path';
2
3
 
3
4
  import { loadProductConfig } from '../../../shared/product-config.mjs';
4
5
 
@@ -23,9 +24,9 @@ function runInstaller(command: string, environment: NodeJS.ProcessEnv) {
23
24
  });
24
25
  }
25
26
 
26
- function productRepository(): string | null {
27
+ function productRepository(appRoot: string): string | null {
27
28
  try {
28
- return loadProductConfig().repository;
29
+ return loadProductConfig(process.env.CLOUDCLI_PRODUCT_CONFIG || path.join(appRoot, 'product.config.json')).repository;
29
30
  } catch {
30
31
  return null;
31
32
  }
@@ -39,7 +40,7 @@ export function createPieceMakerDesktopUpdateRouter(appRoot: string) {
39
40
  return createDesktopUpdateRouter(createDesktopUpdateService({
40
41
  appRoot,
41
42
  platform: process.platform,
42
- repository: productRepository(),
43
+ repository: productRepository(appRoot),
43
44
  applicationPid: process.ppid,
44
45
  environment: process.env,
45
46
  runInstaller,
@@ -0,0 +1,54 @@
1
+ import { randomUUID } from 'crypto';
2
+ import { rename, rm, stat, writeFile } from 'fs/promises';
3
+ import path from 'path';
4
+
5
+ import express, { type Request, type Response } from 'express';
6
+
7
+ import { projectsDb } from '@/modules/database/index.js';
8
+
9
+ const MAX_DOCX_BYTES = '100mb';
10
+
11
+ function resolveDocxPath(request: Request): string | null {
12
+ const projectId = typeof request.query.projectId === 'string' ? request.query.projectId : '';
13
+ const filePath = typeof request.query.path === 'string' ? request.query.path : '';
14
+ const projectRoot = projectId ? projectsDb.getProjectPathById(projectId) : null;
15
+ if (!projectRoot || !filePath.toLowerCase().endsWith('.docx')) return null;
16
+ const resolved = path.resolve(projectRoot, filePath);
17
+ return resolved.startsWith(path.resolve(projectRoot) + path.sep) ? resolved : null;
18
+ }
19
+
20
+ async function readVersion(filePath: string): Promise<number> {
21
+ return (await stat(filePath)).mtimeMs;
22
+ }
23
+
24
+ export function createDocxDocumentRouter() {
25
+ const router = express.Router();
26
+
27
+ router.get('/docx-document/version', async (request: Request, response: Response) => {
28
+ const filePath = resolveDocxPath(request);
29
+ if (!filePath) return response.status(400).json({ error: 'Invalid docx path' });
30
+ try {
31
+ response.json({ version: await readVersion(filePath) });
32
+ } catch {
33
+ response.status(404).json({ error: 'File not found' });
34
+ }
35
+ });
36
+
37
+ router.put('/docx-document', express.raw({ type: () => true, limit: MAX_DOCX_BYTES }), async (request: Request, response: Response) => {
38
+ const filePath = resolveDocxPath(request);
39
+ if (!filePath || !Buffer.isBuffer(request.body) || request.body.length === 0) {
40
+ return response.status(400).json({ error: 'Invalid docx payload' });
41
+ }
42
+ const temporaryPath = `${filePath}.${randomUUID()}.tmp`;
43
+ try {
44
+ await writeFile(temporaryPath, request.body);
45
+ await rename(temporaryPath, filePath);
46
+ response.json({ version: await readVersion(filePath) });
47
+ } catch (error) {
48
+ await rm(temporaryPath, { force: true });
49
+ response.status(500).json({ error: error instanceof Error ? error.message : String(error) });
50
+ }
51
+ });
52
+
53
+ return router;
54
+ }
@@ -3,7 +3,7 @@
3
3
  /**
4
4
  * Capture des décisions Légifrance lues via l'outil MCP `consulter_decision`,
5
5
  * repérées dans un corps de requête SORTANTE déjà analysé en JSON par le
6
- * proxy PII (`server/piecemaker/anonymizer/proxy.cjs`).
6
+ * proxy PII (`server/piecemaker/anonymizer/rewriter-bridge.cjs`).
7
7
  *
8
8
  * Équivalent serveur du hook `decision-cache.mjs` de l'autre dépôt
9
9
  * (`piecemaker-plugin/scripts/decision-cache.mjs`, PostToolUse Claude Code) :
@@ -1,9 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * Façade du harnais de citations vérifiées, à brancher sur le proxy PII
5
- * local (`server/piecemaker/anonymizer/proxy.cjs`) — câblage laissé à une
6
- * tâche ultérieure, ce module ne modifie rien de `proxy.cjs`.
4
+ * Façade du harnais de citations vérifiées, branchée sur le réécrivain du
5
+ * proxy PII (`server/piecemaker/anonymizer/rewriter-bridge.cjs`).
7
6
  *
8
7
  * Deux observateurs, symétriques au sens et au retour du proxy lui-même :
9
8
  * - `observerRequete` regarde ce qui PART vers le fournisseur (avant
@@ -6,7 +6,7 @@
6
6
  * (`piecemaker-plugin/scripts/verify-citations.mjs`).
7
7
  *
8
8
  * Ce dépôt n'a pas de hook Claude Code : tout le trafic IA passe par le
9
- * proxy PII local (`server/piecemaker/anonymizer/proxy.cjs`), qui est donc
9
+ * proxy PII local (`server/piecemaker/anonymizer/rewriter-bridge.cjs`), qui est donc
10
10
  * l'unique point d'observation possible pour un texte produit par le modèle.
11
11
  * Contrairement au hook d'origine, ce module n'a pas le pouvoir de bloquer
12
12
  * un tour (il n'y a pas de conversation Claude Code à interrompre côté
@@ -4,6 +4,7 @@ import path from 'path';
4
4
  import type { Router } from 'express';
5
5
 
6
6
  import { providerRuntimeService, sessionsService } from '@/modules/providers/index.js';
7
+ import { providerRegistry } from '@/modules/providers/provider.registry.js';
7
8
  import { findApplicationRoot, getModuleDirectory } from '@/shared/utils.js';
8
9
 
9
10
 
@@ -16,7 +17,10 @@ import { createKnowledgeBackend } from './knowledge/index.js';
16
17
  import { createTimesheetBackend } from './timesheet/index.js';
17
18
  import { createCompanySearchRouter } from './company-search.js';
18
19
  import { createBodaccSearchRouter } from './bodacc-search.js';
20
+ import { createDocxDocumentRouter } from './docx-document.js';
19
21
  import { createShellEnvironmentRouter, resolveDesktopShellEnvironment } from './shell-environment.js';
22
+ import { installModelDiscovery } from './model-discovery/index.js';
23
+ import { startProjectRegistry } from './project-registry.js';
20
24
 
21
25
  /**
22
26
  * Point d'entrée PieceMaker. Les modules de `server/piecemaker/vendor/` sont du
@@ -37,8 +41,9 @@ type PieceMakerRuntimeStatus = {
37
41
  };
38
42
 
39
43
  type PieceMakerVendorModule = {
40
- createPieceMakerRouter(options?: { getRuntimeStatus?: () => PieceMakerRuntimeStatus; anonymizer?: unknown }): Router;
44
+ createPieceMakerRouter(options?: { getRuntimeStatus?: () => PieceMakerRuntimeStatus; anonymizer?: unknown; publishProjects?: () => void }): Router;
41
45
  piecemakerHome(): string;
46
+ publishProjectSource(sourceId: string, folders: string[] | null): void;
42
47
  stopOriginalsJobs(): Promise<void>;
43
48
  };
44
49
 
@@ -46,13 +51,15 @@ const shellEnvironment = await resolveDesktopShellEnvironment();
46
51
  const vendor = createRequire(import.meta.url)(routerPath) as PieceMakerVendorModule;
47
52
 
48
53
  export const { piecemakerHome, stopOriginalsJobs } = vendor;
54
+ const publishProjects = startProjectRegistry(vendor.publishProjectSource);
49
55
  const { createAnonymizerService } = createRequire(import.meta.url)(path.join(applicationRoot, 'server/piecemaker/anonymizer/service.cjs'));
50
- const anonymizer = createAnonymizerService({ homeDir: piecemakerHome(), required: true });
56
+ const anonymizer = createAnonymizerService({ homeDir: piecemakerHome() });
51
57
  const ensureProxy = await startRequiredAnonymizer(anonymizer);
52
58
  const citations = createCitationStore(piecemakerHome());
53
59
  installChatCitationHarness({ runtime: providerRuntimeService, sessions: sessionsService, store: citations, ensureProxy });
54
60
  const library = await openLibrary(piecemakerHome(), applicationRoot);
55
61
  installLibraryRuntime(providerRuntimeService, sessionsService, library.store);
62
+ installModelDiscovery(providerRegistry);
56
63
  const timesheet = createTimesheetBackend(piecemakerHome());
57
64
  const knowledge = createKnowledgeBackend(applicationRoot);
58
65
 
@@ -61,7 +68,7 @@ export function createPieceMakerLocalRouter() {
61
68
  }
62
69
 
63
70
  export function createPieceMakerRouter(options: { getRuntimeStatus?: () => PieceMakerRuntimeStatus } = {}) {
64
- const router = vendor.createPieceMakerRouter({ ...options, anonymizer });
71
+ const router = vendor.createPieceMakerRouter({ ...options, anonymizer, publishProjects });
65
72
  router.use(createCitationsRouter(citations, (id) => {
66
73
  try { sessionsService.getSessionDetailsById(id); return true; } catch { return false; }
67
74
  }));
@@ -70,6 +77,7 @@ export function createPieceMakerRouter(options: { getRuntimeStatus?: () => Piece
70
77
  router.use(knowledge.router);
71
78
  router.use(createCompanySearchRouter());
72
79
  router.use(createBodaccSearchRouter());
80
+ router.use(createDocxDocumentRouter());
73
81
  router.use(createShellEnvironmentRouter(shellEnvironment));
74
82
  return router;
75
83
  }