@kaitranntt/ccs 7.55.0 → 7.56.0-dev.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 (189) hide show
  1. package/README.md +13 -3
  2. package/dist/api/services/cliproxy-profile-bridge.d.ts +13 -0
  3. package/dist/api/services/cliproxy-profile-bridge.d.ts.map +1 -0
  4. package/dist/api/services/cliproxy-profile-bridge.js +147 -0
  5. package/dist/api/services/cliproxy-profile-bridge.js.map +1 -0
  6. package/dist/api/services/index.d.ts +3 -1
  7. package/dist/api/services/index.d.ts.map +1 -1
  8. package/dist/api/services/index.js +9 -1
  9. package/dist/api/services/index.js.map +1 -1
  10. package/dist/api/services/profile-reader.d.ts.map +1 -1
  11. package/dist/api/services/profile-reader.js +37 -14
  12. package/dist/api/services/profile-reader.js.map +1 -1
  13. package/dist/api/services/profile-types.d.ts +35 -0
  14. package/dist/api/services/profile-types.d.ts.map +1 -1
  15. package/dist/api/services/profile-writer.d.ts +7 -1
  16. package/dist/api/services/profile-writer.d.ts.map +1 -1
  17. package/dist/api/services/profile-writer.js +44 -1
  18. package/dist/api/services/profile-writer.js.map +1 -1
  19. package/dist/auth/commands/create-command.js +1 -1
  20. package/dist/auth/commands/create-command.js.map +1 -1
  21. package/dist/auth/commands/remove-command.js +1 -1
  22. package/dist/auth/commands/remove-command.js.map +1 -1
  23. package/dist/cliproxy/ai-providers/config-store.d.ts +19 -0
  24. package/dist/cliproxy/ai-providers/config-store.d.ts.map +1 -0
  25. package/dist/cliproxy/ai-providers/config-store.js +117 -0
  26. package/dist/cliproxy/ai-providers/config-store.js.map +1 -0
  27. package/dist/cliproxy/ai-providers/config-yaml-sections.d.ts +2 -0
  28. package/dist/cliproxy/ai-providers/config-yaml-sections.d.ts.map +1 -0
  29. package/dist/cliproxy/ai-providers/config-yaml-sections.js +43 -0
  30. package/dist/cliproxy/ai-providers/config-yaml-sections.js.map +1 -0
  31. package/dist/cliproxy/ai-providers/index.d.ts +4 -0
  32. package/dist/cliproxy/ai-providers/index.d.ts.map +1 -0
  33. package/dist/cliproxy/ai-providers/index.js +12 -0
  34. package/dist/cliproxy/ai-providers/index.js.map +1 -0
  35. package/dist/cliproxy/ai-providers/service.d.ts +6 -0
  36. package/dist/cliproxy/ai-providers/service.d.ts.map +1 -0
  37. package/dist/cliproxy/ai-providers/service.js +191 -0
  38. package/dist/cliproxy/ai-providers/service.js.map +1 -0
  39. package/dist/cliproxy/ai-providers/types.d.ts +97 -0
  40. package/dist/cliproxy/ai-providers/types.d.ts.map +1 -0
  41. package/dist/cliproxy/ai-providers/types.js +53 -0
  42. package/dist/cliproxy/ai-providers/types.js.map +1 -0
  43. package/dist/cliproxy/base-config-loader.d.ts +1 -1
  44. package/dist/cliproxy/base-config-loader.d.ts.map +1 -1
  45. package/dist/cliproxy/codex-plan-compatibility.d.ts +19 -0
  46. package/dist/cliproxy/codex-plan-compatibility.d.ts.map +1 -1
  47. package/dist/cliproxy/codex-plan-compatibility.js +62 -1
  48. package/dist/cliproxy/codex-plan-compatibility.js.map +1 -1
  49. package/dist/cliproxy/codex-reasoning-proxy.d.ts +5 -0
  50. package/dist/cliproxy/codex-reasoning-proxy.d.ts.map +1 -1
  51. package/dist/cliproxy/codex-reasoning-proxy.js +103 -20
  52. package/dist/cliproxy/codex-reasoning-proxy.js.map +1 -1
  53. package/dist/cliproxy/composite-validator.d.ts.map +1 -1
  54. package/dist/cliproxy/composite-validator.js.map +1 -1
  55. package/dist/cliproxy/config/env-builder.d.ts +2 -2
  56. package/dist/cliproxy/config/env-builder.d.ts.map +1 -1
  57. package/dist/cliproxy/config/extended-context-config.d.ts +1 -1
  58. package/dist/cliproxy/config/extended-context-config.d.ts.map +1 -1
  59. package/dist/cliproxy/config/generator.d.ts +1 -1
  60. package/dist/cliproxy/config/generator.d.ts.map +1 -1
  61. package/dist/cliproxy/config/path-resolver.d.ts +1 -1
  62. package/dist/cliproxy/config/path-resolver.d.ts.map +1 -1
  63. package/dist/cliproxy/config/thinking-config.d.ts +2 -2
  64. package/dist/cliproxy/config/thinking-config.d.ts.map +1 -1
  65. package/dist/cliproxy/config/thinking-config.js.map +1 -1
  66. package/dist/cliproxy/index.d.ts +2 -0
  67. package/dist/cliproxy/index.d.ts.map +1 -1
  68. package/dist/cliproxy/index.js +9 -2
  69. package/dist/cliproxy/index.js.map +1 -1
  70. package/dist/cliproxy/management-api-client.d.ts +9 -0
  71. package/dist/cliproxy/management-api-client.d.ts.map +1 -1
  72. package/dist/cliproxy/management-api-client.js +16 -3
  73. package/dist/cliproxy/management-api-client.js.map +1 -1
  74. package/dist/cliproxy/model-catalog.d.ts +1 -1
  75. package/dist/cliproxy/model-catalog.d.ts.map +1 -1
  76. package/dist/cliproxy/model-id-normalizer.d.ts +1 -1
  77. package/dist/cliproxy/model-id-normalizer.d.ts.map +1 -1
  78. package/dist/cliproxy/thinking-validator.d.ts +1 -1
  79. package/dist/cliproxy/thinking-validator.d.ts.map +1 -1
  80. package/dist/cliproxy/thinking-validator.js.map +1 -1
  81. package/dist/cliproxy/types.d.ts +22 -0
  82. package/dist/cliproxy/types.d.ts.map +1 -1
  83. package/dist/commands/api-command/create-command.d.ts.map +1 -1
  84. package/dist/commands/api-command/create-command.js +89 -0
  85. package/dist/commands/api-command/create-command.js.map +1 -1
  86. package/dist/commands/api-command/help.d.ts.map +1 -1
  87. package/dist/commands/api-command/help.js +6 -0
  88. package/dist/commands/api-command/help.js.map +1 -1
  89. package/dist/commands/api-command/shared.d.ts +2 -1
  90. package/dist/commands/api-command/shared.d.ts.map +1 -1
  91. package/dist/commands/api-command/shared.js +6 -0
  92. package/dist/commands/api-command/shared.js.map +1 -1
  93. package/dist/commands/config-dashboard-host.js +2 -2
  94. package/dist/commands/config-dashboard-host.js.map +1 -1
  95. package/dist/commands/help-command.d.ts.map +1 -1
  96. package/dist/commands/help-command.js +4 -0
  97. package/dist/commands/help-command.js.map +1 -1
  98. package/dist/config/unified-config-loader.d.ts +1 -1
  99. package/dist/config/unified-config-loader.d.ts.map +1 -1
  100. package/dist/config/unified-config-loader.js.map +1 -1
  101. package/dist/management/instance-manager.d.ts +2 -1
  102. package/dist/management/instance-manager.d.ts.map +1 -1
  103. package/dist/management/instance-manager.js +23 -9
  104. package/dist/management/instance-manager.js.map +1 -1
  105. package/dist/management/profile-context-sync-lock.d.ts +3 -0
  106. package/dist/management/profile-context-sync-lock.d.ts.map +1 -1
  107. package/dist/management/profile-context-sync-lock.js +75 -3
  108. package/dist/management/profile-context-sync-lock.js.map +1 -1
  109. package/dist/management/shared-manager.d.ts +21 -8
  110. package/dist/management/shared-manager.d.ts.map +1 -1
  111. package/dist/management/shared-manager.js +353 -24
  112. package/dist/management/shared-manager.js.map +1 -1
  113. package/dist/shared/claude-extension-setup.js +2 -2
  114. package/dist/shared/claude-extension-setup.js.map +1 -1
  115. package/dist/types/cli.d.ts +10 -9
  116. package/dist/types/cli.d.ts.map +1 -1
  117. package/dist/types/cli.js +8 -9
  118. package/dist/types/cli.js.map +1 -1
  119. package/dist/types/utils.d.ts +7 -6
  120. package/dist/types/utils.d.ts.map +1 -1
  121. package/dist/types/utils.js +6 -7
  122. package/dist/types/utils.js.map +1 -1
  123. package/dist/ui/assets/{accounts-CjGyCl_8.js → accounts-CccaoV-N.js} +1 -1
  124. package/dist/ui/assets/{alert-dialog-cy99V8EM.js → alert-dialog-D838sIju.js} +1 -1
  125. package/dist/ui/assets/antigravity-responsibility-constants-Bvh4Ybz0.js +1 -0
  126. package/dist/ui/assets/api-IXigV-A_.js +4 -0
  127. package/dist/ui/assets/{auth-section-DsDyGmzl.js → auth-section-BGCaHAcN.js} +1 -1
  128. package/dist/ui/assets/{backups-section-B63xrUmB.js → backups-section-CqZN-2Qq.js} +1 -1
  129. package/dist/ui/assets/{checkbox-AxZFvatO.js → checkbox-BgLi38k2.js} +1 -1
  130. package/dist/ui/assets/{claude-extension-N0lxo7BY.js → claude-extension-ClHnH_4i.js} +1 -1
  131. package/dist/ui/assets/cliproxy-ai-providers-EhHqrkRr.js +21 -0
  132. package/dist/ui/assets/cliproxy-control-panel-lRQBQhPS.js +1 -0
  133. package/dist/ui/assets/cliproxy-qX23viWV.js +3 -0
  134. package/dist/ui/assets/{confirm-dialog-D2bDFGvT.js → confirm-dialog-t6i94F2B.js} +1 -1
  135. package/dist/ui/assets/{copilot-INm6uOZF.js → copilot-DlSkTZ2q.js} +1 -1
  136. package/dist/ui/assets/{cursor-C8rDHKEp.js → cursor-DJvTEVHh.js} +1 -1
  137. package/dist/ui/assets/{droid-OZDvbiwC.js → droid-wevxXBVG.js} +2 -2
  138. package/dist/ui/assets/{globalenv-section-ClTBgKg5.js → globalenv-section-De072K_j.js} +1 -1
  139. package/dist/ui/assets/{health-CGaRANip.js → health-Wv80MRCF.js} +1 -1
  140. package/dist/ui/assets/icons-DMeZET56.js +1 -0
  141. package/dist/ui/assets/{index-DQ4jn6yA.js → index-BmB4ckDm.js} +1 -1
  142. package/dist/ui/assets/{index-CGOZU3jq.js → index-BqdBnk5l.js} +1 -1
  143. package/dist/ui/assets/{index-CCSdUs7x.js → index-C9TiCNLM.js} +1 -1
  144. package/dist/ui/assets/{index-DS1QEaxk.js → index-CxPqnEpn.js} +1 -1
  145. package/dist/ui/assets/index-DRmZP4OP.css +1 -0
  146. package/dist/ui/assets/index-N_jd9sKU.js +47 -0
  147. package/dist/ui/assets/providers/codex-color.svg +24 -0
  148. package/dist/ui/assets/providers/vertex.svg +25 -0
  149. package/dist/ui/assets/proxy-status-widget-DMe8konR.js +1 -0
  150. package/dist/ui/assets/{searchable-select-D6dcPTiL.js → searchable-select-C-dUgzQ_.js} +1 -1
  151. package/dist/ui/assets/{separator-Czy9IRpl.js → separator-TnQMGoEt.js} +1 -1
  152. package/dist/ui/assets/{shared-BUJNJgAr.js → shared-Cbcz8ZIu.js} +1 -1
  153. package/dist/ui/assets/{switch-Bog43RUy.js → switch-Df15Gmz9.js} +1 -1
  154. package/dist/ui/assets/{updates-jcWPnn9L.js → updates-WOEYNHTJ.js} +1 -1
  155. package/dist/ui/index.html +3 -3
  156. package/dist/utils/claude-config-path.d.ts.map +1 -1
  157. package/dist/utils/claude-config-path.js +2 -5
  158. package/dist/utils/claude-config-path.js.map +1 -1
  159. package/dist/utils/config-manager.d.ts +7 -1
  160. package/dist/utils/config-manager.d.ts.map +1 -1
  161. package/dist/utils/config-manager.js +30 -1
  162. package/dist/utils/config-manager.js.map +1 -1
  163. package/dist/utils/shell-executor.js +1 -1
  164. package/dist/utils/shell-executor.js.map +1 -1
  165. package/dist/web-server/routes/account-routes.d.ts.map +1 -1
  166. package/dist/web-server/routes/account-routes.js +26 -6
  167. package/dist/web-server/routes/account-routes.js.map +1 -1
  168. package/dist/web-server/routes/ai-provider-routes.d.ts +3 -0
  169. package/dist/web-server/routes/ai-provider-routes.d.ts.map +1 -0
  170. package/dist/web-server/routes/ai-provider-routes.js +112 -0
  171. package/dist/web-server/routes/ai-provider-routes.js.map +1 -0
  172. package/dist/web-server/routes/index.d.ts.map +1 -1
  173. package/dist/web-server/routes/index.js +2 -0
  174. package/dist/web-server/routes/index.js.map +1 -1
  175. package/dist/web-server/routes/profile-routes.d.ts.map +1 -1
  176. package/dist/web-server/routes/profile-routes.js +45 -0
  177. package/dist/web-server/routes/profile-routes.js.map +1 -1
  178. package/dist/web-server/routes/settings-routes.d.ts.map +1 -1
  179. package/dist/web-server/routes/settings-routes.js +3 -0
  180. package/dist/web-server/routes/settings-routes.js.map +1 -1
  181. package/package.json +2 -2
  182. package/scripts/dev-release.sh +103 -51
  183. package/dist/ui/assets/api-DKrhDjiy.js +0 -4
  184. package/dist/ui/assets/cliproxy-D2ozaUJ3.js +0 -3
  185. package/dist/ui/assets/cliproxy-control-panel-5fYLcZKs.js +0 -1
  186. package/dist/ui/assets/icons-DtwH984l.js +0 -1
  187. package/dist/ui/assets/index-ClEn7Y7g.css +0 -1
  188. package/dist/ui/assets/index-tMnuUCFi.js +0 -47
  189. package/dist/ui/assets/proxy-status-widget-PESjh9-t.js +0 -1
