@piecemaker-legal/piecemaker 1.1.0 → 2.0.1

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 (144) hide show
  1. package/dist/assets/{abnfDiagram-VCTEODGH-C-dTfD4s.js → abnfDiagram-VCTEODGH-C2JDmlZx.js} +1 -1
  2. package/dist/assets/{arc-BwDrelvt.js → arc-XhSg5Mrj.js} +1 -1
  3. package/dist/assets/{architectureDiagram-5GKGNRK7-CuViaxCV.js → architectureDiagram-5GKGNRK7-DKREuXxk.js} +1 -1
  4. package/dist/assets/{blockDiagram-I7D4REHJ-CODUNfgC.js → blockDiagram-I7D4REHJ-CNK0Tp5d.js} +1 -1
  5. package/dist/assets/{c4Diagram-7LVT6UL2-CiQtyYDd.js → c4Diagram-7LVT6UL2-C8fJzWVp.js} +1 -1
  6. package/dist/assets/channel-DO5hoFdU.js +1 -0
  7. package/dist/assets/{chunk-2Q5K7J3B-DZjjPgAs.js → chunk-2Q5K7J3B-DlE8nLJo.js} +1 -1
  8. package/dist/assets/{chunk-5VM5RSS4-C-SPnj5I.js → chunk-5VM5RSS4-CSm-2TZ4.js} +1 -1
  9. package/dist/assets/{chunk-F27PBJKO-D5b0-fye.js → chunk-F27PBJKO-CSgnXT2c.js} +1 -1
  10. package/dist/assets/{chunk-IMKFNOWR-CWflUKd3.js → chunk-IMKFNOWR-C6bkt6C1.js} +1 -1
  11. package/dist/assets/{chunk-JWPE2WC7-DFltof8Z.js → chunk-JWPE2WC7-jUR52MBO.js} +1 -1
  12. package/dist/assets/{chunk-POPQ4Y6H-moIw94hw.js → chunk-POPQ4Y6H-D0UAL7O7.js} +1 -1
  13. package/dist/assets/{chunk-SVP7TREG--H3pige-.js → chunk-SVP7TREG-C9dtOEii.js} +1 -1
  14. package/dist/assets/{chunk-TICWLB2K-BfujErBt.js → chunk-TICWLB2K-BEzWKT39.js} +1 -1
  15. package/dist/assets/{chunk-XXDRQBXY-C5jFgw8p.js → chunk-XXDRQBXY-uD1oklVY.js} +1 -1
  16. package/dist/assets/classDiagram-ZZMXUADV-BsQtwznI.js +1 -0
  17. package/dist/assets/classDiagram-v2-VYDZK3BY-BsQtwznI.js +1 -0
  18. package/dist/assets/{cose-bilkent-JH36ORCC-CsT3oVRI.js → cose-bilkent-JH36ORCC-aGjlSdMe.js} +1 -1
  19. package/dist/assets/{cynefin-OW5HDTMX-gdpEHNLr.js → cynefin-OW5HDTMX-D3G4OYOn.js} +1 -1
  20. package/dist/assets/{cynefinDiagram-5FMLGOSQ-v-JmI3Zj.js → cynefinDiagram-5FMLGOSQ-9swRDUnu.js} +1 -1
  21. package/dist/assets/{dagre-GXQ25YYZ-BvJ_SGF3.js → dagre-GXQ25YYZ-lvaAGdqY.js} +1 -1
  22. package/dist/assets/{diagram-S7CK7UJ4-B7EHfyrx.js → diagram-S7CK7UJ4-BaZ6YIkR.js} +1 -1
  23. package/dist/assets/{diagram-UQ7AKVKN-DX5RvUaF.js → diagram-UQ7AKVKN-COJ2Gb38.js} +1 -1
  24. package/dist/assets/{diagram-VSXAHHWV-D_PJ-E3G.js → diagram-VSXAHHWV-CYZEkbzP.js} +1 -1
  25. package/dist/assets/{diagram-VX7I27RA-CAtayL03.js → diagram-VX7I27RA-fFeQJ4ae.js} +1 -1
  26. package/dist/assets/{diagram-Z3DM3KII-BhCCqDLU.js → diagram-Z3DM3KII-B7knszDV.js} +1 -1
  27. package/dist/assets/{ebnfDiagram-PWID7BFC-BCb926kC.js → ebnfDiagram-PWID7BFC-Dk0l329Y.js} +1 -1
  28. package/dist/assets/{erDiagram-RLTQ6QDP-5Fw1sblj.js → erDiagram-RLTQ6QDP-CtpYxui8.js} +1 -1
  29. package/dist/assets/{flowDiagram-HODETNUW-DDUdOLnK.js → flowDiagram-HODETNUW-aVWueRJD.js} +1 -1
  30. package/dist/assets/{ganttDiagram-EL5Y4UJY-B4IG1idB.js → ganttDiagram-EL5Y4UJY-DfrX-B7p.js} +1 -1
  31. package/dist/assets/{gitGraphDiagram-WWUBYQGX-S6wvUCsC.js → gitGraphDiagram-WWUBYQGX-BFB1ZPF3.js} +1 -1
  32. package/dist/assets/{index-CcC0QCGH.js → index-DqRygn9E.js} +170 -169
  33. package/dist/assets/index-Ds4HaAjz.css +1 -0
  34. package/dist/assets/{infoDiagram-27XIBGKW-BNv6fEro.js → infoDiagram-27XIBGKW-CuYkzm43.js} +1 -1
  35. package/dist/assets/{ishikawaDiagram-5VMMS53U-BJZwt-TZ.js → ishikawaDiagram-5VMMS53U-EMiw8Akh.js} +1 -1
  36. package/dist/assets/{journeyDiagram-3NMN7TZE-BAGhL9Uv.js → journeyDiagram-3NMN7TZE-DCoLV1f8.js} +1 -1
  37. package/dist/assets/{kanban-definition-UXKFOSKX-DmoLE6ku.js → kanban-definition-UXKFOSKX-CyA4Yinu.js} +1 -1
  38. package/dist/assets/{layout-ALipSwVQ.js → layout-BlaB0grA.js} +1 -1
  39. package/dist/assets/{linear-BJkOgP0g.js → linear-DLX3dJDH.js} +1 -1
  40. package/dist/assets/{mermaid.core-C20iPvcV.js → mermaid.core-Cy51CT89.js} +6 -6
  41. package/dist/assets/{mindmap-definition-YA3MSWOX-CSI558Pw.js → mindmap-definition-YA3MSWOX-OBXjfL7I.js} +1 -1
  42. package/dist/assets/{pegDiagram-XKGWAZYB-DFtdseIb.js → pegDiagram-XKGWAZYB-C_BJjUh1.js} +1 -1
  43. package/dist/assets/{pieDiagram-E7YTZNPT-ByPKZBBy.js → pieDiagram-E7YTZNPT-jIpbr3Fi.js} +1 -1
  44. package/dist/assets/{quadrantDiagram-AXDQQJYC-tH7z7eR2.js → quadrantDiagram-AXDQQJYC-C1A15xI5.js} +1 -1
  45. package/dist/assets/{railroadDiagram-O6MQD6OU-D7ZdvS0P.js → railroadDiagram-O6MQD6OU-Rslzn1pI.js} +1 -1
  46. package/dist/assets/{requirementDiagram-BXWQKSXE-nSTqp6wv.js → requirementDiagram-BXWQKSXE-LlfURO5-.js} +1 -1
  47. package/dist/assets/{sankeyDiagram-P5KCCOFB-Bg3ivRCq.js → sankeyDiagram-P5KCCOFB-B5BPF4-O.js} +1 -1
  48. package/dist/assets/{sequenceDiagram-WJ2MYXX4-BFs4BVKR.js → sequenceDiagram-WJ2MYXX4-orYZe9zX.js} +1 -1
  49. package/dist/assets/{sizeCapture-INFHLROL-DuSN3zyt.js → sizeCapture-INFHLROL-Co-K5Cfb.js} +1 -1
  50. package/dist/assets/{stateDiagram-D77RDMKH---hK4wFR.js → stateDiagram-D77RDMKH-COEx1aH2.js} +1 -1
  51. package/dist/assets/{stateDiagram-v2-MP3YSRHH-DxBEemHS.js → stateDiagram-v2-MP3YSRHH-DaeTZvmy.js} +1 -1
  52. package/dist/assets/{swimlanes-42K2YHIH-C6RXO1Rv.js → swimlanes-42K2YHIH-BHQaDhfZ.js} +1 -1
  53. package/dist/assets/swimlanesDiagram-VR7AAH4N-BHW8q5M5.js +8 -0
  54. package/dist/assets/{timeline-definition-24CTP7MA-Ci0Toxgn.js → timeline-definition-24CTP7MA-COqCRwGX.js} +1 -1
  55. package/dist/assets/{vennDiagram-4TSXK5OY-CKF_VmKX.js → vennDiagram-4TSXK5OY-nym5XSnr.js} +1 -1
  56. package/dist/assets/{wardleyDiagram-VM6X3IG4-Xao4cR_d.js → wardleyDiagram-VM6X3IG4-DEPPey_W.js} +1 -1
  57. package/dist/assets/{xychartDiagram-S5SC5T6Z-BAZUt04h.js → xychartDiagram-S5SC5T6Z-uSJ404kL.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/modules/cli/desktop-shortcut.js +80 -0
  63. package/dist-server/server/modules/cli/desktop-shortcut.js.map +1 -0
  64. package/dist-server/server/modules/cli/pwa-launcher.js +27 -4
  65. package/dist-server/server/modules/cli/pwa-launcher.js.map +1 -1
  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 +3 -0
  86. package/scripts/piecemaker/build-app-icon.py +88 -0
  87. package/scripts/piecemaker/cli/lib/app-window.mjs +47 -0
  88. package/scripts/piecemaker/cli/lib/piecemaker-anonymizer.mjs +2 -2
  89. package/scripts/piecemaker/cli/lib/pwa.mjs +10 -13
  90. package/scripts/piecemaker/cli/piecemaker.mjs +15 -1
  91. package/scripts/piecemaker/hooks/proxy-guard.mjs +19 -54
  92. package/scripts/release/assert-piecemaker-version.mjs +25 -0
  93. package/server/index.ts +4 -1
  94. package/server/modules/cli/desktop-shortcut.ts +89 -0
  95. package/server/modules/cli/pwa-launcher.ts +29 -4
  96. package/server/piecemaker/anonymizer/client-config.cjs +45 -162
  97. package/server/piecemaker/anonymizer/hudsucker-fixture.cjs +127 -0
  98. package/server/piecemaker/anonymizer/hudsucker-proxy/Cargo.lock +2396 -0
  99. package/server/piecemaker/anonymizer/hudsucker-proxy/Cargo.toml +14 -0
  100. package/server/piecemaker/anonymizer/hudsucker-proxy/src/main.rs +501 -0
  101. package/server/piecemaker/anonymizer/providers.cjs +14 -135
  102. package/server/piecemaker/anonymizer/rewriter-bridge.cjs +221 -0
  103. package/server/piecemaker/anonymizer/service.cjs +144 -28
  104. package/server/piecemaker/index.ts +8 -3
  105. package/server/piecemaker/knowledge/index.ts +14 -6
  106. package/server/piecemaker/knowledge/local-routes.ts +72 -0
  107. package/server/piecemaker/knowledge/scan-jobs.ts +4 -1
  108. package/server/piecemaker/library/index.ts +14 -34
  109. package/server/piecemaker/library/store.ts +11 -3
  110. package/server/piecemaker/library/workspace-installation.ts +20 -3
  111. package/server/piecemaker/timesheet/service.ts +9 -7
  112. package/server/piecemaker/timesheet/store.ts +5 -3
  113. package/server/piecemaker/vendor/installer/assets/claude-hooks/piecemaker-guard-secrets.mjs +0 -1
  114. package/server/piecemaker/vendor/installer/assets/piecemaker.icns +0 -0
  115. package/server/piecemaker/vendor/installer/assets/piecemaker.ico +0 -0
  116. package/server/piecemaker/vendor/installer/bin/piecemaker.mjs +56 -206
  117. package/server/piecemaker/vendor/installer/lib/commandes.mjs +2 -4
  118. package/server/piecemaker/vendor/installer/lib/conversion-client.mjs +74 -0
  119. package/server/piecemaker/vendor/installer/lib/service.mjs +4 -358
  120. package/server/piecemaker/vendor/installer/lib/state.mjs +0 -6
  121. package/server/piecemaker/vendor/installer/steps/03-python-gliner.mjs +4 -4
  122. package/server/piecemaker/vendor/installer/steps/09-claude-assets.mjs +0 -31
  123. package/server/piecemaker/vendor/piecemaker-plugin/scripts/statusline.mjs +3 -4
  124. package/server/piecemaker/vendor/websocket-server/admin-routes.cjs +1 -49
  125. package/server/piecemaker/vendor/websocket-server/claude-hooks.cjs +14 -12
  126. package/server/piecemaker/vendor/websocket-server/originals-pipeline.cjs +17 -476
  127. package/server/piecemaker/vendor/websocket-server/scripts/convert_and_scan_pipeline.py +2 -2
  128. package/server/piecemaker/vendor/websocket-server/scripts/mineru_improved_1.py +11 -2
  129. package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/scanner_worker.py +13 -37
  130. package/server/piecemaker/vendor/websocket-server/scripts/requirements.txt +1 -7
  131. package/server/piecemaker/vendor/websocket-server/scripts/warmup.py +14 -85
  132. package/dist/assets/channel-DIjrikYC.js +0 -1
  133. package/dist/assets/classDiagram-ZZMXUADV-Yagpznfi.js +0 -1
  134. package/dist/assets/classDiagram-v2-VYDZK3BY-Yagpznfi.js +0 -1
  135. package/dist/assets/index-D_Dc_nQv.css +0 -1
  136. package/dist/assets/swimlanesDiagram-VR7AAH4N-BC3fwmoQ.js +0 -8
  137. package/server/piecemaker/anonymizer/proxy.cjs +0 -365
  138. package/server/piecemaker/vendor/installer/assets/PieceMakerApp.swift +0 -189
  139. package/server/piecemaker/vendor/installer/lib/desktop-launcher.mjs +0 -305
  140. package/server/piecemaker/vendor/installer/steps/05-certificats.mjs +0 -171
  141. package/server/piecemaker/vendor/installer/steps/14-mxc-sandbox.mjs +0 -271
  142. package/server/piecemaker/vendor/installer/steps/15-pwa-desktop.mjs +0 -64
  143. package/server/piecemaker/vendor/installer/templates/root-CLAUDE.md +0 -56
  144. package/server/piecemaker/vendor/installer/templates/workspace-CLAUDE.md +0 -52
@@ -11,7 +11,7 @@ import { startRequiredAnonymizer } from './anonymizer/lifecycle.js';
11
11
  import { createCitationStore } from './harness/citation-store.js';
12
12
  import { installChatCitationHarness } from './harness/chat-harness.js';
13
13
  import { createCitationsRouter } from './harness/citations.routes.js';
14
- import { startLibraryBackend, installLibraryRuntime } from './library/index.js';
14
+ import { openLibrary, installLibraryRuntime } from './library/index.js';
15
15
  import { createKnowledgeBackend } from './knowledge/index.js';
16
16
  import { createTimesheetBackend } from './timesheet/index.js';
17
17
  import { createCompanySearchRouter } from './company-search.js';
@@ -49,17 +49,22 @@ const anonymizer = createAnonymizerService({ homeDir: piecemakerHome(), required
49
49
  const ensureProxy = await startRequiredAnonymizer(anonymizer);
50
50
  const citations = createCitationStore(piecemakerHome());
51
51
  installChatCitationHarness({ runtime: providerRuntimeService, sessions: sessionsService, store: citations, ensureProxy });
52
- const library = await startLibraryBackend(piecemakerHome(), applicationRoot);
53
- installLibraryRuntime(providerRuntimeService, sessionsService, library);
52
+ const library = await openLibrary(piecemakerHome(), applicationRoot);
53
+ installLibraryRuntime(providerRuntimeService, sessionsService, library.store);
54
54
  const timesheet = createTimesheetBackend(piecemakerHome());
55
55
  const knowledge = createKnowledgeBackend(applicationRoot);
56
56
 
57
+ export function createPieceMakerLocalRouter() {
58
+ return knowledge.localRouter;
59
+ }
60
+
57
61
  export function createPieceMakerRouter(options: { getRuntimeStatus?: () => PieceMakerRuntimeStatus } = {}) {
58
62
  const router = vendor.createPieceMakerRouter({ ...options, anonymizer });
59
63
  router.use(createCitationsRouter(citations, (id) => {
60
64
  try { sessionsService.getSessionDetailsById(id); return true; } catch { return false; }
61
65
  }));
62
66
  router.use(timesheet);
67
+ router.use('/library', library.router);
63
68
  router.use(knowledge.router);
64
69
  router.use(createCompanySearchRouter());
65
70
  router.use(createBodaccSearchRouter());
@@ -4,24 +4,32 @@ import { getConnection, projectsDb } from '@/modules/database/index.js';
4
4
 
5
5
  import { KnowledgeStore } from '../../../plugins/piecemaker-dossier/src/knowledge.js';
6
6
 
7
+ import { createKnowledgeLocalRouter } from './local-routes.js';
7
8
  import { createKnowledgePipeline } from './pipeline.js';
8
9
  import { createKnowledgeRouter } from './routes.js';
9
10
  import { createKnowledgeService } from './service.js';
10
11
 
11
12
  export function createKnowledgeBackend(applicationRoot: string) {
12
- const router = express.Router();
13
- let knowledgeRouter: express.Router | null = null;
14
13
  let store: KnowledgeStore | null = null;
14
+ let service: ReturnType<typeof createKnowledgeService> | null = null;
15
15
  const getStore = () => {
16
16
  if (!store) store = new KnowledgeStore(getConnection());
17
17
  return store;
18
18
  };
19
- router.use((request, response, next) => {
20
- if (!knowledgeRouter) {
19
+ const getService = () => {
20
+ if (!service) {
21
21
  const pipeline = createKnowledgePipeline({ applicationRoot, projects: projectsDb, store: getStore() });
22
- knowledgeRouter = createKnowledgeRouter(createKnowledgeService(getStore(), projectsDb, pipeline));
22
+ service = createKnowledgeService(getStore(), projectsDb, pipeline);
23
23
  }
24
+ return service;
25
+ };
26
+
27
+ const router = express.Router();
28
+ let knowledgeRouter: express.Router | null = null;
29
+ router.use((request, response, next) => {
30
+ if (!knowledgeRouter) knowledgeRouter = createKnowledgeRouter(getService());
24
31
  knowledgeRouter(request, response, next);
25
32
  });
26
- return { router };
33
+
34
+ return { router, localRouter: createKnowledgeLocalRouter(getService, projectsDb) };
27
35
  }
@@ -0,0 +1,72 @@
1
+ import express from 'express';
2
+
3
+ import type { createKnowledgeService } from './service.js';
4
+
5
+ type KnowledgeService = ReturnType<typeof createKnowledgeService>;
6
+
7
+ type ProjectPathLookup = {
8
+ getProjectPath(projectPath: string): { project_id: string; project_path: string } | null;
9
+ };
10
+
11
+ const LOOPBACK_ADDRESSES = new Set(['127.0.0.1', '::1', '::ffff:127.0.0.1']);
12
+
13
+ function isLoopback(request: express.Request): boolean {
14
+ return LOOPBACK_ADDRESSES.has(request.socket.remoteAddress || '');
15
+ }
16
+
17
+ function statusFor(error: unknown): number {
18
+ if (error instanceof TypeError) return 400;
19
+ if (error instanceof Error && error.message === 'Project not found.') return 404;
20
+ return 500;
21
+ }
22
+
23
+ export function createKnowledgeLocalRouter(
24
+ getService: () => KnowledgeService,
25
+ projects: ProjectPathLookup,
26
+ ) {
27
+ const router = express.Router();
28
+
29
+ router.use((request, response, next) => {
30
+ if (!isLoopback(request)) {
31
+ response.status(403).json({ error: 'Local scan is restricted to loopback callers.' });
32
+ return;
33
+ }
34
+ next();
35
+ });
36
+
37
+ const resolveProjectId = (body: unknown): string => {
38
+ const source = (body ?? {}) as { projectId?: unknown; folder?: unknown };
39
+ if (typeof source.projectId === 'string' && source.projectId.trim()) return source.projectId.trim();
40
+ if (typeof source.folder !== 'string' || !source.folder.trim()) {
41
+ throw new TypeError('folder or projectId is required.');
42
+ }
43
+ const project = projects.getProjectPath(source.folder.trim());
44
+ if (!project) throw new Error('Project not found.');
45
+ return project.project_id;
46
+ };
47
+
48
+ const respond = (operation: () => unknown | Promise<unknown>, response: express.Response) => {
49
+ Promise.resolve().then(operation).then((result) => response.json(result)).catch((error: unknown) => {
50
+ response.status(statusFor(error)).json({ error: error instanceof Error ? error.message : 'Local scan failed.' });
51
+ });
52
+ };
53
+
54
+ router.post('/scan', (request, response) => respond(() => {
55
+ const id = resolveProjectId(request.body);
56
+ return { projectId: id, ...getService().scan(id, request.body?.files) };
57
+ }, response));
58
+
59
+ router.get('/scan/job', (request, response) => respond(() => {
60
+ const id = typeof request.query.projectId === 'string' && request.query.projectId.trim()
61
+ ? request.query.projectId.trim()
62
+ : resolveProjectId({ folder: request.query.folder });
63
+ return { projectId: id, ...getService().scanJob(request.query.id, id) };
64
+ }, response));
65
+
66
+ router.post('/scan/cancel', (request, response) => respond(() => {
67
+ const id = resolveProjectId(request.body);
68
+ return { projectId: id, ...getService().cancelScan(request.body?.id, id) };
69
+ }, response));
70
+
71
+ return router;
72
+ }
@@ -20,6 +20,7 @@ export type KnowledgeScanJob = {
20
20
  processed: number;
21
21
  total: number;
22
22
  error: string | null;
23
+ result: unknown;
23
24
  startedAt: string;
24
25
  finishedAt: string | null;
25
26
  };
@@ -63,6 +64,7 @@ export function createKnowledgeScanJobs() {
63
64
  processed: 0,
64
65
  total: 0,
65
66
  error: null,
67
+ result: null,
66
68
  startedAt: new Date().toISOString(),
67
69
  finishedAt: null,
68
70
  };
@@ -76,11 +78,12 @@ export function createKnowledgeScanJobs() {
76
78
  };
77
79
  const controller = new AbortController();
78
80
  controllers.set(job.id, controller);
79
- run(report, controller.signal).then(() => {
81
+ run(report, controller.signal).then((result: unknown) => {
80
82
  if (job.state !== 'running') return;
81
83
  job.state = 'done';
82
84
  job.phase = 'commit';
83
85
  job.percent = 100;
86
+ job.result = result ?? null;
84
87
  }, (error: unknown) => {
85
88
  if (job.state !== 'running') return;
86
89
  job.state = 'error';
@@ -1,8 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
- import { randomBytes } from 'node:crypto';
5
- import { createRequire } from 'node:module';
6
4
 
7
5
  import express from 'express';
8
6
 
@@ -15,44 +13,26 @@ import { scanAndPersistLibraryProviderSkills } from './provider-skills.js';
15
13
 
16
14
  export { installLibraryRuntime } from './runtime.js';
17
15
 
18
- export async function startLibraryBackend(home: string, applicationRoot: string) {
16
+ const LIBRARY_BODY_LIMIT = 1024 * 1024;
17
+
18
+ export async function openLibrary(home: string, applicationRoot: string) {
19
19
  const store = createLibraryStore(home);
20
20
  try { await scanAndPersistLibraryProviderSkills(store, undefined, undefined, os.homedir()); } catch {}
21
21
  try { scanAndPersistLibraryProviderAgents(store, os.homedir()); } catch {}
22
22
  try { scanAndPersistLibraryProviderConnectors(store, os.homedir()); } catch {}
23
23
  try { scanInstalledLibraryCollections(store, os.homedir()); } catch {}
24
- const require = createRequire(import.meta.url);
25
- const { createActivationRouter } = require(path.join(applicationRoot, 'server/piecemaker/activation/index.cjs'));
26
- const options = { repoRoot: path.join(applicationRoot, 'server/piecemaker/vendor'), piecemakerHome: home, homeDir: home, userHome: os.homedir(), isOriginAllowed: () => true };
27
- const token = randomBytes(32).toString('hex');
28
- const app = express();
29
- app.use((req, res, next) => {
30
- if (req.headers.authorization !== `Bearer ${token}`) { res.sendStatus(401); return; }
24
+ try { fs.unlinkSync(path.join(store.directory, 'connection.json')); } catch {}
25
+ const router = express.Router();
26
+ router.use((req, res, next) => {
27
+ const length = Number(req.headers['content-length'] || 0);
28
+ if (Number.isFinite(length) && length > LIBRARY_BODY_LIMIT) {
29
+ res.status(413).json({ error: 'Requête trop volumineuse.' });
30
+ return;
31
+ }
31
32
  res.setHeader('Cache-Control', 'no-store');
32
33
  next();
33
34
  });
34
- app.use(express.json({ limit: '1mb' }));
35
- app.use(createLibraryRouter(store));
36
- app.use(createLibraryMarketplaceRouter(store, applicationRoot, os.homedir()));
37
- app.use((req, res, next) => {
38
- if ((req.method === 'GET' && req.path === '/activation')
39
- || (req.method === 'POST' && req.path === '/activation/toggle')) { next(); return; }
40
- res.sendStatus(404);
41
- });
42
- app.use(createActivationRouter(options));
43
- const server = app.listen(0, '127.0.0.1');
44
- await new Promise<void>((resolve, reject) => { server.once('listening', resolve); server.once('error', reject); });
45
- const address = server.address();
46
- if (!address || typeof address === 'string') throw new Error('Bibliothèque indisponible.');
47
- const connection = path.join(store.directory, 'connection.json');
48
- const temporary = `${connection}.${process.pid}.tmp`;
49
- fs.writeFileSync(temporary, JSON.stringify({ port: address.port, token }), { mode: 0o600 });
50
- fs.renameSync(temporary, connection);
51
- server.unref();
52
- process.once('exit', () => {
53
- try {
54
- if (JSON.parse(fs.readFileSync(connection, 'utf8')).token === token) fs.unlinkSync(connection);
55
- } catch {}
56
- });
57
- return store;
35
+ router.use(createLibraryRouter(store));
36
+ router.use(createLibraryMarketplaceRouter(store, applicationRoot, os.homedir()));
37
+ return { store, router };
58
38
  }
@@ -7,7 +7,7 @@ import Database from 'better-sqlite3';
7
7
  import { parseFrontMatter } from '@/shared/frontmatter.js';
8
8
 
9
9
  import { parseConnectorConfig, prepareConnectorInstallation, type LibraryConnectorConfig } from './connector-installation.js';
10
- import { prepareWorkspaceInstallation } from './workspace-installation.js';
10
+ import { componentFolderName, prepareWorkspaceInstallation } from './workspace-installation.js';
11
11
 
12
12
  type StoredLibraryEntry = {
13
13
  id: string;
@@ -64,6 +64,11 @@ function decodeEditableFile(bytes: Buffer) {
64
64
  return decodeUtf8File(bytes);
65
65
  }
66
66
 
67
+ function installationFolder(content: string, fallback = '') {
68
+ const { data } = parseFrontMatter(content);
69
+ return componentFolderName(String(data.name || data.metadata?.title || fallback));
70
+ }
71
+
67
72
  function collectionMainPath(entry: LibraryEntry, rootPath: string) {
68
73
  return entry.kind === 'skill' && !rootPath.toLowerCase().endsWith('.md') ? `${rootPath}/SKILL.md` : rootPath;
69
74
  }
@@ -219,6 +224,10 @@ export function createLibraryStore(home: string) {
219
224
  return db.transaction(() => {
220
225
  const entry = document(id);
221
226
  if (entry.content !== previousContent) throw new Error('Le document a été modifié ailleurs. Rouvrez-le avant d’enregistrer.');
227
+ const active = db.prepare('SELECT workspace FROM activation WHERE entry_id = ?').all(id) as Array<{ workspace: string }>;
228
+ if (entry.kind !== 'connector' && installationFolder(content) !== installationFolder(entry.content)) {
229
+ for (const { workspace: selected } of active) setEnabled(selected, id, false);
230
+ }
222
231
  if (entry.kind === 'connector') {
223
232
  const config = parseConnectorConfig(content);
224
233
  const description = config.url || config.command || entry.name;
@@ -229,7 +238,6 @@ export function createLibraryStore(home: string) {
229
238
  const description = typeof data.description === 'string' ? data.description : '';
230
239
  db.prepare('UPDATE entries SET name = ?, description = ?, content = ? WHERE id = ?').run(name, description, content, id);
231
240
  }
232
- const active = db.prepare('SELECT workspace FROM activation WHERE entry_id = ?').all(id) as Array<{ workspace: string }>;
233
241
  for (const { workspace: selected } of active) setEnabled(selected, id, true);
234
242
  return document(id);
235
243
  })();
@@ -465,7 +473,7 @@ export function createLibraryStore(home: string) {
465
473
  return { ok: true, enabled };
466
474
  }
467
475
  const packageRoot = path.join(directory, 'active', createHash('sha256').update(selected).digest('hex'), id);
468
- const install = prepareWorkspaceInstallation(selected, id, packageRoot, entry.kind, enabled);
476
+ const install = prepareWorkspaceInstallation(selected, id, installationFolder(entry.content, entry.name), packageRoot, entry.kind, enabled);
469
477
  if (enabled) {
470
478
  fs.mkdirSync(path.dirname(packageRoot), { recursive: true, mode: 0o700 });
471
479
  if (!fs.realpathSync(path.dirname(packageRoot)).startsWith(fs.realpathSync(directory) + path.sep)) throw new Error('Répertoire d’activation non autorisé.');
@@ -10,6 +10,12 @@ const AGENT_TARGETS = [
10
10
  { provider: '.grok', folder: 'agents', extension: '.md', sourceFile: 'agent.md' },
11
11
  ] as const;
12
12
 
13
+ export function componentFolderName(name: string) {
14
+ const slug = String(name).normalize('NFKD').replace(/[^\p{Letter}\p{Number}._-]+/gu, '-').replace(/^[-._]+|[-._]+$/g, '');
15
+ if (!slug) throw new Error(`Nom de composant inutilisable : ${name}`);
16
+ return slug;
17
+ }
18
+
13
19
  function assertWorkspaceDirectory(workspace: string, directory: string, enabled: boolean) {
14
20
  if (!fs.existsSync(directory)) {
15
21
  if (enabled) fs.mkdirSync(directory);
@@ -20,14 +26,16 @@ function assertWorkspaceDirectory(workspace: string, directory: string, enabled:
20
26
  }
21
27
  }
22
28
 
23
- export function prepareWorkspaceInstallation(workspace: string, id: string, packageRoot: string, kind: 'skill' | 'agent', enabled: boolean) {
29
+ export function prepareWorkspaceInstallation(workspace: string, id: string, name: string, packageRoot: string, kind: 'skill' | 'agent', enabled: boolean) {
30
+ const slug = componentFolderName(name);
24
31
  const targets = kind === 'skill'
25
32
  ? SKILL_PROVIDERS.map((provider) => {
26
33
  const parent = path.join(workspace, provider, 'skills');
27
34
  for (const directory of [path.dirname(parent), parent]) assertWorkspaceDirectory(workspace, directory, enabled);
28
35
  return {
29
36
  parent,
30
- target: path.join(parent, `piecemaker-${id}`),
37
+ target: path.join(parent, slug),
38
+ legacy: path.join(parent, `piecemaker-${id}`),
31
39
  source: packageRoot,
32
40
  kind: 'skill' as const,
33
41
  };
@@ -37,7 +45,8 @@ export function prepareWorkspaceInstallation(workspace: string, id: string, pack
37
45
  for (const directory of [path.dirname(parent), parent]) assertWorkspaceDirectory(workspace, directory, enabled);
38
46
  return {
39
47
  parent,
40
- target: path.join(parent, `piecemaker-${id}${entry.extension}`),
48
+ target: path.join(parent, `${slug}${entry.extension}`),
49
+ legacy: path.join(parent, `piecemaker-${id}${entry.extension}`),
41
50
  source: path.join(packageRoot, entry.sourceFile),
42
51
  kind: 'agent' as const,
43
52
  };
@@ -82,5 +91,13 @@ export function prepareWorkspaceInstallation(workspace: string, id: string, pack
82
91
  }
83
92
  throw error;
84
93
  }
94
+ for (const { legacy, source, kind: targetKind } of prepared) {
95
+ let existing: fs.Stats;
96
+ try { existing = fs.lstatSync(legacy); } catch { continue; }
97
+ const owned = targetKind === 'skill'
98
+ ? existing.isSymbolicLink() && fs.existsSync(source) && fs.realpathSync(legacy) === fs.realpathSync(source)
99
+ : existing.isFile() && fs.existsSync(source) && fs.readFileSync(legacy).equals(fs.readFileSync(source));
100
+ if (owned) fs.unlinkSync(legacy);
101
+ }
85
102
  };
86
103
  }
@@ -36,7 +36,7 @@ type SessionRecord = ReturnType<typeof sessionsDb.getAllSessions>[number];
36
36
  type TimesheetStore = {
37
37
  upsert(entry: TimesheetEntry): void;
38
38
  findBySessionId(sessionId: string): TimesheetEntry | null;
39
- list(projectId?: string): TimesheetEntry[];
39
+ list(projectPath?: string): TimesheetEntry[];
40
40
  };
41
41
 
42
42
  type TimesheetDependencies = {
@@ -154,10 +154,13 @@ export function createTimesheetService(
154
154
  return project;
155
155
  }
156
156
 
157
+ function projectPathFor(query: TimesheetQuery): string | undefined {
158
+ if (query.scope !== 'project') return undefined;
159
+ return resolveProject(query.projectId as string).project_path;
160
+ }
161
+
157
162
  function sessionsFor(query: TimesheetQuery): SessionRecord[] {
158
- const projectPath = query.scope === 'project'
159
- ? resolveProject(query.projectId as string).project_path
160
- : null;
163
+ const projectPath = projectPathFor(query) ?? null;
161
164
  const activeSessions = projectPath
162
165
  ? dependencies.sessions.getSessionsByProjectPath(projectPath)
163
166
  : dependencies.sessions.getAllSessions();
@@ -197,7 +200,7 @@ export function createTimesheetService(
197
200
  }
198
201
 
199
202
  return {
200
- entries: store.list(query.scope === 'project' ? query.projectId : undefined).map(toPublicEntry),
203
+ entries: store.list(projectPathFor(query)).map(toPublicEntry),
201
204
  refreshedAt: new Date().toISOString(),
202
205
  refreshedCount,
203
206
  skippedCount,
@@ -221,8 +224,7 @@ export function createTimesheetService(
221
224
 
222
225
  return {
223
226
  list(query: TimesheetQuery): TimesheetPublicEntry[] {
224
- if (query.scope === 'project') resolveProject(query.projectId as string);
225
- return store.list(query.scope === 'project' ? query.projectId : undefined).map(toPublicEntry);
227
+ return store.list(projectPathFor(query)).map(toPublicEntry);
226
228
  },
227
229
  refresh,
228
230
  };
@@ -60,6 +60,8 @@ function createSchema(database: TimesheetDatabase): void {
60
60
  );
61
61
  CREATE INDEX IF NOT EXISTS timesheet_project_date
62
62
  ON timesheet_sessions(project_id, started_at);
63
+ CREATE INDEX IF NOT EXISTS timesheet_project_path_date
64
+ ON timesheet_sessions(project_path, started_at);
63
65
  CREATE INDEX IF NOT EXISTS timesheet_date
64
66
  ON timesheet_sessions(started_at);
65
67
  `);
@@ -144,7 +146,7 @@ export function createTimesheetStore(homeDir: string) {
144
146
  `);
145
147
  const findBySessionId = database.prepare('SELECT * FROM timesheet_sessions WHERE session_id = ?');
146
148
  const listAll = database.prepare('SELECT * FROM timesheet_sessions ORDER BY datetime(started_at) DESC, session_id DESC');
147
- const listByProject = database.prepare('SELECT * FROM timesheet_sessions WHERE project_id = ? ORDER BY datetime(started_at) DESC, session_id DESC');
149
+ const listByProjectPath = database.prepare('SELECT * FROM timesheet_sessions WHERE project_path = ? ORDER BY datetime(started_at) DESC, session_id DESC');
148
150
 
149
151
  return {
150
152
  databasePath,
@@ -156,8 +158,8 @@ export function createTimesheetStore(homeDir: string) {
156
158
  const row = findBySessionId.get(sessionId) as TimesheetEntryRow | undefined;
157
159
  return row ? toEntry(row) : null;
158
160
  },
159
- list(projectId?: string): TimesheetEntry[] {
160
- const rows = (projectId ? listByProject.all(projectId) : listAll.all()) as TimesheetEntryRow[];
161
+ list(projectPath?: string): TimesheetEntry[] {
162
+ const rows = (projectPath ? listByProjectPath.all(projectPath) : listAll.all()) as TimesheetEntryRow[];
161
163
  return rows.map(toEntry);
162
164
  },
163
165
  close(): void {
@@ -42,7 +42,6 @@ const CONFIG_FILE = path.join(os.homedir(), '.claude', 'piecemaker-secret-paths.
42
42
  /** Never empty: at minimum, the general PieceMaker runtime .env. */
43
43
  const DEFAULT_BLOCKLIST = [
44
44
  path.join(os.homedir(), 'PieceMaker', '.env'),
45
- path.join(os.homedir(), 'Sites', 'PieceMaker-Installer', '.env'),
46
45
  ];
47
46
 
48
47
  const PATH_TOOLS = new Set(['Read', 'Grep', 'Glob', 'Edit', 'Write', 'NotebookEdit', 'MultiEdit']);