@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.
- package/dist/assets/{abnfDiagram-VCTEODGH-DEXWh3LH.js → abnfDiagram-VCTEODGH-BeQS_HnO.js} +1 -1
- package/dist/assets/{arc-CoTRLXKW.js → arc-ClYjD3aM.js} +1 -1
- package/dist/assets/{architectureDiagram-5GKGNRK7-Cg-jO9oR.js → architectureDiagram-5GKGNRK7-Csdihb1L.js} +1 -1
- package/dist/assets/{blockDiagram-I7D4REHJ-ChXwQSIq.js → blockDiagram-I7D4REHJ-BuXey9ht.js} +1 -1
- package/dist/assets/{c4Diagram-7LVT6UL2-Bw2VGggd.js → c4Diagram-7LVT6UL2-DdO-ovOy.js} +1 -1
- package/dist/assets/channel-DhuuSCHK.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-C1WdqrLP.js → chunk-2Q5K7J3B-D3hzWzSc.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-BerUZBa1.js → chunk-5VM5RSS4-DeT3cR6k.js} +1 -1
- package/dist/assets/{chunk-F27PBJKO-5W7ax2gB.js → chunk-F27PBJKO-bazGi1ru.js} +1 -1
- package/dist/assets/{chunk-IMKFNOWR-JgDN1h4T.js → chunk-IMKFNOWR-BE3ZXrPd.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-ulNHnEdQ.js → chunk-JWPE2WC7-dTAsgByE.js} +1 -1
- package/dist/assets/{chunk-POPQ4Y6H-D4aTDC2T.js → chunk-POPQ4Y6H-DKYrzMSd.js} +1 -1
- package/dist/assets/{chunk-SVP7TREG-BA5zX4XF.js → chunk-SVP7TREG-BTtrMvhE.js} +1 -1
- package/dist/assets/{chunk-TICWLB2K-DhOULVhX.js → chunk-TICWLB2K-CZPHShPS.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-CkpCgrVg.js → chunk-XXDRQBXY-B0byFHaU.js} +1 -1
- package/dist/assets/classDiagram-ZZMXUADV-CO0E82Dl.js +1 -0
- package/dist/assets/classDiagram-v2-VYDZK3BY-CO0E82Dl.js +1 -0
- package/dist/assets/{cose-bilkent-JH36ORCC-0xUSfjfV.js → cose-bilkent-JH36ORCC-BbLCzlaU.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-BE6fNu2_.js → cynefin-OW5HDTMX-CQ8ui4YD.js} +1 -1
- package/dist/assets/{cynefinDiagram-5FMLGOSQ-rFMnZ9b3.js → cynefinDiagram-5FMLGOSQ-T-GL1aIf.js} +1 -1
- package/dist/assets/{dagre-GXQ25YYZ-C1p4ffd7.js → dagre-GXQ25YYZ-CXxVKznH.js} +1 -1
- package/dist/assets/{diagram-S7CK7UJ4-BV_3Xj4O.js → diagram-S7CK7UJ4-B_-1Mf_7.js} +1 -1
- package/dist/assets/{diagram-UQ7AKVKN-Bb_Evkrm.js → diagram-UQ7AKVKN-Bs-OB8xI.js} +1 -1
- package/dist/assets/{diagram-VSXAHHWV-BzgupjxD.js → diagram-VSXAHHWV-BpUqq93o.js} +1 -1
- package/dist/assets/{diagram-VX7I27RA-D1c67z76.js → diagram-VX7I27RA-CZ6hSUo6.js} +1 -1
- package/dist/assets/{diagram-Z3DM3KII-DsJAJGKE.js → diagram-Z3DM3KII-DVZiDO6S.js} +1 -1
- package/dist/assets/{ebnfDiagram-PWID7BFC-B1ge_obq.js → ebnfDiagram-PWID7BFC-D_1izoKK.js} +1 -1
- package/dist/assets/{erDiagram-RLTQ6QDP-DrV-tggw.js → erDiagram-RLTQ6QDP-_jSi4sg-.js} +1 -1
- package/dist/assets/{flowDiagram-HODETNUW-CVzDiSQ3.js → flowDiagram-HODETNUW-BtBM7ez3.js} +1 -1
- package/dist/assets/{ganttDiagram-EL5Y4UJY-CGRdSN9D.js → ganttDiagram-EL5Y4UJY-Bz2nxw7c.js} +1 -1
- package/dist/assets/{gitGraphDiagram-WWUBYQGX-CAfvFXzL.js → gitGraphDiagram-WWUBYQGX-8Q3I3hoE.js} +1 -1
- package/dist/assets/{index-DaxH81ZJ.js → index-CZUj0Tlu.js} +152 -152
- package/dist/assets/index-DcfdIMj-.css +1 -0
- package/dist/assets/{infoDiagram-27XIBGKW-DTshEkGK.js → infoDiagram-27XIBGKW-Cc6eKjfU.js} +1 -1
- package/dist/assets/{ishikawaDiagram-5VMMS53U-DkbshVxp.js → ishikawaDiagram-5VMMS53U-Bd3w97qM.js} +1 -1
- package/dist/assets/{journeyDiagram-3NMN7TZE-v5qE8xpn.js → journeyDiagram-3NMN7TZE-Cy6XbK5X.js} +1 -1
- package/dist/assets/{kanban-definition-UXKFOSKX-a9CNMFLt.js → kanban-definition-UXKFOSKX-kyLqWDpr.js} +1 -1
- package/dist/assets/{layout-DrW4R1TH.js → layout-DXpPSEft.js} +1 -1
- package/dist/assets/{linear-Cs3qpXT_.js → linear-DH_h9cDZ.js} +1 -1
- package/dist/assets/{mermaid.core-C7Ti-Fty.js → mermaid.core-BghXiB7p.js} +6 -6
- package/dist/assets/{mindmap-definition-YA3MSWOX-B0VDdIcG.js → mindmap-definition-YA3MSWOX-C2xxEXsP.js} +1 -1
- package/dist/assets/{pegDiagram-XKGWAZYB-CV1L7MjP.js → pegDiagram-XKGWAZYB-DuppHX4-.js} +1 -1
- package/dist/assets/{pieDiagram-E7YTZNPT-DSCuzVTV.js → pieDiagram-E7YTZNPT-BCukPZ2a.js} +1 -1
- package/dist/assets/{quadrantDiagram-AXDQQJYC-hzX-9AKo.js → quadrantDiagram-AXDQQJYC-Dm_ojY_7.js} +1 -1
- package/dist/assets/{railroadDiagram-O6MQD6OU-BbmzIqcp.js → railroadDiagram-O6MQD6OU-BzdOv7BJ.js} +1 -1
- package/dist/assets/{requirementDiagram-BXWQKSXE-CQCRRBSL.js → requirementDiagram-BXWQKSXE-DgewPP_g.js} +1 -1
- package/dist/assets/{sankeyDiagram-P5KCCOFB-kiaLcW6X.js → sankeyDiagram-P5KCCOFB-vEIMvCSt.js} +1 -1
- package/dist/assets/{sequenceDiagram-WJ2MYXX4-Bgx8ST7Q.js → sequenceDiagram-WJ2MYXX4-BL_31NoY.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-BTjJ-ygN.js → sizeCapture-INFHLROL-Ch6u4xo6.js} +1 -1
- package/dist/assets/{stateDiagram-D77RDMKH-D48RR1FL.js → stateDiagram-D77RDMKH-ByquMuJv.js} +1 -1
- package/dist/assets/{stateDiagram-v2-MP3YSRHH-B-XwSR-z.js → stateDiagram-v2-MP3YSRHH-_QRJP_vn.js} +1 -1
- package/dist/assets/{swimlanes-42K2YHIH-BhE-QuEZ.js → swimlanes-42K2YHIH-BZyXEHOb.js} +1 -1
- package/dist/assets/swimlanesDiagram-VR7AAH4N-sqZwZwbP.js +8 -0
- package/dist/assets/{timeline-definition-24CTP7MA-Cm7EUWWt.js → timeline-definition-24CTP7MA-CLBivayK.js} +1 -1
- package/dist/assets/{vennDiagram-4TSXK5OY-X-nC9wZ0.js → vennDiagram-4TSXK5OY-3dJ2PRqR.js} +1 -1
- package/dist/assets/{wardleyDiagram-VM6X3IG4-D33YvEQZ.js → wardleyDiagram-VM6X3IG4-C279LT36.js} +1 -1
- package/dist/assets/{xychartDiagram-S5SC5T6Z-lziwBKsM.js → xychartDiagram-S5SC5T6Z-FoqPDrei.js} +1 -1
- package/dist/index.html +2 -2
- package/dist/sw.js +1 -1
- package/dist-server/server/index.js +3 -1
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/piecemaker/harness/citations.routes.js +18 -1
- package/dist-server/server/piecemaker/harness/citations.routes.js.map +1 -1
- package/dist-server/server/piecemaker/harness/legifrance-html.js +112 -0
- package/dist-server/server/piecemaker/harness/legifrance-html.js.map +1 -0
- package/dist-server/server/piecemaker/index.js +7 -3
- package/dist-server/server/piecemaker/index.js.map +1 -1
- package/dist-server/server/piecemaker/knowledge/index.js +13 -6
- package/dist-server/server/piecemaker/knowledge/index.js.map +1 -1
- package/dist-server/server/piecemaker/knowledge/local-routes.js +55 -0
- package/dist-server/server/piecemaker/knowledge/local-routes.js.map +1 -0
- package/dist-server/server/piecemaker/knowledge/scan-jobs.js +3 -1
- package/dist-server/server/piecemaker/knowledge/scan-jobs.js.map +1 -1
- package/dist-server/server/piecemaker/library/index.js +14 -41
- package/dist-server/server/piecemaker/library/index.js.map +1 -1
- package/dist-server/server/piecemaker/library/store.js +11 -3
- package/dist-server/server/piecemaker/library/store.js.map +1 -1
- package/dist-server/server/piecemaker/library/workspace-installation.js +26 -3
- package/dist-server/server/piecemaker/library/workspace-installation.js.map +1 -1
- package/dist-server/server/piecemaker/timesheet/service.js +8 -7
- package/dist-server/server/piecemaker/timesheet/service.js.map +1 -1
- package/dist-server/server/piecemaker/timesheet/store.js +5 -3
- package/dist-server/server/piecemaker/timesheet/store.js.map +1 -1
- package/package.json +1 -1
- package/product.config.json +2 -0
- package/scripts/piecemaker/build-app-icon.py +88 -0
- package/scripts/piecemaker/cli/lib/piecemaker-anonymizer.mjs +2 -2
- package/scripts/piecemaker/hooks/proxy-guard.mjs +19 -54
- package/scripts/release/assert-piecemaker-version.mjs +25 -0
- package/server/index.ts +4 -1
- package/server/piecemaker/anonymizer/client-config.cjs +45 -162
- package/server/piecemaker/anonymizer/hudsucker-fixture.cjs +127 -0
- package/server/piecemaker/anonymizer/hudsucker-proxy/Cargo.lock +2396 -0
- package/server/piecemaker/anonymizer/hudsucker-proxy/Cargo.toml +14 -0
- package/server/piecemaker/anonymizer/hudsucker-proxy/src/main.rs +501 -0
- package/server/piecemaker/anonymizer/providers.cjs +14 -135
- package/server/piecemaker/anonymizer/rewriter-bridge.cjs +221 -0
- package/server/piecemaker/anonymizer/service.cjs +144 -28
- package/server/piecemaker/harness/citations.routes.ts +23 -1
- package/server/piecemaker/harness/legifrance-html.ts +107 -0
- package/server/piecemaker/index.ts +8 -3
- package/server/piecemaker/knowledge/index.ts +14 -6
- package/server/piecemaker/knowledge/local-routes.ts +72 -0
- package/server/piecemaker/knowledge/scan-jobs.ts +4 -1
- package/server/piecemaker/library/index.ts +14 -34
- package/server/piecemaker/library/store.ts +11 -3
- package/server/piecemaker/library/workspace-installation.ts +20 -3
- package/server/piecemaker/timesheet/service.ts +9 -7
- package/server/piecemaker/timesheet/store.ts +5 -3
- package/server/piecemaker/vendor/installer/assets/claude-hooks/piecemaker-guard-secrets.mjs +0 -1
- 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 +56 -206
- package/server/piecemaker/vendor/installer/lib/commandes.mjs +2 -4
- package/server/piecemaker/vendor/installer/lib/conversion-client.mjs +74 -0
- package/server/piecemaker/vendor/installer/lib/service.mjs +4 -358
- package/server/piecemaker/vendor/installer/lib/state.mjs +0 -6
- package/server/piecemaker/vendor/installer/steps/03-python-gliner.mjs +4 -4
- package/server/piecemaker/vendor/installer/steps/09-claude-assets.mjs +0 -31
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/statusline.mjs +3 -4
- package/server/piecemaker/vendor/websocket-server/admin-routes.cjs +1 -49
- package/server/piecemaker/vendor/websocket-server/claude-hooks.cjs +14 -12
- package/server/piecemaker/vendor/websocket-server/originals-pipeline.cjs +17 -476
- package/server/piecemaker/vendor/websocket-server/scripts/convert_and_scan_pipeline.py +2 -2
- package/server/piecemaker/vendor/websocket-server/scripts/mineru_improved_1.py +11 -2
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/scanner_worker.py +13 -37
- package/server/piecemaker/vendor/websocket-server/scripts/requirements.txt +1 -7
- package/server/piecemaker/vendor/websocket-server/scripts/warmup.py +14 -85
- package/dist/assets/channel-DdzrPZ6Y.js +0 -1
- package/dist/assets/classDiagram-ZZMXUADV-Dim2o8E5.js +0 -1
- package/dist/assets/classDiagram-v2-VYDZK3BY-Dim2o8E5.js +0 -1
- package/dist/assets/index-D_Dc_nQv.css +0 -1
- package/dist/assets/swimlanesDiagram-VR7AAH4N-DR525d5V.js +0 -8
- package/server/piecemaker/anonymizer/proxy.cjs +0 -365
- package/server/piecemaker/vendor/installer/assets/PieceMakerApp.swift +0 -189
- package/server/piecemaker/vendor/installer/lib/desktop-launcher.mjs +0 -305
- package/server/piecemaker/vendor/installer/steps/05-certificats.mjs +0 -171
- package/server/piecemaker/vendor/installer/steps/14-mxc-sandbox.mjs +0 -271
- package/server/piecemaker/vendor/installer/steps/15-pwa-desktop.mjs +0 -64
- package/server/piecemaker/vendor/installer/templates/root-CLAUDE.md +0 -56
- package/server/piecemaker/vendor/installer/templates/workspace-CLAUDE.md +0 -52
|
@@ -1,30 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Branchement des
|
|
2
|
+
* Branchement des clients IA sur le proxy d'anonymisation.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Elle ne suffit pourtant que là où le CLI honore une base d'URL, d'où un
|
|
7
|
-
* second levier — le fichier de configuration utilisateur — pour Codex et
|
|
8
|
-
* opencode, et un troisième pour Cursor, qui n'en honore aucun.
|
|
4
|
+
* Le serveur pose `HTTPS_PROXY` et le certificat de l'autorité locale.
|
|
5
|
+
* Les URL des fournisseurs ne sont pas réécrites.
|
|
9
6
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* | claude | `ANTHROPIC_BASE_URL` + `~/.claude/settings.json` |
|
|
13
|
-
* | codex | `~/.codex/config.toml` (bloc géré) |
|
|
14
|
-
* | opencode | `provider.*.options.baseURL` de sa configuration |
|
|
15
|
-
* | cursor | aucun — voir plus bas |
|
|
16
|
-
*
|
|
17
|
-
* **Cursor.** `cursor-agent` parle le protocole d'authentification de Cursor et
|
|
18
|
-
* ignore toute base d'URL : il n'est pas interceptable. Le laisser tourner
|
|
19
|
-
* enverrait des noms de parties en clair. À défaut de pouvoir le filtrer, on
|
|
20
|
-
* l'empêche de partir : un exécutable `cursor-agent` posé en tête de `PATH`
|
|
21
|
-
* refuse de s'exécuter tant qu'un mapping existe, et relaie le vrai binaire
|
|
22
|
-
* sinon. C'est le seul fournisseur pour lequel la garantie se paie d'un refus,
|
|
23
|
-
* et cela reste préférable à une fuite silencieuse.
|
|
24
|
-
*
|
|
25
|
-
* La configuration Claude et Codex est écrite par `./client-config.cjs`,
|
|
26
|
-
* propre à ce dépôt : écriture atomique, détection de conflit et
|
|
27
|
-
* réversibilité, sous l'identifiant de bloc `piecemaker_proxy`.
|
|
7
|
+
* Cursor ignore tout proxy HTTP. Tant qu'un mapping existe, un shim en tête
|
|
8
|
+
* de `PATH` refuse de lancer `cursor-agent`.
|
|
28
9
|
*/
|
|
29
10
|
const fs = require('node:fs');
|
|
30
11
|
const os = require('node:os');
|
|
@@ -37,110 +18,12 @@ const {
|
|
|
37
18
|
bypassCodexProxy,
|
|
38
19
|
} = require('./client-config.cjs');
|
|
39
20
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
try {
|
|
43
|
-
const url = new URL(String(value || ''));
|
|
44
|
-
return ['127.0.0.1', 'localhost'].includes(url.hostname)
|
|
45
|
-
&& url.pathname.replace(/\/$/, '') === suffix;
|
|
46
|
-
} catch {
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function opencodeConfigFile(userHome) {
|
|
52
|
-
if (process.env.OPENCODE_CONFIG) return process.env.OPENCODE_CONFIG;
|
|
53
|
-
const base = process.env.XDG_CONFIG_HOME || path.join(userHome, '.config');
|
|
54
|
-
return path.join(base, 'opencode', 'opencode.json');
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function writeJsonAtomic(file, value) {
|
|
58
|
-
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
59
|
-
const temporary = `${file}.piecemaker-${process.pid}`;
|
|
60
|
-
fs.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
61
|
-
fs.renameSync(temporary, file);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* opencode lit `provider.<id>.options.baseURL`. On câble les deux familles que
|
|
66
|
-
* le proxy sait router ; les autres fournisseurs éventuellement configurés
|
|
67
|
-
* restent intacts, et donc hors couverture — le statut le dit.
|
|
68
|
-
*/
|
|
69
|
-
const OPENCODE_ROUTES = [
|
|
70
|
-
{ id: 'anthropic', suffix: '/anthropic' },
|
|
71
|
-
{ id: 'openai', suffix: '/openai' },
|
|
72
|
-
];
|
|
73
|
-
|
|
74
|
-
function configureOpencode({ origin, userHome }) {
|
|
75
|
-
const file = opencodeConfigFile(userHome);
|
|
76
|
-
let config = {};
|
|
77
|
-
try {
|
|
78
|
-
if (fs.existsSync(file)) {
|
|
79
|
-
const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
80
|
-
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error('objet JSON attendu');
|
|
81
|
-
config = parsed;
|
|
82
|
-
}
|
|
83
|
-
} catch {
|
|
84
|
-
return { configured: false, changed: false, conflict: true, file, reason: 'config-invalid' };
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (config.provider !== undefined
|
|
88
|
-
&& (!config.provider || typeof config.provider !== 'object' || Array.isArray(config.provider))) {
|
|
89
|
-
return { configured: false, changed: false, conflict: true, file, reason: 'provider-invalid' };
|
|
90
|
-
}
|
|
91
|
-
if (!config.provider) config.provider = {};
|
|
92
|
-
|
|
93
|
-
let changed = false;
|
|
94
|
-
for (const { id, suffix } of OPENCODE_ROUTES) {
|
|
95
|
-
const target = `${origin}${suffix}`;
|
|
96
|
-
const entry = config.provider[id];
|
|
97
|
-
if (entry !== undefined && (!entry || typeof entry !== 'object' || Array.isArray(entry))) {
|
|
98
|
-
return { configured: false, changed: false, conflict: true, file, reason: `provider-${id}-invalid` };
|
|
99
|
-
}
|
|
100
|
-
const options = entry?.options;
|
|
101
|
-
if (options !== undefined && (!options || typeof options !== 'object' || Array.isArray(options))) {
|
|
102
|
-
return { configured: false, changed: false, conflict: true, file, reason: `options-${id}-invalid` };
|
|
103
|
-
}
|
|
104
|
-
const existing = options?.baseURL;
|
|
105
|
-
// Une base tierce délibérément posée par l'utilisateur n'est pas écrasée :
|
|
106
|
-
// on signale le conflit plutôt que de casser sa configuration en silence.
|
|
107
|
-
if (existing && existing !== target && !isOwnLoopbackUrl(existing, suffix)) {
|
|
108
|
-
return { configured: false, changed: false, conflict: true, file, reason: `base-url-conflict:${id}` };
|
|
109
|
-
}
|
|
110
|
-
if (existing === target) continue;
|
|
111
|
-
config.provider[id] = { ...entry, options: { ...options, baseURL: target } };
|
|
112
|
-
changed = true;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (changed) writeJsonAtomic(file, config);
|
|
116
|
-
return { configured: true, changed, conflict: false, file };
|
|
21
|
+
function configureOpencode() {
|
|
22
|
+
return { configured: true, changed: false, conflict: false, file: null };
|
|
117
23
|
}
|
|
118
24
|
|
|
119
|
-
function bypassOpencode(
|
|
120
|
-
|
|
121
|
-
if (!fs.existsSync(file)) return { bypassed: true, changed: false, conflict: false, file };
|
|
122
|
-
|
|
123
|
-
let config;
|
|
124
|
-
try {
|
|
125
|
-
config = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
126
|
-
if (!config || typeof config !== 'object' || Array.isArray(config)) throw new Error('objet JSON attendu');
|
|
127
|
-
} catch {
|
|
128
|
-
return { bypassed: false, changed: false, conflict: true, file, reason: 'config-invalid' };
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
let changed = false;
|
|
132
|
-
for (const { id, suffix } of OPENCODE_ROUTES) {
|
|
133
|
-
const options = config.provider?.[id]?.options;
|
|
134
|
-
if (!options || typeof options !== 'object' || Array.isArray(options)) continue;
|
|
135
|
-
if (!isOwnLoopbackUrl(options.baseURL, suffix)) continue;
|
|
136
|
-
delete options.baseURL;
|
|
137
|
-
if (Object.keys(options).length === 0) delete config.provider[id].options;
|
|
138
|
-
if (Object.keys(config.provider[id]).length === 0) delete config.provider[id];
|
|
139
|
-
changed = true;
|
|
140
|
-
}
|
|
141
|
-
if (changed && config.provider && Object.keys(config.provider).length === 0) delete config.provider;
|
|
142
|
-
if (changed) writeJsonAtomic(file, config);
|
|
143
|
-
return { bypassed: true, changed, conflict: false, file };
|
|
25
|
+
function bypassOpencode() {
|
|
26
|
+
return { bypassed: true, changed: false, conflict: false, file: null };
|
|
144
27
|
}
|
|
145
28
|
|
|
146
29
|
/** Première ligne du garde-fou Cursor : ce que le shim écrit avant de renoncer. */
|
|
@@ -177,7 +60,7 @@ exec cursor-agent "$@"
|
|
|
177
60
|
* dans le statut, ce qui est l'information utile, plutôt que d'empêcher le
|
|
178
61
|
* serveur de démarrer.
|
|
179
62
|
*/
|
|
180
|
-
async function configureProviders({ origin, userHome = os.homedir(), binDir, mappingFile }) {
|
|
63
|
+
async function configureProviders({ origin, caFile, userHome = os.homedir(), binDir, mappingFile }) {
|
|
181
64
|
const report = {};
|
|
182
65
|
|
|
183
66
|
const attempt = (name, run) => {
|
|
@@ -188,12 +71,11 @@ async function configureProviders({ origin, userHome = os.homedir(), binDir, map
|
|
|
188
71
|
}
|
|
189
72
|
};
|
|
190
73
|
|
|
191
|
-
attempt('claude', () => configureClaudeCodeProxy({
|
|
74
|
+
attempt('claude', () => configureClaudeCodeProxy({ proxyUrl: origin, caFile, userHome }));
|
|
192
75
|
attempt('codex', () => configureCodexProxy({
|
|
193
|
-
baseUrl: `${origin}/chatgpt`,
|
|
194
76
|
codexHome: process.env.CODEX_HOME || path.join(userHome, '.codex'),
|
|
195
77
|
}));
|
|
196
|
-
attempt('opencode', () => configureOpencode(
|
|
78
|
+
attempt('opencode', () => configureOpencode());
|
|
197
79
|
attempt('cursor', () => ({
|
|
198
80
|
configured: false,
|
|
199
81
|
changed: false,
|
|
@@ -218,8 +100,8 @@ async function bypassProviders({ userHome = os.homedir(), binDir }) {
|
|
|
218
100
|
};
|
|
219
101
|
|
|
220
102
|
attempt('claude', () => bypassClaudeCodeProxy({ userHome }));
|
|
221
|
-
attempt('codex', () => bypassCodexProxy(
|
|
222
|
-
attempt('opencode', () => bypassOpencode(
|
|
103
|
+
attempt('codex', () => bypassCodexProxy());
|
|
104
|
+
attempt('opencode', () => bypassOpencode());
|
|
223
105
|
attempt('cursor', () => {
|
|
224
106
|
const file = path.join(binDir, 'cursor-agent');
|
|
225
107
|
if (fs.existsSync(file)) fs.rmSync(file);
|
|
@@ -230,12 +112,9 @@ async function bypassProviders({ userHome = os.homedir(), binDir }) {
|
|
|
230
112
|
|
|
231
113
|
module.exports = {
|
|
232
114
|
CURSOR_REFUSAL,
|
|
233
|
-
OPENCODE_ROUTES,
|
|
234
115
|
bypassOpencode,
|
|
235
116
|
bypassProviders,
|
|
236
117
|
configureOpencode,
|
|
237
118
|
configureProviders,
|
|
238
119
|
installCursorGuard,
|
|
239
|
-
isOwnLoopbackUrl,
|
|
240
|
-
opencodeConfigFile,
|
|
241
120
|
};
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
const http = require('node:http');
|
|
2
|
+
|
|
3
|
+
const { anonymize, deanonymize } = require('./dictionary.cjs');
|
|
4
|
+
const { createSseRewriter, rewriteJsonBody } = require('./rewrite.cjs');
|
|
5
|
+
const { createCollecteurTexte } = require('../harness/flux-sse.cjs');
|
|
6
|
+
|
|
7
|
+
const MAX_BODY = 64 * 1024 * 1024;
|
|
8
|
+
|
|
9
|
+
function readBody(request) {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
const chunks = [];
|
|
12
|
+
let size = 0;
|
|
13
|
+
request.on('data', (chunk) => {
|
|
14
|
+
size += chunk.length;
|
|
15
|
+
if (size > MAX_BODY) {
|
|
16
|
+
reject(new Error('corps trop volumineux'));
|
|
17
|
+
request.destroy();
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
chunks.push(chunk);
|
|
21
|
+
});
|
|
22
|
+
request.on('end', () => resolve(Buffer.concat(chunks)));
|
|
23
|
+
request.on('error', reject);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function header(request, name) {
|
|
28
|
+
const value = request.headers[name];
|
|
29
|
+
return Array.isArray(value) ? value[0] : value || '';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function observeRequest(harness, body, contentType, provider) {
|
|
33
|
+
if (!harness) return;
|
|
34
|
+
try {
|
|
35
|
+
harness.observerRequete(body, contentType, { session: provider });
|
|
36
|
+
} catch {
|
|
37
|
+
// une observation ne doit pas empêcher le relais
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function observeResponse(harness, text, provider) {
|
|
42
|
+
if (!harness) return;
|
|
43
|
+
try {
|
|
44
|
+
Promise.resolve(harness.observerReponse(text, { session: provider })).catch(() => {});
|
|
45
|
+
} catch {
|
|
46
|
+
// une observation ne doit pas empêcher le relais
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function assistantText(raw) {
|
|
51
|
+
try {
|
|
52
|
+
const parsed = JSON.parse(raw.toString('utf8'));
|
|
53
|
+
if (Array.isArray(parsed?.content)) {
|
|
54
|
+
return parsed.content
|
|
55
|
+
.filter((block) => block?.type === 'text' && typeof block.text === 'string')
|
|
56
|
+
.map((block) => block.text)
|
|
57
|
+
.join('');
|
|
58
|
+
}
|
|
59
|
+
if (Array.isArray(parsed?.output)) {
|
|
60
|
+
return parsed.output
|
|
61
|
+
.flatMap((item) => (Array.isArray(item?.content) ? item.content : []))
|
|
62
|
+
.filter((block) => block?.type === 'output_text' && typeof block.text === 'string')
|
|
63
|
+
.map((block) => block.text)
|
|
64
|
+
.join('');
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
return '';
|
|
68
|
+
}
|
|
69
|
+
return '';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function ssePayloads(tail) {
|
|
73
|
+
return String(tail || '')
|
|
74
|
+
.split('\n')
|
|
75
|
+
.filter((line) => line.startsWith('data: '))
|
|
76
|
+
.map((line) => line.slice(6))
|
|
77
|
+
.filter((line) => line && line !== '[DONE]');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function startRewriterBridge({ dictionary, harness = null }) {
|
|
81
|
+
const sessions = new Map();
|
|
82
|
+
const stats = { requests: 0, anonymized: 0, deanonymized: 0, failures: 0, lastError: null };
|
|
83
|
+
|
|
84
|
+
const server = http.createServer((request, response) => {
|
|
85
|
+
const provider = header(request, 'x-piecemaker-provider') || 'claude';
|
|
86
|
+
const current = () => dictionary.get();
|
|
87
|
+
|
|
88
|
+
if (request.method === 'POST' && request.url === '/v1/request') {
|
|
89
|
+
readBody(request).then((body) => {
|
|
90
|
+
stats.requests += 1;
|
|
91
|
+
const contentType = header(request, 'content-type') || 'application/json';
|
|
92
|
+
observeRequest(harness, body, contentType, provider);
|
|
93
|
+
const dict = current();
|
|
94
|
+
const rewritten = !body.length || dict.empty
|
|
95
|
+
? body
|
|
96
|
+
: Buffer.from(rewriteJsonBody(body, (text) => anonymize(text, dict)), 'utf8');
|
|
97
|
+
if (!rewritten.equals(body)) stats.anonymized += 1;
|
|
98
|
+
response.writeHead(200, { 'content-type': 'application/octet-stream', 'content-length': rewritten.length });
|
|
99
|
+
response.end(rewritten);
|
|
100
|
+
}).catch((error) => {
|
|
101
|
+
stats.failures += 1;
|
|
102
|
+
stats.lastError = error.message;
|
|
103
|
+
response.writeHead(500);
|
|
104
|
+
response.end();
|
|
105
|
+
});
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (request.method === 'POST' && request.url === '/v1/response') {
|
|
110
|
+
readBody(request).then((body) => {
|
|
111
|
+
const dict = current();
|
|
112
|
+
const rewritten = !body.length || dict.empty
|
|
113
|
+
? body
|
|
114
|
+
: Buffer.from(rewriteJsonBody(body, (text) => deanonymize(text, dict)), 'utf8');
|
|
115
|
+
if (!rewritten.equals(body)) stats.deanonymized += 1;
|
|
116
|
+
observeResponse(harness, assistantText(rewritten), provider);
|
|
117
|
+
response.writeHead(200, { 'content-type': 'application/octet-stream', 'content-length': rewritten.length });
|
|
118
|
+
response.end(rewritten);
|
|
119
|
+
}).catch((error) => {
|
|
120
|
+
stats.failures += 1;
|
|
121
|
+
stats.lastError = error.message;
|
|
122
|
+
response.writeHead(500);
|
|
123
|
+
response.end();
|
|
124
|
+
});
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (request.method === 'POST' && request.url === '/v1/sse') {
|
|
129
|
+
const dict = current();
|
|
130
|
+
const rewriter = createSseRewriter((text) => deanonymize(text, dict));
|
|
131
|
+
const collecteur = createCollecteurTexte();
|
|
132
|
+
response.writeHead(200, {
|
|
133
|
+
'content-type': 'text/event-stream',
|
|
134
|
+
'cache-control': 'no-cache',
|
|
135
|
+
});
|
|
136
|
+
response.flushHeaders();
|
|
137
|
+
request.setEncoding('utf8');
|
|
138
|
+
request.on('data', (chunk) => {
|
|
139
|
+
const out = rewriter.push(chunk);
|
|
140
|
+
if (!out) return;
|
|
141
|
+
response.write(out);
|
|
142
|
+
collecteur.push(out);
|
|
143
|
+
});
|
|
144
|
+
request.on('end', () => {
|
|
145
|
+
const tail = rewriter.end();
|
|
146
|
+
if (tail) {
|
|
147
|
+
response.write(tail);
|
|
148
|
+
collecteur.push(tail);
|
|
149
|
+
}
|
|
150
|
+
stats.deanonymized += 1;
|
|
151
|
+
response.end();
|
|
152
|
+
observeResponse(harness, collecteur.texte(), provider);
|
|
153
|
+
});
|
|
154
|
+
request.on('error', () => response.end());
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (request.method === 'POST' && request.url === '/v1/ws') {
|
|
159
|
+
const id = header(request, 'x-piecemaker-ws');
|
|
160
|
+
const direction = header(request, 'x-piecemaker-direction');
|
|
161
|
+
readBody(request).then((body) => {
|
|
162
|
+
const dict = current();
|
|
163
|
+
const text = body.toString('utf8');
|
|
164
|
+
if (direction === 'out') {
|
|
165
|
+
observeRequest(harness, body, 'application/json', provider);
|
|
166
|
+
const rewritten = dict.empty ? text : rewriteJsonBody(text, (value) => anonymize(value, dict));
|
|
167
|
+
response.writeHead(200, { 'content-type': 'text/plain; charset=utf-8' });
|
|
168
|
+
response.end(rewritten);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
let entry = sessions.get(id);
|
|
172
|
+
if (!entry) {
|
|
173
|
+
entry = { rewriter: createSseRewriter((value) => deanonymize(value, dict)) };
|
|
174
|
+
sessions.set(id, entry);
|
|
175
|
+
}
|
|
176
|
+
if (!text.includes('\n')) {
|
|
177
|
+
const payloads = ssePayloads(entry.rewriter.push(`data: ${text}\n\n`));
|
|
178
|
+
response.writeHead(200, { 'content-type': 'text/plain; charset=utf-8' });
|
|
179
|
+
response.end(payloads.join('\n'));
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const rewritten = dict.empty ? text : rewriteJsonBody(text, (value) => deanonymize(value, dict));
|
|
183
|
+
response.writeHead(200, { 'content-type': 'text/plain; charset=utf-8' });
|
|
184
|
+
response.end(rewritten);
|
|
185
|
+
}).catch(() => {
|
|
186
|
+
response.writeHead(500);
|
|
187
|
+
response.end();
|
|
188
|
+
});
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (request.method === 'POST' && request.url === '/v1/ws-end') {
|
|
193
|
+
const id = header(request, 'x-piecemaker-ws');
|
|
194
|
+
const entry = sessions.get(id);
|
|
195
|
+
sessions.delete(id);
|
|
196
|
+
const messages = entry ? ssePayloads(entry.rewriter.end()) : [];
|
|
197
|
+
response.writeHead(200, { 'content-type': 'application/json' });
|
|
198
|
+
response.end(JSON.stringify({ messages }));
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
response.writeHead(404);
|
|
203
|
+
response.end();
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
return new Promise((resolve, reject) => {
|
|
207
|
+
server.once('error', reject);
|
|
208
|
+
server.listen(0, '127.0.0.1', () => {
|
|
209
|
+
const { port } = server.address();
|
|
210
|
+
resolve({
|
|
211
|
+
port,
|
|
212
|
+
stats,
|
|
213
|
+
close() {
|
|
214
|
+
return new Promise((done) => server.close(() => done()));
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
module.exports = { startRewriterBridge };
|
|
@@ -15,17 +15,24 @@
|
|
|
15
15
|
* Le proxy vit et meurt avec le serveur : à l'arrêt, les bases écrites sont
|
|
16
16
|
* retirées pour ne pas laisser les clients pointer un port fermé.
|
|
17
17
|
*/
|
|
18
|
+
const { spawn } = require('node:child_process');
|
|
18
19
|
const fs = require('node:fs');
|
|
19
20
|
const os = require('node:os');
|
|
20
21
|
const path = require('node:path');
|
|
21
22
|
|
|
22
23
|
const { createSqliteDictionaryLoader } = require('./sqlite-dictionary.cjs');
|
|
23
|
-
const {
|
|
24
|
+
const { startRewriterBridge } = require('./rewriter-bridge.cjs');
|
|
24
25
|
const { bypassProviders, configureProviders } = require('./providers.cjs');
|
|
25
26
|
const { createHarnessJuridique } = require('../harness/index.cjs');
|
|
26
27
|
|
|
27
28
|
const ENV_VAR = 'ANTHROPIC_BASE_URL';
|
|
28
29
|
const OPENAI_ENV_VAR = 'OPENAI_BASE_URL';
|
|
30
|
+
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
|
+
];
|
|
29
36
|
|
|
30
37
|
/** Interrupteur : `PIECEMAKER_ANONYMIZER=off`, ou `anonymizer.enabled: false` dans `config.json`. */
|
|
31
38
|
function isDisabled(homeDir) {
|
|
@@ -76,6 +83,66 @@ function summarizeCoverage(report) {
|
|
|
76
83
|
return coverage;
|
|
77
84
|
}
|
|
78
85
|
|
|
86
|
+
function hudsuckerBinary() {
|
|
87
|
+
if (process.env.PIECEMAKER_HUDSUCKER_BIN) return process.env.PIECEMAKER_HUDSUCKER_BIN;
|
|
88
|
+
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;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function certificatePaths() {
|
|
99
|
+
const directory = path.join(os.homedir(), '.piecemaker', 'certs');
|
|
100
|
+
return {
|
|
101
|
+
cert: path.join(directory, 'piecemaker-ca.crt'),
|
|
102
|
+
key: path.join(directory, 'piecemaker-ca.key'),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function waitForOutput(child, needle, timeoutMs) {
|
|
107
|
+
return new Promise((resolve, reject) => {
|
|
108
|
+
let buffer = '';
|
|
109
|
+
let settled = false;
|
|
110
|
+
const finish = (failed, value) => {
|
|
111
|
+
if (settled) return;
|
|
112
|
+
settled = true;
|
|
113
|
+
clearTimeout(timer);
|
|
114
|
+
child.stdout?.off('data', take);
|
|
115
|
+
child.stderr?.off('data', take);
|
|
116
|
+
child.off('exit', onExit);
|
|
117
|
+
if (failed) reject(value);
|
|
118
|
+
else resolve(value);
|
|
119
|
+
};
|
|
120
|
+
const timer = setTimeout(() => finish(true, new Error(`hudsucker n'a pas écouté : ${buffer}`)), timeoutMs);
|
|
121
|
+
const take = (chunk) => {
|
|
122
|
+
buffer += chunk.toString();
|
|
123
|
+
if (buffer.includes(needle)) finish(false, buffer);
|
|
124
|
+
};
|
|
125
|
+
const onExit = (code) => finish(true, new Error(`hudsucker sorti (${code}) : ${buffer}`));
|
|
126
|
+
child.stdout?.on('data', take);
|
|
127
|
+
child.stderr?.on('data', take);
|
|
128
|
+
child.once('exit', onExit);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function proxyEnvironment(proxyUrl, caFile) {
|
|
133
|
+
return {
|
|
134
|
+
HTTPS_PROXY: proxyUrl,
|
|
135
|
+
HTTP_PROXY: proxyUrl,
|
|
136
|
+
ALL_PROXY: proxyUrl,
|
|
137
|
+
NO_PROXY: 'localhost,127.0.0.1,::1',
|
|
138
|
+
NODE_USE_ENV_PROXY: '1',
|
|
139
|
+
NODE_EXTRA_CA_CERTS: caFile,
|
|
140
|
+
SSL_CERT_FILE: caFile,
|
|
141
|
+
CODEX_CA_CERTIFICATE: caFile,
|
|
142
|
+
REQUESTS_CA_BUNDLE: caFile,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
79
146
|
function createAnonymizerService({ homeDir, userHome = os.homedir(), logger = console, required = false }) {
|
|
80
147
|
const dictionary = createSqliteDictionaryLoader({ databasePath: process.env.DATABASE_PATH || path.join(homeDir, 'auth.db') });
|
|
81
148
|
const legacyMappingFile = path.join(homeDir, 'central-mapping.json');
|
|
@@ -96,6 +163,7 @@ function createAnonymizerService({ homeDir, userHome = os.homedir(), logger = co
|
|
|
96
163
|
coverage: {},
|
|
97
164
|
};
|
|
98
165
|
let proxy = null;
|
|
166
|
+
let bridge = null;
|
|
99
167
|
let previousEnv = null;
|
|
100
168
|
|
|
101
169
|
async function start() {
|
|
@@ -105,34 +173,68 @@ function createAnonymizerService({ homeDir, userHome = os.homedir(), logger = co
|
|
|
105
173
|
return state;
|
|
106
174
|
}
|
|
107
175
|
|
|
176
|
+
const binary = hudsuckerBinary();
|
|
177
|
+
const certificates = certificatePaths();
|
|
178
|
+
if (!fs.existsSync(binary) || !fs.existsSync(certificates.cert) || !fs.existsSync(certificates.key)) {
|
|
179
|
+
state.reason = 'binary_or_ca_missing';
|
|
180
|
+
logger.warn?.('[piecemaker] hudsucker ou autorité locale absent');
|
|
181
|
+
return state;
|
|
182
|
+
}
|
|
183
|
+
|
|
108
184
|
const claudeUpstream = resolveUpstream(process.env[ENV_VAR]);
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
+
}
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
bridge = await startRewriterBridge({ dictionary, harness });
|
|
195
|
+
} catch (error) {
|
|
196
|
+
state.reason = `listen_failed: ${error.message}`;
|
|
197
|
+
logger.warn?.(`[piecemaker] réécriture PII non démarrée : ${error.message}`);
|
|
198
|
+
return state;
|
|
199
|
+
}
|
|
119
200
|
|
|
120
|
-
|
|
121
|
-
|
|
201
|
+
const listenedPort = DEFAULT_PORT;
|
|
202
|
+
const origin = `http://127.0.0.1:${listenedPort}`;
|
|
203
|
+
const child = spawn(binary, [
|
|
204
|
+
'--listen', `127.0.0.1:${listenedPort}`,
|
|
205
|
+
'--rewriter', `http://127.0.0.1:${bridge.port}`,
|
|
206
|
+
'--ca-cert', certificates.cert,
|
|
207
|
+
'--ca-key', certificates.key,
|
|
208
|
+
'--hosts', hosts.join(','),
|
|
209
|
+
], {
|
|
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
|
+
},
|
|
220
|
+
});
|
|
221
|
+
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()}`));
|
|
122
224
|
try {
|
|
123
|
-
(
|
|
225
|
+
await waitForOutput(child, `listening 127.0.0.1:${listenedPort}`, 15000);
|
|
124
226
|
} catch (error) {
|
|
227
|
+
child.kill('SIGKILL');
|
|
125
228
|
proxy = null;
|
|
229
|
+
await bridge.close();
|
|
230
|
+
bridge = null;
|
|
126
231
|
state.reason = `listen_failed: ${error.message}`;
|
|
127
232
|
logger.warn?.(`[piecemaker] proxy PII non démarré : ${error.message}`);
|
|
128
233
|
return state;
|
|
129
234
|
}
|
|
130
235
|
|
|
131
|
-
// Publie le port
|
|
132
|
-
//
|
|
133
|
-
// dans la config partagée, pour que d'autres consommateurs indépendants
|
|
134
|
-
// du process serveur (ex. le sous-service Docker "mike") puissent le
|
|
135
|
-
// retrouver sans le redéduire eux-mêmes ni le coder en dur.
|
|
236
|
+
// Publie le port dans la config partagée pour que les consommateurs
|
|
237
|
+
// indépendants du process serveur puissent le retrouver.
|
|
136
238
|
try {
|
|
137
239
|
const configFile = path.join(homeDir, 'config.json');
|
|
138
240
|
const current = JSON.parse(fs.readFileSync(configFile, 'utf8'));
|
|
@@ -148,19 +250,24 @@ function createAnonymizerService({ homeDir, userHome = os.homedir(), logger = co
|
|
|
148
250
|
[OPENAI_ENV_VAR]: process.env[OPENAI_ENV_VAR],
|
|
149
251
|
PATH: process.env.PATH,
|
|
150
252
|
};
|
|
151
|
-
process.env[
|
|
152
|
-
process.env
|
|
153
|
-
// En tête de chemin : le shim doit être trouvé avant le vrai `cursor-agent`.
|
|
253
|
+
for (const key of PROXY_ENV_KEYS) previousEnv[key] = process.env[key];
|
|
254
|
+
Object.assign(process.env, proxyEnvironment(origin, certificates.cert));
|
|
154
255
|
process.env.PATH = `${binDir}${path.delimiter}${process.env.PATH || ''}`;
|
|
155
256
|
|
|
156
|
-
const report = await configureProviders({
|
|
257
|
+
const report = await configureProviders({
|
|
258
|
+
origin,
|
|
259
|
+
caFile: certificates.cert,
|
|
260
|
+
userHome,
|
|
261
|
+
binDir,
|
|
262
|
+
mappingFile: legacyMappingFile,
|
|
263
|
+
});
|
|
157
264
|
Object.assign(state, {
|
|
158
265
|
enabled: true,
|
|
159
266
|
reason: 'running',
|
|
160
267
|
origin,
|
|
161
268
|
upstream: claudeUpstream,
|
|
162
269
|
startedAt: new Date().toISOString(),
|
|
163
|
-
routes:
|
|
270
|
+
routes: hosts.map((host) => ({ provider: host, upstream: `https://${host}` })),
|
|
164
271
|
coverage: summarizeCoverage(report),
|
|
165
272
|
});
|
|
166
273
|
|
|
@@ -175,11 +282,20 @@ function createAnonymizerService({ homeDir, userHome = os.homedir(), logger = co
|
|
|
175
282
|
}
|
|
176
283
|
|
|
177
284
|
async function stop() {
|
|
178
|
-
if (!proxy) {
|
|
285
|
+
if (!proxy && !bridge) {
|
|
179
286
|
dictionary.close();
|
|
180
287
|
return;
|
|
181
288
|
}
|
|
182
|
-
|
|
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');
|
|
296
|
+
}
|
|
297
|
+
if (bridge) await bridge.close();
|
|
298
|
+
bridge = null;
|
|
183
299
|
await bypassProviders({ userHome, binDir });
|
|
184
300
|
if (previousEnv) {
|
|
185
301
|
for (const [key, value] of Object.entries(previousEnv)) {
|
|
@@ -197,7 +313,7 @@ function createAnonymizerService({ homeDir, userHome = os.homedir(), logger = co
|
|
|
197
313
|
const current = dictionary.get();
|
|
198
314
|
return {
|
|
199
315
|
...state,
|
|
200
|
-
listening: Boolean(proxy
|
|
316
|
+
listening: Boolean(proxy && proxy.exitCode === null && !proxy.killed),
|
|
201
317
|
dictionary: {
|
|
202
318
|
file: dictionary.file,
|
|
203
319
|
exists: dictionary.exists(),
|
|
@@ -206,7 +322,7 @@ function createAnonymizerService({ homeDir, userHome = os.homedir(), logger = co
|
|
|
206
322
|
updatedAt: current.updatedAt,
|
|
207
323
|
empty: current.empty,
|
|
208
324
|
},
|
|
209
|
-
stats:
|
|
325
|
+
stats: bridge ? { ...bridge.stats } : null,
|
|
210
326
|
};
|
|
211
327
|
}
|
|
212
328
|
|