@piecemaker-legal/piecemaker 1.0.6 → 1.0.7
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-DF7LFtT-.js → abnfDiagram-VCTEODGH-CR3_ADBu.js} +1 -1
- package/dist/assets/{arc-xOmjWeVH.js → arc-b92imMmc.js} +1 -1
- package/dist/assets/{architectureDiagram-5GKGNRK7-CESzNPwa.js → architectureDiagram-5GKGNRK7-Dfm5Epud.js} +1 -1
- package/dist/assets/{blockDiagram-I7D4REHJ-BjdN78Ad.js → blockDiagram-I7D4REHJ-B8kLLyAv.js} +1 -1
- package/dist/assets/{c4Diagram-7LVT6UL2-BcK9EDWT.js → c4Diagram-7LVT6UL2-BWSyAAWX.js} +1 -1
- package/dist/assets/channel-BbcW_Tkh.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-DGUN1JFP.js → chunk-2Q5K7J3B-Ci743mcC.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-BG3BBcFr.js → chunk-5VM5RSS4-SdXpal6h.js} +1 -1
- package/dist/assets/{chunk-F27PBJKO-CLIOfrF5.js → chunk-F27PBJKO-Rof94ueX.js} +1 -1
- package/dist/assets/{chunk-IMKFNOWR-D6lGkqij.js → chunk-IMKFNOWR-B3st6ECc.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-BmF9vLBT.js → chunk-JWPE2WC7-alXmu5xU.js} +1 -1
- package/dist/assets/{chunk-POPQ4Y6H-C_0SR3ak.js → chunk-POPQ4Y6H-dMs1OCce.js} +1 -1
- package/dist/assets/{chunk-SVP7TREG-rT20Wfpp.js → chunk-SVP7TREG-BmM8FsmF.js} +1 -1
- package/dist/assets/{chunk-TICWLB2K-Cn2hkmcf.js → chunk-TICWLB2K-CL1LytYG.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-Dm_eP1_v.js → chunk-XXDRQBXY-7U9iRPT1.js} +1 -1
- package/dist/assets/classDiagram-ZZMXUADV-D9_YRZ7i.js +1 -0
- package/dist/assets/classDiagram-v2-VYDZK3BY-D9_YRZ7i.js +1 -0
- package/dist/assets/{cose-bilkent-JH36ORCC-cJeeJsep.js → cose-bilkent-JH36ORCC-DFOMlhj2.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-DX2ePIXe.js → cynefin-OW5HDTMX-CDdJFd9d.js} +1 -1
- package/dist/assets/{cynefinDiagram-5FMLGOSQ-BvWXN-RR.js → cynefinDiagram-5FMLGOSQ-DPyVNrMW.js} +1 -1
- package/dist/assets/{dagre-GXQ25YYZ-CkwPRhGR.js → dagre-GXQ25YYZ-B3bpHa9D.js} +1 -1
- package/dist/assets/{diagram-S7CK7UJ4-CBGNzYyv.js → diagram-S7CK7UJ4-DeFO2rXB.js} +1 -1
- package/dist/assets/{diagram-UQ7AKVKN-CiwCU0SJ.js → diagram-UQ7AKVKN-CbdGTOVf.js} +1 -1
- package/dist/assets/{diagram-VSXAHHWV-Aekg4nTk.js → diagram-VSXAHHWV-DFfNQyMd.js} +1 -1
- package/dist/assets/{diagram-VX7I27RA-DPFP5K7r.js → diagram-VX7I27RA-DbNZ6_X3.js} +1 -1
- package/dist/assets/{diagram-Z3DM3KII-BHJkxoey.js → diagram-Z3DM3KII-B8F-ZkNJ.js} +1 -1
- package/dist/assets/{ebnfDiagram-PWID7BFC-2rV1P32T.js → ebnfDiagram-PWID7BFC-BfCCPPAP.js} +1 -1
- package/dist/assets/{erDiagram-RLTQ6QDP-C4BjyS_H.js → erDiagram-RLTQ6QDP-DcV-3Kzv.js} +1 -1
- package/dist/assets/{flowDiagram-HODETNUW-DL1TBlAG.js → flowDiagram-HODETNUW-BwSkJXCo.js} +1 -1
- package/dist/assets/{ganttDiagram-EL5Y4UJY-MKjvzCWz.js → ganttDiagram-EL5Y4UJY-BffHk77T.js} +1 -1
- package/dist/assets/{gitGraphDiagram-WWUBYQGX-CS_oTGK5.js → gitGraphDiagram-WWUBYQGX-CQ1EWa74.js} +1 -1
- package/dist/assets/{index-XOGQDqU3.js → index-BqK33gaZ.js} +120 -120
- package/dist/assets/index-D_Dc_nQv.css +1 -0
- package/dist/assets/{infoDiagram-27XIBGKW-ihUr2GjN.js → infoDiagram-27XIBGKW-DqIaBXQn.js} +1 -1
- package/dist/assets/{ishikawaDiagram-5VMMS53U-D2YAnuir.js → ishikawaDiagram-5VMMS53U-HlhsaZgv.js} +1 -1
- package/dist/assets/{journeyDiagram-3NMN7TZE-Bc2hSZGD.js → journeyDiagram-3NMN7TZE-BYO8-7rN.js} +1 -1
- package/dist/assets/{kanban-definition-UXKFOSKX-LRMGNonX.js → kanban-definition-UXKFOSKX-DXb8zHGT.js} +1 -1
- package/dist/assets/{layout-CcfWOlPV.js → layout-CREOVuEZ.js} +1 -1
- package/dist/assets/{linear-7JoS2gQO.js → linear-CVCrIRIY.js} +1 -1
- package/dist/assets/{mermaid.core-ijh_SsuN.js → mermaid.core-DFqeWtQR.js} +6 -6
- package/dist/assets/{mindmap-definition-YA3MSWOX-DCE4nzfF.js → mindmap-definition-YA3MSWOX-SUpXz333.js} +1 -1
- package/dist/assets/{pegDiagram-XKGWAZYB-SjaGa7lE.js → pegDiagram-XKGWAZYB-D-yWpve3.js} +1 -1
- package/dist/assets/{pieDiagram-E7YTZNPT-1xcRidSZ.js → pieDiagram-E7YTZNPT-BJX4Zp4Z.js} +1 -1
- package/dist/assets/{quadrantDiagram-AXDQQJYC-obHAHnYV.js → quadrantDiagram-AXDQQJYC-CHajheTu.js} +1 -1
- package/dist/assets/{railroadDiagram-O6MQD6OU-BsCVcRZb.js → railroadDiagram-O6MQD6OU-DHJWzW5A.js} +1 -1
- package/dist/assets/{requirementDiagram-BXWQKSXE-CUNkhvqb.js → requirementDiagram-BXWQKSXE-DigUIdso.js} +1 -1
- package/dist/assets/{sankeyDiagram-P5KCCOFB-DhezXMKm.js → sankeyDiagram-P5KCCOFB-CFUDDbA5.js} +1 -1
- package/dist/assets/{sequenceDiagram-WJ2MYXX4-CL9VswXH.js → sequenceDiagram-WJ2MYXX4-6tSaXfYD.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-t0g9jiEz.js → sizeCapture-INFHLROL-CXASEDKe.js} +1 -1
- package/dist/assets/{stateDiagram-D77RDMKH-BW-g-IOz.js → stateDiagram-D77RDMKH-DWTCAQfZ.js} +1 -1
- package/dist/assets/stateDiagram-v2-MP3YSRHH-HDQF4MGA.js +1 -0
- package/dist/assets/{swimlanes-42K2YHIH-LsuyeBte.js → swimlanes-42K2YHIH-7-oz5r-e.js} +1 -1
- package/dist/assets/swimlanesDiagram-VR7AAH4N-CJbgQFt8.js +8 -0
- package/dist/assets/{timeline-definition-24CTP7MA-B0cFka1R.js → timeline-definition-24CTP7MA-DVH5g4ty.js} +1 -1
- package/dist/assets/{vendor-codemirror-C5qqu4R7.js → vendor-codemirror-CjW0lHR5.js} +5 -5
- package/dist/assets/{vennDiagram-4TSXK5OY-D3HNCDR3.js → vennDiagram-4TSXK5OY-CQvY_ALZ.js} +1 -1
- package/dist/assets/{wardleyDiagram-VM6X3IG4-DJ3Co8nb.js → wardleyDiagram-VM6X3IG4-BdlTJgEc.js} +1 -1
- package/dist/assets/{xychartDiagram-S5SC5T6Z-Cz5E9New.js → xychartDiagram-S5SC5T6Z-CYn2sV77.js} +1 -1
- package/dist/favicon.png +0 -0
- package/dist/favicon.svg +1 -9
- package/dist/generate-icons.js +47 -46
- package/dist/icons/icon-128x128.png +0 -0
- package/dist/icons/icon-128x128.svg +1 -12
- package/dist/icons/icon-144x144.png +0 -0
- package/dist/icons/icon-144x144.svg +1 -12
- package/dist/icons/icon-152x152.png +0 -0
- package/dist/icons/icon-152x152.svg +1 -12
- package/dist/icons/icon-192x192.png +0 -0
- package/dist/icons/icon-192x192.svg +1 -12
- package/dist/icons/icon-384x384.png +0 -0
- package/dist/icons/icon-384x384.svg +1 -12
- package/dist/icons/icon-512x512.png +0 -0
- package/dist/icons/icon-512x512.svg +1 -12
- package/dist/icons/icon-72x72.png +0 -0
- package/dist/icons/icon-72x72.svg +1 -12
- package/dist/icons/icon-96x96.png +0 -0
- package/dist/icons/icon-96x96.svg +1 -12
- package/dist/index.html +3 -3
- package/dist/logo-128.png +0 -0
- package/dist/logo-256.png +0 -0
- package/dist/logo-32.png +0 -0
- package/dist/logo-512.png +0 -0
- package/dist/logo-64.png +0 -0
- package/dist/logo-sources/logo-black.png +0 -0
- package/dist/logo-sources/logo-white.png +0 -0
- package/dist/logo.svg +1 -17
- package/dist/sw.js +1 -1
- package/dist-server/plugins/piecemaker-dossier/src/institutional-terms.js +91 -0
- package/dist-server/plugins/piecemaker-dossier/src/institutional-terms.js.map +1 -0
- package/dist-server/plugins/piecemaker-dossier/src/knowledge.js +112 -10
- package/dist-server/plugins/piecemaker-dossier/src/knowledge.js.map +1 -1
- package/dist-server/plugins/piecemaker-dossier/src/scan-result.js +5 -1
- package/dist-server/plugins/piecemaker-dossier/src/scan-result.js.map +1 -1
- package/dist-server/server/index.js +60 -26
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/modules/database/repositories/projects.db.js +28 -6
- package/dist-server/server/modules/database/repositories/projects.db.js.map +1 -1
- package/dist-server/server/modules/database/tests/projects.db.integration.test.js +58 -1
- package/dist-server/server/modules/database/tests/projects.db.integration.test.js.map +1 -1
- package/dist-server/server/modules/plugins/plugins.routes.js +8 -1
- package/dist-server/server/modules/plugins/plugins.routes.js.map +1 -1
- package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js +2 -0
- package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js.map +1 -1
- package/dist-server/server/piecemaker/bodacc-search.js +191 -0
- package/dist-server/server/piecemaker/bodacc-search.js.map +1 -0
- package/dist-server/server/piecemaker/company-search.js +145 -0
- package/dist-server/server/piecemaker/company-search.js.map +1 -0
- package/dist-server/server/piecemaker/company-search.test.js +18 -0
- package/dist-server/server/piecemaker/company-search.test.js.map +1 -0
- package/dist-server/server/piecemaker/index.js +6 -2
- package/dist-server/server/piecemaker/index.js.map +1 -1
- package/dist-server/server/piecemaker/knowledge/index.js +9 -4
- package/dist-server/server/piecemaker/knowledge/index.js.map +1 -1
- package/dist-server/server/piecemaker/knowledge/pipeline.js +68 -58
- package/dist-server/server/piecemaker/knowledge/pipeline.js.map +1 -1
- package/dist-server/server/piecemaker/knowledge/routes.js +2 -4
- package/dist-server/server/piecemaker/knowledge/routes.js.map +1 -1
- package/dist-server/server/piecemaker/knowledge/scan-jobs.js +91 -0
- package/dist-server/server/piecemaker/knowledge/scan-jobs.js.map +1 -0
- package/dist-server/server/piecemaker/knowledge/service.js +18 -36
- package/dist-server/server/piecemaker/knowledge/service.js.map +1 -1
- package/dist-server/server/piecemaker/library/connector-installation.js +224 -0
- package/dist-server/server/piecemaker/library/connector-installation.js.map +1 -0
- package/dist-server/server/piecemaker/library/index.js +11 -1
- package/dist-server/server/piecemaker/library/index.js.map +1 -1
- package/dist-server/server/piecemaker/library/marketplace.js +102 -43
- package/dist-server/server/piecemaker/library/marketplace.js.map +1 -1
- package/dist-server/server/piecemaker/library/migrate.js +8 -1
- package/dist-server/server/piecemaker/library/migrate.js.map +1 -1
- package/dist-server/server/piecemaker/library/provider-agents.js +50 -0
- package/dist-server/server/piecemaker/library/provider-agents.js.map +1 -0
- package/dist-server/server/piecemaker/library/provider-connectors.js +80 -0
- package/dist-server/server/piecemaker/library/provider-connectors.js.map +1 -0
- package/dist-server/server/piecemaker/library/provider-skills.js +11 -2
- package/dist-server/server/piecemaker/library/provider-skills.js.map +1 -1
- package/dist-server/server/piecemaker/library/store.js +125 -35
- package/dist-server/server/piecemaker/library/store.js.map +1 -1
- package/dist-server/server/piecemaker/library/workspace-installation.js +53 -29
- package/dist-server/server/piecemaker/library/workspace-installation.js.map +1 -1
- package/dist-server/server/piecemaker/timesheet/service.js +4 -21
- package/dist-server/server/piecemaker/timesheet/service.js.map +1 -1
- package/package.json +3 -2
- package/scripts/piecemaker/cli/install-command.mjs +35 -9
- package/scripts/piecemaker/cli/lib/config.mjs +1 -0
- package/scripts/piecemaker/cli/lib/piecemaker-anonymizer.mjs +78 -0
- package/scripts/piecemaker/cli/lib/plugins.mjs +2 -0
- package/scripts/piecemaker/cli/lib/pwa.mjs +46 -7
- package/scripts/piecemaker/cli/lib/services.mjs +65 -4
- package/scripts/piecemaker/cli/piecemaker-command.test.mjs +25 -0
- package/scripts/piecemaker/cli/piecemaker.mjs +45 -44
- package/scripts/piecemaker/cli/piecemaker.ps1 +8 -0
- package/scripts/piecemaker/cli/piecemaker.sh +6 -1
- package/server/index.ts +52 -23
- package/server/modules/database/repositories/projects.db.ts +28 -6
- package/server/modules/database/tests/projects.db.integration.test.ts +63 -1
- package/server/modules/plugins/plugins.routes.ts +8 -1
- package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +5 -0
- package/server/piecemaker/anonymizer/client-config.cjs +10 -2
- package/server/piecemaker/anonymizer/dictionary.cjs +6 -72
- package/server/piecemaker/bodacc-search.ts +205 -0
- package/server/piecemaker/company-search.test.ts +26 -0
- package/server/piecemaker/company-search.ts +183 -0
- package/server/piecemaker/index.ts +8 -2
- package/server/piecemaker/knowledge/index.ts +9 -4
- package/server/piecemaker/knowledge/pipeline.ts +89 -57
- package/server/piecemaker/knowledge/routes.ts +2 -4
- package/server/piecemaker/knowledge/scan-jobs.ts +111 -0
- package/server/piecemaker/knowledge/service.ts +18 -33
- package/server/piecemaker/library/connector-installation.ts +248 -0
- package/server/piecemaker/library/index.ts +5 -1
- package/server/piecemaker/library/marketplace.ts +104 -39
- package/server/piecemaker/library/migrate.ts +8 -1
- package/server/piecemaker/library/provider-agents.ts +49 -0
- package/server/piecemaker/library/provider-connectors.ts +89 -0
- package/server/piecemaker/library/provider-skills.ts +10 -1
- package/server/piecemaker/library/store.ts +129 -36
- package/server/piecemaker/library/workspace-installation.ts +54 -29
- package/server/piecemaker/router.cjs +2 -1
- package/server/piecemaker/timesheet/service.ts +4 -16
- package/server/piecemaker/vendor/installer/steps/03-python-gliner.mjs +0 -27
- package/server/piecemaker/vendor/installer/steps/14-mxc-sandbox.mjs +1 -1
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/substitution.cjs +2 -3
- package/server/piecemaker/vendor/piecemaker-plugin/skills/conversion-md/SKILL.md +3 -3
- package/server/piecemaker/vendor/websocket-server/admin-routes.cjs +3 -10
- package/server/piecemaker/vendor/websocket-server/originals-pipeline.cjs +135 -4
- package/server/piecemaker/vendor/websocket-server/process-group.cjs +95 -0
- package/server/piecemaker/vendor/websocket-server/scripts/__pycache__/scan_utils.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/convert_and_scan_pipeline.py +280 -149
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/__pycache__/model_config.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/__pycache__/scanner_worker.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/scanner_worker.py +35 -29
- package/server/piecemaker/vendor/websocket-server/scripts/requirements.txt +0 -5
- package/server/shared/types.ts +1 -0
- package/dist/assets/channel-Dz3eE5vU.js +0 -1
- package/dist/assets/classDiagram-ZZMXUADV-BC1eKV2j.js +0 -1
- package/dist/assets/classDiagram-v2-VYDZK3BY-BC1eKV2j.js +0 -1
- package/dist/assets/index-DhwJpHCq.css +0 -1
- package/dist/assets/stateDiagram-v2-MP3YSRHH-BXuT4zDG.js +0 -1
- package/dist/assets/swimlanesDiagram-VR7AAH4N-B0eYrB4U.js +0 -8
- package/server/piecemaker/vendor/websocket-server/case-instructions.cjs +0 -111
- package/server/piecemaker/vendor/websocket-server/scripts/ADDRESS_ANONYMIZATION.md +0 -216
- package/server/piecemaker/vendor/websocket-server/scripts/DEDUPLICATION_CHANGES.md +0 -134
- package/server/piecemaker/vendor/websocket-server/scripts/GLINER2_README.md +0 -138
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/AUDIT_PERF_QUALITE.md +0 -246
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/MODELE_COREML.md +0 -62
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/__pycache__/coreml_runtime.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/build_coreml.py +0 -118
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/coreml_runtime.py +0 -164
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/presidio-gliner.py +0 -479
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
1
2
|
import crypto from 'node:crypto';
|
|
2
3
|
import fs from 'node:fs';
|
|
3
4
|
import os from 'node:os';
|
|
4
5
|
import path from 'node:path';
|
|
5
|
-
import { spawn } from 'node:child_process';
|
|
6
6
|
|
|
7
7
|
import type { KnowledgeStore } from '../../../plugins/piecemaker-dossier/src/knowledge.js';
|
|
8
|
-
import type { GlinerDocument, GlinerMappingDocument, JsonData } from '../../../plugins/piecemaker-dossier/src/types.js';
|
|
9
8
|
import { persistScanResult, scanResultOperations } from '../../../plugins/piecemaker-dossier/src/scan-result.js';
|
|
9
|
+
import type { GlinerDocument, GlinerMappingDocument, JsonData } from '../../../plugins/piecemaker-dossier/src/types.js';
|
|
10
|
+
|
|
11
|
+
import type { KnowledgeScanProgress } from './scan-jobs.js';
|
|
10
12
|
|
|
11
13
|
type ProjectLookup = {
|
|
12
14
|
getProjectById(projectId: string): { project_id: string; project_path: string } | null;
|
|
@@ -19,6 +21,29 @@ type PipelineOptions = {
|
|
|
19
21
|
pythonPath?: string;
|
|
20
22
|
};
|
|
21
23
|
|
|
24
|
+
type OriginalFile = { path: string; resource?: boolean };
|
|
25
|
+
|
|
26
|
+
type OriginalsPipeline = {
|
|
27
|
+
caseMappingFile(caseRoot: string): string;
|
|
28
|
+
listOriginals(caseRoot: string): Promise<OriginalFile[]>;
|
|
29
|
+
runManagedPythonJob(options: {
|
|
30
|
+
action: 'convert' | 'anonymize';
|
|
31
|
+
script: string;
|
|
32
|
+
args: string[];
|
|
33
|
+
onProgress?: (progress: KnowledgeScanProgress) => void;
|
|
34
|
+
signal?: AbortSignal;
|
|
35
|
+
}): Promise<unknown>;
|
|
36
|
+
writeCaseMapping(caseRoot: string, document: GlinerMappingDocument): unknown;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const require = createRequire(import.meta.url);
|
|
40
|
+
const {
|
|
41
|
+
caseMappingFile,
|
|
42
|
+
listOriginals,
|
|
43
|
+
runManagedPythonJob,
|
|
44
|
+
writeCaseMapping,
|
|
45
|
+
} = require('../vendor/websocket-server/originals-pipeline.cjs') as OriginalsPipeline;
|
|
46
|
+
|
|
22
47
|
const SUPPORTED_EXTENSIONS = new Set(['.pdf', '.docx', '.doc', '.odt', '.rtf', '.txt', '.md', '.png', '.jpg', '.jpeg', '.tif', '.tiff', '.bmp']);
|
|
23
48
|
|
|
24
49
|
function relativeKey(projectPath: string, filePath: string): string {
|
|
@@ -26,15 +51,18 @@ function relativeKey(projectPath: string, filePath: string): string {
|
|
|
26
51
|
return crypto.createHash('sha256').update(relative).digest('hex');
|
|
27
52
|
}
|
|
28
53
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
54
|
+
/** Pièces scannées par défaut : même liste que l'administration, récursive, hors Markdown généré. */
|
|
55
|
+
export async function defaultScanFiles(projectPath: string): Promise<string[]> {
|
|
56
|
+
const root = fs.realpathSync(projectPath);
|
|
57
|
+
const originals = await listOriginals(root);
|
|
58
|
+
return originals
|
|
59
|
+
.filter((file) => !file.resource)
|
|
60
|
+
.map((file) => path.resolve(root, file.path));
|
|
33
61
|
}
|
|
34
62
|
|
|
35
|
-
function safeFiles(projectPath: string, requested: unknown): string[] {
|
|
63
|
+
async function safeFiles(projectPath: string, requested: unknown): Promise<string[]> {
|
|
36
64
|
const root = fs.realpathSync(projectPath);
|
|
37
|
-
const candidates = Array.isArray(requested) && requested.length ? requested :
|
|
65
|
+
const candidates = Array.isArray(requested) && requested.length ? requested : await defaultScanFiles(root);
|
|
38
66
|
return [...new Set(candidates.map((candidate) => {
|
|
39
67
|
const raw = String(candidate || '');
|
|
40
68
|
const resolved = path.resolve(root, raw);
|
|
@@ -45,29 +73,18 @@ function safeFiles(projectPath: string, requested: unknown): string[] {
|
|
|
45
73
|
}))];
|
|
46
74
|
}
|
|
47
75
|
|
|
48
|
-
function runProcess(executable: string, args: string[], cwd: string): Promise<{ stdout: string; stderr: string }> {
|
|
49
|
-
return new Promise((resolve, reject) => {
|
|
50
|
-
const child = spawn(executable, args, { cwd, env: process.env, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
51
|
-
let stdout = '';
|
|
52
|
-
let stderr = '';
|
|
53
|
-
child.stdout.setEncoding('utf8');
|
|
54
|
-
child.stderr.setEncoding('utf8');
|
|
55
|
-
child.stdout.on('data', (chunk) => { stdout += chunk; });
|
|
56
|
-
child.stderr.on('data', (chunk) => { stderr += chunk; });
|
|
57
|
-
child.once('error', reject);
|
|
58
|
-
child.once('close', (code) => {
|
|
59
|
-
if (code === 0) resolve({ stdout, stderr });
|
|
60
|
-
else reject(new Error(stderr.trim() || stdout.trim() || `Pipeline failed with code ${code ?? 1}.`));
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
76
|
function parseObject(filePath: string): JsonData {
|
|
66
|
-
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8').replace(
|
|
77
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8').replace(/^\uFEFF/, '')) as unknown;
|
|
67
78
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error('Invalid pipeline output.');
|
|
68
79
|
return parsed as JsonData;
|
|
69
80
|
}
|
|
70
81
|
|
|
82
|
+
function readDocumentIndex(projectPath: string): JsonData {
|
|
83
|
+
const indexPath = path.join(projectPath, '.piecemaker', 'document-index.json');
|
|
84
|
+
if (!fs.existsSync(indexPath)) return { documents: {} };
|
|
85
|
+
return parseObject(indexPath);
|
|
86
|
+
}
|
|
87
|
+
|
|
71
88
|
function documentsFromIndex(projectPath: string, files: string[], documentIndex: JsonData): GlinerDocument[] {
|
|
72
89
|
const entries = documentIndex.documents && typeof documentIndex.documents === 'object' && !Array.isArray(documentIndex.documents)
|
|
73
90
|
? documentIndex.documents as Record<string, JsonData>
|
|
@@ -128,46 +145,61 @@ function textValue(value: unknown): string {
|
|
|
128
145
|
return typeof value === 'string' ? value.trim() : '';
|
|
129
146
|
}
|
|
130
147
|
|
|
148
|
+
async function withPythonPath<T>(pythonPath: string, run: () => Promise<T>): Promise<T> {
|
|
149
|
+
const previous = process.env.PYTHON_PATH;
|
|
150
|
+
process.env.PYTHON_PATH = pythonPath;
|
|
151
|
+
try {
|
|
152
|
+
return await run();
|
|
153
|
+
} finally {
|
|
154
|
+
if (previous === undefined) delete process.env.PYTHON_PATH;
|
|
155
|
+
else process.env.PYTHON_PATH = previous;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
131
159
|
export function createKnowledgePipeline(options: PipelineOptions) {
|
|
132
160
|
const script = path.join(options.applicationRoot, 'server', 'piecemaker', 'vendor', 'websocket-server', 'scripts', 'convert_and_scan_pipeline.py');
|
|
133
161
|
return {
|
|
134
|
-
async scan(projectId: string, requestedFiles?: unknown) {
|
|
162
|
+
async scan(projectId: string, requestedFiles?: unknown, onProgress?: (progress: KnowledgeScanProgress) => void, signal?: AbortSignal) {
|
|
135
163
|
const project = options.projects.getProjectById(projectId);
|
|
136
164
|
if (!project) throw new Error('Project not found.');
|
|
137
165
|
const projectPath = fs.realpathSync(project.project_path);
|
|
138
|
-
const
|
|
166
|
+
const explicitFiles = Array.isArray(requestedFiles) && requestedFiles.length > 0;
|
|
167
|
+
const files = await safeFiles(projectPath, requestedFiles);
|
|
139
168
|
if (!files.length) throw new Error('No supported file to scan.');
|
|
140
|
-
const
|
|
141
|
-
const
|
|
142
|
-
const stateFile = path.join(temporaryRoot, 'state.json');
|
|
169
|
+
const mappingFile = caseMappingFile(projectPath);
|
|
170
|
+
const stateFile = path.join(projectPath, '.piecemaker', 'anonymization-state.json');
|
|
143
171
|
const outputDirectory = path.join(projectPath, 'Fichiers convertis PieceMaker');
|
|
172
|
+
fs.mkdirSync(path.dirname(mappingFile), { recursive: true });
|
|
173
|
+
fs.mkdirSync(path.dirname(stateFile), { recursive: true });
|
|
144
174
|
fs.mkdirSync(outputDirectory, { recursive: true });
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
175
|
+
fs.writeFileSync(mappingFile, JSON.stringify(mappingSeed(projectId, projectPath, options.store)), { mode: 0o600 });
|
|
176
|
+
const args = [
|
|
177
|
+
...files,
|
|
178
|
+
'-o',
|
|
179
|
+
outputDirectory,
|
|
180
|
+
'--mapping-file',
|
|
181
|
+
mappingFile,
|
|
182
|
+
'--case-root',
|
|
183
|
+
projectPath,
|
|
184
|
+
'--state-file',
|
|
185
|
+
stateFile,
|
|
186
|
+
];
|
|
187
|
+
if (!explicitFiles) args.push('--skip-existing');
|
|
188
|
+
await withPythonPath(pythonExecutable(options.pythonPath), () => runManagedPythonJob({
|
|
189
|
+
action: 'anonymize',
|
|
190
|
+
script,
|
|
191
|
+
args,
|
|
192
|
+
onProgress,
|
|
193
|
+
signal,
|
|
194
|
+
}));
|
|
195
|
+
const mapping = parseObject(mappingFile) as GlinerMappingDocument;
|
|
196
|
+
writeCaseMapping(projectPath, mapping);
|
|
197
|
+
const documents = documentsFromIndex(projectPath, files, readDocumentIndex(projectPath));
|
|
198
|
+
const scanResult = { projectId, mapping, documents };
|
|
199
|
+
const result = explicitFiles
|
|
200
|
+
? options.store.update({ projectId, operations: scanResultOperations(scanResult) })
|
|
201
|
+
: persistScanResult(scanResult, options.store);
|
|
202
|
+
return { ...result, documents: documents.length };
|
|
171
203
|
},
|
|
172
204
|
};
|
|
173
205
|
}
|
|
@@ -17,13 +17,11 @@ export function createKnowledgeRouter(service: KnowledgeService) {
|
|
|
17
17
|
response.status(statusFor(error)).json({ error: error instanceof Error ? error.message : 'Knowledge operation failed.' });
|
|
18
18
|
});
|
|
19
19
|
};
|
|
20
|
-
router.get('/knowledge/overview', (request, response) => respond(() => service.overview(request.query.projectId), response));
|
|
21
|
-
router.get('/knowledge/mapping', (request, response) => respond(() => service.mapping(request.query.projectId), response));
|
|
22
|
-
router.get('/knowledge/chronology', (request, response) => respond(() => service.chronology(request.query.projectId), response));
|
|
23
20
|
router.get('/knowledge/graph', (request, response) => respond(() => service.graph(request.query.projectId), response));
|
|
24
|
-
router.get('/knowledge/agents', (request, response) => respond(() => service.agents(request.query.projectId), response));
|
|
25
21
|
router.post('/knowledge/query', (request, response) => respond(() => service.query(request.body), response));
|
|
26
22
|
router.post('/knowledge/update', (request, response) => respond(() => service.update(request.body), response));
|
|
27
23
|
router.post('/knowledge/scan', (request, response) => respond(() => service.scan(request.body?.projectId, request.body?.files), response));
|
|
24
|
+
router.get('/knowledge/scan/job', (request, response) => respond(() => service.scanJob(request.query.id, request.query.projectId), response));
|
|
25
|
+
router.post('/knowledge/scan/cancel', (request, response) => respond(() => service.cancelScan(request.body?.id, request.body?.projectId), response));
|
|
28
26
|
return router;
|
|
29
27
|
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
export type KnowledgeScanPhase = 'convert' | 'scan' | 'commit';
|
|
4
|
+
|
|
5
|
+
export type KnowledgeScanProgress = {
|
|
6
|
+
phase: KnowledgeScanPhase;
|
|
7
|
+
percent: number;
|
|
8
|
+
processed: number;
|
|
9
|
+
total: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type KnowledgeScanJob = {
|
|
13
|
+
id: string;
|
|
14
|
+
projectId: string;
|
|
15
|
+
source: 'knowledge';
|
|
16
|
+
action: 'anonymize';
|
|
17
|
+
state: 'running' | 'done' | 'error' | 'cancelled';
|
|
18
|
+
phase: KnowledgeScanPhase;
|
|
19
|
+
percent: number;
|
|
20
|
+
processed: number;
|
|
21
|
+
total: number;
|
|
22
|
+
error: string | null;
|
|
23
|
+
startedAt: string;
|
|
24
|
+
finishedAt: string | null;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const COMPLETED_RETENTION_MS = 5 * 60_000;
|
|
28
|
+
|
|
29
|
+
function isPending(job: KnowledgeScanJob): boolean {
|
|
30
|
+
return job.state === 'running';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function createKnowledgeScanJobs() {
|
|
34
|
+
const jobs = new Map<string, KnowledgeScanJob>();
|
|
35
|
+
const controllers = new Map<string, AbortController>();
|
|
36
|
+
|
|
37
|
+
const prune = () => {
|
|
38
|
+
const deadline = Date.now() - COMPLETED_RETENTION_MS;
|
|
39
|
+
for (const [id, job] of jobs) {
|
|
40
|
+
if (isPending(job)) continue;
|
|
41
|
+
if (Date.parse(job.finishedAt || job.startedAt) < deadline) jobs.delete(id);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const runningForProject = (projectId: string): KnowledgeScanJob | null => {
|
|
46
|
+
for (const job of jobs.values()) if (job.projectId === projectId && isPending(job)) return job;
|
|
47
|
+
return null;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
start(projectId: string, run: (report: (progress: KnowledgeScanProgress) => void, signal: AbortSignal) => Promise<unknown>): KnowledgeScanJob {
|
|
52
|
+
prune();
|
|
53
|
+
const running = runningForProject(projectId);
|
|
54
|
+
if (running) return running;
|
|
55
|
+
const job: KnowledgeScanJob = {
|
|
56
|
+
id: crypto.randomUUID(),
|
|
57
|
+
projectId,
|
|
58
|
+
source: 'knowledge',
|
|
59
|
+
action: 'anonymize',
|
|
60
|
+
state: 'running',
|
|
61
|
+
phase: 'convert',
|
|
62
|
+
percent: 0,
|
|
63
|
+
processed: 0,
|
|
64
|
+
total: 0,
|
|
65
|
+
error: null,
|
|
66
|
+
startedAt: new Date().toISOString(),
|
|
67
|
+
finishedAt: null,
|
|
68
|
+
};
|
|
69
|
+
jobs.set(job.id, job);
|
|
70
|
+
const report = (progress: KnowledgeScanProgress) => {
|
|
71
|
+
if (job.state !== 'running') return;
|
|
72
|
+
job.phase = progress.phase;
|
|
73
|
+
job.percent = Math.max(job.percent, Math.min(100, progress.percent));
|
|
74
|
+
job.processed = progress.processed;
|
|
75
|
+
job.total = progress.total || job.total;
|
|
76
|
+
};
|
|
77
|
+
const controller = new AbortController();
|
|
78
|
+
controllers.set(job.id, controller);
|
|
79
|
+
run(report, controller.signal).then(() => {
|
|
80
|
+
if (job.state !== 'running') return;
|
|
81
|
+
job.state = 'done';
|
|
82
|
+
job.phase = 'commit';
|
|
83
|
+
job.percent = 100;
|
|
84
|
+
}, (error: unknown) => {
|
|
85
|
+
if (job.state !== 'running') return;
|
|
86
|
+
job.state = 'error';
|
|
87
|
+
job.error = error instanceof Error ? error.message : String(error);
|
|
88
|
+
}).finally(() => {
|
|
89
|
+
controllers.delete(job.id);
|
|
90
|
+
job.finishedAt = job.finishedAt || new Date().toISOString();
|
|
91
|
+
});
|
|
92
|
+
return job;
|
|
93
|
+
},
|
|
94
|
+
get(id: unknown): KnowledgeScanJob | null {
|
|
95
|
+
prune();
|
|
96
|
+
return typeof id === 'string' ? jobs.get(id) || null : null;
|
|
97
|
+
},
|
|
98
|
+
runningForProject(projectId: string): KnowledgeScanJob | null {
|
|
99
|
+
prune();
|
|
100
|
+
return runningForProject(projectId);
|
|
101
|
+
},
|
|
102
|
+
cancel(job: KnowledgeScanJob | null): KnowledgeScanJob | null {
|
|
103
|
+
if (!job || job.state !== 'running') return job;
|
|
104
|
+
job.state = 'cancelled';
|
|
105
|
+
job.finishedAt = new Date().toISOString();
|
|
106
|
+
controllers.get(job.id)?.abort();
|
|
107
|
+
controllers.delete(job.id);
|
|
108
|
+
return job;
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
|
|
4
1
|
import type { KnowledgeStore } from '../../../plugins/piecemaker-dossier/src/knowledge.js';
|
|
5
2
|
import { exclusionNodeOperation } from '../../../plugins/piecemaker-dossier/src/scan-result.js';
|
|
6
3
|
import type { KnowledgeQueryInput, KnowledgeUpdateInput } from '../../../plugins/piecemaker-dossier/src/types.js';
|
|
4
|
+
|
|
7
5
|
import { legacyExclusions } from './pipeline.js';
|
|
8
6
|
import type { createKnowledgePipeline } from './pipeline.js';
|
|
7
|
+
import { createKnowledgeScanJobs } from './scan-jobs.js';
|
|
9
8
|
|
|
10
9
|
type ProjectLookup = {
|
|
11
10
|
getProjectById(projectId: string): { project_id: string; project_path: string } | null;
|
|
@@ -17,6 +16,7 @@ function projectId(value: unknown): string {
|
|
|
17
16
|
}
|
|
18
17
|
|
|
19
18
|
export function createKnowledgeService(store: KnowledgeStore, projects: ProjectLookup, pipeline: ReturnType<typeof createKnowledgePipeline>) {
|
|
19
|
+
const scanJobs = createKnowledgeScanJobs();
|
|
20
20
|
const ensureProject = (value: unknown): string => {
|
|
21
21
|
const id = projectId(value);
|
|
22
22
|
if (!projects.getProjectById(id)) throw new Error('Project not found.');
|
|
@@ -39,42 +39,27 @@ export function createKnowledgeService(store: KnowledgeStore, projects: ProjectL
|
|
|
39
39
|
update(input: KnowledgeUpdateInput) {
|
|
40
40
|
return store.update({ ...input, projectId: ensureProject(input.projectId) });
|
|
41
41
|
},
|
|
42
|
-
overview(value: unknown) {
|
|
43
|
-
const id = ensureProject(value);
|
|
44
|
-
const nodes = snapshot(id).nodes;
|
|
45
|
-
const counts = nodes.reduce<Record<string, number>>((result, node) => {
|
|
46
|
-
result[node.kind] = (result[node.kind] || 0) + 1;
|
|
47
|
-
return result;
|
|
48
|
-
}, {});
|
|
49
|
-
return { projectId: id, counts, total: nodes.length };
|
|
50
|
-
},
|
|
51
|
-
mapping(value: unknown) {
|
|
52
|
-
const id = ensureProject(value);
|
|
53
|
-
const current = snapshot(id);
|
|
54
|
-
return { projectId: id, nodes: current.nodes.filter((node) => node.kind !== 'document'), mappings: current.mappings, exclusions: current.exclusions || [] };
|
|
55
|
-
},
|
|
56
|
-
chronology(value: unknown) {
|
|
57
|
-
const id = ensureProject(value);
|
|
58
|
-
const current = snapshot(id);
|
|
59
|
-
return { projectId: id, documents: current.nodes.filter((node) => node.kind === 'document'), links: current.links.filter((link) => link.relation === 'mentions') };
|
|
60
|
-
},
|
|
61
42
|
graph(value: unknown) {
|
|
62
43
|
const id = ensureProject(value);
|
|
63
44
|
return snapshot(id);
|
|
64
45
|
},
|
|
65
|
-
|
|
46
|
+
scan(value: unknown, files?: unknown) {
|
|
66
47
|
const id = ensureProject(value);
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return { projectId: id, content: fs.readFileSync(real, 'utf8').slice(0, 2_000_000), exists: true };
|
|
48
|
+
return {
|
|
49
|
+
job: scanJobs.start(id, async (report, signal) => {
|
|
50
|
+
const result = await pipeline.scan(id, files, report, signal);
|
|
51
|
+
store.markAnonymizationComplete(id);
|
|
52
|
+
return result;
|
|
53
|
+
}),
|
|
54
|
+
};
|
|
75
55
|
},
|
|
76
|
-
|
|
77
|
-
|
|
56
|
+
scanJob(jobId: unknown, value?: unknown) {
|
|
57
|
+
const job = scanJobs.get(jobId) || (value ? scanJobs.runningForProject(ensureProject(value)) : null);
|
|
58
|
+
return { job };
|
|
59
|
+
},
|
|
60
|
+
cancelScan(jobId: unknown, value?: unknown) {
|
|
61
|
+
const job = scanJobs.get(jobId) || (value ? scanJobs.runningForProject(ensureProject(value)) : null);
|
|
62
|
+
return { job: scanJobs.cancel(job) };
|
|
78
63
|
},
|
|
79
64
|
};
|
|
80
65
|
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
const TOML = require('@iarna/toml') as {
|
|
7
|
+
parse(value: string): Record<string, unknown>;
|
|
8
|
+
stringify(value: Record<string, unknown>): string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type LibraryConnectorConfig = {
|
|
12
|
+
transport: 'http' | 'stdio';
|
|
13
|
+
url?: string;
|
|
14
|
+
command?: string;
|
|
15
|
+
args?: string[];
|
|
16
|
+
env?: Record<string, string>;
|
|
17
|
+
headers?: Record<string, string>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type FileSnapshot = { filePath: string; previous: string | null };
|
|
21
|
+
|
|
22
|
+
function readJson(filePath: string) {
|
|
23
|
+
if (!fs.existsSync(filePath)) return {};
|
|
24
|
+
const value = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
25
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function readToml(filePath: string) {
|
|
29
|
+
if (!fs.existsSync(filePath)) return {};
|
|
30
|
+
const value = TOML.parse(fs.readFileSync(filePath, 'utf8'));
|
|
31
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function writeJson(filePath: string, value: Record<string, unknown>) {
|
|
35
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
|
|
36
|
+
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function writeToml(filePath: string, value: Record<string, unknown>) {
|
|
40
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
|
|
41
|
+
fs.writeFileSync(filePath, TOML.stringify(value), { mode: 0o600 });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function snapshotFile(filePath: string): FileSnapshot {
|
|
45
|
+
return { filePath, previous: fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : null };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function restoreFile({ filePath, previous }: FileSnapshot) {
|
|
49
|
+
if (previous === null) {
|
|
50
|
+
fs.rmSync(filePath, { force: true });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
|
|
54
|
+
fs.writeFileSync(filePath, previous, { mode: 0o600 });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function assertWorkspaceFile(workspace: string, filePath: string) {
|
|
58
|
+
const resolvedWorkspace = fs.realpathSync(workspace);
|
|
59
|
+
const parent = path.dirname(filePath);
|
|
60
|
+
if (fs.existsSync(parent)) {
|
|
61
|
+
const resolvedParent = fs.realpathSync(parent);
|
|
62
|
+
if (resolvedParent !== resolvedWorkspace && !resolvedParent.startsWith(resolvedWorkspace + path.sep)) {
|
|
63
|
+
throw new Error(`Répertoire de composants hors du dossier : ${parent}`);
|
|
64
|
+
}
|
|
65
|
+
} else if (!parent.startsWith(resolvedWorkspace + path.sep) && parent !== resolvedWorkspace) {
|
|
66
|
+
throw new Error(`Répertoire de composants hors du dossier : ${parent}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function objectRecord(value: unknown) {
|
|
71
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function claudeServer(config: LibraryConnectorConfig) {
|
|
75
|
+
if (config.transport === 'http') {
|
|
76
|
+
return {
|
|
77
|
+
type: 'http',
|
|
78
|
+
url: config.url,
|
|
79
|
+
...(config.headers && Object.keys(config.headers).length ? { headers: config.headers } : {}),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
type: 'stdio',
|
|
84
|
+
command: config.command,
|
|
85
|
+
args: config.args ?? [],
|
|
86
|
+
...(config.env && Object.keys(config.env).length ? { env: config.env } : {}),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function cursorServer(config: LibraryConnectorConfig, enabled: boolean) {
|
|
91
|
+
const server = config.transport === 'http'
|
|
92
|
+
? { url: config.url, ...(config.headers && Object.keys(config.headers).length ? { headers: config.headers } : {}) }
|
|
93
|
+
: { command: config.command, args: config.args ?? [], ...(config.env && Object.keys(config.env).length ? { env: config.env } : {}) };
|
|
94
|
+
return enabled ? server : { ...server, disabled: true };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function codexServer(config: LibraryConnectorConfig, enabled: boolean) {
|
|
98
|
+
if (config.transport === 'http') {
|
|
99
|
+
return {
|
|
100
|
+
url: config.url,
|
|
101
|
+
enabled,
|
|
102
|
+
...(config.headers && Object.keys(config.headers).length ? { http_headers: config.headers } : {}),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
command: config.command,
|
|
107
|
+
args: config.args ?? [],
|
|
108
|
+
enabled,
|
|
109
|
+
...(config.env && Object.keys(config.env).length ? { env: config.env } : {}),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function grokServer(config: LibraryConnectorConfig, enabled: boolean) {
|
|
114
|
+
if (config.transport === 'http') {
|
|
115
|
+
return {
|
|
116
|
+
url: config.url,
|
|
117
|
+
enabled,
|
|
118
|
+
...(config.headers && Object.keys(config.headers).length ? { headers: config.headers } : {}),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
command: config.command,
|
|
123
|
+
args: config.args ?? [],
|
|
124
|
+
enabled,
|
|
125
|
+
...(config.env && Object.keys(config.env).length ? { env: config.env } : {}),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function opencodeServer(config: LibraryConnectorConfig, enabled: boolean) {
|
|
130
|
+
if (config.transport === 'http') {
|
|
131
|
+
return {
|
|
132
|
+
type: 'remote',
|
|
133
|
+
url: config.url,
|
|
134
|
+
enabled,
|
|
135
|
+
...(config.headers && Object.keys(config.headers).length ? { headers: config.headers } : {}),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
type: 'local',
|
|
140
|
+
command: [config.command, ...(config.args ?? [])].filter(Boolean),
|
|
141
|
+
enabled,
|
|
142
|
+
...(config.env && Object.keys(config.env).length ? { environment: config.env } : {}),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function opencodeConfigPath(workspace: string) {
|
|
147
|
+
const jsonPath = path.join(workspace, 'opencode.json');
|
|
148
|
+
const jsoncPath = path.join(workspace, 'opencode.jsonc');
|
|
149
|
+
if (fs.existsSync(jsonPath)) return jsonPath;
|
|
150
|
+
if (fs.existsSync(jsoncPath)) return jsoncPath;
|
|
151
|
+
return jsonPath;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function parseConnectorConfig(content: string): LibraryConnectorConfig {
|
|
155
|
+
const parsed = JSON.parse(content) as LibraryConnectorConfig;
|
|
156
|
+
if (parsed?.transport !== 'http' && parsed?.transport !== 'stdio') throw new Error('Connecteur invalide.');
|
|
157
|
+
if (parsed.transport === 'http' && typeof parsed.url !== 'string') throw new Error('Connecteur invalide.');
|
|
158
|
+
if (parsed.transport === 'stdio' && typeof parsed.command !== 'string') throw new Error('Connecteur invalide.');
|
|
159
|
+
return parsed;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function normalizeProviderConnector(raw: unknown): LibraryConnectorConfig | null {
|
|
163
|
+
const config = objectRecord(raw);
|
|
164
|
+
if (typeof config.url === 'string' || config.type === 'http' || config.type === 'remote' || config.type === 'sse') {
|
|
165
|
+
const url = typeof config.url === 'string' ? config.url : '';
|
|
166
|
+
if (!url) return null;
|
|
167
|
+
const headers = { ...objectRecord(config.headers), ...objectRecord(config.http_headers) };
|
|
168
|
+
return {
|
|
169
|
+
transport: 'http',
|
|
170
|
+
url,
|
|
171
|
+
headers: Object.fromEntries(Object.entries(headers).filter((entry): entry is [string, string] => typeof entry[1] === 'string')),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const commandParts = typeof config.command === 'string'
|
|
175
|
+
? [config.command, ...(Array.isArray(config.args) ? config.args.filter((value): value is string => typeof value === 'string') : [])]
|
|
176
|
+
: Array.isArray(config.command) ? config.command.filter((value): value is string => typeof value === 'string') : [];
|
|
177
|
+
if (!commandParts[0]) return null;
|
|
178
|
+
const env = { ...objectRecord(config.env), ...objectRecord(config.environment) };
|
|
179
|
+
return {
|
|
180
|
+
transport: 'stdio',
|
|
181
|
+
command: commandParts[0],
|
|
182
|
+
args: commandParts.slice(1),
|
|
183
|
+
env: Object.fromEntries(Object.entries(env).filter((entry): entry is [string, string] => typeof entry[1] === 'string')),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function prepareConnectorInstallation(workspace: string, name: string, config: LibraryConnectorConfig, enabled: boolean) {
|
|
188
|
+
const mcpJsonPath = path.join(workspace, '.mcp.json');
|
|
189
|
+
const claudeSettingsPath = path.join(workspace, '.claude', 'settings.local.json');
|
|
190
|
+
const codexPath = path.join(workspace, '.codex', 'config.toml');
|
|
191
|
+
const cursorPath = path.join(workspace, '.cursor', 'mcp.json');
|
|
192
|
+
const opencodePath = opencodeConfigPath(workspace);
|
|
193
|
+
const grokPath = path.join(workspace, '.grok', 'config.toml');
|
|
194
|
+
for (const filePath of [mcpJsonPath, claudeSettingsPath, codexPath, cursorPath, opencodePath, grokPath]) {
|
|
195
|
+
assertWorkspaceFile(workspace, filePath);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const snapshots = [mcpJsonPath, claudeSettingsPath, codexPath, cursorPath, opencodePath, grokPath].map(snapshotFile);
|
|
199
|
+
|
|
200
|
+
return () => {
|
|
201
|
+
try {
|
|
202
|
+
const mcpJson = readJson(mcpJsonPath);
|
|
203
|
+
const mcpServers = objectRecord(mcpJson.mcpServers);
|
|
204
|
+
if (enabled) mcpServers[name] = claudeServer(config);
|
|
205
|
+
else delete mcpServers[name];
|
|
206
|
+
mcpJson.mcpServers = mcpServers;
|
|
207
|
+
writeJson(mcpJsonPath, mcpJson);
|
|
208
|
+
|
|
209
|
+
const claudeSettings = readJson(claudeSettingsPath);
|
|
210
|
+
const disabled = Array.isArray(claudeSettings.disabledMcpServers)
|
|
211
|
+
? claudeSettings.disabledMcpServers.filter((entry) => entry !== name)
|
|
212
|
+
: [];
|
|
213
|
+
claudeSettings.disabledMcpServers = enabled ? disabled : [...disabled, name];
|
|
214
|
+
writeJson(claudeSettingsPath, claudeSettings);
|
|
215
|
+
|
|
216
|
+
const codexConfig = readToml(codexPath);
|
|
217
|
+
const codexServers = objectRecord(codexConfig.mcp_servers);
|
|
218
|
+
codexServers[name] = enabled
|
|
219
|
+
? codexServer(config, true)
|
|
220
|
+
: { ...objectRecord(codexServers[name]), ...codexServer(config, false), enabled: false };
|
|
221
|
+
codexConfig.mcp_servers = codexServers;
|
|
222
|
+
writeToml(codexPath, codexConfig);
|
|
223
|
+
|
|
224
|
+
const cursorConfig = readJson(cursorPath);
|
|
225
|
+
const cursorServers = objectRecord(cursorConfig.mcpServers);
|
|
226
|
+
cursorServers[name] = cursorServer(config, enabled);
|
|
227
|
+
cursorConfig.mcpServers = cursorServers;
|
|
228
|
+
writeJson(cursorPath, cursorConfig);
|
|
229
|
+
|
|
230
|
+
const opencodeConfig = readJson(opencodePath);
|
|
231
|
+
const opencodeServers = objectRecord(opencodeConfig.mcp);
|
|
232
|
+
opencodeServers[name] = opencodeServer(config, enabled);
|
|
233
|
+
opencodeConfig.mcp = opencodeServers;
|
|
234
|
+
writeJson(opencodePath, opencodeConfig);
|
|
235
|
+
|
|
236
|
+
const grokConfig = readToml(grokPath);
|
|
237
|
+
const grokServers = objectRecord(grokConfig.mcp_servers);
|
|
238
|
+
grokServers[name] = enabled
|
|
239
|
+
? grokServer(config, true)
|
|
240
|
+
: { ...objectRecord(grokServers[name]), ...grokServer(config, false), enabled: false };
|
|
241
|
+
grokConfig.mcp_servers = grokServers;
|
|
242
|
+
writeToml(grokPath, grokConfig);
|
|
243
|
+
} catch (error) {
|
|
244
|
+
for (const snapshot of snapshots.reverse()) restoreFile(snapshot);
|
|
245
|
+
throw error;
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
}
|