@piecemaker-legal/piecemaker 1.1.1 → 2.0.2

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 (141) hide show
  1. package/dist/assets/{abnfDiagram-VCTEODGH-DEXWh3LH.js → abnfDiagram-VCTEODGH-BeQS_HnO.js} +1 -1
  2. package/dist/assets/{arc-CoTRLXKW.js → arc-ClYjD3aM.js} +1 -1
  3. package/dist/assets/{architectureDiagram-5GKGNRK7-Cg-jO9oR.js → architectureDiagram-5GKGNRK7-Csdihb1L.js} +1 -1
  4. package/dist/assets/{blockDiagram-I7D4REHJ-ChXwQSIq.js → blockDiagram-I7D4REHJ-BuXey9ht.js} +1 -1
  5. package/dist/assets/{c4Diagram-7LVT6UL2-Bw2VGggd.js → c4Diagram-7LVT6UL2-DdO-ovOy.js} +1 -1
  6. package/dist/assets/channel-DhuuSCHK.js +1 -0
  7. package/dist/assets/{chunk-2Q5K7J3B-C1WdqrLP.js → chunk-2Q5K7J3B-D3hzWzSc.js} +1 -1
  8. package/dist/assets/{chunk-5VM5RSS4-BerUZBa1.js → chunk-5VM5RSS4-DeT3cR6k.js} +1 -1
  9. package/dist/assets/{chunk-F27PBJKO-5W7ax2gB.js → chunk-F27PBJKO-bazGi1ru.js} +1 -1
  10. package/dist/assets/{chunk-IMKFNOWR-JgDN1h4T.js → chunk-IMKFNOWR-BE3ZXrPd.js} +1 -1
  11. package/dist/assets/{chunk-JWPE2WC7-ulNHnEdQ.js → chunk-JWPE2WC7-dTAsgByE.js} +1 -1
  12. package/dist/assets/{chunk-POPQ4Y6H-D4aTDC2T.js → chunk-POPQ4Y6H-DKYrzMSd.js} +1 -1
  13. package/dist/assets/{chunk-SVP7TREG-BA5zX4XF.js → chunk-SVP7TREG-BTtrMvhE.js} +1 -1
  14. package/dist/assets/{chunk-TICWLB2K-DhOULVhX.js → chunk-TICWLB2K-CZPHShPS.js} +1 -1
  15. package/dist/assets/{chunk-XXDRQBXY-CkpCgrVg.js → chunk-XXDRQBXY-B0byFHaU.js} +1 -1
  16. package/dist/assets/classDiagram-ZZMXUADV-CO0E82Dl.js +1 -0
  17. package/dist/assets/classDiagram-v2-VYDZK3BY-CO0E82Dl.js +1 -0
  18. package/dist/assets/{cose-bilkent-JH36ORCC-0xUSfjfV.js → cose-bilkent-JH36ORCC-BbLCzlaU.js} +1 -1
  19. package/dist/assets/{cynefin-OW5HDTMX-BE6fNu2_.js → cynefin-OW5HDTMX-CQ8ui4YD.js} +1 -1
  20. package/dist/assets/{cynefinDiagram-5FMLGOSQ-rFMnZ9b3.js → cynefinDiagram-5FMLGOSQ-T-GL1aIf.js} +1 -1
  21. package/dist/assets/{dagre-GXQ25YYZ-C1p4ffd7.js → dagre-GXQ25YYZ-CXxVKznH.js} +1 -1
  22. package/dist/assets/{diagram-S7CK7UJ4-BV_3Xj4O.js → diagram-S7CK7UJ4-B_-1Mf_7.js} +1 -1
  23. package/dist/assets/{diagram-UQ7AKVKN-Bb_Evkrm.js → diagram-UQ7AKVKN-Bs-OB8xI.js} +1 -1
  24. package/dist/assets/{diagram-VSXAHHWV-BzgupjxD.js → diagram-VSXAHHWV-BpUqq93o.js} +1 -1
  25. package/dist/assets/{diagram-VX7I27RA-D1c67z76.js → diagram-VX7I27RA-CZ6hSUo6.js} +1 -1
  26. package/dist/assets/{diagram-Z3DM3KII-DsJAJGKE.js → diagram-Z3DM3KII-DVZiDO6S.js} +1 -1
  27. package/dist/assets/{ebnfDiagram-PWID7BFC-B1ge_obq.js → ebnfDiagram-PWID7BFC-D_1izoKK.js} +1 -1
  28. package/dist/assets/{erDiagram-RLTQ6QDP-DrV-tggw.js → erDiagram-RLTQ6QDP-_jSi4sg-.js} +1 -1
  29. package/dist/assets/{flowDiagram-HODETNUW-CVzDiSQ3.js → flowDiagram-HODETNUW-BtBM7ez3.js} +1 -1
  30. package/dist/assets/{ganttDiagram-EL5Y4UJY-CGRdSN9D.js → ganttDiagram-EL5Y4UJY-Bz2nxw7c.js} +1 -1
  31. package/dist/assets/{gitGraphDiagram-WWUBYQGX-CAfvFXzL.js → gitGraphDiagram-WWUBYQGX-8Q3I3hoE.js} +1 -1
  32. package/dist/assets/{index-DaxH81ZJ.js → index-CZUj0Tlu.js} +152 -152
  33. package/dist/assets/index-DcfdIMj-.css +1 -0
  34. package/dist/assets/{infoDiagram-27XIBGKW-DTshEkGK.js → infoDiagram-27XIBGKW-Cc6eKjfU.js} +1 -1
  35. package/dist/assets/{ishikawaDiagram-5VMMS53U-DkbshVxp.js → ishikawaDiagram-5VMMS53U-Bd3w97qM.js} +1 -1
  36. package/dist/assets/{journeyDiagram-3NMN7TZE-v5qE8xpn.js → journeyDiagram-3NMN7TZE-Cy6XbK5X.js} +1 -1
  37. package/dist/assets/{kanban-definition-UXKFOSKX-a9CNMFLt.js → kanban-definition-UXKFOSKX-kyLqWDpr.js} +1 -1
  38. package/dist/assets/{layout-DrW4R1TH.js → layout-DXpPSEft.js} +1 -1
  39. package/dist/assets/{linear-Cs3qpXT_.js → linear-DH_h9cDZ.js} +1 -1
  40. package/dist/assets/{mermaid.core-C7Ti-Fty.js → mermaid.core-BghXiB7p.js} +6 -6
  41. package/dist/assets/{mindmap-definition-YA3MSWOX-B0VDdIcG.js → mindmap-definition-YA3MSWOX-C2xxEXsP.js} +1 -1
  42. package/dist/assets/{pegDiagram-XKGWAZYB-CV1L7MjP.js → pegDiagram-XKGWAZYB-DuppHX4-.js} +1 -1
  43. package/dist/assets/{pieDiagram-E7YTZNPT-DSCuzVTV.js → pieDiagram-E7YTZNPT-BCukPZ2a.js} +1 -1
  44. package/dist/assets/{quadrantDiagram-AXDQQJYC-hzX-9AKo.js → quadrantDiagram-AXDQQJYC-Dm_ojY_7.js} +1 -1
  45. package/dist/assets/{railroadDiagram-O6MQD6OU-BbmzIqcp.js → railroadDiagram-O6MQD6OU-BzdOv7BJ.js} +1 -1
  46. package/dist/assets/{requirementDiagram-BXWQKSXE-CQCRRBSL.js → requirementDiagram-BXWQKSXE-DgewPP_g.js} +1 -1
  47. package/dist/assets/{sankeyDiagram-P5KCCOFB-kiaLcW6X.js → sankeyDiagram-P5KCCOFB-vEIMvCSt.js} +1 -1
  48. package/dist/assets/{sequenceDiagram-WJ2MYXX4-Bgx8ST7Q.js → sequenceDiagram-WJ2MYXX4-BL_31NoY.js} +1 -1
  49. package/dist/assets/{sizeCapture-INFHLROL-BTjJ-ygN.js → sizeCapture-INFHLROL-Ch6u4xo6.js} +1 -1
  50. package/dist/assets/{stateDiagram-D77RDMKH-D48RR1FL.js → stateDiagram-D77RDMKH-ByquMuJv.js} +1 -1
  51. package/dist/assets/{stateDiagram-v2-MP3YSRHH-B-XwSR-z.js → stateDiagram-v2-MP3YSRHH-_QRJP_vn.js} +1 -1
  52. package/dist/assets/{swimlanes-42K2YHIH-BhE-QuEZ.js → swimlanes-42K2YHIH-BZyXEHOb.js} +1 -1
  53. package/dist/assets/swimlanesDiagram-VR7AAH4N-sqZwZwbP.js +8 -0
  54. package/dist/assets/{timeline-definition-24CTP7MA-Cm7EUWWt.js → timeline-definition-24CTP7MA-CLBivayK.js} +1 -1
  55. package/dist/assets/{vennDiagram-4TSXK5OY-X-nC9wZ0.js → vennDiagram-4TSXK5OY-3dJ2PRqR.js} +1 -1
  56. package/dist/assets/{wardleyDiagram-VM6X3IG4-D33YvEQZ.js → wardleyDiagram-VM6X3IG4-C279LT36.js} +1 -1
  57. package/dist/assets/{xychartDiagram-S5SC5T6Z-lziwBKsM.js → xychartDiagram-S5SC5T6Z-FoqPDrei.js} +1 -1
  58. package/dist/index.html +2 -2
  59. package/dist/sw.js +1 -1
  60. package/dist-server/server/index.js +3 -1
  61. package/dist-server/server/index.js.map +1 -1
  62. package/dist-server/server/piecemaker/harness/citations.routes.js +18 -1
  63. package/dist-server/server/piecemaker/harness/citations.routes.js.map +1 -1
  64. package/dist-server/server/piecemaker/harness/legifrance-html.js +112 -0
  65. package/dist-server/server/piecemaker/harness/legifrance-html.js.map +1 -0
  66. package/dist-server/server/piecemaker/index.js +7 -3
  67. package/dist-server/server/piecemaker/index.js.map +1 -1
  68. package/dist-server/server/piecemaker/knowledge/index.js +13 -6
  69. package/dist-server/server/piecemaker/knowledge/index.js.map +1 -1
  70. package/dist-server/server/piecemaker/knowledge/local-routes.js +55 -0
  71. package/dist-server/server/piecemaker/knowledge/local-routes.js.map +1 -0
  72. package/dist-server/server/piecemaker/knowledge/scan-jobs.js +3 -1
  73. package/dist-server/server/piecemaker/knowledge/scan-jobs.js.map +1 -1
  74. package/dist-server/server/piecemaker/library/index.js +14 -41
  75. package/dist-server/server/piecemaker/library/index.js.map +1 -1
  76. package/dist-server/server/piecemaker/library/store.js +11 -3
  77. package/dist-server/server/piecemaker/library/store.js.map +1 -1
  78. package/dist-server/server/piecemaker/library/workspace-installation.js +26 -3
  79. package/dist-server/server/piecemaker/library/workspace-installation.js.map +1 -1
  80. package/dist-server/server/piecemaker/timesheet/service.js +8 -7
  81. package/dist-server/server/piecemaker/timesheet/service.js.map +1 -1
  82. package/dist-server/server/piecemaker/timesheet/store.js +5 -3
  83. package/dist-server/server/piecemaker/timesheet/store.js.map +1 -1
  84. package/package.json +1 -1
  85. package/product.config.json +2 -0
  86. package/scripts/piecemaker/build-app-icon.py +88 -0
  87. package/scripts/piecemaker/cli/lib/piecemaker-anonymizer.mjs +2 -2
  88. package/scripts/piecemaker/hooks/proxy-guard.mjs +19 -54
  89. package/scripts/release/assert-piecemaker-version.mjs +25 -0
  90. package/server/index.ts +4 -1
  91. package/server/piecemaker/anonymizer/client-config.cjs +45 -162
  92. package/server/piecemaker/anonymizer/hudsucker-fixture.cjs +127 -0
  93. package/server/piecemaker/anonymizer/hudsucker-proxy/Cargo.lock +2396 -0
  94. package/server/piecemaker/anonymizer/hudsucker-proxy/Cargo.toml +14 -0
  95. package/server/piecemaker/anonymizer/hudsucker-proxy/src/main.rs +501 -0
  96. package/server/piecemaker/anonymizer/providers.cjs +14 -135
  97. package/server/piecemaker/anonymizer/rewriter-bridge.cjs +221 -0
  98. package/server/piecemaker/anonymizer/service.cjs +144 -28
  99. package/server/piecemaker/harness/citations.routes.ts +23 -1
  100. package/server/piecemaker/harness/legifrance-html.ts +107 -0
  101. package/server/piecemaker/index.ts +8 -3
  102. package/server/piecemaker/knowledge/index.ts +14 -6
  103. package/server/piecemaker/knowledge/local-routes.ts +72 -0
  104. package/server/piecemaker/knowledge/scan-jobs.ts +4 -1
  105. package/server/piecemaker/library/index.ts +14 -34
  106. package/server/piecemaker/library/store.ts +11 -3
  107. package/server/piecemaker/library/workspace-installation.ts +20 -3
  108. package/server/piecemaker/timesheet/service.ts +9 -7
  109. package/server/piecemaker/timesheet/store.ts +5 -3
  110. package/server/piecemaker/vendor/installer/assets/claude-hooks/piecemaker-guard-secrets.mjs +0 -1
  111. package/server/piecemaker/vendor/installer/assets/piecemaker.icns +0 -0
  112. package/server/piecemaker/vendor/installer/assets/piecemaker.ico +0 -0
  113. package/server/piecemaker/vendor/installer/bin/piecemaker.mjs +56 -206
  114. package/server/piecemaker/vendor/installer/lib/commandes.mjs +2 -4
  115. package/server/piecemaker/vendor/installer/lib/conversion-client.mjs +74 -0
  116. package/server/piecemaker/vendor/installer/lib/service.mjs +4 -358
  117. package/server/piecemaker/vendor/installer/lib/state.mjs +0 -6
  118. package/server/piecemaker/vendor/installer/steps/03-python-gliner.mjs +4 -4
  119. package/server/piecemaker/vendor/installer/steps/09-claude-assets.mjs +0 -31
  120. package/server/piecemaker/vendor/piecemaker-plugin/scripts/statusline.mjs +3 -4
  121. package/server/piecemaker/vendor/websocket-server/admin-routes.cjs +1 -49
  122. package/server/piecemaker/vendor/websocket-server/claude-hooks.cjs +14 -12
  123. package/server/piecemaker/vendor/websocket-server/originals-pipeline.cjs +17 -476
  124. package/server/piecemaker/vendor/websocket-server/scripts/convert_and_scan_pipeline.py +2 -2
  125. package/server/piecemaker/vendor/websocket-server/scripts/mineru_improved_1.py +11 -2
  126. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/scanner_worker.py +13 -37
  127. package/server/piecemaker/vendor/websocket-server/scripts/requirements.txt +1 -7
  128. package/server/piecemaker/vendor/websocket-server/scripts/warmup.py +14 -85
  129. package/dist/assets/channel-DdzrPZ6Y.js +0 -1
  130. package/dist/assets/classDiagram-ZZMXUADV-Dim2o8E5.js +0 -1
  131. package/dist/assets/classDiagram-v2-VYDZK3BY-Dim2o8E5.js +0 -1
  132. package/dist/assets/index-D_Dc_nQv.css +0 -1
  133. package/dist/assets/swimlanesDiagram-VR7AAH4N-DR525d5V.js +0 -8
  134. package/server/piecemaker/anonymizer/proxy.cjs +0 -365
  135. package/server/piecemaker/vendor/installer/assets/PieceMakerApp.swift +0 -189
  136. package/server/piecemaker/vendor/installer/lib/desktop-launcher.mjs +0 -305
  137. package/server/piecemaker/vendor/installer/steps/05-certificats.mjs +0 -171
  138. package/server/piecemaker/vendor/installer/steps/14-mxc-sandbox.mjs +0 -271
  139. package/server/piecemaker/vendor/installer/steps/15-pwa-desktop.mjs +0 -64
  140. package/server/piecemaker/vendor/installer/templates/root-CLAUDE.md +0 -56
  141. package/server/piecemaker/vendor/installer/templates/workspace-CLAUDE.md +0 -52
