@piecemaker-legal/piecemaker 2.0.17 → 2.0.19

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 (99) hide show
  1. package/dist/assets/{FindReplaceDialog-KU5TEIBJ-B4N6W1_z.js → FindReplaceDialog-KU5TEIBJ-D-E_iQrz.js} +1 -1
  2. package/dist/assets/{FootnotePropertiesDialog-W3YHM4BV-D_X_JkAZ.js → FootnotePropertiesDialog-W3YHM4BV-aF_8ncE9.js} +1 -1
  3. package/dist/assets/{HyperlinkDialog-ZYH7ZHRF-EbyukgDx.js → HyperlinkDialog-ZYH7ZHRF-BvhTCTt_.js} +4 -4
  4. package/dist/assets/{ImagePositionDialog-HEWYR6Q3-D9jEaxlE.js → ImagePositionDialog-HEWYR6Q3-CC7jJcbZ.js} +1 -1
  5. package/dist/assets/{ImagePropertiesDialog-RAB47C6M-DvA1kZnM.js → ImagePropertiesDialog-RAB47C6M-C5fs5OZU.js} +1 -1
  6. package/dist/assets/{PageSetupDialog-AIGIXO3E-ChfsKF43.js → PageSetupDialog-AIGIXO3E-DhHTvZ6Q.js} +1 -1
  7. package/dist/assets/{SplitCellDialog-IFBKYCD6-uM26FNXt.js → SplitCellDialog-IFBKYCD6-BoFyEVsb.js} +1 -1
  8. package/dist/assets/{TablePropertiesDialog-TMLKGOOW-By6EQI5w.js → TablePropertiesDialog-TMLKGOOW-CBb7GEtl.js} +1 -1
  9. package/dist/assets/{WatermarkDialog-KRL7WMIS-Bss9F6Ej.js → WatermarkDialog-KRL7WMIS-BJbsMnmA.js} +1 -1
  10. package/dist/assets/{abnfDiagram-VCTEODGH-yLjNPHY7.js → abnfDiagram-VCTEODGH-C6ojlqY3.js} +1 -1
  11. package/dist/assets/{arc-CMQiMSh4.js → arc-BoDdi4D9.js} +1 -1
  12. package/dist/assets/{architectureDiagram-5GKGNRK7-Cyna8IJZ.js → architectureDiagram-5GKGNRK7-CWIahr5B.js} +1 -1
  13. package/dist/assets/{blockDiagram-I7D4REHJ-DHAdjRYY.js → blockDiagram-I7D4REHJ-CLEp2VLU.js} +1 -1
  14. package/dist/assets/{c4Diagram-7LVT6UL2-qqZ0w4LC.js → c4Diagram-7LVT6UL2-BnPyO1ST.js} +1 -1
  15. package/dist/assets/channel-DV0VwszZ.js +1 -0
  16. package/dist/assets/{chunk-2Q5K7J3B-NS0halN6.js → chunk-2Q5K7J3B-C7HwgL9V.js} +1 -1
  17. package/dist/assets/{chunk-5VM5RSS4-BF6-6Cah.js → chunk-5VM5RSS4-DXEfMQFg.js} +1 -1
  18. package/dist/assets/{chunk-F27PBJKO-Dnj2jFRB.js → chunk-F27PBJKO-L8IW1AJ2.js} +1 -1
  19. package/dist/assets/{chunk-IMKFNOWR-CmRgl1EE.js → chunk-IMKFNOWR-M0u3FR4R.js} +1 -1
  20. package/dist/assets/{chunk-JWPE2WC7-BwC19UFc.js → chunk-JWPE2WC7-BTrrWfya.js} +1 -1
  21. package/dist/assets/{chunk-POPQ4Y6H-C1YWVD4-.js → chunk-POPQ4Y6H-DKzOkm9u.js} +1 -1
  22. package/dist/assets/{chunk-SVP7TREG-D7XQTPRq.js → chunk-SVP7TREG-ChWxgGeB.js} +1 -1
  23. package/dist/assets/{chunk-TICWLB2K-BkmTGk1O.js → chunk-TICWLB2K-FU0TG8_V.js} +1 -1
  24. package/dist/assets/{chunk-XXDRQBXY-CYW7YgXL.js → chunk-XXDRQBXY-D--n_LHM.js} +1 -1
  25. package/dist/assets/classDiagram-ZZMXUADV-QmelLege.js +1 -0
  26. package/dist/assets/classDiagram-v2-VYDZK3BY-QmelLege.js +1 -0
  27. package/dist/assets/{cose-bilkent-JH36ORCC-BSktt6pW.js → cose-bilkent-JH36ORCC-VcyzT4Am.js} +1 -1
  28. package/dist/assets/{cynefin-OW5HDTMX-BNMRWXNG.js → cynefin-OW5HDTMX-CF_CzN2W.js} +1 -1
  29. package/dist/assets/{cynefinDiagram-5FMLGOSQ-Dk59YHuO.js → cynefinDiagram-5FMLGOSQ-tYmbUXoj.js} +1 -1
  30. package/dist/assets/{dagre-GXQ25YYZ-DFhWeh0x.js → dagre-GXQ25YYZ-Dl-3sgkS.js} +1 -1
  31. package/dist/assets/{diagram-S7CK7UJ4-BH_8x-q5.js → diagram-S7CK7UJ4-DyYBtOKJ.js} +1 -1
  32. package/dist/assets/{diagram-UQ7AKVKN-DmxmlXg5.js → diagram-UQ7AKVKN-BfibAHRJ.js} +1 -1
  33. package/dist/assets/{diagram-VSXAHHWV-DpG29cD4.js → diagram-VSXAHHWV-BvuChn2K.js} +1 -1
  34. package/dist/assets/{diagram-VX7I27RA-CvvYXTie.js → diagram-VX7I27RA-ZSTD84JR.js} +1 -1
  35. package/dist/assets/{diagram-Z3DM3KII-xOXaaZqs.js → diagram-Z3DM3KII-CYWcW_j4.js} +1 -1
  36. package/dist/assets/{ebnfDiagram-PWID7BFC-CtfFGqJB.js → ebnfDiagram-PWID7BFC-CKssYpWu.js} +1 -1
  37. package/dist/assets/{erDiagram-RLTQ6QDP-AoCjoHUY.js → erDiagram-RLTQ6QDP-H6Rihy54.js} +1 -1
  38. package/dist/assets/{flowDiagram-HODETNUW-Bl9_mq95.js → flowDiagram-HODETNUW-BYcs_7fG.js} +1 -1
  39. package/dist/assets/{ganttDiagram-EL5Y4UJY-DREJQ2BY.js → ganttDiagram-EL5Y4UJY-Dt0zBRmP.js} +1 -1
  40. package/dist/assets/{gitGraphDiagram-WWUBYQGX-C1q5-iep.js → gitGraphDiagram-WWUBYQGX-DJ4ZVb5_.js} +1 -1
  41. package/dist/assets/{index-RKNvOXcq.js → index-skgP2UD3.js} +4 -4
  42. package/dist/assets/{infoDiagram-27XIBGKW-Cz2KUKWU.js → infoDiagram-27XIBGKW-DZmrJj_e.js} +1 -1
  43. package/dist/assets/{ishikawaDiagram-5VMMS53U-BU6SdWsw.js → ishikawaDiagram-5VMMS53U-zTPWVhqW.js} +1 -1
  44. package/dist/assets/{journeyDiagram-3NMN7TZE-CMMoiPf0.js → journeyDiagram-3NMN7TZE-CFxtAX_q.js} +1 -1
  45. package/dist/assets/{kanban-definition-UXKFOSKX-KpKnC3dd.js → kanban-definition-UXKFOSKX-BIBQGYrN.js} +1 -1
  46. package/dist/assets/{layout-6C8d-KNC.js → layout-5xNw8gsu.js} +1 -1
  47. package/dist/assets/{linear-CUINCO59.js → linear-CW6Efbjk.js} +1 -1
  48. package/dist/assets/{mermaid.core-ClgFTKfs.js → mermaid.core-CV1UELnp.js} +6 -6
  49. package/dist/assets/{mindmap-definition-YA3MSWOX-oywqmpy6.js → mindmap-definition-YA3MSWOX-zpWSRS8g.js} +1 -1
  50. package/dist/assets/{pegDiagram-XKGWAZYB-Uk65qoVL.js → pegDiagram-XKGWAZYB-4u0O4rTZ.js} +1 -1
  51. package/dist/assets/{pieDiagram-E7YTZNPT-XxQhMRKu.js → pieDiagram-E7YTZNPT-CXmymaRq.js} +1 -1
  52. package/dist/assets/{quadrantDiagram-AXDQQJYC-Djngo9IG.js → quadrantDiagram-AXDQQJYC-DpCRdfYk.js} +1 -1
  53. package/dist/assets/{railroadDiagram-O6MQD6OU-C4I8SNPI.js → railroadDiagram-O6MQD6OU-DisvzZ3q.js} +1 -1
  54. package/dist/assets/{requirementDiagram-BXWQKSXE-D62oiVFl.js → requirementDiagram-BXWQKSXE-DvU7Z8z5.js} +1 -1
  55. package/dist/assets/{sankeyDiagram-P5KCCOFB-HZuSub58.js → sankeyDiagram-P5KCCOFB-Df5nogtS.js} +1 -1
  56. package/dist/assets/{sequenceDiagram-WJ2MYXX4-B8_t7SDx.js → sequenceDiagram-WJ2MYXX4-B5BKf6O8.js} +1 -1
  57. package/dist/assets/{sizeCapture-INFHLROL-D2Lk0zlm.js → sizeCapture-INFHLROL-1EZabZaY.js} +1 -1
  58. package/dist/assets/{stateDiagram-D77RDMKH-DC3NnV2S.js → stateDiagram-D77RDMKH-DCSKdcyr.js} +1 -1
  59. package/dist/assets/{stateDiagram-v2-MP3YSRHH-DSsQOGCi.js → stateDiagram-v2-MP3YSRHH-Db5HOoa1.js} +1 -1
  60. package/dist/assets/{swimlanes-42K2YHIH-6RzklsF9.js → swimlanes-42K2YHIH-CmrUx1kp.js} +1 -1
  61. package/dist/assets/swimlanesDiagram-VR7AAH4N-B6BaxAGn.js +8 -0
  62. package/dist/assets/{timeline-definition-24CTP7MA-Beuacf_x.js → timeline-definition-24CTP7MA-D1e5FRLS.js} +1 -1
  63. package/dist/assets/{vennDiagram-4TSXK5OY-CMqthbQr.js → vennDiagram-4TSXK5OY-DbclmB6P.js} +1 -1
  64. package/dist/assets/{wardleyDiagram-VM6X3IG4-DKrdN08q.js → wardleyDiagram-VM6X3IG4-BL3cDBgt.js} +1 -1
  65. package/dist/assets/{xychartDiagram-S5SC5T6Z-BtMW2Qlh.js → xychartDiagram-S5SC5T6Z-BnyVnEkb.js} +1 -1
  66. package/dist/index.html +1 -1
  67. package/dist/sw.js +1 -1
  68. package/dist-server/server/piecemaker/library/connector-installation.js +68 -17
  69. package/dist-server/server/piecemaker/library/connector-installation.js.map +1 -1
  70. package/dist-server/server/piecemaker/library/global-state.js +60 -0
  71. package/dist-server/server/piecemaker/library/global-state.js.map +1 -0
  72. package/dist-server/server/piecemaker/library/index.js +10 -0
  73. package/dist-server/server/piecemaker/library/index.js.map +1 -1
  74. package/dist-server/server/piecemaker/library/marketplace.js +61 -42
  75. package/dist-server/server/piecemaker/library/marketplace.js.map +1 -1
  76. package/dist-server/server/piecemaker/library/migrate.js +0 -35
  77. package/dist-server/server/piecemaker/library/migrate.js.map +1 -1
  78. package/dist-server/server/piecemaker/library/routes.js +6 -2
  79. package/dist-server/server/piecemaker/library/routes.js.map +1 -1
  80. package/dist-server/server/piecemaker/library/store.js +95 -67
  81. package/dist-server/server/piecemaker/library/store.js.map +1 -1
  82. package/dist-server/server/piecemaker/library/workspace-installation.js +172 -87
  83. package/dist-server/server/piecemaker/library/workspace-installation.js.map +1 -1
  84. package/package.json +1 -1
  85. package/server/piecemaker/library/connector-installation.ts +65 -16
  86. package/server/piecemaker/library/global-state.ts +55 -0
  87. package/server/piecemaker/library/index.ts +7 -0
  88. package/server/piecemaker/library/marketplace.ts +52 -48
  89. package/server/piecemaker/library/migrate.ts +0 -35
  90. package/server/piecemaker/library/routes.ts +9 -2
  91. package/server/piecemaker/library/store.ts +94 -57
  92. package/server/piecemaker/library/workspace-installation.ts +180 -77
  93. package/dist/assets/channel-B3AYuE-l.js +0 -1
  94. package/dist/assets/classDiagram-ZZMXUADV-CE2Zkr7h.js +0 -1
  95. package/dist/assets/classDiagram-v2-VYDZK3BY-CE2Zkr7h.js +0 -1
  96. package/dist/assets/swimlanesDiagram-VR7AAH4N-ZU4NRSFy.js +0 -8
  97. package/dist-server/server/piecemaker/library/migrate-cli.js +0 -13
  98. package/dist-server/server/piecemaker/library/migrate-cli.js.map +0 -1
  99. package/server/piecemaker/library/migrate-cli.ts +0 -11
