@kaitranntt/ccs 7.54.0-dev.9 → 7.55.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.
- package/README.md +10 -3
- package/dist/auth/commands/create-command.js +1 -1
- package/dist/auth/commands/create-command.js.map +1 -1
- package/dist/auth/commands/remove-command.js +1 -1
- package/dist/auth/commands/remove-command.js.map +1 -1
- package/dist/ccs.js +68 -258
- package/dist/ccs.js.map +1 -1
- package/dist/commands/api-command/create-command.d.ts.map +1 -1
- package/dist/commands/api-command/create-command.js +10 -0
- package/dist/commands/api-command/create-command.js.map +1 -1
- package/dist/commands/env-command.d.ts.map +1 -1
- package/dist/commands/env-command.js +5 -0
- package/dist/commands/env-command.js.map +1 -1
- package/dist/commands/help-command.d.ts.map +1 -1
- package/dist/commands/help-command.js +0 -1
- package/dist/commands/help-command.js.map +1 -1
- package/dist/commands/persist-command.js +1 -1
- package/dist/commands/persist-command.js.map +1 -1
- package/dist/config/unified-config-loader.js +1 -1
- package/dist/config/unified-config-loader.js.map +1 -1
- package/dist/glmt/glmt-proxy.d.ts +4 -3
- package/dist/glmt/glmt-proxy.d.ts.map +1 -1
- package/dist/glmt/glmt-proxy.js +4 -3
- package/dist/glmt/glmt-proxy.js.map +1 -1
- package/dist/management/instance-manager.d.ts +2 -1
- package/dist/management/instance-manager.d.ts.map +1 -1
- package/dist/management/instance-manager.js +23 -9
- package/dist/management/instance-manager.js.map +1 -1
- package/dist/management/profile-context-sync-lock.d.ts +3 -0
- package/dist/management/profile-context-sync-lock.d.ts.map +1 -1
- package/dist/management/profile-context-sync-lock.js +75 -3
- package/dist/management/profile-context-sync-lock.js.map +1 -1
- package/dist/management/recovery-manager.d.ts +2 -2
- package/dist/management/recovery-manager.js +2 -2
- package/dist/management/shared-manager.d.ts +21 -8
- package/dist/management/shared-manager.d.ts.map +1 -1
- package/dist/management/shared-manager.js +353 -24
- package/dist/management/shared-manager.js.map +1 -1
- package/dist/shared/claude-extension-setup.d.ts.map +1 -1
- package/dist/shared/claude-extension-setup.js +15 -4
- package/dist/shared/claude-extension-setup.js.map +1 -1
- package/dist/shared/provider-preset-catalog.d.ts +1 -1
- package/dist/shared/provider-preset-catalog.d.ts.map +1 -1
- package/dist/shared/provider-preset-catalog.js +2 -25
- package/dist/shared/provider-preset-catalog.js.map +1 -1
- package/dist/ui/assets/{accounts-v8ElDmpv.js → accounts-CjGyCl_8.js} +1 -1
- package/dist/ui/assets/{alert-dialog-L5Ct0Tm9.js → alert-dialog-cy99V8EM.js} +1 -1
- package/dist/ui/assets/{api-C5CuNrT6.js → api-DKrhDjiy.js} +1 -1
- package/dist/ui/assets/{auth-section-P8GsBk1u.js → auth-section-DsDyGmzl.js} +1 -1
- package/dist/ui/assets/{backups-section-gBIJ2rQw.js → backups-section-B63xrUmB.js} +1 -1
- package/dist/ui/assets/{checkbox-DQ1oAx_4.js → checkbox-AxZFvatO.js} +1 -1
- package/dist/ui/assets/{claude-extension-CjEyjaGL.js → claude-extension-N0lxo7BY.js} +1 -1
- package/dist/ui/assets/{cliproxy-C0dWlSJs.js → cliproxy-D2ozaUJ3.js} +1 -1
- package/dist/ui/assets/{cliproxy-control-panel-CdCrzgLe.js → cliproxy-control-panel-5fYLcZKs.js} +1 -1
- package/dist/ui/assets/{confirm-dialog-DaydoeD7.js → confirm-dialog-D2bDFGvT.js} +1 -1
- package/dist/ui/assets/{copilot-SA3U22Y9.js → copilot-INm6uOZF.js} +1 -1
- package/dist/ui/assets/{cursor-BfkF3VwC.js → cursor-C8rDHKEp.js} +1 -1
- package/dist/ui/assets/{droid-BfSJyZXD.js → droid-OZDvbiwC.js} +1 -1
- package/dist/ui/assets/{globalenv-section-rg8Ctc2R.js → globalenv-section-ClTBgKg5.js} +1 -1
- package/dist/ui/assets/{health-Cii2iTlp.js → health-CGaRANip.js} +1 -1
- package/dist/ui/assets/{index-B4Qc134R.js → index-CCSdUs7x.js} +1 -1
- package/dist/ui/assets/{index-D1CmdSLO.js → index-CGOZU3jq.js} +1 -1
- package/dist/ui/assets/{index-Gpb2Emvl.js → index-DQ4jn6yA.js} +1 -1
- package/dist/ui/assets/{index-CBleAyoZ.js → index-DS1QEaxk.js} +1 -1
- package/dist/ui/assets/{index-og5NMtJP.js → index-tMnuUCFi.js} +14 -14
- package/dist/ui/assets/{proxy-status-widget-CZnGc4zY.js → proxy-status-widget-PESjh9-t.js} +1 -1
- package/dist/ui/assets/{searchable-select-BHhY-dTx.js → searchable-select-D6dcPTiL.js} +1 -1
- package/dist/ui/assets/{separator-7ggevEK6.js → separator-Czy9IRpl.js} +1 -1
- package/dist/ui/assets/{shared-CoiUG_Qa.js → shared-BUJNJgAr.js} +1 -1
- package/dist/ui/assets/{switch-3-7lTxFj.js → switch-Bog43RUy.js} +1 -1
- package/dist/ui/assets/{updates-rQ3GVJIq.js → updates-jcWPnn9L.js} +1 -1
- package/dist/ui/index.html +1 -1
- package/dist/utils/config-manager.d.ts +1 -1
- package/dist/utils/config-manager.js +1 -1
- package/dist/utils/glmt-deprecation.d.ts +11 -0
- package/dist/utils/glmt-deprecation.d.ts.map +1 -0
- package/dist/utils/glmt-deprecation.js +64 -0
- package/dist/utils/glmt-deprecation.js.map +1 -0
- package/dist/utils/shell-executor.js +1 -1
- package/dist/utils/shell-executor.js.map +1 -1
- package/dist/web-server/routes/account-routes.js +2 -2
- package/dist/web-server/routes/account-routes.js.map +1 -1
- package/package.json +2 -2
- package/scripts/completion/README.md +1 -1
- package/scripts/completion/ccs.fish +1 -2
- package/scripts/completion/ccs.zsh +1 -2
- package/scripts/postinstall.js +3 -3
|
@@ -6,8 +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): string;
|
|
10
|
-
export declare function normalizePluginMetadataContent(original: string): string;
|
|
9
|
+
export declare function normalizePluginMetadataPathString(input: string, targetConfigDir?: string): string;
|
|
10
|
+
export declare function normalizePluginMetadataContent(original: string, targetConfigDir?: string): string;
|
|
11
11
|
/**
|
|
12
12
|
* SharedManager Class
|
|
13
13
|
*/
|
|
@@ -16,7 +16,10 @@ declare class SharedManager {
|
|
|
16
16
|
private readonly sharedDir;
|
|
17
17
|
private readonly claudeDir;
|
|
18
18
|
private readonly instancesDir;
|
|
19
|
+
private readonly pluginLayoutLock;
|
|
19
20
|
private readonly sharedItems;
|
|
21
|
+
private readonly sharedPluginEntries;
|
|
22
|
+
private readonly instanceLocalPluginMetadataFiles;
|
|
20
23
|
private readonly advancedContinuityItems;
|
|
21
24
|
constructor();
|
|
22
25
|
/**
|
|
@@ -32,6 +35,11 @@ declare class SharedManager {
|
|
|
32
35
|
* Link shared directories to instance
|
|
33
36
|
*/
|
|
34
37
|
linkSharedDirectories(instancePath: string): void;
|
|
38
|
+
detachSharedDirectories(instancePath: string): void;
|
|
39
|
+
private ensureSharedPluginLayoutDefaults;
|
|
40
|
+
private linkInstancePlugins;
|
|
41
|
+
private getSharedPluginLinkItems;
|
|
42
|
+
private removeExistingPath;
|
|
35
43
|
/**
|
|
36
44
|
* Sync project workspace context based on account policy.
|
|
37
45
|
*
|
|
@@ -57,9 +65,10 @@ declare class SharedManager {
|
|
|
57
65
|
*/
|
|
58
66
|
syncProjectMemories(instancePath: string): Promise<void>;
|
|
59
67
|
/**
|
|
60
|
-
* Normalize
|
|
68
|
+
* Normalize plugin metadata and reconcile marketplace metadata for the active config dir.
|
|
61
69
|
*/
|
|
62
70
|
normalizeSharedPluginMetadataPaths(configDir?: string): void;
|
|
71
|
+
normalizeSharedPluginMetadataPathsLocked(configDir?: string): void;
|
|
63
72
|
/**
|
|
64
73
|
* Normalize plugin registry paths to use canonical ~/.claude/ paths
|
|
65
74
|
* instead of instance-specific ~/.ccs/instances/<name>/ paths.
|
|
@@ -69,16 +78,17 @@ declare class SharedManager {
|
|
|
69
78
|
*/
|
|
70
79
|
normalizePluginRegistryPaths(configDir?: string): void;
|
|
71
80
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* This ensures known_marketplaces.json is consistent regardless of
|
|
76
|
-
* which CCS instance added the marketplace.
|
|
81
|
+
* Reconcile marketplace registry content into the active config dir while
|
|
82
|
+
* keeping the global ~/.claude copy up to date for non-instance flows.
|
|
77
83
|
*/
|
|
78
84
|
normalizeMarketplaceRegistryPaths(configDir?: string): void;
|
|
79
85
|
private normalizePluginMetadataFiles;
|
|
80
86
|
private getPluginMetadataFilePaths;
|
|
81
87
|
private normalizePluginMetadataFile;
|
|
88
|
+
private getMarketplaceRegistrySourcePaths;
|
|
89
|
+
private buildMarketplaceRegistryContent;
|
|
90
|
+
private discoverMarketplaceEntries;
|
|
91
|
+
private writePluginMetadataFile;
|
|
82
92
|
/**
|
|
83
93
|
* Migrate from v3.1.1 (copied data in ~/.ccs/shared/) to v3.2.0 (symlinks to ~/.claude/)
|
|
84
94
|
* Runs once on upgrade
|
|
@@ -136,6 +146,9 @@ declare class SharedManager {
|
|
|
136
146
|
* Build a non-destructive conflict copy path.
|
|
137
147
|
*/
|
|
138
148
|
private getConflictCopyPath;
|
|
149
|
+
private symlinkPointsTo;
|
|
150
|
+
private detachManagedPluginLayout;
|
|
151
|
+
private reconcileLocalMarketplaceRegistry;
|
|
139
152
|
private resolveCanonicalPath;
|
|
140
153
|
private isPathWithinDirectory;
|
|
141
154
|
private pathExists;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared-manager.d.ts","sourceRoot":"","sources":["../../src/management/shared-manager.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"shared-manager.d.ts","sourceRoot":"","sources":["../../src/management/shared-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -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
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
*
|
|
494
|
-
*
|
|
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
|
-
|
|
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);
|