@kaitranntt/ccs 7.54.0 → 7.55.0-dev.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. package/README.md +26 -4
  2. package/config/base-codex.settings.json +4 -4
  3. package/dist/auth/commands/create-command.js +1 -1
  4. package/dist/auth/commands/create-command.js.map +1 -1
  5. package/dist/auth/commands/remove-command.js +1 -1
  6. package/dist/auth/commands/remove-command.js.map +1 -1
  7. package/dist/ccs.js +70 -370
  8. package/dist/ccs.js.map +1 -1
  9. package/dist/cliproxy/codex-plan-compatibility.d.ts +29 -0
  10. package/dist/cliproxy/codex-plan-compatibility.d.ts.map +1 -0
  11. package/dist/cliproxy/codex-plan-compatibility.js +130 -0
  12. package/dist/cliproxy/codex-plan-compatibility.js.map +1 -0
  13. package/dist/cliproxy/codex-reasoning-proxy.d.ts +5 -0
  14. package/dist/cliproxy/codex-reasoning-proxy.d.ts.map +1 -1
  15. package/dist/cliproxy/codex-reasoning-proxy.js +103 -20
  16. package/dist/cliproxy/codex-reasoning-proxy.js.map +1 -1
  17. package/dist/cliproxy/executor/index.d.ts.map +1 -1
  18. package/dist/cliproxy/executor/index.js +8 -0
  19. package/dist/cliproxy/executor/index.js.map +1 -1
  20. package/dist/cliproxy/index.d.ts +1 -0
  21. package/dist/cliproxy/index.d.ts.map +1 -1
  22. package/dist/cliproxy/index.js +6 -2
  23. package/dist/cliproxy/index.js.map +1 -1
  24. package/dist/cliproxy/model-catalog.d.ts.map +1 -1
  25. package/dist/cliproxy/model-catalog.js +81 -12
  26. package/dist/cliproxy/model-catalog.js.map +1 -1
  27. package/dist/cliproxy/model-config.d.ts.map +1 -1
  28. package/dist/cliproxy/model-config.js +2 -2
  29. package/dist/cliproxy/model-config.js.map +1 -1
  30. package/dist/cliproxy/services/variant-settings.d.ts +3 -1
  31. package/dist/cliproxy/services/variant-settings.d.ts.map +1 -1
  32. package/dist/cliproxy/services/variant-settings.js +5 -2
  33. package/dist/cliproxy/services/variant-settings.js.map +1 -1
  34. package/dist/commands/api-command/copy-command.d.ts +2 -0
  35. package/dist/commands/api-command/copy-command.d.ts.map +1 -0
  36. package/dist/commands/api-command/copy-command.js +41 -0
  37. package/dist/commands/api-command/copy-command.js.map +1 -0
  38. package/dist/commands/api-command/create-command.d.ts +2 -0
  39. package/dist/commands/api-command/create-command.d.ts.map +1 -0
  40. package/dist/commands/api-command/create-command.js +249 -0
  41. package/dist/commands/api-command/create-command.js.map +1 -0
  42. package/dist/commands/api-command/discover-command.d.ts +2 -0
  43. package/dist/commands/api-command/discover-command.d.ts.map +1 -0
  44. package/dist/commands/api-command/discover-command.js +69 -0
  45. package/dist/commands/api-command/discover-command.js.map +1 -0
  46. package/dist/commands/api-command/export-command.d.ts +2 -0
  47. package/dist/commands/api-command/export-command.d.ts.map +1 -0
  48. package/dist/commands/api-command/export-command.js +73 -0
  49. package/dist/commands/api-command/export-command.js.map +1 -0
  50. package/dist/commands/api-command/help.d.ts +3 -0
  51. package/dist/commands/api-command/help.d.ts.map +1 -0
  52. package/dist/commands/api-command/help.js +100 -0
  53. package/dist/commands/api-command/help.js.map +1 -0
  54. package/dist/commands/api-command/import-command.d.ts +2 -0
  55. package/dist/commands/api-command/import-command.d.ts.map +1 -0
  56. package/dist/commands/api-command/import-command.js +111 -0
  57. package/dist/commands/api-command/import-command.js.map +1 -0
  58. package/dist/commands/api-command/index.d.ts +3 -0
  59. package/dist/commands/api-command/index.d.ts.map +1 -0
  60. package/dist/commands/api-command/index.js +34 -0
  61. package/dist/commands/api-command/index.js.map +1 -0
  62. package/dist/commands/api-command/list-command.d.ts +2 -0
  63. package/dist/commands/api-command/list-command.d.ts.map +1 -0
  64. package/dist/commands/api-command/list-command.js +53 -0
  65. package/dist/commands/api-command/list-command.js.map +1 -0
  66. package/dist/commands/api-command/remove-command.d.ts +2 -0
  67. package/dist/commands/api-command/remove-command.d.ts.map +1 -0
  68. package/dist/commands/api-command/remove-command.js +63 -0
  69. package/dist/commands/api-command/remove-command.js.map +1 -0
  70. package/dist/commands/api-command/shared.d.ts +36 -0
  71. package/dist/commands/api-command/shared.d.ts.map +1 -0
  72. package/dist/commands/api-command/shared.js +164 -0
  73. package/dist/commands/api-command/shared.js.map +1 -0
  74. package/dist/commands/api-command.d.ts +1 -26
  75. package/dist/commands/api-command.d.ts.map +1 -1
  76. package/dist/commands/api-command.js +3 -807
  77. package/dist/commands/api-command.js.map +1 -1
  78. package/dist/commands/arg-extractor.d.ts +15 -0
  79. package/dist/commands/arg-extractor.d.ts.map +1 -1
  80. package/dist/commands/arg-extractor.js +48 -2
  81. package/dist/commands/arg-extractor.js.map +1 -1
  82. package/dist/commands/config-auth/index.d.ts.map +1 -1
  83. package/dist/commands/config-auth/index.js +35 -19
  84. package/dist/commands/config-auth/index.js.map +1 -1
  85. package/dist/commands/config-command-options.d.ts +14 -0
  86. package/dist/commands/config-command-options.d.ts.map +1 -0
  87. package/dist/commands/config-command-options.js +108 -0
  88. package/dist/commands/config-command-options.js.map +1 -0
  89. package/dist/commands/config-command.d.ts +1 -1
  90. package/dist/commands/config-command.d.ts.map +1 -1
  91. package/dist/commands/config-command.js +91 -104
  92. package/dist/commands/config-command.js.map +1 -1
  93. package/dist/commands/config-dashboard-host.d.ts +17 -0
  94. package/dist/commands/config-dashboard-host.d.ts.map +1 -0
  95. package/dist/commands/config-dashboard-host.js +99 -0
  96. package/dist/commands/config-dashboard-host.js.map +1 -0
  97. package/dist/commands/env-command.d.ts.map +1 -1
  98. package/dist/commands/env-command.js +5 -0
  99. package/dist/commands/env-command.js.map +1 -1
  100. package/dist/commands/help-command.js +1 -1
  101. package/dist/commands/help-command.js.map +1 -1
  102. package/dist/commands/named-command-router.d.ts +17 -0
  103. package/dist/commands/named-command-router.d.ts.map +1 -0
  104. package/dist/commands/named-command-router.js +39 -0
  105. package/dist/commands/named-command-router.js.map +1 -0
  106. package/dist/commands/persist-command.js +1 -1
  107. package/dist/commands/persist-command.js.map +1 -1
  108. package/dist/commands/root-command-router.d.ts +2 -0
  109. package/dist/commands/root-command-router.d.ts.map +1 -0
  110. package/dist/commands/root-command-router.js +209 -0
  111. package/dist/commands/root-command-router.js.map +1 -0
  112. package/dist/config/unified-config-loader.js +1 -1
  113. package/dist/config/unified-config-loader.js.map +1 -1
  114. package/dist/cursor/cursor-anthropic-response.d.ts +6 -0
  115. package/dist/cursor/cursor-anthropic-response.d.ts.map +1 -0
  116. package/dist/cursor/cursor-anthropic-response.js +190 -0
  117. package/dist/cursor/cursor-anthropic-response.js.map +1 -0
  118. package/dist/cursor/cursor-anthropic-translator.d.ts +11 -0
  119. package/dist/cursor/cursor-anthropic-translator.d.ts.map +1 -0
  120. package/dist/cursor/cursor-anthropic-translator.js +167 -0
  121. package/dist/cursor/cursor-anthropic-translator.js.map +1 -0
  122. package/dist/cursor/cursor-anthropic-types.d.ts +46 -0
  123. package/dist/cursor/cursor-anthropic-types.d.ts.map +1 -0
  124. package/dist/cursor/cursor-anthropic-types.js +3 -0
  125. package/dist/cursor/cursor-anthropic-types.js.map +1 -0
  126. package/dist/cursor/cursor-daemon-entry.d.ts.map +1 -1
  127. package/dist/cursor/cursor-daemon-entry.js +53 -24
  128. package/dist/cursor/cursor-daemon-entry.js.map +1 -1
  129. package/dist/cursor/cursor-models.d.ts.map +1 -1
  130. package/dist/cursor/cursor-models.js +36 -2
  131. package/dist/cursor/cursor-models.js.map +1 -1
  132. package/dist/glmt/glmt-proxy.d.ts +4 -3
  133. package/dist/glmt/glmt-proxy.d.ts.map +1 -1
  134. package/dist/glmt/glmt-proxy.js +4 -3
  135. package/dist/glmt/glmt-proxy.js.map +1 -1
  136. package/dist/glmt/sse-parser.d.ts +2 -0
  137. package/dist/glmt/sse-parser.d.ts.map +1 -1
  138. package/dist/glmt/sse-parser.js +4 -0
  139. package/dist/glmt/sse-parser.js.map +1 -1
  140. package/dist/management/instance-manager.d.ts +2 -1
  141. package/dist/management/instance-manager.d.ts.map +1 -1
  142. package/dist/management/instance-manager.js +23 -8
  143. package/dist/management/instance-manager.js.map +1 -1
  144. package/dist/management/profile-context-sync-lock.d.ts +3 -0
  145. package/dist/management/profile-context-sync-lock.d.ts.map +1 -1
  146. package/dist/management/profile-context-sync-lock.js +75 -3
  147. package/dist/management/profile-context-sync-lock.js.map +1 -1
  148. package/dist/management/recovery-manager.d.ts +2 -2
  149. package/dist/management/recovery-manager.js +2 -2
  150. package/dist/management/shared-manager.d.ts +31 -1
  151. package/dist/management/shared-manager.d.ts.map +1 -1
  152. package/dist/management/shared-manager.js +420 -23
  153. package/dist/management/shared-manager.js.map +1 -1
  154. package/dist/shared/claude-extension-setup.d.ts.map +1 -1
  155. package/dist/shared/claude-extension-setup.js +16 -2
  156. package/dist/shared/claude-extension-setup.js.map +1 -1
  157. package/dist/shared/provider-preset-catalog.d.ts +1 -1
  158. package/dist/shared/provider-preset-catalog.d.ts.map +1 -1
  159. package/dist/shared/provider-preset-catalog.js +34 -40
  160. package/dist/shared/provider-preset-catalog.js.map +1 -1
  161. package/dist/ui/assets/{accounts-CZEg1_PX.js → accounts-CxIwtPW5.js} +1 -1
  162. package/dist/ui/assets/{alert-dialog-DhwS38kc.js → alert-dialog-D0M-j0xk.js} +1 -1
  163. package/dist/ui/assets/{api-sWNND4wP.js → api-D_GvXEjg.js} +1 -1
  164. package/dist/ui/assets/{auth-section-nJIpOcnm.js → auth-section-DtxR8sof.js} +1 -1
  165. package/dist/ui/assets/{backups-section-D3A6hmrU.js → backups-section-DAPwVCGp.js} +1 -1
  166. package/dist/ui/assets/checkbox-D470Q1y9.js +1 -0
  167. package/dist/ui/assets/{claude-extension-BjInaILv.js → claude-extension-vRIHOe5q.js} +1 -1
  168. package/dist/ui/assets/cliproxy-DGsWe0cf.js +3 -0
  169. package/dist/ui/assets/{cliproxy-control-panel-CKO2Sn9B.js → cliproxy-control-panel-BoKpt64d.js} +1 -1
  170. package/dist/ui/assets/{confirm-dialog-DTKxwrat.js → confirm-dialog-BnNUvB5u.js} +1 -1
  171. package/dist/ui/assets/copilot-DCkXk9mK.js +3 -0
  172. package/dist/ui/assets/cursor-9aQfYlGU.js +1 -0
  173. package/dist/ui/assets/{droid-Cl8QsJJL.js → droid-Bzp6uHqU.js} +2 -2
  174. package/dist/ui/assets/{globalenv-section-C3dxxoD9.js → globalenv-section-BM9oGUk_.js} +1 -1
  175. package/dist/ui/assets/{health-BUifaDU7.js → health-DLIgkfxF.js} +1 -1
  176. package/dist/ui/assets/icons-DtwH984l.js +1 -0
  177. package/dist/ui/assets/index-C-7tLTU2.js +47 -0
  178. package/dist/ui/assets/index-ClEn7Y7g.css +1 -0
  179. package/dist/ui/assets/{index-CPdceT1C.js → index-D9tmeP-H.js} +1 -1
  180. package/dist/ui/assets/{index-CYo-E5rU.js → index-DGQhzUIq.js} +1 -1
  181. package/dist/ui/assets/{index-BOsbrhaa.js → index-DimlYMhI.js} +1 -1
  182. package/dist/ui/assets/{index-xayyyR26.js → index-DykKl5b0.js} +1 -1
  183. package/dist/ui/assets/providers/llama-cpp.svg +5 -0
  184. package/dist/ui/assets/{proxy-status-widget-D94htBPb.js → proxy-status-widget-DloYg7yP.js} +1 -1
  185. package/dist/ui/assets/{radix-ui-BR1vy4kf.js → radix-ui-Dt3edmE5.js} +8 -8
  186. package/dist/ui/assets/searchable-select-CSREngvO.js +1 -0
  187. package/dist/ui/assets/{separator-3fBbTn-V.js → separator-DzrBeBn-.js} +1 -1
  188. package/dist/ui/assets/{shared-q_FNNbjD.js → shared-qRzQxB-N.js} +1 -1
  189. package/dist/ui/assets/{switch-5N8qBdBr.js → switch-BP9rnaI6.js} +1 -1
  190. package/dist/ui/assets/{tanstack-e99Cjjy2.js → tanstack-B8i0evp-.js} +1 -1
  191. package/dist/ui/assets/{updates-CubQ54J0.js → updates-BQvHbU9s.js} +1 -1
  192. package/dist/ui/icons/novita.svg +9 -0
  193. package/dist/ui/index.html +5 -5
  194. package/dist/utils/config-manager.d.ts +1 -1
  195. package/dist/utils/config-manager.js +1 -1
  196. package/dist/utils/fetch-proxy-setup.d.ts.map +1 -1
  197. package/dist/utils/fetch-proxy-setup.js.map +1 -1
  198. package/dist/utils/glmt-deprecation.d.ts +11 -0
  199. package/dist/utils/glmt-deprecation.d.ts.map +1 -0
  200. package/dist/utils/glmt-deprecation.js +64 -0
  201. package/dist/utils/glmt-deprecation.js.map +1 -0
  202. package/dist/utils/shell-executor.d.ts.map +1 -1
  203. package/dist/utils/shell-executor.js +12 -0
  204. package/dist/utils/shell-executor.js.map +1 -1
  205. package/dist/web-server/index.d.ts +1 -0
  206. package/dist/web-server/index.d.ts.map +1 -1
  207. package/dist/web-server/index.js +39 -3
  208. package/dist/web-server/index.js.map +1 -1
  209. package/dist/web-server/routes/account-routes.js +2 -2
  210. package/dist/web-server/routes/account-routes.js.map +1 -1
  211. package/dist/web-server/routes/profile-routes.d.ts +1 -0
  212. package/dist/web-server/routes/profile-routes.d.ts.map +1 -1
  213. package/dist/web-server/routes/profile-routes.js +3 -0
  214. package/dist/web-server/routes/profile-routes.js.map +1 -1
  215. package/dist/web-server/routes/variant-routes.d.ts +1 -0
  216. package/dist/web-server/routes/variant-routes.d.ts.map +1 -1
  217. package/dist/web-server/routes/variant-routes.js +3 -0
  218. package/dist/web-server/routes/variant-routes.js.map +1 -1
  219. package/package.json +3 -2
  220. package/scripts/completion/README.md +1 -1
  221. package/scripts/completion/ccs.fish +1 -2
  222. package/scripts/completion/ccs.zsh +1 -2
  223. package/scripts/postinstall.js +3 -3
  224. package/dist/ui/assets/checkbox-CZrxD1iS.js +0 -1
  225. package/dist/ui/assets/cliproxy-BGiSCGkl.js +0 -3
  226. package/dist/ui/assets/copilot-CuRngdBg.js +0 -3
  227. package/dist/ui/assets/cursor-Dxo0uIiU.js +0 -1
  228. package/dist/ui/assets/icons-DrEfTmfX.js +0 -1
  229. package/dist/ui/assets/index-Btf_ow2V.css +0 -1
  230. package/dist/ui/assets/index-Cw9Urr0S.js +0 -47