@@ -0,0 +1,55 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ import { listUserProviderConnectors } from './provider-connectors.js';
5
+
6
+ const GLOBAL_DIRECTORIES = [
7
+ ['.claude/skills', 'Claude'],
8
+ ['.claude/agents', 'Claude'],
9
+ ['.codex/skills', 'Codex'],
10
+ ['.codex/agents', 'Codex'],
11
+ ['.agents/skills', 'Codex'],
12
+ ['.cursor/skills', 'Cursor'],
13
+ ['.cursor/agents', 'Cursor'],
14
+ ['.config/opencode/skills', 'OpenCode'],
15
+ ['.config/opencode/agent', 'OpenCode'],
16
+ ['.config/opencode/agents', 'OpenCode'],
17
+ ['.grok/skills', 'Grok'],
18
+ ['.grok/agents', 'Grok'],
19
+ ] as const;
20
+
21
+ const GLOBAL_CONNECTOR_FILES = [
22
+ ['.claude.json', 'Claude'],
23
+ ['.codex/config.toml', 'Codex'],
24
+ ['.cursor/mcp.json', 'Cursor'],
25
+ ['.config/opencode/opencode.json', 'OpenCode'],
26
+ ['.config/opencode/opencode.jsonc', 'OpenCode'],
27
+ ['.grok/config.toml', 'Grok'],
28
+ ] as const;
29
+
30
+ export function globalProviderIndex(userHome: string) {
31
+ const providers = new Map<string, Set<string>>();
32
+ const record = (source: string, provider: string) => {
33
+ providers.set(source, (providers.get(source) ?? new Set()).add(provider));
34
+ };
35
+ for (const [directory, provider] of GLOBAL_DIRECTORIES) {
36
+ const root = path.join(userHome, directory);
37
+ let children: string[];
38
+ try { children = fs.readdirSync(root); } catch { continue; }
39
+ for (const child of children) {
40
+ for (const candidate of [path.join(root, child), path.join(root, child, 'SKILL.md')]) {
41
+ try {
42
+ if (!fs.statSync(candidate).isFile()) continue;
43
+ record(candidate, provider);
44
+ record(fs.realpathSync(candidate), provider);
45
+ } catch {}
46
+ }
47
+ }
48
+ }
49
+ for (const connector of listUserProviderConnectors(userHome)) {
50
+ const file = connector.source.slice(0, connector.source.lastIndexOf('#'));
51
+ const match = GLOBAL_CONNECTOR_FILES.find(([name]) => path.join(userHome, name) === file);
52
+ if (match) record(connector.source, match[1]);
53
+ }
54
+ return (sources: string[]) => [...new Set(sources.flatMap((source) => [...(providers.get(source) ?? [])]))].sort();
55
+ }
@@ -7,6 +7,7 @@ import express from 'express';
7
7
  import { createLibraryStore } from './store.js';