@@ -1,19 +1,7 @@
1
- /**
2
- * Écriture atomique des configurations Claude Code et Codex pour les faire
3
- * passer par le proxy PII de ce dépôt (`./proxy.cjs`).
4
- *
5
- * Propre à ce fichier, sans dépendance vendor : l'identifiant de bloc
6
- * `piecemaker_proxy` et les marqueurs qui l'entourent n'appartiennent qu'à
7
- * ce proxy. Le fournisseur Codex géré n'a jamais parlé que HTTP Responses.
8
- */
9
1
  const fs = require('node:fs');
10
2
  const os = require('node:os');
11
3
  const path = require('node:path');
12
4
 
13
- const CODEX_PROVIDER_ID = 'piecemaker_proxy';
14
- const CODEX_BLOCK_START = '# >>> PieceMaker Proxy PII (géré automatiquement)';
15
- const CODEX_BLOCK_END = '# <<< PieceMaker Proxy PII';
16
-
17
5
  function atomicWrite(file, content, mode = 0o600) {
18
6
  fs.mkdirSync(path.dirname(file), { recursive: true });
19
7
  const temporary = `${file}.piecemaker-${process.pid}-${Date.now()}`;
@@ -21,18 +9,28 @@ function atomicWrite(file, content, mode = 0o600) {
21
9
  fs.renameSync(temporary, file);
22
10
  }
23
11
 
24
- /** Une base que PieceMaker a écrite — donc que PieceMaker peut réécrire ou retirer. */
25
- function isOwnLoopbackUrl(value, suffix) {
12
+ const PROXY_ENV = {
13
+ HTTPS_PROXY: (proxyUrl) => proxyUrl,
14
+ HTTP_PROXY: (proxyUrl) => proxyUrl,
15
+ ALL_PROXY: (proxyUrl) => proxyUrl,
16
+ NO_PROXY: () => 'localhost,127.0.0.1,::1',
17
+ NODE_USE_ENV_PROXY: () => '1',
18
+ NODE_EXTRA_CA_CERTS: (_proxyUrl, caFile) => caFile,
19
+ SSL_CERT_FILE: (_proxyUrl, caFile) => caFile,
20
+ CODEX_CA_CERTIFICATE: (_proxyUrl, caFile) => caFile,
21
+ REQUESTS_CA_BUNDLE: (_proxyUrl, caFile) => caFile,
22
+ };
23
+
24
+ function isLoopbackProxy(value) {
26
25
  try {
27
26
  const url = new URL(String(value || ''));
28
- return ['127.0.0.1', 'localhost'].includes(url.hostname)
29
- && url.pathname.replace(/\/$/, '') === suffix;
27
+ return ['127.0.0.1', 'localhost'].includes(url.hostname);
30
28
  } catch {
31
29
  return false;
32
30
  }
33
31
  }
34
32
 
35
- function configureClaudeCodeProxy({ baseUrl, userHome = os.homedir() } = {}) {
33
+ function configureClaudeCodeProxy({ proxyUrl, caFile, userHome = os.homedir() } = {}) {
36
34
  const settingsFile = path.join(userHome, '.claude', 'settings.json');
37
35
  let settings = {};
38
36
  try {
@@ -50,13 +48,21 @@ function configureClaudeCodeProxy({ baseUrl, userHome = os.homedir() } = {}) {
50
48
  return { configured: false, changed: false, conflict: true, file: settingsFile, reason: 'env-invalid' };
51
49
  }
52
50
  if (!settings.env) settings.env = {};
53
- const existing = settings.env.ANTHROPIC_BASE_URL;
54
- if (existing && existing !== baseUrl && !isOwnLoopbackUrl(existing, '/anthropic')) {
55
- return { configured: false, changed: false, conflict: true, file: settingsFile, reason: 'base-url-conflict' };
51
+ const existingProxy = settings.env.HTTPS_PROXY;
52
+ if (existingProxy && existingProxy !== proxyUrl && !isLoopbackProxy(existingProxy)) {
53
+ return { configured: false, changed: false, conflict: true, file: settingsFile, reason: 'proxy-conflict' };
54
+ }
55
+
56
+ let changed = false;
57
+ for (const [key, produce] of Object.entries(PROXY_ENV)) {
58
+ const value = produce(proxyUrl, caFile);
59
+ if (!value) continue;
60
+ if (settings.env[key] !== value) {
61
+ settings.env[key] = value;
62
+ changed = true;
63
+ }
56
64
  }
57
- if (existing === baseUrl) return { configured: true, changed: false, conflict: false, file: settingsFile };
58
-
59
- settings.env.ANTHROPIC_BASE_URL = baseUrl;
65
+ if (!changed) return { configured: true, changed: false, conflict: false, file: settingsFile };
60
66
  atomicWrite(settingsFile, `${JSON.stringify(settings, null, 2)}\n`);
61
67
  return { configured: true, changed: true, conflict: false, file: settingsFile };
62
68
  }
@@ -76,159 +82,36 @@ function bypassClaudeCodeProxy({ userHome = os.homedir() } = {}) {
76
82
  }
77
83
 
78
84
  const env = settings.env;
79
- if (env === undefined || (env && typeof env === 'object' && !Array.isArray(env)
80
- && !env.ANTHROPIC_BASE_URL)) {
81
- return { bypassed: true, changed: false, conflict: false, file: settingsFile };
82
- }
83
85
  if (!env || typeof env !== 'object' || Array.isArray(env)) {
86
+ if (env === undefined) return { bypassed: true, changed: false, conflict: false, file: settingsFile };
84
87
  return { bypassed: false, changed: false, conflict: true, file: settingsFile, reason: 'env-invalid' };
85
88
  }
86
- if (!isOwnLoopbackUrl(env.ANTHROPIC_BASE_URL, '/anthropic')) {
87
- return { bypassed: true, changed: false, conflict: false, file: settingsFile };
88
- }
89
+ if (!isLoopbackProxy(env.HTTPS_PROXY)) return { bypassed: true, changed: false, conflict: false, file: settingsFile };
89
90
 
90
- delete env.ANTHROPIC_BASE_URL;
91
+ let changed = false;
92
+ if (isLoopbackProxy(env.HTTPS_PROXY)) {
93
+ for (const key of Object.keys(PROXY_ENV)) {
94
+ if (env[key] !== undefined) {
95
+ delete env[key];
96
+ changed = true;
97
+ }
98
+ }
99
+ }
100
+ if (!changed) return { bypassed: true, changed: false, conflict: false, file: settingsFile };
91
101
  if (Object.keys(env).length === 0) delete settings.env;
92
102
  atomicWrite(settingsFile, `${JSON.stringify(settings, null, 2)}\n`);
93
103
  return { bypassed: true, changed: true, conflict: false, file: settingsFile };
94
104
  }
95
105
 
96
- function tomlString(value) {
97
- return JSON.stringify(String(value));
98
- }
99
-
100
- function topLevelAssignment(lines, key) {
101
- const expression = new RegExp(`^\\s*${key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*=\\s*(.*?)\\s*$`);
102
- for (let index = 0; index < lines.length; index += 1) {
103
- const line = lines[index];
104
- if (/^\s*\[/.test(line)) break;
105
- const match = line.match(expression);
106
- if (match) return { index, raw: match[1] };
107
- }
108
- return null;
109
- }
110
-
111
- function parseTomlString(raw) {
112
- const value = String(raw || '').trim();
113
- if (value.startsWith('"')) {
114
- const literal = value.match(/^("(?:\\.|[^"\\])*")/)?.[1];
115
- try { return literal ? JSON.parse(literal) : null; } catch { return null; }
116
- }
117
- const literal = value.match(/^'([^']*)'/)?.[1];
118
- if (literal !== undefined) return literal;
119
- return null;
120
- }
121
-
122
- function configureCodexProxy({
123
- baseUrl,
124
- codexHome = process.env.CODEX_HOME || path.join(os.homedir(), '.codex'),
125
- } = {}) {
126
- const configFile = path.join(codexHome, 'config.toml');
127
- let content = '';
128
- try { content = fs.existsSync(configFile) ? fs.readFileSync(configFile, 'utf8') : ''; } catch {
129
- return { configured: false, changed: false, conflict: true, file: configFile, reason: 'config-unreadable' };
130
- }
131
-
132
- const lines = content.replace(/^/, '').split(/\r?\n/);
133
- const provider = topLevelAssignment(lines, 'model_provider');
134
- const providerValue = provider ? parseTomlString(provider.raw) : 'openai';
135
- if (provider && !['openai', CODEX_PROVIDER_ID].includes(providerValue)) {
136
- return { configured: false, changed: false, conflict: true, file: configFile, reason: 'provider-conflict' };
137
- }
138
-
139
- const managedTable = lines.findIndex((line) => line.trim() === `[model_providers.${CODEX_PROVIDER_ID}]`);
140
- const managedStart = lines.findIndex((line) => line.trim() === CODEX_BLOCK_START);
141
- let managedEnd = lines.findIndex((line) => line.trim() === CODEX_BLOCK_END);
142
- if (managedTable >= 0 && managedStart < 0) {
143
- return { configured: false, changed: false, conflict: true, file: configFile, reason: 'provider-table-conflict' };
144
- }
145
- if (managedStart >= 0 && managedEnd < managedStart) {
146
- let end = managedTable >= managedStart ? managedTable : managedStart;
147
- for (let index = end + 1; index < lines.length; index += 1) {
148
- if (/^\s*\[/.test(lines[index])) break;
149
- end = index;
150
- }
151
- managedEnd = end;
152
- }
153
-
154
- if (provider) {
155
- lines[provider.index] = `model_provider = ${tomlString(CODEX_PROVIDER_ID)} # géré par PieceMaker`;
156
- } else {
157
- const firstTable = lines.findIndex((line) => /^\s*\[/.test(line));
158
- lines.splice(firstTable < 0 ? 0 : firstTable, 0,
159
- '# Fournisseur Responses protégé par PieceMaker.',
160
- `model_provider = ${tomlString(CODEX_PROVIDER_ID)} # géré par PieceMaker`,
161
- '');
162
- }
163
-
164
- // Le proxy de ce dépôt parle HTTP Responses, jamais WebSocket.
165
- const block = [
166
- CODEX_BLOCK_START,
167
- `[model_providers.${CODEX_PROVIDER_ID}]`,
168
- 'name = "PieceMaker · Proxy PII"',
169
- `base_url = ${tomlString(baseUrl)}`,
170
- 'requires_openai_auth = true',
171
- 'wire_api = "responses"',
172
- 'supports_websockets = false',
173
- CODEX_BLOCK_END,
174
- ];
175
- if (managedStart >= 0 && managedEnd >= managedStart) {
176
- lines.splice(managedStart, managedEnd - managedStart + 1, ...block);
177
- } else {
178
- while (lines.at(-1) === '') lines.pop();
179
- lines.push('', ...block);
180
- }
181
- const normalized = `${lines.join('\n').replace(/\n+$/, '')}\n`;
182
- if (normalized === `${content.replace(/^/, '').replace(/\n*$/, '')}\n`) {
183
- return { configured: true, changed: false, conflict: false, file: configFile };
184
- }
185
- atomicWrite(configFile, normalized);
186
- return { configured: true, changed: true, conflict: false, file: configFile };
106
+ function configureCodexProxy() {
107
+ return { configured: true, changed: false, conflict: false, file: null };
187
108
  }
188
109
 
189
- function bypassCodexProxy({
190
- codexHome = process.env.CODEX_HOME || path.join(os.homedir(), '.codex'),
191
- } = {}) {
192
- const configFile = path.join(codexHome, 'config.toml');
193
- if (!fs.existsSync(configFile)) {
194
- return { bypassed: true, changed: false, conflict: false, file: configFile };
195
- }
196
-
197
- let content;
198
- try { content = fs.readFileSync(configFile, 'utf8'); } catch {
199
- return { bypassed: false, changed: false, conflict: true, file: configFile, reason: 'config-unreadable' };
200
- }
201
-
202
- const lines = content.replace(/^/, '').split(/\r?\n/);
203
- const provider = topLevelAssignment(lines, 'model_provider');
204
- const providerValue = provider ? parseTomlString(provider.raw) : 'openai';
205
- const managedStart = lines.findIndex((line) => line.trim() === CODEX_BLOCK_START);
206
- const managedEnd = lines.findIndex((line) => line.trim() === CODEX_BLOCK_END);
207
- if ((managedStart < 0) !== (managedEnd < 0) || managedEnd < managedStart) {
208
- return { bypassed: false, changed: false, conflict: true, file: configFile, reason: 'provider-block-invalid' };
209
- }
210
- if (providerValue !== CODEX_PROVIDER_ID && managedStart < 0) {
211
- return { bypassed: true, changed: false, conflict: false, file: configFile };
212
- }
213
- if (managedStart >= 0) lines.splice(managedStart, managedEnd - managedStart + 1);
214
-
215
- if (providerValue === CODEX_PROVIDER_ID) {
216
- lines[provider.index] = 'model_provider = "openai"';
217
- if (lines[provider.index - 1]?.trim() === '# Fournisseur Responses protégé par PieceMaker.') {
218
- lines.splice(provider.index - 1, 1);
219
- }
220
- }
221
-
222
- const normalized = `${lines.join('\n').replace(/\n+$/, '')}\n`;
223
- if (normalized === `${content.replace(/^/, '').replace(/\n*$/, '')}\n`) {
224
- return { bypassed: true, changed: false, conflict: false, file: configFile };
225
- }
226
- atomicWrite(configFile, normalized);
227
- return { bypassed: true, changed: true, conflict: false, file: configFile };
110
+ function bypassCodexProxy() {
111
+ return { bypassed: true, changed: false, conflict: false, file: null };
228
112
  }
229
113
 
230
114
  module.exports = {
231
- CODEX_PROVIDER_ID,
232
115
  configureClaudeCodeProxy,
233
116
  bypassClaudeCodeProxy,
234
117
  configureCodexProxy,
@@ -0,0 +1,127 @@
1
+ const { execFileSync, spawn } = require('node:child_process');
2
+ const fs = require('node:fs');
3
+ const net = require('node:net');
4
+ const os = require('node:os');
5
+ const path = require('node:path');
6
+
7
+ const { startRewriterBridge } = require('./rewriter-bridge.cjs');
8
+ const { createSqliteDictionaryLoader } = require('./sqlite-dictionary.cjs');
9
+
10
+ function hudsuckerBinary() {
11
+ if (process.env.PIECEMAKER_HUDSUCKER_BIN) return process.env.PIECEMAKER_HUDSUCKER_BIN;
12
+ return path.join(__dirname, 'hudsucker-proxy', 'target', 'release', 'piecemaker-hudsucker');
13
+ }
14
+
15
+ function makeCertificateAuthority(directory) {
16
+ const cert = path.join(directory, 'ca.crt');
17
+ const key = path.join(directory, 'ca.key');
18
+ execFileSync('openssl', [
19
+ 'req', '-x509', '-newkey', 'rsa:2048', '-nodes',
20
+ '-keyout', key, '-out', cert, '-days', '30',
21
+ '-subj', '/CN=PieceMaker Test CA',
22
+ '-addext', 'basicConstraints=critical,CA:true,pathlen:0',
23
+ '-addext', 'keyUsage=critical,keyCertSign,cRLSign',
24
+ ], { stdio: 'ignore' });
25
+ return { cert, key };
26
+ }
27
+
28
+ function freePort() {
29
+ return new Promise((resolve, reject) => {
30
+ const server = net.createServer();
31
+ server.once('error', reject);
32
+ server.listen(0, '127.0.0.1', () => {
33
+ const { port } = server.address();
34
+ server.close((error) => (error ? reject(error) : resolve(port)));
35
+ });
36
+ });
37
+ }
38
+
39
+ function waitForOutput(child, needle, timeoutMs) {
40
+ return new Promise((resolve, reject) => {
41
+ let buffer = '';
42
+ const timer = setTimeout(() => {
43
+ reject(new Error(`hudsucker n'a pas écouté : ${buffer}`));
44
+ }, timeoutMs);
45
+ const take = (chunk) => {
46
+ buffer += chunk.toString();
47
+ if (!buffer.includes(needle)) return;
48
+ clearTimeout(timer);
49
+ resolve(buffer);
50
+ };
51
+ child.stdout.on('data', take);
52
+ child.stderr.on('data', take);
53
+ child.once('exit', (code) => {
54
+ clearTimeout(timer);
55
+ reject(new Error(`hudsucker sorti (${code}) : ${buffer}`));
56
+ });
57
+ });
58
+ }
59
+
60
+ async function startHudsuckerSession({
61
+ databasePath,
62
+ upstreamPort,
63
+ harness = null,
64
+ hosts = ['api.anthropic.com', 'api.openai.com', 'chatgpt.com'],
65
+ directory = fs.mkdtempSync(path.join(os.tmpdir(), 'pm-hudsucker-')),
66
+ } = {}) {
67
+ const binary = hudsuckerBinary();
68
+ if (!fs.existsSync(binary)) {
69
+ throw new Error(`binaire hudsucker absent : ${binary}`);
70
+ }
71
+ const ca = makeCertificateAuthority(directory);
72
+ const dictionary = createSqliteDictionaryLoader({ databasePath });
73
+ const bridge = await startRewriterBridge({ dictionary, harness });
74
+ const port = await freePort();
75
+ const upstreamMap = hosts.map((host) => `${host}=127.0.0.1:${upstreamPort}`).join(',');
76
+ const child = spawn(binary, [
77
+ '--listen', `127.0.0.1:${port}`,
78
+ '--rewriter', `http://127.0.0.1:${bridge.port}`,
79
+ '--ca-cert', ca.cert,
80
+ '--ca-key', ca.key,
81
+ '--hosts', hosts.join(','),
82
+ '--upstream-map', upstreamMap,
83
+ ], {
84
+ stdio: ['ignore', 'pipe', 'pipe'],
85
+ env: { ...process.env, HTTPS_PROXY: '', HTTP_PROXY: '', ALL_PROXY: '', http_proxy: '', https_proxy: '' },
86
+ });
87
+ try {
88
+ await waitForOutput(child, `listening 127.0.0.1:${port}`, 15000);
89
+ } catch (error) {
90
+ child.kill('SIGKILL');
91
+ await bridge.close();
92
+ dictionary.close();
93
+ throw error;
94
+ }
95
+ return {
96
+ origin: `http://127.0.0.1:${port}`,
97
+ caFile: ca.cert,
98
+ stats: bridge.stats,
99
+ async close() {
100
+ child.kill('SIGTERM');
101
+ await new Promise((resolve) => child.once('exit', resolve));
102
+ await bridge.close();
103
+ dictionary.close();
104
+ },
105
+ };
106
+ }
107
+
108
+ async function postThrough(origin, caFile, body, route = '/v1/messages', host = 'api.anthropic.com') {
109
+ const { ProxyAgent, fetch } = await import('undici');
110
+ const agent = new ProxyAgent({
111
+ uri: origin,
112
+ requestTls: { ca: fs.readFileSync(caFile) },
113
+ });
114
+ const response = await fetch(`https://${host}${route}`, {
115
+ method: 'POST',
116
+ headers: { 'content-type': 'application/json' },
117
+ body,
118
+ dispatcher: agent,
119
+ });
120
+ return response.text();
121
+ }
122
+
123
+ module.exports = {
124
+ hudsuckerBinary,
125
+ postThrough,
126
+ startHudsuckerSession,
127
+ };