mcpick 0.0.18 → 0.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscode/settings.json +5 -0
- package/CHANGELOG.md +7 -0
- package/dist/add-B9nVyh8T.js +113 -0
- package/dist/add-json-CXNDl3al.js +58 -0
- package/dist/atomic-write-BqEykHp9.js +26 -0
- package/dist/backup-DSDhHI5f.js +64 -0
- package/dist/cache-D6kd7qE8.js +226 -0
- package/dist/claude-cli-BeA-bmoW.js +394 -0
- package/dist/cli-DNNZjJYL.js +84 -0
- package/dist/clone-DLFLewBY.js +88 -0
- package/dist/config-DijVdEFn.js +176 -0
- package/dist/dev-DRJRNp7y.js +265 -0
- package/dist/disable-BA8tXPJN.js +39 -0
- package/dist/enable-Bdnnn_Cq.js +40 -0
- package/dist/get-BPjMXTMc.js +41 -0
- package/dist/hook-state-Di8lUsPr.js +171 -0
- package/dist/hooks-Bmn7pUZa.js +280 -0
- package/dist/index.js +1230 -305
- package/dist/list-B8YeDWt6.js +64 -0
- package/dist/marketplace-Br89Tg-Z.js +168 -0
- package/dist/output-BchYq0mR.js +15 -0
- package/dist/paths-BPISiJi4.js +124 -0
- package/dist/plugin-cache-Bby9Dxm9.js +405 -0
- package/dist/plugins-DHYJF5CP.js +212 -0
- package/dist/profile-CX97sMGp.js +120 -0
- package/dist/profile-DkY_lBEm.js +70 -0
- package/dist/redact-O35tjnRD.js +26 -0
- package/dist/registry-CfUKT7_C.js +92 -0
- package/dist/reload-CYDhkCVZ.js +31 -0
- package/dist/remove-DIPWYMpk.js +31 -0
- package/dist/reset-project-choices-DRM5KByw.js +28 -0
- package/dist/restore-DdMfUljI.js +84 -0
- package/dist/rolldown-runtime-CiIaOW0V.js +13 -0
- package/dist/settings-DEcWtzLE.js +201 -0
- package/dist/validation-xMlbgGCF.js +44 -0
- package/package.json +20 -19
- package/dist/cli/commands/add-json.js +0 -60
- package/dist/cli/commands/add.js +0 -135
- package/dist/cli/commands/backup.js +0 -83
- package/dist/cli/commands/cache.js +0 -296
- package/dist/cli/commands/clone.js +0 -108
- package/dist/cli/commands/dev.js +0 -167
- package/dist/cli/commands/disable.js +0 -36
- package/dist/cli/commands/enable.js +0 -39
- package/dist/cli/commands/get.js +0 -45
- package/dist/cli/commands/hooks.js +0 -314
- package/dist/cli/commands/list.js +0 -64
- package/dist/cli/commands/marketplace.js +0 -211
- package/dist/cli/commands/plugins.js +0 -265
- package/dist/cli/commands/profile.js +0 -134
- package/dist/cli/commands/reload.js +0 -36
- package/dist/cli/commands/remove.js +0 -35
- package/dist/cli/commands/reset-project-choices.js +0 -32
- package/dist/cli/commands/restore.js +0 -105
- package/dist/cli/index.js +0 -29
- package/dist/cli/output.js +0 -21
- package/dist/commands/add-server.js +0 -310
- package/dist/commands/backup.js +0 -60
- package/dist/commands/edit-config.js +0 -109
- package/dist/commands/edit-plugins.js +0 -201
- package/dist/commands/manage-cache.js +0 -155
- package/dist/commands/manage-hooks.js +0 -99
- package/dist/commands/manage-marketplace.js +0 -293
- package/dist/commands/restore.js +0 -118
- package/dist/core/config.js +0 -200
- package/dist/core/dev-override.js +0 -155
- package/dist/core/hook-state.js +0 -220
- package/dist/core/plugin-cache.js +0 -506
- package/dist/core/profile.js +0 -94
- package/dist/core/registry.js +0 -121
- package/dist/core/settings.js +0 -243
- package/dist/core/validation.js +0 -49
- package/dist/types.js +0 -2
- package/dist/utils/atomic-write.js +0 -27
- package/dist/utils/claude-cli.js +0 -485
- package/dist/utils/paths.js +0 -114
- package/dist/utils/redact.js +0 -28
|
@@ -1,506 +0,0 @@
|
|
|
1
|
-
import { exec } from 'node:child_process';
|
|
2
|
-
import { lstat, readdir, readFile, readlink, rename, rm, symlink, } from 'node:fs/promises';
|
|
3
|
-
import { join, resolve } from 'node:path';
|
|
4
|
-
import { promisify } from 'node:util';
|
|
5
|
-
import { atomic_json_write } from '../utils/atomic-write.js';
|
|
6
|
-
import { ensure_directory_exists, get_installed_plugins_path, get_known_marketplaces_path, get_marketplace_manifest_path, get_plugin_cache_dir, } from '../utils/paths.js';
|
|
7
|
-
const execAsync = promisify(exec);
|
|
8
|
-
const EMPTY_INSTALLED = {
|
|
9
|
-
version: 2,
|
|
10
|
-
plugins: {},
|
|
11
|
-
};
|
|
12
|
-
// --- Data reading ---
|
|
13
|
-
export async function read_installed_plugins() {
|
|
14
|
-
try {
|
|
15
|
-
const content = await readFile(get_installed_plugins_path(), 'utf-8');
|
|
16
|
-
return JSON.parse(content);
|
|
17
|
-
}
|
|
18
|
-
catch {
|
|
19
|
-
return { ...EMPTY_INSTALLED, plugins: {} };
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
export async function write_installed_plugins(data) {
|
|
23
|
-
await atomic_json_write(get_installed_plugins_path(), () => data);
|
|
24
|
-
}
|
|
25
|
-
export async function read_known_marketplaces() {
|
|
26
|
-
try {
|
|
27
|
-
const content = await readFile(get_known_marketplaces_path(), 'utf-8');
|
|
28
|
-
return JSON.parse(content);
|
|
29
|
-
}
|
|
30
|
-
catch {
|
|
31
|
-
return {};
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
export async function read_marketplace_manifest(name) {
|
|
35
|
-
try {
|
|
36
|
-
const content = await readFile(get_marketplace_manifest_path(name), 'utf-8');
|
|
37
|
-
return JSON.parse(content);
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
// --- Git operations ---
|
|
44
|
-
async function get_marketplace_head_sha(marketplace_path) {
|
|
45
|
-
try {
|
|
46
|
-
const { stdout } = await execAsync(`git -C ${JSON.stringify(marketplace_path)} rev-parse HEAD`, { timeout: 10_000 });
|
|
47
|
-
return stdout.trim() || null;
|
|
48
|
-
}
|
|
49
|
-
catch {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Recover a marketplace clone stuck on a deleted branch.
|
|
55
|
-
* Resets the fetch refspec, fetches all branches, and checks out the default branch.
|
|
56
|
-
*/
|
|
57
|
-
async function recover_deleted_branch(dir) {
|
|
58
|
-
const q = JSON.stringify(dir);
|
|
59
|
-
try {
|
|
60
|
-
// Reset narrow refspec to fetch all branches
|
|
61
|
-
await execAsync(`git -C ${q} remote set-branches origin '*'`, {
|
|
62
|
-
timeout: 10_000,
|
|
63
|
-
});
|
|
64
|
-
await execAsync(`git -C ${q} fetch origin`, {
|
|
65
|
-
timeout: 30_000,
|
|
66
|
-
});
|
|
67
|
-
// Detect default branch
|
|
68
|
-
let default_branch = 'main';
|
|
69
|
-
try {
|
|
70
|
-
const { stdout } = await execAsync(`git -C ${q} symbolic-ref refs/remotes/origin/HEAD`, { timeout: 5_000 });
|
|
71
|
-
const match = stdout
|
|
72
|
-
.trim()
|
|
73
|
-
.match(/refs\/remotes\/origin\/(.+)/);
|
|
74
|
-
if (match)
|
|
75
|
-
default_branch = match[1];
|
|
76
|
-
}
|
|
77
|
-
catch {
|
|
78
|
-
// symbolic-ref not set — try main, then master
|
|
79
|
-
try {
|
|
80
|
-
await execAsync(`git -C ${q} rev-parse --verify origin/main`, { timeout: 5_000 });
|
|
81
|
-
default_branch = 'main';
|
|
82
|
-
}
|
|
83
|
-
catch {
|
|
84
|
-
default_branch = 'master';
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
await execAsync(`git -C ${q} checkout ${default_branch}`, {
|
|
88
|
-
timeout: 10_000,
|
|
89
|
-
});
|
|
90
|
-
await execAsync(`git -C ${q} reset --hard origin/${default_branch}`, { timeout: 10_000 });
|
|
91
|
-
return { recovered: true };
|
|
92
|
-
}
|
|
93
|
-
catch (err) {
|
|
94
|
-
const msg = err instanceof Error ? err.message : 'Unknown error';
|
|
95
|
-
return { recovered: false, error: msg };
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
export async function refresh_marketplace(name, marketplace) {
|
|
99
|
-
const dir = marketplace.installLocation;
|
|
100
|
-
try {
|
|
101
|
-
await execAsync(`git -C ${JSON.stringify(dir)} pull --ff-only`, {
|
|
102
|
-
timeout: 30_000,
|
|
103
|
-
});
|
|
104
|
-
return { success: true };
|
|
105
|
-
}
|
|
106
|
-
catch {
|
|
107
|
-
// Fast-forward failed — attempt recovery from deleted branch
|
|
108
|
-
const recovery = await recover_deleted_branch(dir);
|
|
109
|
-
if (recovery.recovered) {
|
|
110
|
-
return { success: true };
|
|
111
|
-
}
|
|
112
|
-
return {
|
|
113
|
-
success: false,
|
|
114
|
-
error: `${name}: recovery failed: ${recovery.error}`,
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
export async function refresh_all_marketplaces() {
|
|
119
|
-
const marketplaces = await read_known_marketplaces();
|
|
120
|
-
const results = new Map();
|
|
121
|
-
for (const [name, info] of Object.entries(marketplaces)) {
|
|
122
|
-
results.set(name, await refresh_marketplace(name, info));
|
|
123
|
-
}
|
|
124
|
-
return results;
|
|
125
|
-
}
|
|
126
|
-
// --- Orphaned version detection ---
|
|
127
|
-
async function find_orphaned_versions(marketplace, plugin_name) {
|
|
128
|
-
const cache_dir = get_plugin_cache_dir();
|
|
129
|
-
const plugin_dir = join(cache_dir, marketplace, plugin_name);
|
|
130
|
-
const orphaned = [];
|
|
131
|
-
try {
|
|
132
|
-
const versions = await readdir(plugin_dir, {
|
|
133
|
-
withFileTypes: true,
|
|
134
|
-
});
|
|
135
|
-
for (const entry of versions) {
|
|
136
|
-
if (!entry.isDirectory())
|
|
137
|
-
continue;
|
|
138
|
-
try {
|
|
139
|
-
const marker = join(plugin_dir, entry.name, '.orphaned_at');
|
|
140
|
-
await readFile(marker);
|
|
141
|
-
orphaned.push(entry.name);
|
|
142
|
-
}
|
|
143
|
-
catch {
|
|
144
|
-
// No .orphaned_at marker — not orphaned
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
catch {
|
|
149
|
-
// Plugin dir doesn't exist
|
|
150
|
-
}
|
|
151
|
-
return orphaned;
|
|
152
|
-
}
|
|
153
|
-
// --- Staleness analysis ---
|
|
154
|
-
export function parse_plugin_key(key) {
|
|
155
|
-
const at_index = key.lastIndexOf('@');
|
|
156
|
-
return {
|
|
157
|
-
name: at_index > 0 ? key.substring(0, at_index) : key,
|
|
158
|
-
marketplace: at_index > 0 ? key.substring(at_index + 1) : 'unknown',
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
export async function get_cached_plugins_info() {
|
|
162
|
-
const installed = await read_installed_plugins();
|
|
163
|
-
const marketplaces = await read_known_marketplaces();
|
|
164
|
-
// Load all marketplace manifests
|
|
165
|
-
const manifests = new Map();
|
|
166
|
-
for (const name of Object.keys(marketplaces)) {
|
|
167
|
-
const manifest = await read_marketplace_manifest(name);
|
|
168
|
-
if (manifest)
|
|
169
|
-
manifests.set(name, manifest);
|
|
170
|
-
}
|
|
171
|
-
// Get HEAD SHAs for each marketplace clone
|
|
172
|
-
const sha_cache = new Map();
|
|
173
|
-
for (const [name, info] of Object.entries(marketplaces)) {
|
|
174
|
-
sha_cache.set(name, await get_marketplace_head_sha(info.installLocation));
|
|
175
|
-
}
|
|
176
|
-
const results = [];
|
|
177
|
-
for (const [key, entries] of Object.entries(installed.plugins)) {
|
|
178
|
-
const entry = entries[0];
|
|
179
|
-
if (!entry)
|
|
180
|
-
continue;
|
|
181
|
-
const { name, marketplace } = parse_plugin_key(key);
|
|
182
|
-
// Version comparison
|
|
183
|
-
const manifest = manifests.get(marketplace);
|
|
184
|
-
const manifest_plugin = manifest?.plugins.find((p) => p.name === name);
|
|
185
|
-
const latest_version = manifest_plugin?.version ?? null;
|
|
186
|
-
const is_version_stale = latest_version !== null && latest_version !== entry.version;
|
|
187
|
-
// SHA comparison
|
|
188
|
-
const remote_sha = sha_cache.get(marketplace) ?? null;
|
|
189
|
-
const is_sha_stale = remote_sha !== null &&
|
|
190
|
-
entry.gitCommitSha !== '' &&
|
|
191
|
-
remote_sha !== entry.gitCommitSha;
|
|
192
|
-
// Orphaned versions
|
|
193
|
-
const orphaned = await find_orphaned_versions(marketplace, name);
|
|
194
|
-
results.push({
|
|
195
|
-
key,
|
|
196
|
-
name,
|
|
197
|
-
marketplace,
|
|
198
|
-
installedVersion: entry.version,
|
|
199
|
-
latestVersion: latest_version,
|
|
200
|
-
installedSha: entry.gitCommitSha,
|
|
201
|
-
remoteSha: remote_sha,
|
|
202
|
-
isVersionStale: is_version_stale,
|
|
203
|
-
isShaStale: is_sha_stale,
|
|
204
|
-
orphanedVersions: orphaned,
|
|
205
|
-
installPath: entry.installPath,
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
return results;
|
|
209
|
-
}
|
|
210
|
-
// --- Cache scanning ---
|
|
211
|
-
/**
|
|
212
|
-
* Scan the cache directory on disk to find all plugin keys,
|
|
213
|
-
* including marketplace-sourced plugins not tracked in installed_plugins.json.
|
|
214
|
-
*/
|
|
215
|
-
export async function scan_all_cache_keys() {
|
|
216
|
-
const cache_dir = get_plugin_cache_dir();
|
|
217
|
-
const keys = [];
|
|
218
|
-
try {
|
|
219
|
-
const marketplaces = await readdir(cache_dir, {
|
|
220
|
-
withFileTypes: true,
|
|
221
|
-
});
|
|
222
|
-
for (const mkt of marketplaces) {
|
|
223
|
-
if (!mkt.isDirectory() && !mkt.isSymbolicLink())
|
|
224
|
-
continue;
|
|
225
|
-
const mkt_path = join(cache_dir, mkt.name);
|
|
226
|
-
try {
|
|
227
|
-
const plugins = await readdir(mkt_path, {
|
|
228
|
-
withFileTypes: true,
|
|
229
|
-
});
|
|
230
|
-
for (const plugin of plugins) {
|
|
231
|
-
if (!plugin.isDirectory() && !plugin.isSymbolicLink())
|
|
232
|
-
continue;
|
|
233
|
-
keys.push(`${plugin.name}@${mkt.name}`);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
catch {
|
|
237
|
-
// Skip unreadable marketplace dirs
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
catch {
|
|
242
|
-
// Cache dir doesn't exist
|
|
243
|
-
}
|
|
244
|
-
return keys;
|
|
245
|
-
}
|
|
246
|
-
// --- Cache clearing ---
|
|
247
|
-
function is_safe_cache_path(path) {
|
|
248
|
-
const cache_dir = resolve(get_plugin_cache_dir());
|
|
249
|
-
const target = resolve(path);
|
|
250
|
-
return target.startsWith(cache_dir + '/');
|
|
251
|
-
}
|
|
252
|
-
export async function clear_plugin_caches(keys) {
|
|
253
|
-
const installed = await read_installed_plugins();
|
|
254
|
-
const marketplaces = await read_known_marketplaces();
|
|
255
|
-
const cleared = [];
|
|
256
|
-
const errors = [];
|
|
257
|
-
// Collect unique marketplaces to refresh
|
|
258
|
-
const marketplace_names = new Set();
|
|
259
|
-
for (const key of keys) {
|
|
260
|
-
const { marketplace } = parse_plugin_key(key);
|
|
261
|
-
marketplace_names.add(marketplace);
|
|
262
|
-
}
|
|
263
|
-
// Refresh relevant marketplaces first
|
|
264
|
-
for (const mkt_name of marketplace_names) {
|
|
265
|
-
const mkt_info = marketplaces[mkt_name];
|
|
266
|
-
if (mkt_info) {
|
|
267
|
-
const result = await refresh_marketplace(mkt_name, mkt_info);
|
|
268
|
-
if (!result.success) {
|
|
269
|
-
errors.push(`Marketplace refresh failed: ${result.error}`);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
// Delete cache dirs and remove from installed_plugins.json
|
|
274
|
-
const cache_dir = get_plugin_cache_dir();
|
|
275
|
-
for (const key of keys) {
|
|
276
|
-
const { name, marketplace } = parse_plugin_key(key);
|
|
277
|
-
const plugin_cache_path = join(cache_dir, marketplace, name);
|
|
278
|
-
if (!is_safe_cache_path(plugin_cache_path)) {
|
|
279
|
-
errors.push(`Unsafe path, skipped: ${plugin_cache_path}`);
|
|
280
|
-
continue;
|
|
281
|
-
}
|
|
282
|
-
try {
|
|
283
|
-
await rm(plugin_cache_path, {
|
|
284
|
-
recursive: true,
|
|
285
|
-
force: true,
|
|
286
|
-
});
|
|
287
|
-
delete installed.plugins[key];
|
|
288
|
-
cleared.push(key);
|
|
289
|
-
}
|
|
290
|
-
catch (err) {
|
|
291
|
-
const msg = err instanceof Error ? err.message : 'Unknown error';
|
|
292
|
-
errors.push(`Failed to clear ${key}: ${msg}`);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
// Write back updated installed_plugins.json
|
|
296
|
-
await write_installed_plugins(installed);
|
|
297
|
-
return { cleared, errors };
|
|
298
|
-
}
|
|
299
|
-
// --- Orphaned cleanup ---
|
|
300
|
-
export async function clean_orphaned_versions() {
|
|
301
|
-
const cache_dir = get_plugin_cache_dir();
|
|
302
|
-
const cleaned_paths = [];
|
|
303
|
-
try {
|
|
304
|
-
const marketplaces = await readdir(cache_dir, {
|
|
305
|
-
withFileTypes: true,
|
|
306
|
-
});
|
|
307
|
-
for (const mkt of marketplaces) {
|
|
308
|
-
if (!mkt.isDirectory())
|
|
309
|
-
continue;
|
|
310
|
-
const mkt_path = join(cache_dir, mkt.name);
|
|
311
|
-
const plugins = await readdir(mkt_path, {
|
|
312
|
-
withFileTypes: true,
|
|
313
|
-
});
|
|
314
|
-
for (const plugin of plugins) {
|
|
315
|
-
if (!plugin.isDirectory())
|
|
316
|
-
continue;
|
|
317
|
-
const plugin_path = join(mkt_path, plugin.name);
|
|
318
|
-
const versions = await readdir(plugin_path, {
|
|
319
|
-
withFileTypes: true,
|
|
320
|
-
});
|
|
321
|
-
for (const version of versions) {
|
|
322
|
-
if (!version.isDirectory())
|
|
323
|
-
continue;
|
|
324
|
-
const version_path = join(plugin_path, version.name);
|
|
325
|
-
try {
|
|
326
|
-
await readFile(join(version_path, '.orphaned_at'));
|
|
327
|
-
// Has orphaned marker — safe to delete
|
|
328
|
-
if (is_safe_cache_path(version_path)) {
|
|
329
|
-
await rm(version_path, {
|
|
330
|
-
recursive: true,
|
|
331
|
-
force: true,
|
|
332
|
-
});
|
|
333
|
-
cleaned_paths.push(`${mkt.name}/${plugin.name}/${version.name}`);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
catch {
|
|
337
|
-
// No marker — keep it
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
catch {
|
|
344
|
-
// Cache dir doesn't exist
|
|
345
|
-
}
|
|
346
|
-
return { cleaned: cleaned_paths.length, paths: cleaned_paths };
|
|
347
|
-
}
|
|
348
|
-
// --- Cache linking ---
|
|
349
|
-
/**
|
|
350
|
-
* Symlink a local directory into the plugin cache.
|
|
351
|
-
* Backs up existing cache directory if present.
|
|
352
|
-
*/
|
|
353
|
-
export async function link_local_plugin(local_path, key) {
|
|
354
|
-
const resolved_path = resolve(local_path);
|
|
355
|
-
const { name, marketplace } = parse_plugin_key(key);
|
|
356
|
-
// Validate local path exists
|
|
357
|
-
try {
|
|
358
|
-
const stat = await lstat(resolved_path);
|
|
359
|
-
if (!stat.isDirectory()) {
|
|
360
|
-
return {
|
|
361
|
-
success: false,
|
|
362
|
-
key,
|
|
363
|
-
symlinkPath: '',
|
|
364
|
-
targetPath: resolved_path,
|
|
365
|
-
error: `Path is not a directory: ${resolved_path}`,
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
catch {
|
|
370
|
-
return {
|
|
371
|
-
success: false,
|
|
372
|
-
key,
|
|
373
|
-
symlinkPath: '',
|
|
374
|
-
targetPath: resolved_path,
|
|
375
|
-
error: `Path does not exist: ${resolved_path}`,
|
|
376
|
-
};
|
|
377
|
-
}
|
|
378
|
-
const cache_dir = get_plugin_cache_dir();
|
|
379
|
-
const plugin_dir = join(cache_dir, marketplace, name);
|
|
380
|
-
// Ensure parent directory exists
|
|
381
|
-
await ensure_directory_exists(join(cache_dir, marketplace));
|
|
382
|
-
// If plugin_dir exists and is not a symlink, back it up
|
|
383
|
-
try {
|
|
384
|
-
const stat = await lstat(plugin_dir);
|
|
385
|
-
if (stat.isSymbolicLink()) {
|
|
386
|
-
// Already a symlink — remove it
|
|
387
|
-
await rm(plugin_dir);
|
|
388
|
-
}
|
|
389
|
-
else if (stat.isDirectory()) {
|
|
390
|
-
// Back up existing directory
|
|
391
|
-
const backup_path = `${plugin_dir}.backup`;
|
|
392
|
-
await rename(plugin_dir, backup_path);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
catch {
|
|
396
|
-
// Doesn't exist — fine
|
|
397
|
-
}
|
|
398
|
-
// Create symlink
|
|
399
|
-
try {
|
|
400
|
-
await symlink(resolved_path, plugin_dir, 'dir');
|
|
401
|
-
}
|
|
402
|
-
catch (err) {
|
|
403
|
-
const msg = err instanceof Error ? err.message : 'Unknown error';
|
|
404
|
-
return {
|
|
405
|
-
success: false,
|
|
406
|
-
key,
|
|
407
|
-
symlinkPath: plugin_dir,
|
|
408
|
-
targetPath: resolved_path,
|
|
409
|
-
error: `Failed to create symlink: ${msg}`,
|
|
410
|
-
};
|
|
411
|
-
}
|
|
412
|
-
return {
|
|
413
|
-
success: true,
|
|
414
|
-
key,
|
|
415
|
-
symlinkPath: plugin_dir,
|
|
416
|
-
targetPath: resolved_path,
|
|
417
|
-
};
|
|
418
|
-
}
|
|
419
|
-
/**
|
|
420
|
-
* Remove a symlink from the plugin cache and restore backup if present.
|
|
421
|
-
*/
|
|
422
|
-
export async function unlink_local_plugin(key) {
|
|
423
|
-
const { name, marketplace } = parse_plugin_key(key);
|
|
424
|
-
const cache_dir = get_plugin_cache_dir();
|
|
425
|
-
const plugin_dir = join(cache_dir, marketplace, name);
|
|
426
|
-
// Verify it's actually a symlink
|
|
427
|
-
try {
|
|
428
|
-
const stat = await lstat(plugin_dir);
|
|
429
|
-
if (!stat.isSymbolicLink()) {
|
|
430
|
-
return {
|
|
431
|
-
success: false,
|
|
432
|
-
key,
|
|
433
|
-
restored: false,
|
|
434
|
-
error: `'${key}' is not a symlink — nothing to unlink`,
|
|
435
|
-
};
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
catch {
|
|
439
|
-
return {
|
|
440
|
-
success: false,
|
|
441
|
-
key,
|
|
442
|
-
restored: false,
|
|
443
|
-
error: `'${key}' not found in cache`,
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
// Remove symlink
|
|
447
|
-
await rm(plugin_dir);
|
|
448
|
-
// Restore backup if present
|
|
449
|
-
const backup_path = `${plugin_dir}.backup`;
|
|
450
|
-
let restored = false;
|
|
451
|
-
try {
|
|
452
|
-
await lstat(backup_path);
|
|
453
|
-
await rename(backup_path, plugin_dir);
|
|
454
|
-
restored = true;
|
|
455
|
-
}
|
|
456
|
-
catch {
|
|
457
|
-
// No backup to restore
|
|
458
|
-
}
|
|
459
|
-
return { success: true, key, restored };
|
|
460
|
-
}
|
|
461
|
-
/**
|
|
462
|
-
* List all symlinked entries in the plugin cache.
|
|
463
|
-
*/
|
|
464
|
-
export async function list_linked_plugins() {
|
|
465
|
-
const cache_dir = get_plugin_cache_dir();
|
|
466
|
-
const links = [];
|
|
467
|
-
try {
|
|
468
|
-
const marketplaces = await readdir(cache_dir, {
|
|
469
|
-
withFileTypes: true,
|
|
470
|
-
});
|
|
471
|
-
for (const mkt of marketplaces) {
|
|
472
|
-
if (!mkt.isDirectory() && !mkt.isSymbolicLink())
|
|
473
|
-
continue;
|
|
474
|
-
const mkt_path = join(cache_dir, mkt.name);
|
|
475
|
-
let entries;
|
|
476
|
-
try {
|
|
477
|
-
entries = await readdir(mkt_path, { withFileTypes: true });
|
|
478
|
-
}
|
|
479
|
-
catch {
|
|
480
|
-
continue;
|
|
481
|
-
}
|
|
482
|
-
for (const entry of entries) {
|
|
483
|
-
const entry_path = join(mkt_path, entry.name);
|
|
484
|
-
try {
|
|
485
|
-
const stat = await lstat(entry_path);
|
|
486
|
-
if (stat.isSymbolicLink()) {
|
|
487
|
-
const target = await readlink(entry_path);
|
|
488
|
-
links.push({
|
|
489
|
-
key: `${entry.name}@${mkt.name}`,
|
|
490
|
-
symlinkPath: entry_path,
|
|
491
|
-
targetPath: resolve(join(cache_dir, mkt.name), target),
|
|
492
|
-
});
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
catch {
|
|
496
|
-
// Skip on error
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
catch {
|
|
502
|
-
// Cache dir doesn't exist
|
|
503
|
-
}
|
|
504
|
-
return links;
|
|
505
|
-
}
|
|
506
|
-
//# sourceMappingURL=plugin-cache.js.map
|
package/dist/core/profile.js
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import { access, readFile, readdir, writeFile, } from 'node:fs/promises';
|
|
2
|
-
import { ensure_directory_exists, get_profile_path, get_profiles_dir, } from '../utils/paths.js';
|
|
3
|
-
import { read_claude_config } from './config.js';
|
|
4
|
-
import { read_claude_settings } from './settings.js';
|
|
5
|
-
import { validate_claude_config } from './validation.js';
|
|
6
|
-
export async function load_profile(name) {
|
|
7
|
-
const profile_path = get_profile_path(name);
|
|
8
|
-
try {
|
|
9
|
-
await access(profile_path);
|
|
10
|
-
const content = await readFile(profile_path, 'utf-8');
|
|
11
|
-
const parsed = JSON.parse(content);
|
|
12
|
-
// Profile can be either full format or just mcpServers object
|
|
13
|
-
let config;
|
|
14
|
-
if (parsed.mcpServers) {
|
|
15
|
-
config = validate_claude_config(parsed);
|
|
16
|
-
}
|
|
17
|
-
else if (!parsed.enabledPlugins) {
|
|
18
|
-
// Bare servers object (legacy)
|
|
19
|
-
config = validate_claude_config({ mcpServers: parsed });
|
|
20
|
-
}
|
|
21
|
-
else {
|
|
22
|
-
config = validate_claude_config({
|
|
23
|
-
mcpServers: parsed.mcpServers || {},
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
return {
|
|
27
|
-
config,
|
|
28
|
-
enabledPlugins: parsed.enabledPlugins,
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
catch (error) {
|
|
32
|
-
if (error instanceof Error &&
|
|
33
|
-
'code' in error &&
|
|
34
|
-
error.code === 'ENOENT') {
|
|
35
|
-
throw new Error(`Profile '${name}' not found at ${profile_path}`);
|
|
36
|
-
}
|
|
37
|
-
throw error;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
export async function list_profiles() {
|
|
41
|
-
const profiles_dir = get_profiles_dir();
|
|
42
|
-
try {
|
|
43
|
-
await access(profiles_dir);
|
|
44
|
-
const files = await readdir(profiles_dir);
|
|
45
|
-
const json_files = files.filter((f) => f.endsWith('.json'));
|
|
46
|
-
const profiles = [];
|
|
47
|
-
for (const file of json_files) {
|
|
48
|
-
try {
|
|
49
|
-
const path = get_profile_path(file);
|
|
50
|
-
const content = await readFile(path, 'utf-8');
|
|
51
|
-
const parsed = JSON.parse(content);
|
|
52
|
-
const servers = parsed.mcpServers || parsed;
|
|
53
|
-
const plugins = parsed.enabledPlugins || {};
|
|
54
|
-
profiles.push({
|
|
55
|
-
name: file.replace('.json', ''),
|
|
56
|
-
path,
|
|
57
|
-
serverCount: Object.keys(servers).length,
|
|
58
|
-
pluginCount: Object.keys(plugins).length,
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
catch {
|
|
62
|
-
// Skip invalid profiles
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return profiles;
|
|
66
|
-
}
|
|
67
|
-
catch {
|
|
68
|
-
return [];
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
export async function save_profile(name) {
|
|
72
|
-
const config = await read_claude_config();
|
|
73
|
-
const settings = await read_claude_settings();
|
|
74
|
-
const servers = config.mcpServers || {};
|
|
75
|
-
const plugins = settings.enabledPlugins || {};
|
|
76
|
-
const server_count = Object.keys(servers).length;
|
|
77
|
-
const plugin_count = Object.keys(plugins).length;
|
|
78
|
-
if (server_count === 0 && plugin_count === 0) {
|
|
79
|
-
throw new Error('No MCP servers or plugins configured to save');
|
|
80
|
-
}
|
|
81
|
-
const profiles_dir = get_profiles_dir();
|
|
82
|
-
await ensure_directory_exists(profiles_dir);
|
|
83
|
-
const profile_data = {
|
|
84
|
-
mcpServers: servers,
|
|
85
|
-
};
|
|
86
|
-
if (plugin_count > 0) {
|
|
87
|
-
profile_data.enabledPlugins = plugins;
|
|
88
|
-
}
|
|
89
|
-
const profile_path = get_profile_path(name);
|
|
90
|
-
const content = JSON.stringify(profile_data, null, 2);
|
|
91
|
-
await writeFile(profile_path, content, 'utf-8');
|
|
92
|
-
return { serverCount: server_count, pluginCount: plugin_count };
|
|
93
|
-
}
|
|
94
|
-
//# sourceMappingURL=profile.js.map
|