@@ -6,6 +6,8 @@
6
6
  * ~/.claude/ ← ~/.ccs/shared/ ← instance/
7
7
  */
8
8
  import { AccountContextPolicy } from '../auth/account-context';
9
+ export declare function normalizePluginMetadataPathString(input: string, targetConfigDir?: string): string;
10
+ export declare function normalizePluginMetadataContent(original: string, targetConfigDir?: string): string;
9
11
  /**
10
12
  * SharedManager Class
11
13
  */
@@ -14,7 +16,10 @@ declare class SharedManager {
14
16
  private readonly sharedDir;
15
17
  private readonly claudeDir;
16
18
  private readonly instancesDir;
19
+ private readonly pluginLayoutLock;
17
20
  private readonly sharedItems;
21
+ private readonly sharedPluginEntries;
22
+ private readonly instanceLocalPluginMetadataFiles;
18
23
  private readonly advancedContinuityItems;
19
24
  constructor();
20
25
  /**
@@ -30,6 +35,11 @@ declare class SharedManager {
30
35
  * Link shared directories to instance
31
36
  */
32
37
  linkSharedDirectories(instancePath: string): void;
38
+ detachSharedDirectories(instancePath: string): void;
39
+ private ensureSharedPluginLayoutDefaults;
40
+ private linkInstancePlugins;
41
+ private getSharedPluginLinkItems;
42
+ private removeExistingPath;
33
43
  /**
34
44
  * Sync project workspace context based on account policy.
35
45
  *
@@ -54,6 +64,11 @@ declare class SharedManager {
54
64
  * ~/.ccs/shared/memory/<project>/
55
65
  */
56
66
  syncProjectMemories(instancePath: string): Promise<void>;
67
+ /**
68
+ * Normalize plugin metadata and reconcile marketplace metadata for the active config dir.
69
+ */
70
+ normalizeSharedPluginMetadataPaths(configDir?: string): void;
71
+ normalizeSharedPluginMetadataPathsLocked(configDir?: string): void;
57
72
  /**
58
73
  * Normalize plugin registry paths to use canonical ~/.claude/ paths
59
74
  * instead of instance-specific ~/.ccs/instances/<name>/ paths.
@@ -61,7 +76,19 @@ declare class SharedManager {
61
76
  * This ensures installed_plugins.json is consistent regardless of
62
77
  * which CCS instance installed the plugin.
63
78
  */
64
- normalizePluginRegistryPaths(): void;
79
+ normalizePluginRegistryPaths(configDir?: string): void;
80
+ /**
81
+ * Reconcile marketplace registry content into the active config dir while
82
+ * keeping the global ~/.claude copy up to date for non-instance flows.
83
+ */
84
+ normalizeMarketplaceRegistryPaths(configDir?: string): void;
85
+ private normalizePluginMetadataFiles;
86
+ private getPluginMetadataFilePaths;
87
+ private normalizePluginMetadataFile;
88
+ private getMarketplaceRegistrySourcePaths;
89
+ private buildMarketplaceRegistryContent;
90
+ private discoverMarketplaceEntries;
91
+ private writePluginMetadataFile;
65
92
  /**
66
93
  * Migrate from v3.1.1 (copied data in ~/.ccs/shared/) to v3.2.0 (symlinks to ~/.claude/)
67
94
  * Runs once on upgrade
@@ -119,6 +146,9 @@ declare class SharedManager {
119
146
  * Build a non-destructive conflict copy path.
120
147
  */
121
148
  private getConflictCopyPath;
149
+ private symlinkPointsTo;
150
+ private detachManagedPluginLayout;
151
+ private reconcileLocalMarketplaceRegistry;
122
152
  private resolveCanonicalPath;
123
153
  private isPathWithinDirectory;
124
154
  private pathExists;
@@ -1 +1 @@
1
- {"version":3,"file":"shared-manager.d.ts","sourceRoot":"","sources":["../../src/management/shared-manager.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,EAAE,oBAAoB,EAAiC,MAAM,yBAAyB,CAAC;AAQ9F;;GAEG;AACH,cAAM,aAAa;IACjB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAe;IAC3C,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAKtC;;IAiBF;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAoC7B;;;OAGG;IACH,uBAAuB,IAAI,IAAI;IA8E/B;;OAEG;IACH,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;IAyCjD;;;;;OAKG;IACG,kBAAkB,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IAoG3F;;;;;OAKG;IACG,+BAA+B,CACnC,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,IAAI,CAAC;IAsHhB;;;;;;;;OAQG;IACG,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAmF9D;;;;;;OAMG;IACH,4BAA4B,IAAI,IAAI;IA4BpC;;;OAGG;IACH,eAAe,IAAI,IAAI;IAoGvB;;;OAGG;IACH,uBAAuB,IAAI,IAAI;IA4E/B;;;OAGG;YACW,uBAAuB;IAkCrC;;OAEG;YACW,eAAe;IAgB7B;;OAEG;YACW,wBAAwB;IAStC;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAejC;;OAEG;IACH,OAAO,CAAC,2BAA2B;IA6BnC;;OAEG;YACW,yBAAyB;IAmBvC;;;OAGG;YACW,6BAA6B;IAiD3C;;OAEG;YACW,aAAa;IAc3B;;;OAGG;YACW,gCAAgC;IA0C9C;;OAEG;YACW,iBAAiB;IAiB/B;;OAEG;YACW,mBAAmB;IAiBjC,OAAO,CAAC,oBAAoB;IAQ5B,OAAO,CAAC,qBAAqB;YAaf,UAAU;YASV,eAAe;YAIf,QAAQ;IAWtB;;OAEG;IACH,OAAO,CAAC,qBAAqB;CAuB9B;AAED,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"shared-manager.d.ts","sourceRoot":"","sources":["../../src/management/shared-manager.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,OAAO,EAAE,oBAAoB,EAAiC,MAAM,yBAAyB,CAAC;AAiC9F,wBAAgB,iCAAiC,CAC/C,KAAK,EAAE,MAAM,EACb,eAAe,SAAqC,GACnD,MAAM,CAeR;AAoCD,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,MAAM,EAChB,eAAe,SAAqC,GACnD,MAAM,CAIR;AAED;;GAEG;AACH,cAAM,aAAa;IACjB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAyB;IAC1D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAe;IAC3C,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAIlC;IACF,OAAO,CAAC,QAAQ,CAAC,gCAAgC,CAAwC;IACzF,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAKtC;;IAkBF;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAoC7B;;;OAGG;IACH,uBAAuB,IAAI,IAAI;IAgF/B;;OAEG;IACH,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;IAsCjD,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;IAyBnD,OAAO,CAAC,gCAAgC;IAwBxC,OAAO,CAAC,mBAAmB;IA+C3B,OAAO,CAAC,wBAAwB;IAsBhC,OAAO,CAAC,kBAAkB;IA2B1B;;;;;OAKG;IACG,kBAAkB,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IAoG3F;;;;;OAKG;IACG,+BAA+B,CACnC,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,IAAI,CAAC;IAsHhB;;;;;;;;OAQG;IACG,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAmF9D;;OAEG;IACH,kCAAkC,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAK5D,wCAAwC,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAMlE;;;;;;OAMG;IACH,4BAA4B,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAStD;;;OAGG;IACH,iCAAiC,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAwB3D,OAAO,CAAC,4BAA4B;IAmBpC,OAAO,CAAC,0BAA0B;IAalC,OAAO,CAAC,2BAA2B;IAsBnC,OAAO,CAAC,iCAAiC;IAwBzC,OAAO,CAAC,+BAA+B;IAgDvC,OAAO,CAAC,0BAA0B;IAuBlC,OAAO,CAAC,uBAAuB;IAgB/B;;;OAGG;IACH,eAAe,IAAI,IAAI;IAoGvB;;;OAGG;IACH,uBAAuB,IAAI,IAAI;IA4E/B;;;OAGG;YACW,uBAAuB;IAkCrC;;OAEG;YACW,eAAe;IAgB7B;;OAEG;YACW,wBAAwB;IAStC;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAejC;;OAEG;IACH,OAAO,CAAC,2BAA2B;IA6BnC;;OAEG;YACW,yBAAyB;IAmBvC;;;OAGG;YACW,6BAA6B;IAiD3C;;OAEG;YACW,aAAa;IAc3B;;;OAGG;YACW,gCAAgC;IA0C9C;;OAEG;YACW,iBAAiB;IAiB/B;;OAEG;YACW,mBAAmB;IAiBjC,OAAO,CAAC,eAAe;IAavB,OAAO,CAAC,yBAAyB;IAkDjC,OAAO,CAAC,iCAAiC;IAiDzC,OAAO,CAAC,oBAAoB;IAQ5B,OAAO,CAAC,qBAAqB;YAaf,UAAU;YASV,eAAe;YAIf,QAAQ;IAWtB;;OAEG;IACH,OAAO,CAAC,qBAAqB;CAuB9B;AAED,eAAe,aAAa,CAAC"}
@@ -29,18 +29,85 @@ var __importStar = (this && this.__importStar) || function (mod) {
29
29
  __setModuleDefault(result, mod);
30
30
  return result;
31
31
  };
32
+ var __importDefault = (this && this.__importDefault) || function (mod) {
33
+ return (mod && mod.__esModule) ? mod : { "default": mod };
34
+ };
32
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.normalizePluginMetadataContent = exports.normalizePluginMetadataPathString = void 0;
33
37
  const fs = __importStar(require("fs"));
34
38
  const path = __importStar(require("path"));
35
39
  const os = __importStar(require("os"));
40
+ const profile_context_sync_lock_1 = __importDefault(require("./profile-context-sync-lock"));
36
41
  const ui_1 = require("../utils/ui");
37
42
  const account_context_1 = require("../auth/account-context");
38
43
  const config_manager_1 = require("../utils/config-manager");
44
+ const DEFAULT_INSTALLED_PLUGIN_REGISTRY = JSON.stringify({
45
+ version: 2,
46
+ plugins: {},
47
+ }, null, 2);
48
+ function getPluginPathModule(targetConfigDir, input) {
49
+ return targetConfigDir.includes('\\') || input.includes('\\') ? path.win32 : path.posix;
50
+ }
51
+ function normalizeTargetConfigDir(targetConfigDir, input) {
52
+ const pathModule = getPluginPathModule(targetConfigDir, input);
53
+ return pathModule.normalize(pathModule === path.win32
54
+ ? targetConfigDir.replace(/\//g, '\\')
55
+ : targetConfigDir.replace(/\\/g, '/'));
56
+ }
57
+ function normalizePluginMetadataPathString(input, targetConfigDir = path.join(os.homedir(), '.claude')) {
58
+ const match = input.match(/^(.*?)([\\/])(?:\.claude|\.ccs\2shared|\.ccs\2instances\2[^\\/]+)\2plugins(?:(\2.*))?$/);
59
+ if (!match) {
60
+ return input;
61
+ }
62
+ const pathModule = getPluginPathModule(targetConfigDir, input);
63
+ const normalizedTargetConfigDir = normalizeTargetConfigDir(targetConfigDir, input);
64
+ const suffix = match[3] ?? '';
65
+ const suffixSegments = suffix.split(/[\\/]+/).filter(Boolean);
66
+ return pathModule.join(normalizedTargetConfigDir, 'plugins', ...suffixSegments);
67
+ }
68
+ exports.normalizePluginMetadataPathString = normalizePluginMetadataPathString;
69
+ function normalizePluginMetadataValue(value, targetConfigDir) {
70
+ if (typeof value === 'string') {
71
+ const normalized = normalizePluginMetadataPathString(value, targetConfigDir);
72
+ return { normalized, changed: normalized !== value };
73
+ }
74
+ if (Array.isArray(value)) {
75
+ let changed = false;
76
+ const normalized = value.map((item) => {
77
+ const result = normalizePluginMetadataValue(item, targetConfigDir);
78
+ changed = changed || result.changed;
79
+ return result.normalized;
80
+ });
81
+ return { normalized, changed };
82
+ }
83
+ if (value && typeof value === 'object') {
84
+ let changed = false;
85
+ const normalized = Object.fromEntries(Object.entries(value).map(([key, item]) => {
86
+ const result = normalizePluginMetadataValue(item, targetConfigDir);
87
+ changed = changed || result.changed;
88
+ return [key, result.normalized];
89
+ }));
90
+ return { normalized, changed };
91
+ }
92
+ return { normalized: value, changed: false };
93
+ }
94
+ function normalizePluginMetadataContent(original, targetConfigDir = path.join(os.homedir(), '.claude')) {
95
+ const parsed = JSON.parse(original);
96
+ const result = normalizePluginMetadataValue(parsed, targetConfigDir);
97
+ return result.changed ? JSON.stringify(result.normalized, null, 2) : original;
98
+ }
99
+ exports.normalizePluginMetadataContent = normalizePluginMetadataContent;
39
100
  /**
40
101
  * SharedManager Class
41
102
  */
42
103
  class SharedManager {
43
104
  constructor() {
105
+ this.sharedPluginEntries = [
106
+ { name: 'cache', type: 'directory' },
107
+ { name: 'marketplaces', type: 'directory' },
108
+ { name: 'installed_plugins.json', type: 'file' },
109
+ ];
110
+ this.instanceLocalPluginMetadataFiles = new Set(['known_marketplaces.json']);
44
111
  this.advancedContinuityItems = [
45
112
  'session-env',
46
113
  'file-history',
@@ -52,6 +119,7 @@ class SharedManager {
52
119
  this.sharedDir = path.join(ccsDir, 'shared');
53
120
  this.claudeDir = path.join(this.homeDir, '.claude');
54
121
  this.instancesDir = path.join(ccsDir, 'instances');
122
+ this.pluginLayoutLock = new profile_context_sync_lock_1.default(this.instancesDir);
55
123
  this.sharedItems = [
56
124
  { name: 'commands', type: 'directory' },
57
125
  { name: 'skills', type: 'directory' },
@@ -106,6 +174,7 @@ class SharedManager {
106
174
  if (!fs.existsSync(this.sharedDir)) {
107
175
  fs.mkdirSync(this.sharedDir, { recursive: true, mode: 0o700 });
108
176
  }
177
+ this.ensureSharedPluginLayoutDefaults();
109
178
  // Create symlinks ~/.ccs/shared/* → ~/.claude/*
110
179
  for (const item of this.sharedItems) {
111
180
  const claudePath = path.join(this.claudeDir, item.name);
@@ -176,17 +245,13 @@ class SharedManager {
176
245
  linkSharedDirectories(instancePath) {
177
246
  this.ensureSharedDirectories();
178
247
  for (const item of this.sharedItems) {
248
+ if (item.name === 'plugins') {
249
+ this.linkInstancePlugins(instancePath);
250
+ continue;
251
+ }
179
252
  const linkPath = path.join(instancePath, item.name);
180
253
  const targetPath = path.join(this.sharedDir, item.name);
181
- // Remove existing file/directory/link
182
- if (fs.existsSync(linkPath)) {
183
- if (item.type === 'directory') {
184
- fs.rmSync(linkPath, { recursive: true, force: true });
185
- }
186
- else {
187
- fs.unlinkSync(linkPath);
188
- }
189
- }
254
+ this.removeExistingPath(linkPath, item.type);
190
255
  // Create symlink
191
256
  try {
192
257
  const symlinkType = item.type === 'directory' ? 'dir' : 'file';
@@ -208,8 +273,129 @@ class SharedManager {
208
273
  }
209
274
  }
210
275
  }
211
- // Normalize plugin registry paths after linking
212
- this.normalizePluginRegistryPaths();
276
+ this.normalizeSharedPluginMetadataPaths(instancePath);
277
+ }
278
+ detachSharedDirectories(instancePath) {
279
+ this.ensureSharedDirectories();
280
+ for (const item of this.sharedItems) {
281
+ const managedPath = path.join(instancePath, item.name);
282
+ if (!fs.existsSync(managedPath)) {
283
+ continue;
284
+ }
285
+ if (item.name === 'plugins') {
286
+ this.detachManagedPluginLayout(instancePath);
287
+ continue;
288
+ }
289
+ const stats = fs.lstatSync(managedPath);
290
+ if (!stats.isSymbolicLink()) {
291
+ continue;
292
+ }
293
+ if (this.symlinkPointsTo(managedPath, path.join(this.sharedDir, item.name))) {
294
+ this.removeExistingPath(managedPath, item.type);
295
+ }
296
+ }
297
+ }
298
+ ensureSharedPluginLayoutDefaults() {
299
+ const pluginsDir = path.join(this.claudeDir, 'plugins');
300
+ fs.mkdirSync(pluginsDir, { recursive: true, mode: 0o700 });
301
+ for (const entry of this.sharedPluginEntries) {
302
+ const entryPath = path.join(pluginsDir, entry.name);
303
+ if (fs.existsSync(entryPath)) {
304
+ continue;
305
+ }
306
+ if (entry.type === 'directory') {
307
+ fs.mkdirSync(entryPath, { recursive: true, mode: 0o700 });
308
+ continue;
309
+ }
310
+ fs.writeFileSync(entryPath, DEFAULT_INSTALLED_PLUGIN_REGISTRY, 'utf8');
311
+ }
312
+ const marketplaceRegistryPath = path.join(pluginsDir, 'known_marketplaces.json');
313
+ if (!fs.existsSync(marketplaceRegistryPath)) {
314
+ fs.writeFileSync(marketplaceRegistryPath, JSON.stringify({}, null, 2), 'utf8');
315
+ }
316
+ }
317
+ linkInstancePlugins(instancePath) {
318
+ const linkPath = path.join(instancePath, 'plugins');
319
+ const targetPath = path.join(this.sharedDir, 'plugins');
320
+ let linkStats = null;
321
+ try {
322
+ linkStats = fs.lstatSync(linkPath);
323
+ }
324
+ catch (err) {
325
+ if (err.code !== 'ENOENT') {
326
+ throw err;
327
+ }
328
+ }
329
+ if (linkStats?.isSymbolicLink() || (linkStats && !linkStats.isDirectory())) {
330
+ this.removeExistingPath(linkPath, linkStats.isDirectory() ? 'directory' : 'file');
331
+ }
332
+ if (!linkStats || !linkStats.isDirectory()) {
333
+ fs.mkdirSync(linkPath, { recursive: true, mode: 0o700 });
334
+ }
335
+ for (const item of this.getSharedPluginLinkItems()) {
336
+ const targetEntryPath = path.join(targetPath, item.name);
337
+ const linkEntryPath = path.join(linkPath, item.name);
338
+ this.removeExistingPath(linkEntryPath, item.type);
339
+ try {
340
+ const symlinkType = item.type === 'directory' ? 'dir' : 'file';
341
+ fs.symlinkSync(targetEntryPath, linkEntryPath, symlinkType);
342
+ }
343
+ catch (_err) {
344
+ if (process.platform === 'win32') {
345
+ if (item.type === 'directory') {
346
+ this.copyDirectoryFallback(targetEntryPath, linkEntryPath);
347
+ }
348
+ else {
349
+ fs.copyFileSync(targetEntryPath, linkEntryPath);
350
+ }
351
+ console.log((0, ui_1.warn)(`Symlink failed for plugins/${item.name}, copied instead (enable Developer Mode)`));
352
+ }
353
+ else {
354
+ throw _err;
355
+ }
356
+ }
357
+ }
358
+ }
359
+ getSharedPluginLinkItems() {
360
+ const sharedPluginsPath = path.join(this.sharedDir, 'plugins');
361
+ const items = new Map(this.sharedPluginEntries.map((entry) => [entry.name, { ...entry }]));
362
+ for (const entry of fs.readdirSync(sharedPluginsPath, { withFileTypes: true })) {
363
+ if (items.has(entry.name) || this.instanceLocalPluginMetadataFiles.has(entry.name)) {
364
+ continue;
365
+ }
366
+ const entryPath = path.join(sharedPluginsPath, entry.name);
367
+ const stats = fs.statSync(entryPath);
368
+ items.set(entry.name, {
369
+ name: entry.name,
370
+ type: stats.isDirectory() ? 'directory' : 'file',
371
+ });
372
+ }
373
+ return [...items.values()];
374
+ }
375
+ removeExistingPath(targetPath, typeHint) {
376
+ try {
377
+ const stats = fs.lstatSync(targetPath);
378
+ if (stats.isDirectory() && !stats.isSymbolicLink()) {
379
+ fs.rmSync(targetPath, { recursive: true, force: true });
380
+ return;
381
+ }
382
+ if (stats.isSymbolicLink() || typeHint === 'file') {
383
+ fs.unlinkSync(targetPath);
384
+ return;
385
+ }
386
+ fs.rmSync(targetPath, { recursive: true, force: true });
387
+ }
388
+ catch (err) {
389
+ if (err.code === 'ENOENT') {
390
+ return;
391
+ }
392
+ if (typeHint === 'directory') {
393
+ fs.rmSync(targetPath, { recursive: true, force: true });
394
+ }
395
+ else {
396
+ fs.rmSync(targetPath, { force: true });
397
+ }
398
+ }
213
399
  }
214
400
  /**
215
401
  * Sync project workspace context based on account policy.
@@ -437,6 +623,18 @@ class SharedManager {
437
623
  console.log((0, ui_1.ok)(`Synced shared project memory: ${migrated} migrated, ${merged} merged conflict(s), ${linked} linked`));
438
624
  }
439
625
  }
626
+ /**
627
+ * Normalize plugin metadata and reconcile marketplace metadata for the active config dir.
628
+ */
629
+ normalizeSharedPluginMetadataPaths(configDir) {
630
+ this.normalizePluginRegistryPaths(configDir);
631
+ this.normalizeMarketplaceRegistryPaths(configDir);
632
+ }
633
+ normalizeSharedPluginMetadataPathsLocked(configDir) {
634
+ this.pluginLayoutLock.withNamedLockSync('__plugin-layout__', () => {
635
+ this.normalizeSharedPluginMetadataPaths(configDir);
636
+ });
637
+ }
440
638
  /**
441
639
  * Normalize plugin registry paths to use canonical ~/.claude/ paths
442
640
  * instead of instance-specific ~/.ccs/instances/<name>/ paths.
@@ -444,29 +642,143 @@ class SharedManager {
444
642
  * This ensures installed_plugins.json is consistent regardless of
445
643
  * which CCS instance installed the plugin.
446
644
  */
447
- normalizePluginRegistryPaths() {
448
- const registryPath = path.join(this.claudeDir, 'plugins', 'installed_plugins.json');
449
- // Skip if registry doesn't exist
645
+ normalizePluginRegistryPaths(configDir) {
646
+ this.normalizePluginMetadataFiles('installed_plugins.json', configDir, 'Normalized plugin registry paths', 'plugin registry');
647
+ }
648
+ /**
649
+ * Reconcile marketplace registry content into the active config dir while
650
+ * keeping the global ~/.claude copy up to date for non-instance flows.
651
+ */
652
+ normalizeMarketplaceRegistryPaths(configDir) {
653
+ const successMessage = 'Synchronized marketplace registry paths';
654
+ const warningLabel = 'marketplace registry';
655
+ try {
656
+ const sourcePaths = this.getMarketplaceRegistrySourcePaths(configDir);
657
+ this.writePluginMetadataFile(path.join(this.claudeDir, 'plugins', 'known_marketplaces.json'), this.buildMarketplaceRegistryContent(sourcePaths, this.claudeDir), successMessage);
658
+ if (configDir && path.resolve(configDir) !== path.resolve(this.claudeDir)) {
659
+ this.writePluginMetadataFile(path.join(configDir, 'plugins', 'known_marketplaces.json'), this.buildMarketplaceRegistryContent(sourcePaths, configDir), successMessage);
660
+ }
661
+ }
662
+ catch (err) {
663
+ console.log((0, ui_1.warn)(`Could not synchronize ${warningLabel}: ${err.message}`));
664
+ }
665
+ }
666
+ normalizePluginMetadataFiles(fileName, configDir, successMessage, warningLabel) {
667
+ const seen = new Set();
668
+ for (const registryPath of this.getPluginMetadataFilePaths(fileName, configDir)) {
669
+ const dedupeKey = this.resolveCanonicalPath(registryPath);
670
+ if (seen.has(dedupeKey)) {
671
+ continue;
672
+ }
673
+ seen.add(dedupeKey);
674
+ this.normalizePluginMetadataFile(registryPath, successMessage, warningLabel);
675
+ }
676
+ }
677
+ getPluginMetadataFilePaths(fileName, configDir) {
678
+ const pluginDirs = new Set([
679
+ path.join(this.claudeDir, 'plugins'),
680
+ path.join(this.sharedDir, 'plugins'),
681
+ ]);
682
+ if (configDir && path.resolve(configDir) !== path.resolve(this.claudeDir)) {
683
+ pluginDirs.add(path.join(configDir, 'plugins'));
684
+ }
685
+ return [...pluginDirs].map((pluginDir) => path.join(pluginDir, fileName));
686
+ }
687
+ normalizePluginMetadataFile(registryPath, successMessage, warningLabel) {
450
688
  if (!fs.existsSync(registryPath)) {
451
689
  return;
452
690
  }
453
691
  try {
454
692
  const original = fs.readFileSync(registryPath, 'utf8');
455
- // Replace instance paths with canonical claude path
456
- // Pattern: /.ccs/instances/<instance-name>/ -> /.claude/
457
- const normalized = original.replace(/\/\.ccs\/instances\/[^/]+\//g, '/.claude/');
458
- // Only write if changes were made
693
+ const normalized = normalizePluginMetadataContent(original);
459
694
  if (normalized !== original) {
460
- // Validate JSON before writing
461
- JSON.parse(normalized);
462
695
  fs.writeFileSync(registryPath, normalized, 'utf8');
463
- console.log((0, ui_1.ok)('Normalized plugin registry paths'));
696
+ console.log((0, ui_1.ok)(successMessage));
464
697
  }
465
698
  }
466
699
  catch (err) {
467
- // Log warning but don't fail - registry may be malformed
468
- console.log((0, ui_1.warn)(`Could not normalize plugin registry: ${err.message}`));
700
+ console.log((0, ui_1.warn)(`Could not normalize ${warningLabel}: ${err.message}`));
701
+ }
702
+ }
703
+ getMarketplaceRegistrySourcePaths(configDir) {
704
+ const sourcePaths = new Set([
705
+ path.join(this.claudeDir, 'plugins', 'known_marketplaces.json'),
706
+ ]);
707
+ if (fs.existsSync(this.instancesDir)) {
708
+ for (const entry of fs.readdirSync(this.instancesDir, { withFileTypes: true })) {
709
+ if (!entry.isDirectory() || entry.name.startsWith('.')) {
710
+ continue;
711
+ }
712
+ sourcePaths.add(path.join(this.instancesDir, entry.name, 'plugins', 'known_marketplaces.json'));
713
+ }
714
+ }
715
+ if (configDir && path.resolve(configDir) !== path.resolve(this.claudeDir)) {
716
+ sourcePaths.add(path.join(configDir, 'plugins', 'known_marketplaces.json'));
717
+ }
718
+ return [...sourcePaths];
719
+ }
720
+ buildMarketplaceRegistryContent(sourcePaths, targetConfigDir) {
721
+ const merged = {};
722
+ for (const registryPath of sourcePaths) {
723
+ if (!fs.existsSync(registryPath)) {
724
+ continue;
725
+ }
726
+ try {
727
+ const parsed = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
728
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
729
+ continue;
730
+ }
731
+ for (const [name, value] of Object.entries(parsed)) {
732
+ merged[name] = normalizePluginMetadataValue(value, targetConfigDir).normalized;
733
+ }
734
+ }
735
+ catch (err) {
736
+ console.log((0, ui_1.warn)(`Skipping malformed marketplace registry ${registryPath}: ${err.message}`));
737
+ }
738
+ }
739
+ const discoveredEntries = this.discoverMarketplaceEntries(targetConfigDir);
740
+ for (const [name, value] of Object.entries(discoveredEntries)) {
741
+ const existing = merged[name];
742
+ if (existing && typeof existing === 'object' && !Array.isArray(existing)) {
743
+ merged[name] = {
744
+ ...existing,
745
+ installLocation: value.installLocation,
746
+ };
747
+ continue;
748
+ }
749
+ merged[name] = value;
750
+ }
751
+ for (const name of Object.keys(merged)) {
752
+ if (!(name in discoveredEntries)) {
753
+ delete merged[name];
754
+ }
755
+ }
756
+ return JSON.stringify(merged, null, 2);
757
+ }
758
+ discoverMarketplaceEntries(targetConfigDir) {
759
+ const marketplacesDir = path.join(targetConfigDir, 'plugins', 'marketplaces');
760
+ if (!fs.existsSync(marketplacesDir)) {
761
+ return {};
762
+ }
763
+ const discovered = {};
764
+ for (const entry of fs.readdirSync(marketplacesDir, { withFileTypes: true })) {
765
+ if (!entry.isDirectory()) {
766
+ continue;
767
+ }
768
+ discovered[entry.name] = {
769
+ installLocation: path.join(targetConfigDir, 'plugins', 'marketplaces', entry.name),
770
+ };
771
+ }
772
+ return discovered;
773
+ }
774
+ writePluginMetadataFile(registryPath, content, successMessage) {
775
+ fs.mkdirSync(path.dirname(registryPath), { recursive: true, mode: 0o700 });
776
+ const current = fs.existsSync(registryPath) ? fs.readFileSync(registryPath, 'utf8') : null;
777
+ if (current === content) {
778
+ return;
469
779
  }
780
+ fs.writeFileSync(registryPath, content, 'utf8');
781
+ console.log((0, ui_1.ok)(successMessage));
470
782
  }
471
783
  /**
472
784
  * Migrate from v3.1.1 (copied data in ~/.ccs/shared/) to v3.2.0 (symlinks to ~/.claude/)
@@ -855,6 +1167,91 @@ class SharedManager {
855
1167
  }
856
1168
  return candidate;
857
1169
  }
1170
+ symlinkPointsTo(linkPath, expectedTarget) {
1171
+ try {
1172
+ const currentTarget = fs.readlinkSync(linkPath);
1173
+ const resolvedCurrentTarget = path.resolve(path.dirname(linkPath), currentTarget);
1174
+ return (this.resolveCanonicalPath(resolvedCurrentTarget) ===
1175
+ this.resolveCanonicalPath(expectedTarget));
1176
+ }
1177
+ catch {
1178
+ return false;
1179
+ }
1180
+ }
1181
+ detachManagedPluginLayout(instancePath) {
1182
+ const pluginsPath = path.join(instancePath, 'plugins');
1183
+ if (!fs.existsSync(pluginsPath)) {
1184
+ return;
1185
+ }
1186
+ const stats = fs.lstatSync(pluginsPath);
1187
+ const sharedPluginsPath = path.join(this.sharedDir, 'plugins');
1188
+ if (stats.isSymbolicLink()) {
1189
+ if (this.symlinkPointsTo(pluginsPath, sharedPluginsPath)) {
1190
+ this.removeExistingPath(pluginsPath, 'directory');
1191
+ }
1192
+ return;
1193
+ }
1194
+ if (!stats.isDirectory()) {
1195
+ return;
1196
+ }
1197
+ let removedManagedEntries = false;
1198
+ for (const item of this.getSharedPluginLinkItems()) {
1199
+ const pluginEntryPath = path.join(pluginsPath, item.name);
1200
+ if (!fs.existsSync(pluginEntryPath)) {
1201
+ continue;
1202
+ }
1203
+ const entryStats = fs.lstatSync(pluginEntryPath);
1204
+ if (!entryStats.isSymbolicLink()) {
1205
+ continue;
1206
+ }
1207
+ if (this.symlinkPointsTo(pluginEntryPath, path.join(sharedPluginsPath, item.name))) {
1208
+ this.removeExistingPath(pluginEntryPath, item.type);
1209
+ removedManagedEntries = true;
1210
+ }
1211
+ }
1212
+ if (!removedManagedEntries) {
1213
+ return;
1214
+ }
1215
+ this.reconcileLocalMarketplaceRegistry(instancePath);
1216
+ if (fs.readdirSync(pluginsPath).length === 0) {
1217
+ fs.rmSync(pluginsPath, { recursive: true, force: true });
1218
+ }
1219
+ }
1220
+ reconcileLocalMarketplaceRegistry(configDir) {
1221
+ const registryPath = path.join(configDir, 'plugins', 'known_marketplaces.json');
1222
+ if (!fs.existsSync(registryPath)) {
1223
+ return;
1224
+ }
1225
+ const discoveredEntries = this.discoverMarketplaceEntries(configDir);
1226
+ if (Object.keys(discoveredEntries).length === 0) {
1227
+ this.removeExistingPath(registryPath, 'file');
1228
+ return;
1229
+ }
1230
+ let parsed = {};
1231
+ try {
1232
+ const raw = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
1233
+ if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
1234
+ parsed = raw;
1235
+ }
1236
+ }
1237
+ catch {
1238
+ parsed = {};
1239
+ }
1240
+ const reconciled = Object.fromEntries(Object.entries(discoveredEntries).map(([name, value]) => {
1241
+ const existing = parsed[name];
1242
+ if (existing && typeof existing === 'object' && !Array.isArray(existing)) {
1243
+ return [
1244
+ name,
1245
+ {
1246
+ ...normalizePluginMetadataValue(existing, configDir).normalized,
1247
+ installLocation: value.installLocation,
1248
+ },
1249
+ ];
1250
+ }
1251
+ return [name, value];
1252
+ }));
1253
+ this.writePluginMetadataFile(registryPath, JSON.stringify(reconciled, null, 2), 'Synchronized marketplace registry paths');
1254
+ }
858
1255
  resolveCanonicalPath(targetPath) {
859
1256
  try {
860
1257
  return fs.realpathSync.native(targetPath);