mcpick 0.0.9 → 0.0.11
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/CHANGELOG.md +19 -0
- package/README.md +114 -4
- package/dist/cli/commands/add.js +135 -0
- package/dist/cli/commands/backup.js +83 -0
- package/dist/cli/commands/cache.js +181 -0
- package/dist/cli/commands/disable.js +33 -0
- package/dist/cli/commands/enable.js +39 -0
- package/dist/cli/commands/list.js +63 -0
- package/dist/cli/commands/plugins.js +202 -0
- package/dist/cli/commands/profile.js +134 -0
- package/dist/cli/commands/remove.js +31 -0
- package/dist/cli/commands/restore.js +105 -0
- package/dist/cli/index.js +21 -0
- package/dist/cli/output.js +21 -0
- package/dist/commands/backup.js +28 -22
- package/dist/commands/edit-plugins.js +192 -47
- package/dist/commands/manage-cache.js +155 -0
- package/dist/commands/restore.js +85 -34
- package/dist/core/plugin-cache.js +259 -0
- package/dist/core/profile.js +32 -8
- package/dist/core/registry.js +34 -30
- package/dist/index.js +81 -23
- package/dist/utils/claude-cli.js +69 -0
- package/dist/utils/paths.js +26 -2
- package/package.json +7 -6
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { exec } from 'node:child_process';
|
|
2
|
+
import { readdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
import { get_installed_plugins_path, get_known_marketplaces_path, get_marketplace_manifest_path, get_plugin_cache_dir, } from '../utils/paths.js';
|
|
6
|
+
const execAsync = promisify(exec);
|
|
7
|
+
const EMPTY_INSTALLED = {
|
|
8
|
+
version: 2,
|
|
9
|
+
plugins: {},
|
|
10
|
+
};
|
|
11
|
+
// --- Data reading ---
|
|
12
|
+
export async function read_installed_plugins() {
|
|
13
|
+
try {
|
|
14
|
+
const content = await readFile(get_installed_plugins_path(), 'utf-8');
|
|
15
|
+
return JSON.parse(content);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return { ...EMPTY_INSTALLED, plugins: {} };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export async function write_installed_plugins(data) {
|
|
22
|
+
await writeFile(get_installed_plugins_path(), JSON.stringify(data, null, 2), 'utf-8');
|
|
23
|
+
}
|
|
24
|
+
export async function read_known_marketplaces() {
|
|
25
|
+
try {
|
|
26
|
+
const content = await readFile(get_known_marketplaces_path(), 'utf-8');
|
|
27
|
+
return JSON.parse(content);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return {};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export async function read_marketplace_manifest(name) {
|
|
34
|
+
try {
|
|
35
|
+
const content = await readFile(get_marketplace_manifest_path(name), 'utf-8');
|
|
36
|
+
return JSON.parse(content);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// --- Git operations ---
|
|
43
|
+
async function get_marketplace_head_sha(marketplace_path) {
|
|
44
|
+
try {
|
|
45
|
+
const { stdout } = await execAsync(`git -C ${JSON.stringify(marketplace_path)} rev-parse HEAD`, { timeout: 10_000 });
|
|
46
|
+
return stdout.trim() || null;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export async function refresh_marketplace(name, marketplace) {
|
|
53
|
+
const dir = marketplace.installLocation;
|
|
54
|
+
try {
|
|
55
|
+
await execAsync(`git -C ${JSON.stringify(dir)} pull --ff-only`, {
|
|
56
|
+
timeout: 30_000,
|
|
57
|
+
});
|
|
58
|
+
return { success: true };
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
const message = err instanceof Error ? err.message : 'Unknown error';
|
|
62
|
+
return { success: false, error: `${name}: ${message}` };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export async function refresh_all_marketplaces() {
|
|
66
|
+
const marketplaces = await read_known_marketplaces();
|
|
67
|
+
const results = new Map();
|
|
68
|
+
for (const [name, info] of Object.entries(marketplaces)) {
|
|
69
|
+
results.set(name, await refresh_marketplace(name, info));
|
|
70
|
+
}
|
|
71
|
+
return results;
|
|
72
|
+
}
|
|
73
|
+
// --- Orphaned version detection ---
|
|
74
|
+
async function find_orphaned_versions(marketplace, plugin_name) {
|
|
75
|
+
const cache_dir = get_plugin_cache_dir();
|
|
76
|
+
const plugin_dir = join(cache_dir, marketplace, plugin_name);
|
|
77
|
+
const orphaned = [];
|
|
78
|
+
try {
|
|
79
|
+
const versions = await readdir(plugin_dir, {
|
|
80
|
+
withFileTypes: true,
|
|
81
|
+
});
|
|
82
|
+
for (const entry of versions) {
|
|
83
|
+
if (!entry.isDirectory())
|
|
84
|
+
continue;
|
|
85
|
+
try {
|
|
86
|
+
const marker = join(plugin_dir, entry.name, '.orphaned_at');
|
|
87
|
+
await readFile(marker);
|
|
88
|
+
orphaned.push(entry.name);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// No .orphaned_at marker — not orphaned
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// Plugin dir doesn't exist
|
|
97
|
+
}
|
|
98
|
+
return orphaned;
|
|
99
|
+
}
|
|
100
|
+
// --- Staleness analysis ---
|
|
101
|
+
function parse_plugin_key(key) {
|
|
102
|
+
const at_index = key.lastIndexOf('@');
|
|
103
|
+
return {
|
|
104
|
+
name: at_index > 0 ? key.substring(0, at_index) : key,
|
|
105
|
+
marketplace: at_index > 0 ? key.substring(at_index + 1) : 'unknown',
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
export async function get_cached_plugins_info() {
|
|
109
|
+
const installed = await read_installed_plugins();
|
|
110
|
+
const marketplaces = await read_known_marketplaces();
|
|
111
|
+
// Load all marketplace manifests
|
|
112
|
+
const manifests = new Map();
|
|
113
|
+
for (const name of Object.keys(marketplaces)) {
|
|
114
|
+
const manifest = await read_marketplace_manifest(name);
|
|
115
|
+
if (manifest)
|
|
116
|
+
manifests.set(name, manifest);
|
|
117
|
+
}
|
|
118
|
+
// Get HEAD SHAs for each marketplace clone
|
|
119
|
+
const sha_cache = new Map();
|
|
120
|
+
for (const [name, info] of Object.entries(marketplaces)) {
|
|
121
|
+
sha_cache.set(name, await get_marketplace_head_sha(info.installLocation));
|
|
122
|
+
}
|
|
123
|
+
const results = [];
|
|
124
|
+
for (const [key, entries] of Object.entries(installed.plugins)) {
|
|
125
|
+
const entry = entries[0];
|
|
126
|
+
if (!entry)
|
|
127
|
+
continue;
|
|
128
|
+
const { name, marketplace } = parse_plugin_key(key);
|
|
129
|
+
// Version comparison
|
|
130
|
+
const manifest = manifests.get(marketplace);
|
|
131
|
+
const manifest_plugin = manifest?.plugins.find((p) => p.name === name);
|
|
132
|
+
const latest_version = manifest_plugin?.version ?? null;
|
|
133
|
+
const is_version_stale = latest_version !== null && latest_version !== entry.version;
|
|
134
|
+
// SHA comparison
|
|
135
|
+
const remote_sha = sha_cache.get(marketplace) ?? null;
|
|
136
|
+
const is_sha_stale = remote_sha !== null &&
|
|
137
|
+
entry.gitCommitSha !== '' &&
|
|
138
|
+
remote_sha !== entry.gitCommitSha;
|
|
139
|
+
// Orphaned versions
|
|
140
|
+
const orphaned = await find_orphaned_versions(marketplace, name);
|
|
141
|
+
results.push({
|
|
142
|
+
key,
|
|
143
|
+
name,
|
|
144
|
+
marketplace,
|
|
145
|
+
installedVersion: entry.version,
|
|
146
|
+
latestVersion: latest_version,
|
|
147
|
+
installedSha: entry.gitCommitSha,
|
|
148
|
+
remoteSha: remote_sha,
|
|
149
|
+
isVersionStale: is_version_stale,
|
|
150
|
+
isShaStale: is_sha_stale,
|
|
151
|
+
orphanedVersions: orphaned,
|
|
152
|
+
installPath: entry.installPath,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
return results;
|
|
156
|
+
}
|
|
157
|
+
// --- Cache clearing ---
|
|
158
|
+
function is_safe_cache_path(path) {
|
|
159
|
+
const cache_dir = resolve(get_plugin_cache_dir());
|
|
160
|
+
const target = resolve(path);
|
|
161
|
+
return target.startsWith(cache_dir + '/');
|
|
162
|
+
}
|
|
163
|
+
export async function clear_plugin_caches(keys) {
|
|
164
|
+
const installed = await read_installed_plugins();
|
|
165
|
+
const marketplaces = await read_known_marketplaces();
|
|
166
|
+
const cleared = [];
|
|
167
|
+
const errors = [];
|
|
168
|
+
// Collect unique marketplaces to refresh
|
|
169
|
+
const marketplace_names = new Set();
|
|
170
|
+
for (const key of keys) {
|
|
171
|
+
const { marketplace } = parse_plugin_key(key);
|
|
172
|
+
marketplace_names.add(marketplace);
|
|
173
|
+
}
|
|
174
|
+
// Refresh relevant marketplaces first
|
|
175
|
+
for (const mkt_name of marketplace_names) {
|
|
176
|
+
const mkt_info = marketplaces[mkt_name];
|
|
177
|
+
if (mkt_info) {
|
|
178
|
+
const result = await refresh_marketplace(mkt_name, mkt_info);
|
|
179
|
+
if (!result.success) {
|
|
180
|
+
errors.push(`Marketplace refresh failed: ${result.error}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// Delete cache dirs and remove from installed_plugins.json
|
|
185
|
+
const cache_dir = get_plugin_cache_dir();
|
|
186
|
+
for (const key of keys) {
|
|
187
|
+
const { name, marketplace } = parse_plugin_key(key);
|
|
188
|
+
const plugin_cache_path = join(cache_dir, marketplace, name);
|
|
189
|
+
if (!is_safe_cache_path(plugin_cache_path)) {
|
|
190
|
+
errors.push(`Unsafe path, skipped: ${plugin_cache_path}`);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
try {
|
|
194
|
+
await rm(plugin_cache_path, {
|
|
195
|
+
recursive: true,
|
|
196
|
+
force: true,
|
|
197
|
+
});
|
|
198
|
+
delete installed.plugins[key];
|
|
199
|
+
cleared.push(key);
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
const msg = err instanceof Error ? err.message : 'Unknown error';
|
|
203
|
+
errors.push(`Failed to clear ${key}: ${msg}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// Write back updated installed_plugins.json
|
|
207
|
+
await write_installed_plugins(installed);
|
|
208
|
+
return { cleared, errors };
|
|
209
|
+
}
|
|
210
|
+
// --- Orphaned cleanup ---
|
|
211
|
+
export async function clean_orphaned_versions() {
|
|
212
|
+
const cache_dir = get_plugin_cache_dir();
|
|
213
|
+
const cleaned_paths = [];
|
|
214
|
+
try {
|
|
215
|
+
const marketplaces = await readdir(cache_dir, {
|
|
216
|
+
withFileTypes: true,
|
|
217
|
+
});
|
|
218
|
+
for (const mkt of marketplaces) {
|
|
219
|
+
if (!mkt.isDirectory())
|
|
220
|
+
continue;
|
|
221
|
+
const mkt_path = join(cache_dir, mkt.name);
|
|
222
|
+
const plugins = await readdir(mkt_path, {
|
|
223
|
+
withFileTypes: true,
|
|
224
|
+
});
|
|
225
|
+
for (const plugin of plugins) {
|
|
226
|
+
if (!plugin.isDirectory())
|
|
227
|
+
continue;
|
|
228
|
+
const plugin_path = join(mkt_path, plugin.name);
|
|
229
|
+
const versions = await readdir(plugin_path, {
|
|
230
|
+
withFileTypes: true,
|
|
231
|
+
});
|
|
232
|
+
for (const version of versions) {
|
|
233
|
+
if (!version.isDirectory())
|
|
234
|
+
continue;
|
|
235
|
+
const version_path = join(plugin_path, version.name);
|
|
236
|
+
try {
|
|
237
|
+
await readFile(join(version_path, '.orphaned_at'));
|
|
238
|
+
// Has orphaned marker — safe to delete
|
|
239
|
+
if (is_safe_cache_path(version_path)) {
|
|
240
|
+
await rm(version_path, {
|
|
241
|
+
recursive: true,
|
|
242
|
+
force: true,
|
|
243
|
+
});
|
|
244
|
+
cleaned_paths.push(`${mkt.name}/${plugin.name}/${version.name}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
// No marker — keep it
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
// Cache dir doesn't exist
|
|
256
|
+
}
|
|
257
|
+
return { cleaned: cleaned_paths.length, paths: cleaned_paths };
|
|
258
|
+
}
|
|
259
|
+
//# sourceMappingURL=plugin-cache.js.map
|
package/dist/core/profile.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { access, readFile, readdir, writeFile, } from 'node:fs/promises';
|
|
2
2
|
import { ensure_directory_exists, get_profile_path, get_profiles_dir, } from '../utils/paths.js';
|
|
3
3
|
import { read_claude_config } from './config.js';
|
|
4
|
+
import { read_claude_settings } from './settings.js';
|
|
4
5
|
import { validate_claude_config } from './validation.js';
|
|
5
6
|
export async function load_profile(name) {
|
|
6
7
|
const profile_path = get_profile_path(name);
|
|
@@ -8,12 +9,24 @@ export async function load_profile(name) {
|
|
|
8
9
|
await access(profile_path);
|
|
9
10
|
const content = await readFile(profile_path, 'utf-8');
|
|
10
11
|
const parsed = JSON.parse(content);
|
|
11
|
-
// Profile can be either full
|
|
12
|
+
// Profile can be either full format or just mcpServers object
|
|
13
|
+
let config;
|
|
12
14
|
if (parsed.mcpServers) {
|
|
13
|
-
|
|
15
|
+
config = validate_claude_config(parsed);
|
|
14
16
|
}
|
|
15
|
-
|
|
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
|
+
};
|
|
17
30
|
}
|
|
18
31
|
catch (error) {
|
|
19
32
|
if (error instanceof Error &&
|
|
@@ -37,10 +50,12 @@ export async function list_profiles() {
|
|
|
37
50
|
const content = await readFile(path, 'utf-8');
|
|
38
51
|
const parsed = JSON.parse(content);
|
|
39
52
|
const servers = parsed.mcpServers || parsed;
|
|
53
|
+
const plugins = parsed.enabledPlugins || {};
|
|
40
54
|
profiles.push({
|
|
41
55
|
name: file.replace('.json', ''),
|
|
42
56
|
path,
|
|
43
57
|
serverCount: Object.keys(servers).length,
|
|
58
|
+
pluginCount: Object.keys(plugins).length,
|
|
44
59
|
});
|
|
45
60
|
}
|
|
46
61
|
catch {
|
|
@@ -55,16 +70,25 @@ export async function list_profiles() {
|
|
|
55
70
|
}
|
|
56
71
|
export async function save_profile(name) {
|
|
57
72
|
const config = await read_claude_config();
|
|
73
|
+
const settings = await read_claude_settings();
|
|
58
74
|
const servers = config.mcpServers || {};
|
|
75
|
+
const plugins = settings.enabledPlugins || {};
|
|
59
76
|
const server_count = Object.keys(servers).length;
|
|
60
|
-
|
|
61
|
-
|
|
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');
|
|
62
80
|
}
|
|
63
81
|
const profiles_dir = get_profiles_dir();
|
|
64
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
|
+
}
|
|
65
89
|
const profile_path = get_profile_path(name);
|
|
66
|
-
const content = JSON.stringify(
|
|
90
|
+
const content = JSON.stringify(profile_data, null, 2);
|
|
67
91
|
await writeFile(profile_path, content, 'utf-8');
|
|
68
|
-
return server_count;
|
|
92
|
+
return { serverCount: server_count, pluginCount: plugin_count };
|
|
69
93
|
}
|
|
70
94
|
//# sourceMappingURL=profile.js.map
|
package/dist/core/registry.js
CHANGED
|
@@ -56,36 +56,40 @@ export async function sync_servers_to_registry(servers) {
|
|
|
56
56
|
});
|
|
57
57
|
await write_server_registry(registry);
|
|
58
58
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
catch (error) {
|
|
83
|
-
if (error instanceof Error &&
|
|
84
|
-
'code' in error &&
|
|
85
|
-
error.code === 'ENOENT') {
|
|
86
|
-
return [];
|
|
59
|
+
function parse_backups(prefix, pattern) {
|
|
60
|
+
return async () => {
|
|
61
|
+
const backups_dir = get_backups_dir();
|
|
62
|
+
try {
|
|
63
|
+
await access(backups_dir);
|
|
64
|
+
const files = await readdir(backups_dir);
|
|
65
|
+
const backup_files = files
|
|
66
|
+
.filter((file) => file.startsWith(prefix) && file.endsWith('.json'))
|
|
67
|
+
.map((file) => {
|
|
68
|
+
const timestamp_match = file.match(pattern);
|
|
69
|
+
if (!timestamp_match)
|
|
70
|
+
return null;
|
|
71
|
+
const [, year, month, day, hour, minute, second] = timestamp_match;
|
|
72
|
+
const timestamp = new Date(parseInt(year), parseInt(month) - 1, parseInt(day), parseInt(hour), parseInt(minute), parseInt(second));
|
|
73
|
+
return {
|
|
74
|
+
filename: file,
|
|
75
|
+
timestamp,
|
|
76
|
+
path: join(backups_dir, file),
|
|
77
|
+
};
|
|
78
|
+
})
|
|
79
|
+
.filter((backup) => backup !== null)
|
|
80
|
+
.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
|
|
81
|
+
return backup_files;
|
|
87
82
|
}
|
|
88
|
-
|
|
89
|
-
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (error instanceof Error &&
|
|
85
|
+
'code' in error &&
|
|
86
|
+
error.code === 'ENOENT') {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
90
92
|
}
|
|
93
|
+
export const list_backups = parse_backups('mcp-servers-', /mcp-servers-(\d{4})-(\d{2})-(\d{2})-(\d{2})(\d{2})(\d{2})\.json/);
|
|
94
|
+
export const list_plugin_backups = parse_backups('plugins-', /plugins-(\d{4})-(\d{2})-(\d{2})-(\d{2})(\d{2})(\d{2})\.json/);
|
|
91
95
|
//# sourceMappingURL=registry.js.map
|
package/dist/index.js
CHANGED
|
@@ -4,9 +4,11 @@ import { add_server } from './commands/add-server.js';
|
|
|
4
4
|
import { backup_config } from './commands/backup.js';
|
|
5
5
|
import { edit_config } from './commands/edit-config.js';
|
|
6
6
|
import { edit_plugins } from './commands/edit-plugins.js';
|
|
7
|
+
import { manage_cache } from './commands/manage-cache.js';
|
|
7
8
|
import { restore_config } from './commands/restore.js';
|
|
8
9
|
import { write_claude_config } from './core/config.js';
|
|
9
10
|
import { list_profiles, load_profile, save_profile, } from './core/profile.js';
|
|
11
|
+
import { write_claude_settings } from './core/settings.js';
|
|
10
12
|
function parse_args() {
|
|
11
13
|
const args = process.argv.slice(2);
|
|
12
14
|
const result = {};
|
|
@@ -28,10 +30,17 @@ function parse_args() {
|
|
|
28
30
|
async function apply_profile(name) {
|
|
29
31
|
intro(`MCPick - Loading profile: ${name}`);
|
|
30
32
|
try {
|
|
31
|
-
const
|
|
32
|
-
await write_claude_config(
|
|
33
|
-
const server_count = Object.keys(
|
|
34
|
-
|
|
33
|
+
const profile = await load_profile(name);
|
|
34
|
+
await write_claude_config(profile.config);
|
|
35
|
+
const server_count = Object.keys(profile.config.mcpServers || {}).length;
|
|
36
|
+
const parts = [`${server_count} servers`];
|
|
37
|
+
if (profile.enabledPlugins) {
|
|
38
|
+
await write_claude_settings({
|
|
39
|
+
enabledPlugins: profile.enabledPlugins,
|
|
40
|
+
});
|
|
41
|
+
parts.push(`${Object.keys(profile.enabledPlugins).length} plugins`);
|
|
42
|
+
}
|
|
43
|
+
log.success(`Profile '${name}' applied (${parts.join(', ')})`);
|
|
35
44
|
outro('Done!');
|
|
36
45
|
}
|
|
37
46
|
catch (error) {
|
|
@@ -61,8 +70,12 @@ async function show_profiles() {
|
|
|
61
70
|
async function create_profile(name) {
|
|
62
71
|
intro(`MCPick - Saving profile: ${name}`);
|
|
63
72
|
try {
|
|
64
|
-
const
|
|
65
|
-
|
|
73
|
+
const counts = await save_profile(name);
|
|
74
|
+
const parts = [`${counts.serverCount} servers`];
|
|
75
|
+
if (counts.pluginCount > 0) {
|
|
76
|
+
parts.push(`${counts.pluginCount} plugins`);
|
|
77
|
+
}
|
|
78
|
+
log.success(`Profile '${name}' saved (${parts.join(', ')})`);
|
|
66
79
|
outro('Done!');
|
|
67
80
|
}
|
|
68
81
|
catch (error) {
|
|
@@ -84,18 +97,31 @@ async function handle_load_profile() {
|
|
|
84
97
|
}
|
|
85
98
|
const profile_name = await select({
|
|
86
99
|
message: 'Select a profile to load:',
|
|
87
|
-
options: profiles.map((p) =>
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
100
|
+
options: profiles.map((p) => {
|
|
101
|
+
const parts = [`${p.serverCount} servers`];
|
|
102
|
+
if (p.pluginCount > 0) {
|
|
103
|
+
parts.push(`${p.pluginCount} plugins`);
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
value: p.name,
|
|
107
|
+
label: p.name,
|
|
108
|
+
hint: parts.join(', '),
|
|
109
|
+
};
|
|
110
|
+
}),
|
|
92
111
|
});
|
|
93
112
|
if (isCancel(profile_name))
|
|
94
113
|
return;
|
|
95
|
-
const
|
|
96
|
-
await write_claude_config(
|
|
97
|
-
const server_count = Object.keys(
|
|
98
|
-
|
|
114
|
+
const profile = await load_profile(profile_name);
|
|
115
|
+
await write_claude_config(profile.config);
|
|
116
|
+
const server_count = Object.keys(profile.config.mcpServers || {}).length;
|
|
117
|
+
const parts = [`${server_count} servers`];
|
|
118
|
+
if (profile.enabledPlugins) {
|
|
119
|
+
await write_claude_settings({
|
|
120
|
+
enabledPlugins: profile.enabledPlugins,
|
|
121
|
+
});
|
|
122
|
+
parts.push(`${Object.keys(profile.enabledPlugins).length} plugins`);
|
|
123
|
+
}
|
|
124
|
+
log.success(`Profile '${profile_name}' applied (${parts.join(', ')})`);
|
|
99
125
|
}
|
|
100
126
|
async function handle_save_profile() {
|
|
101
127
|
const name = await text({
|
|
@@ -112,8 +138,12 @@ async function handle_save_profile() {
|
|
|
112
138
|
});
|
|
113
139
|
if (isCancel(name))
|
|
114
140
|
return;
|
|
115
|
-
const
|
|
116
|
-
|
|
141
|
+
const counts = await save_profile(name);
|
|
142
|
+
const parts = [`${counts.serverCount} servers`];
|
|
143
|
+
if (counts.pluginCount > 0) {
|
|
144
|
+
parts.push(`${counts.pluginCount} plugins`);
|
|
145
|
+
}
|
|
146
|
+
log.success(`Profile '${name}' saved (${parts.join(', ')})`);
|
|
117
147
|
}
|
|
118
148
|
async function main() {
|
|
119
149
|
const args = parse_args();
|
|
@@ -145,8 +175,13 @@ async function main() {
|
|
|
145
175
|
},
|
|
146
176
|
{
|
|
147
177
|
value: 'edit-plugins',
|
|
148
|
-
label: '
|
|
149
|
-
hint: 'Toggle
|
|
178
|
+
label: 'Manage plugins',
|
|
179
|
+
hint: 'Toggle, install, uninstall, or update plugins',
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
value: 'manage-cache',
|
|
183
|
+
label: 'Manage plugin cache',
|
|
184
|
+
hint: 'View, clear, or refresh plugin caches',
|
|
150
185
|
},
|
|
151
186
|
{
|
|
152
187
|
value: 'backup',
|
|
@@ -191,6 +226,9 @@ async function main() {
|
|
|
191
226
|
case 'edit-plugins':
|
|
192
227
|
await edit_plugins();
|
|
193
228
|
break;
|
|
229
|
+
case 'manage-cache':
|
|
230
|
+
await manage_cache();
|
|
231
|
+
break;
|
|
194
232
|
case 'backup':
|
|
195
233
|
await backup_config();
|
|
196
234
|
break;
|
|
@@ -232,8 +270,28 @@ async function main() {
|
|
|
232
270
|
}
|
|
233
271
|
}
|
|
234
272
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
273
|
+
const SUBCOMMANDS = new Set([
|
|
274
|
+
'list',
|
|
275
|
+
'enable',
|
|
276
|
+
'disable',
|
|
277
|
+
'remove',
|
|
278
|
+
'add',
|
|
279
|
+
'backup',
|
|
280
|
+
'restore',
|
|
281
|
+
'profile',
|
|
282
|
+
'plugins',
|
|
283
|
+
'cache',
|
|
284
|
+
]);
|
|
285
|
+
const arg = process.argv[2];
|
|
286
|
+
if ((arg && SUBCOMMANDS.has(arg)) ||
|
|
287
|
+
arg === '--help' ||
|
|
288
|
+
arg === '-h') {
|
|
289
|
+
import('./cli/index.js').then((m) => m.run());
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
main().catch((error) => {
|
|
293
|
+
console.error('Fatal error:', error);
|
|
294
|
+
process.exit(1);
|
|
295
|
+
});
|
|
296
|
+
}
|
|
239
297
|
//# sourceMappingURL=index.js.map
|
package/dist/utils/claude-cli.js
CHANGED
|
@@ -115,6 +115,75 @@ export async function remove_mcp_via_cli(name) {
|
|
|
115
115
|
};
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Install a plugin via Claude CLI
|
|
120
|
+
*/
|
|
121
|
+
export async function install_plugin_via_cli(key, scope = 'user') {
|
|
122
|
+
const cli_available = await check_claude_cli();
|
|
123
|
+
if (!cli_available) {
|
|
124
|
+
return {
|
|
125
|
+
success: false,
|
|
126
|
+
error: 'Claude CLI not found. Please install Claude Code CLI.',
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
await execAsync(`claude plugin install ${shell_escape(key)} --scope ${scope}`);
|
|
131
|
+
return { success: true };
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
135
|
+
return {
|
|
136
|
+
success: false,
|
|
137
|
+
error: `Failed to install plugin: ${message}`,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Uninstall a plugin via Claude CLI
|
|
143
|
+
*/
|
|
144
|
+
export async function uninstall_plugin_via_cli(key, scope = 'user') {
|
|
145
|
+
const cli_available = await check_claude_cli();
|
|
146
|
+
if (!cli_available) {
|
|
147
|
+
return {
|
|
148
|
+
success: false,
|
|
149
|
+
error: 'Claude CLI not found. Please install Claude Code CLI.',
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
await execAsync(`claude plugin uninstall ${shell_escape(key)} --scope ${scope}`);
|
|
154
|
+
return { success: true };
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
158
|
+
return {
|
|
159
|
+
success: false,
|
|
160
|
+
error: `Failed to uninstall plugin: ${message}`,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Update a plugin via Claude CLI
|
|
166
|
+
*/
|
|
167
|
+
export async function update_plugin_via_cli(key, scope = 'user') {
|
|
168
|
+
const cli_available = await check_claude_cli();
|
|
169
|
+
if (!cli_available) {
|
|
170
|
+
return {
|
|
171
|
+
success: false,
|
|
172
|
+
error: 'Claude CLI not found. Please install Claude Code CLI.',
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
await execAsync(`claude plugin update ${shell_escape(key)} --scope ${scope}`);
|
|
177
|
+
return { success: true };
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
181
|
+
return {
|
|
182
|
+
success: false,
|
|
183
|
+
error: `Failed to update plugin: ${message}`,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
118
187
|
/**
|
|
119
188
|
* Get the scope description for display
|
|
120
189
|
*/
|