8
8
  import { createLibraryRouter } from './routes.js';
9
9
  import { createLibraryMarketplaceRouter, scanInstalledLibraryCollections } from './marketplace.js';
10
+ import { importLibraryDirectory } from './migrate.js';
10
11
  import { scanAndPersistLibraryProviderAgents } from './provider-agents.js';
11
12
  import { scanAndPersistLibraryProviderConnectors } from './provider-connectors.js';
12
13
  import { scanAndPersistLibraryProviderSkills } from './provider-skills.js';
@@ -22,6 +23,12 @@ export async function openLibrary(home: string, applicationRoot: string) {
22
23
  try { scanAndPersistLibraryProviderConnectors(store, os.homedir()); } catch {}
23
24
  try { scanInstalledLibraryCollections(store, os.homedir()); } catch {}
24
25
  try { fs.unlinkSync(path.join(store.directory, 'connection.json')); } catch {}
26
+ const bundled = path.join(applicationRoot, 'server/piecemaker/vendor/piecemaker-plugin');
27
+ const defaults: string[] = [];
28
+ for (const [folder, kind] of [['skills', 'skill'], ['agents', 'agent']] as const) {
29
+ try { defaults.push(...importLibraryDirectory(store, path.join(bundled, folder), kind).map((entry) => entry.id)); } catch {}
30
+ }
31
+ store.setDefaultEntries(defaults);
25
32
  const router = express.Router();
26
33
  router.use((req, res, next) => {
27
34
  const length = Number(req.headers['content-length'] || 0);
@@ -6,7 +6,7 @@ import { spawnSync } from 'node:child_process';
6
6
  import express from 'express';
7
7
 
8
8
  import type { createLibraryStore } from './store.js';
9
- import { normalizeProviderConnector } from './connector-installation.js';
9
+ import { normalizeProviderConnector, setWorkspaceClaudePlugin, workspaceClaudePlugins } from './connector-installation.js';
10
10
  import { importLibraryDirectory } from './migrate.js';
11
11
  import { scanAndPersistLibraryProviderAgents } from './provider-agents.js';
12
12
  import { scanAndPersistLibraryProviderConnectors } from './provider-connectors.js';
@@ -45,7 +45,16 @@ function marketplacePackageKinds(userHome: string, marketplaceName: string, plug
45
45
  return [...kinds];
46
46
  }
47
47
 
48
- function disableNativeClaudePlugin(userHome: string, id: string) {
48
+ function userClaudePlugins(userHome: string) {
49
+ const settingsFilename = path.join(userHome, '.claude/settings.json');
50
+ if (!fs.existsSync(settingsFilename)) return {};
51
+ const enabledPlugins = JSON.parse(fs.readFileSync(settingsFilename, 'utf8')).enabledPlugins;
52
+ return enabledPlugins && typeof enabledPlugins === 'object' && !Array.isArray(enabledPlugins)
53
+ ? enabledPlugins as Record<string, unknown>
54
+ : {};
55
+ }
56
+
57
+ function setNativeClaudePlugin(userHome: string, id: string, enabled: boolean) {
49
58
  const settingsFilename = path.join(userHome, '.claude/settings.json');
50
59
  const settings = fs.existsSync(settingsFilename)
51
60
  ? JSON.parse(fs.readFileSync(settingsFilename, 'utf8'))
@@ -53,8 +62,8 @@ function disableNativeClaudePlugin(userHome: string, id: string) {
53
62
  const enabledPlugins = settings.enabledPlugins && typeof settings.enabledPlugins === 'object' && !Array.isArray(settings.enabledPlugins)
54
63
  ? settings.enabledPlugins
55
64
  : {};
56
- if (enabledPlugins[id] === false) return;
57
- settings.enabledPlugins = { ...enabledPlugins, [id]: false };
65
+ if (enabledPlugins[id] === enabled) return;
66
+ settings.enabledPlugins = { ...enabledPlugins, [id]: enabled };
58
67
  fs.mkdirSync(path.dirname(settingsFilename), { recursive: true, mode: 0o700 });
59
68
  const temporary = `${settingsFilename}.library.tmp`;
60
69
  fs.writeFileSync(temporary, JSON.stringify(settings, null, 2), { mode: 0o600 });
@@ -129,39 +138,6 @@ export function scanInstalledLibraryCollections(store: ReturnType<typeof createL
129
138
  }
130
139
  }
131
140
 
132
- function readInstalledClaudePlugins(userHome: string) {
133
- const filename = path.join(userHome, '.claude/plugins/installed_plugins.json');
134
- if (!fs.existsSync(filename)) return {};
135
- const config = JSON.parse(fs.readFileSync(filename, 'utf8'));
136
- return config.plugins && typeof config.plugins === 'object' && !Array.isArray(config.plugins)
137
- ? config.plugins as Record<string, unknown>
138
- : {};
139
- }
140
-
141
- function synchronizeInstalledPluginActivation(
142
- store: ReturnType<typeof createLibraryStore>,
143
- userHome: string,
144
- workspacePath: string,
145
- ) {
146
- const settingsFilename = path.join(userHome, '.claude/settings.json');
147
- if (!fs.existsSync(settingsFilename)) return store.listCollections(workspacePath);
148
- const settings = JSON.parse(fs.readFileSync(settingsFilename, 'utf8'));
149
- const enabledPlugins = settings.enabledPlugins && typeof settings.enabledPlugins === 'object' && !Array.isArray(settings.enabledPlugins)
150
- ? settings.enabledPlugins as Record<string, unknown>
151
- : {};
152
- const installedPlugins = readInstalledClaudePlugins(userHome);
153
- const collections = store.listCollections(workspacePath);
154
-
155
- for (const id of Object.keys(installedPlugins)) {
156
- const collection = collections.find((entry) => entry.id === id);
157
- const enabled = enabledPlugins[id];
158
- if (!collection || collection.componentCount === 0 || typeof enabled !== 'boolean' || collection.enabled === enabled) continue;
159
- store.setCollectionEnabled(workspacePath, id, enabled);
160
- }
161
-
162
- return store.listCollections(workspacePath);
163
- }
164
-
165
141
  export function createLibraryMarketplaceRouter(store: ReturnType<typeof createLibraryStore>, applicationRoot: string, userHome: string) {
166
142
  const require = createRequire(import.meta.url);
167
143
  const { listMarketplaceConnectors, registerOfficialMarketplace } = require(path.join(applicationRoot, 'server/piecemaker/vendor/websocket-server/admin-routes.cjs'));
@@ -181,6 +157,19 @@ export function createLibraryMarketplaceRouter(store: ReturnType<typeof createLi
181
157
  return fs.existsSync(filename) ? JSON.parse(fs.readFileSync(filename, 'utf8')).plugins || {} : {};
182
158
  };
183
159
  const importInstalledCollections = () => scanInstalledLibraryCollections(store, userHome);
160
+ const describeCollections = (workspacePath?: string) => {
161
+ const collections = store.listCollections(workspacePath);
162
+ const registry = installed();
163
+ const everywhere = userClaudePlugins(userHome);
164
+ const here = workspacePath ? workspaceClaudePlugins(fs.realpathSync(workspacePath)) : {};
165
+ return collections.map((collection) => {
166
+ const native = Boolean(registry[collection.id]);
167
+ if (!native) return { ...collection, native, global: false };
168
+ const global = everywhere[collection.id] === true;
169
+ const local = here[collection.id];
170
+ return { ...collection, native, global, partial: false, enabled: Boolean(workspacePath) && (typeof local === 'boolean' ? local : global) };
171
+ });
172
+ };
184
173
  router.get('/plugin/marketplace', (req, res) => {
185
174
  try {
186
175
  const marketplace = scope(req.query.scope);
@@ -202,7 +191,7 @@ export function createLibraryMarketplaceRouter(store: ReturnType<typeof createLi
202
191
  router.get('/plugins', (req, res) => {
203
192
  try {
204
193
  const workspacePath = typeof req.query.workspacePath === 'string' ? req.query.workspacePath : undefined;
205
- res.json({ plugins: store.listCollections(workspacePath) });
194
+ res.json({ plugins: describeCollections(workspacePath) });
206
195
  } catch (error) { res.status(400).json({ error: (error as Error).message }); }
207
196
  });
208
197
  router.post('/plugins/sync', async (req, res) => {
@@ -212,12 +201,7 @@ export function createLibraryMarketplaceRouter(store: ReturnType<typeof createLi
212
201
  scanAndPersistLibraryProviderAgents(store, userHome);
213
202
  scanAndPersistLibraryProviderConnectors(store, userHome);
214
203
  scanInstalledLibraryCollections(store, userHome);
215
- res.json({
216
- ok: true,
217
- plugins: workspacePath
218
- ? synchronizeInstalledPluginActivation(store, userHome, workspacePath)
219
- : store.listCollections(),
220
- });
204
+ res.json({ ok: true, plugins: describeCollections(workspacePath) });
221
205
  } catch (error) { res.status(400).json({ error: (error as Error).message }); }
222
206
  });
223
207
  router.post('/plugins/scan', (_req, res) => {
@@ -237,8 +221,28 @@ export function createLibraryMarketplaceRouter(store: ReturnType<typeof createLi
237
221
  catch (error) { res.status(409).json({ error: (error as Error).message }); }
238
222
  });
239
223
  router.put('/plugins/:id/activation', (req, res) => {
240
- try { res.json(store.setCollectionEnabled(req.body?.workspacePath, String(req.params.id), req.body?.enabled)); }
241
- catch (error) { res.status(400).json({ error: (error as Error).message }); }
224
+ try {
225
+ const id = String(req.params.id);
226
+ const workspacePath = req.body?.workspacePath;
227
+ const enabled = req.body?.enabled;
228
+ if (typeof workspacePath !== 'string') throw new Error('Dossier absolu requis.');
229
+ if (typeof enabled !== 'boolean') throw new Error('Activation booléenne requise.');
230
+ const collection = store.listCollections(workspacePath).find((entry) => entry.id === id);
231
+ const native = Boolean(installed()[id]);
232
+ if (!collection || (!native && !collection.componentCount)) throw new Error('Plugin introuvable.');
233
+ if (collection.componentCount) store.setCollectionEnabled(workspacePath, id, enabled, { skipClaude: native });
234
+ if (native) setWorkspaceClaudePlugin(fs.realpathSync(workspacePath), id, enabled ? true : userClaudePlugins(userHome)[id] === true ? false : null);
235
+ res.json(describeCollections(workspacePath).find((entry) => entry.id === id));
236
+ } catch (error) { res.status(400).json({ error: (error as Error).message }); }
237
+ });
238
+ router.put('/plugins/:id/global', (req, res) => {
239
+ try {
240
+ const id = String(req.params.id);
241
+ if (typeof req.body?.enabled !== 'boolean') throw new Error('Activation booléenne requise.');
242
+ if (!installed()[id]) throw new Error('Plugin absent de Claude Code.');
243
+ setNativeClaudePlugin(userHome, id, req.body.enabled);
244
+ res.json({ ok: true, global: req.body.enabled });
245
+ } catch (error) { res.status(400).json({ error: (error as Error).message }); }
242
246
  });
243
247
  router.post('/plugin/marketplace/register', (req, res) => {
244
248
  try { res.json(registerOfficialMarketplace(run, scope(req.body?.scope))); }
@@ -252,10 +256,10 @@ export function createLibraryMarketplaceRouter(store: ReturnType<typeof createLi
252
256
  if (installed()[id]) throw new Error('Déjà installé. Son activation se règle par dossier.');
253
257
  const catalog = listMarketplaceConnectors(run, { marketplaceName: marketplace.name });
254
258
  if (!catalog.plugins.some((plugin: { id: string }) => plugin.id === id)) throw new Error('Élément absent du catalogue.');
255
- disableNativeClaudePlugin(userHome, id);
259
+ setNativeClaudePlugin(userHome, id, false);
256
260
  let result;
257
261
  try { result = run('claude', ['plugin', 'install', id, '--scope', 'user']); }
258
- finally { disableNativeClaudePlugin(userHome, id); }
262
+ finally { setNativeClaudePlugin(userHome, id, false); }
259
263
  if (!result.ok) throw new Error(result.output || 'Installation impossible.');
260
264
  importInstalledCollections();
261
265
  res.json({ ok: true, enabled: false });
@@ -19,38 +19,3 @@ export function importLibraryDirectory(store: ReturnType<typeof createLibrarySto
19
19
  }
20
20
  return imported;
21
21
  }
22
-
23
- export function migratePersonalLibrary(store: ReturnType<typeof createLibraryStore>, userHome: string, applicationRoot: string, withdraw = false) {
24
- const imported = [];
25
- for (const folder of ['assistant-workflows', 'tabular-review-workflows']) {
26
- imported.push(...importLibraryDirectory(store, path.join(applicationRoot, 'server/piecemaker/vendor/mike-defaults-fr', folder), 'skill'));
27
- }
28
- for (const [directory, kind] of [
29
- [path.join(userHome, '.claude', 'skills'), 'skill'],
30
- [path.join(userHome, '.codex', 'skills'), 'skill'],
31
- [path.join(userHome, '.agents', 'skills'), 'skill'],
32
- [path.join(userHome, '.cursor', 'skills'), 'skill'],
33
- [path.join(userHome, '.config', 'opencode', 'skills'), 'skill'],
34
- [path.join(userHome, '.grok', 'skills'), 'skill'],
35
- [path.join(userHome, '.claude', 'agents'), 'agent'],
36
- [path.join(userHome, '.cursor', 'agents'), 'agent'],
37
- [path.join(userHome, '.grok', 'agents'), 'agent'],
38
- [path.join(userHome, '.config', 'opencode', 'agents'), 'agent'],
39
- [path.join(userHome, '.config', 'opencode', 'agent'), 'agent'],
40
- [path.join(userHome, '.piecemaker', 'library', 'skills'), 'skill'],
41
- [path.join(userHome, '.piecemaker', 'library', 'agents'), 'agent'],
42
- ] as const) imported.push(...importLibraryDirectory(store, directory, kind));
43
- if (withdraw) {
44
- const candidates = imported.filter((entry) => ['.claude', '.codex', '.agents', '.cursor', '.grok', path.join('.config', 'opencode')].some((provider) => entry.source.startsWith(path.join(userHome, provider) + path.sep)));
45
- const backup = path.join(store.directory, `migration-${Date.now()}`);
46
- fs.mkdirSync(backup, { recursive: true, mode: 0o700 });
47
- fs.writeFileSync(path.join(backup, 'manifest.json'), JSON.stringify(candidates, null, 2), { mode: 0o600 });
48
- for (const entry of candidates.sort((a, b) => Number(b.linked) - Number(a.linked))) {
49
- const destination = path.join(backup, path.relative(userHome, entry.source));
50
- fs.mkdirSync(path.dirname(destination), { recursive: true, mode: 0o700 });
51
- fs.renameSync(entry.source, destination);
52
- }
53
- fs.writeFileSync(path.join(store.directory, 'centralized.json'), JSON.stringify({ migratedAt: new Date().toISOString(), count: candidates.length }), { mode: 0o600 });
54
- }
55
- return imported;
56
- }
@@ -1,12 +1,19 @@
1
+ import os from 'node:os';
2
+
1
3
  import express from 'express';
2
4
 
3
5
  import type { createLibraryStore } from './store.js';
6
+ import { globalProviderIndex } from './global-state.js';
4
7
  import { listLibraryProviderSkills, scanAndPersistLibraryProviderSkills } from './provider-skills.js';
5
8
 
6
- export function createLibraryRouter(store: ReturnType<typeof createLibraryStore>) {
9
+ export function createLibraryRouter(store: ReturnType<typeof createLibraryStore>, userHome = os.homedir()) {
7
10
  const router = express.Router();
8
11
  router.get('/catalog', (req, res) => {
9
- try { res.json({ entries: store.list(typeof req.query.workspacePath === 'string' ? req.query.workspacePath : undefined) }); }
12
+ try {
13
+ const globalProviders = globalProviderIndex(userHome);
14
+ const entries = store.list(typeof req.query.workspacePath === 'string' ? req.query.workspacePath : undefined);
15
+ res.json({ entries: entries.map(({ sources, ...entry }) => ({ ...entry, globalProviders: globalProviders(sources) })) });
16
+ }
10
17
  catch (error) { res.status(400).json({ error: (error as Error).message }); }
11
18
  });
12
19
  router.post('/catalog', (req, res) => {
@@ -1,4 +1,5 @@
1
1
  import fs from 'node:fs';
2
+ import os from 'node:os';
2
3
  import path from 'node:path';
3
4
  import { createHash, randomBytes } from 'node:crypto';
4
5
 
@@ -7,7 +8,16 @@ import Database from 'better-sqlite3';
7
8
  import { parseFrontMatter } from '@/shared/frontmatter.js';
8
9
 
9
10
  import { parseConnectorConfig, prepareConnectorInstallation, type LibraryConnectorConfig } from './connector-installation.js';
10
- import { componentFolderName, prepareWorkspaceInstallation } from './workspace-installation.js';
11
+ import {
12
+ componentFileHashes,
13
+ componentFolderName,
14
+ prepareWorkspaceInstallation,
15
+ readLibraryManifest,
16
+ removeLegacyLinks,
17
+ writeLibraryManifest,
18
+ type LibraryComponent,
19
+ type LibraryManifestEntry,
20
+ } from './workspace-installation.js';
11
21
 
12
22
  type StoredLibraryEntry = {
13
23
  id: string;
@@ -69,6 +79,25 @@ function installationFolder(content: string, fallback = '') {
69
79
  return componentFolderName(String(data.name || data.metadata?.title || fallback));
70
80
  }
71
81
 
82
+ function installableComponent(entry: LibraryEntry): LibraryComponent {
83
+ const slug = installationFolder(entry.content, entry.name);
84
+ if (entry.kind === 'skill') {
85
+ const files = Object.fromEntries(Object.entries(entry.assets).map(([name, bytes]) => [name, Buffer.from(bytes, 'base64')]));
86
+ return { kind: 'skill', slug, files: { ...files, 'SKILL.md': Buffer.from(entry.content, 'utf8') } };
87
+ }
88
+ const { data, content } = parseFrontMatter(entry.content);
89
+ const toml = (fields: Record<string, string>) => `${Object.entries(fields).map(([key, value]) => `${key} = ${JSON.stringify(value)}`).join('\n')}\n`;
90
+ const name = String(data.name || entry.name);
91
+ const description = entry.description || entry.name;
92
+ return {
93
+ kind: 'agent',
94
+ slug,
95
+ markdown: entry.content,
96
+ codex: toml({ name, description, developer_instructions: content }),
97
+ vibe: toml({ display_name: name, description, agent_type: 'subagent', instructions: content }),
98
+ };
99
+ }
100
+
72
101
  function collectionMainPath(entry: LibraryEntry, rootPath: string) {
73
102
  return entry.kind === 'skill' && !rootPath.toLowerCase().endsWith('.md') ? `${rootPath}/SKILL.md` : rootPath;
74
103
  }
@@ -109,6 +138,7 @@ export function createLibraryStore(home: string) {
109
138
  `);
110
139
  const originColumns = db.prepare('PRAGMA table_info(origins)').all() as Array<{ name: string }>;
111
140
  if (!originColumns.some((column) => column.name === 'source_hash')) db.exec('ALTER TABLE origins ADD COLUMN source_hash TEXT');
141
+ let defaultEntries: string[] = [];
112
142
 
113
143
  function workspace(value: unknown) {
114
144
  if (typeof value !== 'string' || !path.isAbsolute(value)) throw new Error('Dossier absolu requis.');
@@ -123,14 +153,54 @@ export function createLibraryStore(home: string) {
123
153
  return { ...entry, assets: JSON.parse(entry.assets) as Record<string, string> };
124
154
  }
125
155
 
156
+ function activeEntries(selected: string) {
157
+ return (db.prepare('SELECT entry_id AS entryId FROM activation WHERE workspace = ?').all(selected) as Array<{ entryId: string }>).map((row) => row.entryId);
158
+ }
159
+
160
+ function reconcile(selected: string) {
161
+ const known = (id: string) => Boolean(db.prepare('SELECT 1 FROM entries WHERE id = ?').get(id));
162
+ const manifest = readLibraryManifest(selected);
163
+ if (manifest) {
164
+ const expected = manifest.map((entry) => entry.id).filter(known);
165
+ const current = activeEntries(selected);
166
+ if (expected.length === current.length && expected.every((id) => current.includes(id))) return;
167
+ db.transaction(() => {
168
+ db.prepare('DELETE FROM activation WHERE workspace = ?').run(selected);
169
+ for (const id of expected) db.prepare('INSERT OR IGNORE INTO activation VALUES (?, ?)').run(selected, id);
170
+ })();
171
+ return;
172
+ }
173
+ const inherited = activeEntries(selected);
174
+ const defaults = selected === fs.realpathSync(os.homedir()) ? [] : defaultEntries.filter((id) => known(id) && !inherited.includes(id));
175
+ if (!inherited.length && !defaults.length) return;
176
+ const legacy = path.join(directory, 'active');
177
+ try {
178
+ removeLegacyLinks(selected, legacy);
179
+ writeLibraryManifest(selected, inherited.map((id) => {
180
+ const entry = document(id);
181
+ const slug = entry.kind === 'connector' ? entry.name : installationFolder(entry.content, entry.name);
182
+ const files = entry.kind === 'connector' ? {} : componentFileHashes(selected, installableComponent(entry), false);
183
+ return { id, kind: entry.kind, name: entry.name, slug, files, skipClaude: false };
184
+ }));
185
+ } catch { return; }
186
+ db.prepare('DELETE FROM activation WHERE workspace = ?').run(selected);
187
+ for (const id of [...inherited, ...defaults]) {
188
+ try { setEnabled(selected, id, true); }
189
+ catch { writeLibraryManifest(selected, (readLibraryManifest(selected) ?? []).filter((entry) => entry.id !== id)); }
190
+ }
191
+ fs.rmSync(path.join(legacy, createHash('sha256').update(selected).digest('hex')), { recursive: true, force: true });
192
+ }
193
+
126
194
  function list(workspacePath?: string) {
127
195
  const selected = workspacePath ? workspace(workspacePath) : '';
196
+ if (selected) reconcile(selected);
128
197
  return db.prepare(`SELECT e.id, e.kind, e.name, e.description,
129
198
  EXISTS(SELECT 1 FROM activation a WHERE a.entry_id = e.id AND a.workspace = ?) AS enabled,
130
- (SELECT ce.collection_id FROM collection_entries ce WHERE ce.entry_id = e.id LIMIT 1) AS collectionId
199
+ (SELECT ce.collection_id FROM collection_entries ce WHERE ce.entry_id = e.id LIMIT 1) AS collectionId,
200
+ (SELECT json_group_array(o.source) FROM origins o WHERE o.entry_id = e.id) AS sources
131
201
  FROM entries e ORDER BY e.name COLLATE NOCASE`).all(selected).map((row) => {
132
- const entry = row as Omit<StoredLibraryEntry, 'content' | 'assets'> & { enabled: number; collectionId: string | null };
133
- return { ...entry, enabled: Boolean(entry.enabled), collectionId: entry.collectionId || null };
202
+ const entry = row as Omit<StoredLibraryEntry, 'content' | 'assets'> & { enabled: number; collectionId: string | null; sources: string };
203
+ return { ...entry, enabled: Boolean(entry.enabled), collectionId: entry.collectionId || null, sources: JSON.parse(entry.sources) as string[] };
134
204
  });
135
205
  }
136
206
 
@@ -225,9 +295,6 @@ export function createLibraryStore(home: string) {
225
295
  const entry = document(id);
226
296
  if (entry.content !== previousContent) throw new Error('Le document a été modifié ailleurs. Rouvrez-le avant d’enregistrer.');
227
297
  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
- }
231
298
  if (entry.kind === 'connector') {
232
299
  const config = parseConnectorConfig(content);
233
300
  const description = config.url || config.command || entry.name;
@@ -305,6 +372,7 @@ export function createLibraryStore(home: string) {
305
372
 
306
373
  function listCollections(workspacePath?: string) {
307
374
  const selected = workspacePath ? workspace(workspacePath) : '';
375
+ if (selected) reconcile(selected);
308
376
  return db.prepare(`SELECT c.id, c.name, c.description, c.source,
309
377
  COUNT(ce.entry_id) AS entryCount,
310
378
  SUM(CASE WHEN a.entry_id IS NOT NULL THEN 1 ELSE 0 END) AS enabledCount
@@ -442,7 +510,7 @@ export function createLibraryStore(home: string) {
442
510
  throw new Error('Fichier introuvable.');
443
511
  }
444
512
 
445
- function setCollectionEnabled(workspacePath: unknown, id: string, enabled: unknown) {
513
+ function setCollectionEnabled(workspacePath: unknown, id: string, enabled: unknown, options: { skipClaude?: boolean } = {}) {
446
514
  if (typeof enabled !== 'boolean') throw new Error('Activation booléenne requise.');
447
515
  const selected = workspace(workspacePath);
448
516
  const mappings = collectionEntries(id);
@@ -452,7 +520,7 @@ export function createLibraryStore(home: string) {
452
520
  try {
453
521
  for (const mapping of mappings) {
454
522
  if (active.has(mapping.entryId) === enabled) continue;
455
- setEnabled(selected, mapping.entryId, enabled);
523
+ setEnabled(selected, mapping.entryId, enabled, options);
456
524
  changed.push(mapping.entryId);
457
525
  }
458
526
  } catch (error) {
@@ -462,59 +530,26 @@ export function createLibraryStore(home: string) {
462
530
  return listCollections(selected).find((collection) => collection.id === id);
463
531
  }
464
532
 
465
- function setEnabled(workspacePath: unknown, id: string, enabled: unknown) {
533
+ function setEnabled(workspacePath: unknown, id: string, enabled: unknown, options: { skipClaude?: boolean } = {}) {
466
534
  if (typeof enabled !== 'boolean') throw new Error('Activation booléenne requise.');
467
535
  const selected = workspace(workspacePath);
468
536
  const entry = document(id);
537
+ const manifest = readLibraryManifest(selected) ?? [];
538
+ const previous = manifest.find((item) => item.id === id);
539
+ const skipClaude = options.skipClaude ?? previous?.skipClaude ?? false;
540
+ const installed: LibraryManifestEntry = { id, kind: entry.kind, name: entry.name, slug: entry.name, files: {}, skipClaude };
469
541
  if (entry.kind === 'connector') {
470
- prepareConnectorInstallation(selected, entry.name, parseConnectorConfig(entry.content), enabled)();
471
- if (enabled) db.prepare('INSERT OR IGNORE INTO activation VALUES (?, ?)').run(selected, id);
472
- else db.prepare('DELETE FROM activation WHERE workspace = ? AND entry_id = ?').run(selected, id);
473
- return { ok: true, enabled };
474
- }
475
- const packageRoot = path.join(directory, 'active', createHash('sha256').update(selected).digest('hex'), id);
476
- const install = prepareWorkspaceInstallation(selected, id, installationFolder(entry.content, entry.name), packageRoot, entry.kind, enabled);
477
- if (enabled) {
478
- fs.mkdirSync(path.dirname(packageRoot), { recursive: true, mode: 0o700 });
479
- if (!fs.realpathSync(path.dirname(packageRoot)).startsWith(fs.realpathSync(directory) + path.sep)) throw new Error('Répertoire d’activation non autorisé.');
480
- const staging = fs.mkdtempSync(path.join(directory, '.activation-'));
481
- const backup = `${staging}.previous`;
482
- try {
483
- for (const [relative, bytes] of Object.entries(entry.assets)) {
484
- const target = path.resolve(staging, relative);
485
- if (!target.startsWith(staging + path.sep)) throw new Error('Chemin associé invalide.');
486
- fs.mkdirSync(path.dirname(target), { recursive: true, mode: 0o700 });
487
- fs.writeFileSync(target, Buffer.from(bytes, 'base64'), { mode: 0o600 });
488
- }
489
- if (entry.kind === 'skill') fs.writeFileSync(path.join(staging, 'SKILL.md'), entry.content, { mode: 0o600 });
490
- else {
491
- fs.writeFileSync(path.join(staging, 'agent.md'), entry.content, { mode: 0o600 });
492
- const { data, content } = parseFrontMatter(entry.content);
493
- const toml = Object.entries({ name: String(data.name || entry.name), description: entry.description || entry.name, developer_instructions: content })
494
- .map(([key, value]) => `${key} = ${JSON.stringify(value)}`).join('\n');
495
- fs.writeFileSync(path.join(staging, 'agent.toml'), `${toml}\n`, { mode: 0o600 });
496
- }
497
- if (fs.existsSync(packageRoot)) fs.renameSync(packageRoot, backup);
498
- try {
499
- fs.renameSync(staging, packageRoot);
500
- install();
501
- }
502
- catch (error) {
503
- fs.rmSync(packageRoot, { recursive: true, force: true });
504
- if (fs.existsSync(backup)) fs.renameSync(backup, packageRoot);
505
- throw error;
506
- }
507
- } finally {
508
- fs.rmSync(staging, { recursive: true, force: true });
509
- fs.rmSync(backup, { recursive: true, force: true });
510
- }
511
- } else if (fs.existsSync(path.dirname(packageRoot))) {
512
- if (!fs.realpathSync(path.dirname(packageRoot)).startsWith(fs.realpathSync(directory) + path.sep)) throw new Error('Répertoire d’activation non autorisé.');
513
- install();
514
- fs.rmSync(packageRoot, { recursive: true, force: true });
542
+ prepareConnectorInstallation(selected, entry.name, parseConnectorConfig(entry.content), enabled, skipClaude)();
515
543
  } else {
516
- install();
544
+ const component = installableComponent(entry);
545
+ const installation = prepareWorkspaceInstallation(selected, previous, enabled ? { component, skipClaude } : null);
546
+ installation.commit();
547
+ installed.slug = component.slug;
548
+ installed.files = installation.files;
517
549
  }
550
+ const others = manifest.filter((item) => item.id !== id);
551
+ if (enabled) writeLibraryManifest(selected, [...others, installed]);
552
+ else if (previous) writeLibraryManifest(selected, others);
518
553
  if (enabled) db.prepare('INSERT OR IGNORE INTO activation VALUES (?, ?)').run(selected, id);
519
554
  else db.prepare('DELETE FROM activation WHERE workspace = ? AND entry_id = ?').run(selected, id);
520
555
  return { ok: true, enabled };
@@ -522,13 +557,14 @@ export function createLibraryStore(home: string) {
522
557
 
523
558
  function instructions(workspacePath: string) {
524
559
  const selected = workspace(workspacePath);
560
+ reconcile(selected);
525
561
  const entries = db.prepare(`SELECT e.* FROM entries e JOIN activation a ON a.entry_id = e.id
526
562
  WHERE a.workspace = ? AND e.kind IN ('skill', 'agent') ORDER BY e.name`).all(selected) as StoredLibraryEntry[];
527
563
  return entries.map((entry) => {
528
564
  const assets = JSON.parse(entry.assets) as Record<string, string>;
529
565
  const references = Object.entries(assets).filter(([name]) => name === 'table-columns.yaml')
530
566
  .map(([name, bytes]) => `### ${name}\n${Buffer.from(bytes, 'base64').toString('utf8')}`);
531
- const packageRoot = path.join(directory, 'active', createHash('sha256').update(selected).digest('hex'), entry.id);
567
+ const packageRoot = path.join(selected, '.agents', 'skills', installationFolder(entry.content, entry.name));
532
568
  return `## ${entry.kind === 'agent' ? 'Instructions de rôle' : 'Skill'} : ${entry.name}\n${entry.content}\n${references.join('\n\n')}\nFichiers associés disponibles dans : ${packageRoot}\n${Object.keys(assets).join('\n')}`;
533
569
  }).join('\n\n');
534
570
  }
@@ -552,6 +588,7 @@ export function createLibraryStore(home: string) {
552
588
  collectionFile,
553
589
  updateCollectionFile,
554
590
  setCollectionEnabled,
591
+ setDefaultEntries: (ids: string[]) => { defaultEntries = ids; },
555
592
  close: () => db.close(),
556
593
  };
557
594
  }