@@ -29,27 +29,52 @@ 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 });
33
36
  exports.normalizePluginMetadataContent = exports.normalizePluginMetadataPathString = void 0;
34
37
  const fs = __importStar(require("fs"));
35
38
  const path = __importStar(require("path"));
36
39
  const os = __importStar(require("os"));
40
+ const profile_context_sync_lock_1 = __importDefault(require("./profile-context-sync-lock"));
37
41
  const ui_1 = require("../utils/ui");
38
42
  const account_context_1 = require("../auth/account-context");
39
43
  const config_manager_1 = require("../utils/config-manager");
40
- function normalizePluginMetadataPathString(input) {
41
- return input.replace(/([\\/])\.ccs\1instances\1[^\\/]+\1/g, (_match, separator) => `${separator}.claude${separator}`);
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);
42
67
  }
43
68
  exports.normalizePluginMetadataPathString = normalizePluginMetadataPathString;
44
- function normalizePluginMetadataValue(value) {
69
+ function normalizePluginMetadataValue(value, targetConfigDir) {
45
70
  if (typeof value === 'string') {
46
- const normalized = normalizePluginMetadataPathString(value);
71
+ const normalized = normalizePluginMetadataPathString(value, targetConfigDir);
47
72
  return { normalized, changed: normalized !== value };
48
73
  }
49
74
  if (Array.isArray(value)) {
50
75
  let changed = false;
51
76
  const normalized = value.map((item) => {
52
- const result = normalizePluginMetadataValue(item);
77
+ const result = normalizePluginMetadataValue(item, targetConfigDir);
53
78
  changed = changed || result.changed;
54
79
  return result.normalized;
55
80
  });
@@ -58,7 +83,7 @@ function normalizePluginMetadataValue(value) {
58
83
  if (value && typeof value === 'object') {
59
84
  let changed = false;
60
85
  const normalized = Object.fromEntries(Object.entries(value).map(([key, item]) => {
61
- const result = normalizePluginMetadataValue(item);
86
+ const result = normalizePluginMetadataValue(item, targetConfigDir);
62
87
  changed = changed || result.changed;
63
88
  return [key, result.normalized];
64
89
  }));
@@ -66,9 +91,9 @@ function normalizePluginMetadataValue(value) {
66
91
  }
67
92
  return { normalized: value, changed: false };
68
93
  }
69
- function normalizePluginMetadataContent(original) {
94
+ function normalizePluginMetadataContent(original, targetConfigDir = path.join(os.homedir(), '.claude')) {
70
95
  const parsed = JSON.parse(original);
71
- const result = normalizePluginMetadataValue(parsed);
96
+ const result = normalizePluginMetadataValue(parsed, targetConfigDir);
72
97
  return result.changed ? JSON.stringify(result.normalized, null, 2) : original;
73
98
  }
74
99
  exports.normalizePluginMetadataContent = normalizePluginMetadataContent;
@@ -77,6 +102,12 @@ exports.normalizePluginMetadataContent = normalizePluginMetadataContent;
77
102
  */
78
103
  class SharedManager {
79
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']);
80
111
  this.advancedContinuityItems = [
81
112
  'session-env',
82
113
  'file-history',
@@ -88,6 +119,7 @@ class SharedManager {
88
119
  this.sharedDir = path.join(ccsDir, 'shared');
89
120
  this.claudeDir = path.join(this.homeDir, '.claude');
90
121
  this.instancesDir = path.join(ccsDir, 'instances');
122
+ this.pluginLayoutLock = new profile_context_sync_lock_1.default(this.instancesDir);
91
123
  this.sharedItems = [
92
124
  { name: 'commands', type: 'directory' },
93
125
  { name: 'skills', type: 'directory' },
@@ -142,6 +174,7 @@ class SharedManager {
142
174
  if (!fs.existsSync(this.sharedDir)) {
143
175
  fs.mkdirSync(this.sharedDir, { recursive: true, mode: 0o700 });
144
176
  }
177
+ this.ensureSharedPluginLayoutDefaults();
145
178
  // Create symlinks ~/.ccs/shared/* → ~/.claude/*
146
179
  for (const item of this.sharedItems) {
147
180
  const claudePath = path.join(this.claudeDir, item.name);
@@ -212,17 +245,13 @@ class SharedManager {
212
245
  linkSharedDirectories(instancePath) {
213
246
  this.ensureSharedDirectories();
214
247
  for (const item of this.sharedItems) {
248
+ if (item.name === 'plugins') {
249
+ this.linkInstancePlugins(instancePath);
250
+ continue;
251
+ }
215
252
  const linkPath = path.join(instancePath, item.name);
216
253
  const targetPath = path.join(this.sharedDir, item.name);
217
- // Remove existing file/directory/link
218
- if (fs.existsSync(linkPath)) {
219
- if (item.type === 'directory') {
220
- fs.rmSync(linkPath, { recursive: true, force: true });
221
- }
222
- else {
223
- fs.unlinkSync(linkPath);
224
- }
225
- }
254
+ this.removeExistingPath(linkPath, item.type);
226
255
  // Create symlink
227
256
  try {
228
257
  const symlinkType = item.type === 'directory' ? 'dir' : 'file';
@@ -246,6 +275,128 @@ class SharedManager {
246
275
  }
247
276
  this.normalizeSharedPluginMetadataPaths(instancePath);
248
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
+ }
399
+ }
249
400
  /**
250
401
  * Sync project workspace context based on account policy.
251
402
  *
@@ -473,12 +624,17 @@ class SharedManager {
473
624
  }
474
625
  }
475
626
  /**
476
- * Normalize shared plugin metadata files to canonical ~/.claude/ paths.
627
+ * Normalize plugin metadata and reconcile marketplace metadata for the active config dir.
477
628
  */
478
629
  normalizeSharedPluginMetadataPaths(configDir) {
479
630
  this.normalizePluginRegistryPaths(configDir);
480
631
  this.normalizeMarketplaceRegistryPaths(configDir);
481
632
  }
633
+ normalizeSharedPluginMetadataPathsLocked(configDir) {
634
+ this.pluginLayoutLock.withNamedLockSync('__plugin-layout__', () => {
635
+ this.normalizeSharedPluginMetadataPaths(configDir);
636
+ });
637
+ }
482
638
  /**
483
639
  * Normalize plugin registry paths to use canonical ~/.claude/ paths
484
640
  * instead of instance-specific ~/.ccs/instances/<name>/ paths.
@@ -490,14 +646,22 @@ class SharedManager {
490
646
  this.normalizePluginMetadataFiles('installed_plugins.json', configDir, 'Normalized plugin registry paths', 'plugin registry');
491
647
  }
492
648
  /**
493
- * Normalize marketplace registry paths to use canonical ~/.claude/ paths
494
- * instead of instance-specific ~/.ccs/instances/<name>/ paths.
495
- *
496
- * This ensures known_marketplaces.json is consistent regardless of
497
- * which CCS instance added the marketplace.
649
+ * Reconcile marketplace registry content into the active config dir while
650
+ * keeping the global ~/.claude copy up to date for non-instance flows.
498
651
  */
499
652
  normalizeMarketplaceRegistryPaths(configDir) {
500
- this.normalizePluginMetadataFiles('known_marketplaces.json', configDir, 'Normalized marketplace registry paths', 'marketplace registry');
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
+ }
501
665
  }
502
666
  normalizePluginMetadataFiles(fileName, configDir, successMessage, warningLabel) {
503
667
  const seen = new Set();
@@ -536,6 +700,86 @@ class SharedManager {
536
700
  console.log((0, ui_1.warn)(`Could not normalize ${warningLabel}: ${err.message}`));
537
701
  }
538
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;
779
+ }
780
+ fs.writeFileSync(registryPath, content, 'utf8');
781
+ console.log((0, ui_1.ok)(successMessage));
782
+ }
539
783
  /**
540
784
  * Migrate from v3.1.1 (copied data in ~/.ccs/shared/) to v3.2.0 (symlinks to ~/.claude/)
541
785
  * Runs once on upgrade
@@ -923,6 +1167,91 @@ class SharedManager {
923
1167
  }
924
1168
  return candidate;
925
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
+ }
926
1255
  resolveCanonicalPath(targetPath) {
927
1256
  try {
928
1257
  return fs.realpathSync.native(targetPath);