mcpick 0.0.10 → 0.0.12

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 CHANGED
@@ -1,5 +1,22 @@
1
1
  # mcpick
2
2
 
3
+ ## 0.0.12
4
+
5
+ ### Patch Changes
6
+
7
+ - b4f38d5: fix: discover externally-added servers and use atomic JSON
8
+ writes
9
+
10
+ ## 0.0.11
11
+
12
+ ### Patch Changes
13
+
14
+ - 7ec3323: feat: add plugin install, uninstall, and update commands
15
+ via Claude CLI
16
+ - 99b2bf3: feat: add plugin install, uninstall, and update to
17
+ interactive menu
18
+ - 9b1c6d7: feat: add plugin support to backup, restore, and profiles
19
+
3
20
  ## 0.0.10
4
21
 
5
22
  ### Patch Changes
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # McPick
2
2
 
3
- A CLI tool for dynamically managing MCP server configurations in
4
- Claude Code. Enable and disable MCP servers on-demand to optimize
5
- context usage and performance.
3
+ A CLI tool for managing MCP servers, plugins, and plugin caches in
4
+ Claude Code. Toggle servers and plugins on/off, manage stale plugin
5
+ caches, and optimise context usage and performance.
6
6
 
7
7
  ## Installation
8
8
 
@@ -51,10 +51,15 @@ This can lead to:
51
51
 
52
52
  ## The Solution
53
53
 
54
- McPick provides an intuitive CLI menu to:
54
+ McPick provides an intuitive CLI menu and non-interactive subcommands
55
+ to:
55
56
 
56
57
  - ✅ **Toggle servers on/off** - Enable only the MCP servers you need
57
58
  for your current task
59
+ - 🔌 **Toggle plugins on/off** - Enable or disable Claude Code
60
+ marketplace plugins
61
+ - 🗑️ **Manage plugin cache** - Detect stale plugins, clear caches,
62
+ clean orphaned versions, refresh marketplaces
58
63
  - 📁 **Manage server registry** - Keep a database of all your
59
64
  available MCP servers
60
65
  - 🔄 **Safe configuration** - Only modifies the `mcpServers` section,
@@ -71,6 +76,8 @@ McPick provides an intuitive CLI menu to:
71
76
 
72
77
  ◆ What would you like to do?
73
78
  │ ● Enable / Disable MCP servers (Toggle MCP servers on/off)
79
+ │ ○ Enable / Disable plugins (Toggle Claude Code plugins on/off)
80
+ │ ○ Manage plugin cache (View, clear, or refresh plugin caches)
74
81
  │ ○ Backup config
75
82
  │ ○ Add MCP server
76
83
  │ ○ Restore from backup
@@ -162,6 +169,104 @@ Or use full format with `mcpServers` wrapper:
162
169
  }
163
170
  ```
164
171
 
172
+ ### Plugin Cache Management
173
+
174
+ Claude Code caches marketplace plugins at `~/.claude/plugins/cache/`.
175
+ When marketplace authors update plugins, your cached versions can go
176
+ stale. McPick detects this and lets you fix it.
177
+
178
+ #### Interactive
179
+
180
+ Select "Manage plugin cache" from the main menu to:
181
+
182
+ - **View cache status** - See all cached plugins with staleness
183
+ indicators (version mismatch, commits behind, orphaned versions)
184
+ - **Clear plugin caches** - Refreshes the marketplace and clears
185
+ selected caches so they rebuild with the latest version
186
+ - **Clean orphaned versions** - Remove old version directories marked
187
+ as orphaned
188
+ - **Refresh marketplaces** - Git pull all marketplace clones to get
189
+ latest plugin listings
190
+
191
+ #### CLI Subcommands
192
+
193
+ ```bash
194
+ # Show cache status for all plugins
195
+ npx mcpick cache status
196
+ npx mcpick cache status --json
197
+
198
+ # Clear a specific plugin cache (refreshes marketplace first)
199
+ npx mcpick cache clear plugin-name@marketplace
200
+ npx mcpick cache clear --all
201
+
202
+ # Remove orphaned version directories
203
+ npx mcpick cache clean-orphaned
204
+
205
+ # Refresh all marketplace clones
206
+ npx mcpick cache refresh
207
+ ```
208
+
209
+ ### Plugin Management
210
+
211
+ Install, update, and toggle Claude Code marketplace plugins:
212
+
213
+ ```bash
214
+ # List all plugins and their status
215
+ npx mcpick plugins list
216
+ npx mcpick plugins list --json
217
+
218
+ # Install/uninstall a plugin (wraps claude plugin CLI)
219
+ npx mcpick plugins install plugin-name@marketplace
220
+ npx mcpick plugins uninstall plugin-name@marketplace
221
+
222
+ # Update a plugin to latest version
223
+ npx mcpick plugins update plugin-name@marketplace
224
+
225
+ # Enable/disable a plugin
226
+ npx mcpick plugins enable plugin-name@marketplace
227
+ npx mcpick plugins disable plugin-name@marketplace
228
+ ```
229
+
230
+ All plugin commands support `--scope` (user, project, local) and
231
+ `--json` flags.
232
+
233
+ ### CLI Subcommands
234
+
235
+ McPick supports both an interactive menu (default) and non-interactive
236
+ CLI subcommands for scripting and LLM tool use:
237
+
238
+ ```bash
239
+ # MCP server management
240
+ npx mcpick list # List servers
241
+ npx mcpick enable <server> # Enable a server
242
+ npx mcpick disable <server> # Disable a server
243
+ npx mcpick add --name <n> ... # Add a server
244
+ npx mcpick remove <server> # Remove a server
245
+
246
+ # Backups and profiles
247
+ npx mcpick backup # Create backup
248
+ npx mcpick restore [file] # Restore from backup
249
+ npx mcpick profile list # List profiles
250
+ npx mcpick profile load <name> # Load a profile
251
+ npx mcpick profile save <name> # Save current config
252
+
253
+ # Plugin management
254
+ npx mcpick plugins list # List plugins
255
+ npx mcpick plugins enable <key> # Enable plugin
256
+ npx mcpick plugins disable <key> # Disable plugin
257
+ npx mcpick plugins install <key> # Install from marketplace
258
+ npx mcpick plugins uninstall <key> # Remove plugin
259
+ npx mcpick plugins update <key> # Update to latest version
260
+
261
+ # Cache management
262
+ npx mcpick cache status # Show staleness info
263
+ npx mcpick cache clear [key] # Clear plugin cache
264
+ npx mcpick cache clean-orphaned # Remove orphaned dirs
265
+ npx mcpick cache refresh # Git pull marketplaces
266
+ ```
267
+
268
+ All subcommands support `--json` for machine-readable output.
269
+
165
270
  ### Typical Workflow
166
271
 
167
272
  1. **Before a coding session**: Run `mcpick -p <profile>` or use the
@@ -209,6 +314,11 @@ MCPick works with the standard Claude Code configuration format:
209
314
  server database)
210
315
  - **Backups**: `~/.claude/mcpick/backups/` (MCP configuration backups)
211
316
  - **Profiles**: `~/.claude/mcpick/profiles/` (predefined server sets)
317
+ - **Plugin Cache**: `~/.claude/plugins/cache/` (cached plugin files)
318
+ - **Installed Plugins**: `~/.claude/plugins/installed_plugins.json`
319
+ (plugin install registry)
320
+ - **Marketplaces**: `~/.claude/plugins/marketplaces/` (marketplace git
321
+ clones)
212
322
 
213
323
  #### MCP Server Storage by Scope
214
324
 
@@ -2,13 +2,32 @@ import { defineCommand } from 'citty';
2
2
  import { readdir, unlink, writeFile } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
4
  import { read_claude_config } from '../../core/config.js';
5
- import { ensure_directory_exists, get_backup_filename, get_backups_dir, } from '../../utils/paths.js';
5
+ import { read_claude_settings } from '../../core/settings.js';
6
+ import { ensure_directory_exists, get_backup_filename, get_backups_dir, get_plugin_backup_filename, } from '../../utils/paths.js';
6
7
  import { output } from '../output.js';
7
8
  const MAX_BACKUPS = 10;
9
+ async function cleanup_old_backups(prefix) {
10
+ try {
11
+ const backups_dir = get_backups_dir();
12
+ const files = await readdir(backups_dir);
13
+ const backup_files = files
14
+ .filter((f) => f.startsWith(prefix) && f.endsWith('.json'))
15
+ .sort()
16
+ .reverse();
17
+ if (backup_files.length > MAX_BACKUPS) {
18
+ for (const file of backup_files.slice(MAX_BACKUPS)) {
19
+ await unlink(join(backups_dir, file));
20
+ }
21
+ }
22
+ }
23
+ catch {
24
+ // Cleanup is best-effort
25
+ }
26
+ }
8
27
  export default defineCommand({
9
28
  meta: {
10
29
  name: 'backup',
11
- description: 'Create a timestamped backup of MCP server config',
30
+ description: 'Create a timestamped backup of MCP servers and plugins',
12
31
  },
13
32
  args: {
14
33
  json: {
@@ -19,36 +38,45 @@ export default defineCommand({
19
38
  },
20
39
  async run({ args }) {
21
40
  const current_config = await read_claude_config();
41
+ const current_settings = await read_claude_settings();
22
42
  const backups_dir = get_backups_dir();
23
43
  await ensure_directory_exists(backups_dir);
24
- const backup_filename = get_backup_filename();
25
- const backup_path = join(backups_dir, backup_filename);
44
+ // Backup MCP servers
45
+ const mcp_filename = get_backup_filename();
46
+ const mcp_path = join(backups_dir, mcp_filename);
26
47
  const mcp_backup = {
27
48
  mcpServers: current_config.mcpServers || {},
28
49
  };
29
- await writeFile(backup_path, JSON.stringify(mcp_backup, null, 2), 'utf-8');
30
- // Cleanup old backups
31
- try {
32
- const files = await readdir(backups_dir);
33
- const backup_files = files
34
- .filter((f) => f.startsWith('mcp-servers-') && f.endsWith('.json'))
35
- .sort()
36
- .reverse();
37
- if (backup_files.length > MAX_BACKUPS) {
38
- for (const file of backup_files.slice(MAX_BACKUPS)) {
39
- await unlink(join(backups_dir, file));
40
- }
41
- }
42
- }
43
- catch {
44
- // Cleanup is best-effort
50
+ await writeFile(mcp_path, JSON.stringify(mcp_backup, null, 2), 'utf-8');
51
+ // Backup plugins
52
+ const plugins = current_settings.enabledPlugins || {};
53
+ const plugin_count = Object.keys(plugins).length;
54
+ let plugin_path = null;
55
+ if (plugin_count > 0) {
56
+ const plugin_filename = get_plugin_backup_filename();
57
+ plugin_path = join(backups_dir, plugin_filename);
58
+ const plugin_backup = { enabledPlugins: plugins };
59
+ await writeFile(plugin_path, JSON.stringify(plugin_backup, null, 2), 'utf-8');
45
60
  }
61
+ await cleanup_old_backups('mcp-servers-');
62
+ await cleanup_old_backups('plugins-');
46
63
  const server_count = Object.keys(current_config.mcpServers || {}).length;
47
64
  if (args.json) {
48
- output({ path: backup_path, servers: server_count }, true);
65
+ output({
66
+ mcp: { path: mcp_path, servers: server_count },
67
+ plugins: plugin_path
68
+ ? {
69
+ path: plugin_path,
70
+ plugins: plugin_count,
71
+ }
72
+ : null,
73
+ }, true);
49
74
  }
50
75
  else {
51
- console.log(`Backup created: ${backup_path} (${server_count} servers)`);
76
+ console.log(`Backup created: ${mcp_path} (${server_count} servers)`);
77
+ if (plugin_path) {
78
+ console.log(`Plugin backup: ${plugin_path} (${plugin_count} plugins)`);
79
+ }
52
80
  }
53
81
  },
54
82
  });
@@ -1,5 +1,6 @@
1
1
  import { defineCommand } from 'citty';
2
2
  import { build_enabled_plugins, get_all_plugins, read_claude_settings, write_claude_settings, } from '../../core/settings.js';
3
+ import { install_plugin_via_cli, uninstall_plugin_via_cli, update_plugin_via_cli, } from '../../utils/claude-cli.js';
3
4
  import { error, output } from '../output.js';
4
5
  const list = defineCommand({
5
6
  meta: {
@@ -83,11 +84,119 @@ const disable = defineCommand({
83
84
  console.log(`Disabled plugin '${key}'`);
84
85
  },
85
86
  });
87
+ const install = defineCommand({
88
+ meta: {
89
+ name: 'install',
90
+ description: 'Install a plugin from a marketplace',
91
+ },
92
+ args: {
93
+ plugin: {
94
+ type: 'positional',
95
+ description: 'Plugin key (name@marketplace)',
96
+ required: true,
97
+ },
98
+ scope: {
99
+ type: 'string',
100
+ description: 'Installation scope: user, project, or local',
101
+ default: 'user',
102
+ },
103
+ json: {
104
+ type: 'boolean',
105
+ description: 'Output as JSON',
106
+ default: false,
107
+ },
108
+ },
109
+ async run({ args }) {
110
+ const scope = args.scope;
111
+ const result = await install_plugin_via_cli(args.plugin, scope);
112
+ if (args.json) {
113
+ output(result, true);
114
+ }
115
+ else if (result.success) {
116
+ console.log(`Installed plugin '${args.plugin}' (scope: ${scope})`);
117
+ }
118
+ else {
119
+ error(result.error ?? 'Unknown error');
120
+ }
121
+ },
122
+ });
123
+ const uninstall = defineCommand({
124
+ meta: {
125
+ name: 'uninstall',
126
+ description: 'Uninstall a plugin',
127
+ },
128
+ args: {
129
+ plugin: {
130
+ type: 'positional',
131
+ description: 'Plugin key (name@marketplace)',
132
+ required: true,
133
+ },
134
+ scope: {
135
+ type: 'string',
136
+ description: 'Uninstall from scope: user, project, or local',
137
+ default: 'user',
138
+ },
139
+ json: {
140
+ type: 'boolean',
141
+ description: 'Output as JSON',
142
+ default: false,
143
+ },
144
+ },
145
+ async run({ args }) {
146
+ const scope = args.scope;
147
+ const result = await uninstall_plugin_via_cli(args.plugin, scope);
148
+ if (args.json) {
149
+ output(result, true);
150
+ }
151
+ else if (result.success) {
152
+ console.log(`Uninstalled plugin '${args.plugin}' (scope: ${scope})`);
153
+ }
154
+ else {
155
+ error(result.error ?? 'Unknown error');
156
+ }
157
+ },
158
+ });
159
+ const update = defineCommand({
160
+ meta: {
161
+ name: 'update',
162
+ description: 'Update a plugin to latest version',
163
+ },
164
+ args: {
165
+ plugin: {
166
+ type: 'positional',
167
+ description: 'Plugin key (name@marketplace)',
168
+ required: true,
169
+ },
170
+ scope: {
171
+ type: 'string',
172
+ description: 'Scope to update: user, project, or local',
173
+ default: 'user',
174
+ },
175
+ json: {
176
+ type: 'boolean',
177
+ description: 'Output as JSON',
178
+ default: false,
179
+ },
180
+ },
181
+ async run({ args }) {
182
+ const scope = args.scope;
183
+ const result = await update_plugin_via_cli(args.plugin, scope);
184
+ if (args.json) {
185
+ output(result, true);
186
+ }
187
+ else if (result.success) {
188
+ console.log(`Updated plugin '${args.plugin}' (scope: ${scope})`);
189
+ }
190
+ else {
191
+ error(result.error ?? 'Unknown error');
192
+ }
193
+ },
194
+ });
86
195
  export default defineCommand({
87
196
  meta: {
88
197
  name: 'plugins',
89
198
  description: 'Manage Claude Code plugins',
90
199
  },
91
- subCommands: { list, enable, disable },
200
+ subCommands: { list, enable, disable, install, uninstall, update },
92
201
  });
93
202
  //# sourceMappingURL=plugins.js.map
@@ -1,6 +1,7 @@
1
1
  import { defineCommand } from 'citty';
2
2
  import { write_claude_config } from '../../core/config.js';
3
3
  import { list_profiles, load_profile, save_profile, } from '../../core/profile.js';
4
+ import { write_claude_settings } from '../../core/settings.js';
4
5
  import { error, output } from '../output.js';
5
6
  const list = defineCommand({
6
7
  meta: {
@@ -25,7 +26,11 @@ const list = defineCommand({
25
26
  return;
26
27
  }
27
28
  for (const p of profiles) {
28
- console.log(`${p.name} (${p.serverCount} servers)`);
29
+ const parts = [`${p.serverCount} servers`];
30
+ if (p.pluginCount > 0) {
31
+ parts.push(`${p.pluginCount} plugins`);
32
+ }
33
+ console.log(`${p.name} (${parts.join(', ')})`);
29
34
  }
30
35
  }
31
36
  },
@@ -49,17 +54,29 @@ const load = defineCommand({
49
54
  },
50
55
  async run({ args }) {
51
56
  try {
52
- const config = await load_profile(args.name);
53
- await write_claude_config(config);
54
- const server_count = Object.keys(config.mcpServers || {}).length;
57
+ const profile = await load_profile(args.name);
58
+ await write_claude_config(profile.config);
59
+ const server_count = Object.keys(profile.config.mcpServers || {}).length;
60
+ let plugin_count = 0;
61
+ if (profile.enabledPlugins) {
62
+ await write_claude_settings({
63
+ enabledPlugins: profile.enabledPlugins,
64
+ });
65
+ plugin_count = Object.keys(profile.enabledPlugins).length;
66
+ }
55
67
  if (args.json) {
56
68
  output({
57
69
  profile: args.name,
58
70
  servers: server_count,
71
+ plugins: plugin_count,
59
72
  }, true);
60
73
  }
61
74
  else {
62
- console.log(`Profile '${args.name}' applied (${server_count} servers)`);
75
+ const parts = [`${server_count} servers`];
76
+ if (plugin_count > 0) {
77
+ parts.push(`${plugin_count} plugins`);
78
+ }
79
+ console.log(`Profile '${args.name}' applied (${parts.join(', ')})`);
63
80
  }
64
81
  }
65
82
  catch (err) {
@@ -86,15 +103,20 @@ const save = defineCommand({
86
103
  },
87
104
  async run({ args }) {
88
105
  try {
89
- const server_count = await save_profile(args.name);
106
+ const counts = await save_profile(args.name);
90
107
  if (args.json) {
91
108
  output({
92
109
  profile: args.name,
93
- servers: server_count,
110
+ servers: counts.serverCount,
111
+ plugins: counts.pluginCount,
94
112
  }, true);
95
113
  }
96
114
  else {
97
- console.log(`Profile '${args.name}' saved (${server_count} servers)`);
115
+ const parts = [`${counts.serverCount} servers`];
116
+ if (counts.pluginCount > 0) {
117
+ parts.push(`${counts.pluginCount} plugins`);
118
+ }
119
+ console.log(`Profile '${args.name}' saved (${parts.join(', ')})`);
98
120
  }
99
121
  }
100
122
  catch (err) {
@@ -105,7 +127,7 @@ const save = defineCommand({
105
127
  export default defineCommand({
106
128
  meta: {
107
129
  name: 'profile',
108
- description: 'Manage MCP server profiles',
130
+ description: 'Manage profiles (MCP servers + plugins)',
109
131
  },
110
132
  subCommands: { list, load, save },
111
133
  });
@@ -1,5 +1,5 @@
1
1
  import { defineCommand } from 'citty';
2
- import { read_server_registry, write_server_registry, } from '../../core/registry.js';
2
+ import { get_all_available_servers, read_server_registry, write_server_registry, } from '../../core/registry.js';
3
3
  import { remove_mcp_via_cli } from '../../utils/claude-cli.js';
4
4
  import { error } from '../output.js';
5
5
  export default defineCommand({
@@ -15,17 +15,21 @@ export default defineCommand({
15
15
  },
16
16
  },
17
17
  async run({ args }) {
18
+ const all_servers = await get_all_available_servers();
19
+ const found = all_servers.find((s) => s.name === args.server);
20
+ if (!found) {
21
+ error(`Server '${args.server}' not found. Run 'mcpick list' to see available servers.`);
22
+ }
23
+ // Remove from registry if present
18
24
  const registry = await read_server_registry();
19
25
  const index = registry.servers.findIndex((s) => s.name === args.server);
20
- if (index < 0) {
21
- error(`Server '${args.server}' not found in registry. Run 'mcpick list' to see available servers.`);
26
+ if (index >= 0) {
27
+ registry.servers.splice(index, 1);
28
+ await write_server_registry(registry);
22
29
  }
23
- // Remove from registry
24
- registry.servers.splice(index, 1);
25
- await write_server_registry(registry);
26
- // Also disable via CLI (best effort)
30
+ // Remove via CLI
27
31
  await remove_mcp_via_cli(args.server);
28
- console.log(`Removed '${args.server}' from registry`);
32
+ console.log(`Removed '${args.server}'`);
29
33
  },
30
34
  });
31
35
  //# sourceMappingURL=remove.js.map
@@ -1,13 +1,14 @@
1
1
  import { defineCommand } from 'citty';
2
2
  import { readFile } from 'node:fs/promises';
3
3
  import { write_claude_config } from '../../core/config.js';
4
- import { list_backups } from '../../core/registry.js';
4
+ import { list_backups, list_plugin_backups, } from '../../core/registry.js';
5
+ import { write_claude_settings } from '../../core/settings.js';
5
6
  import { validate_claude_config } from '../../core/validation.js';
6
7
  import { error, output } from '../output.js';
7
8
  export default defineCommand({
8
9
  meta: {
9
10
  name: 'restore',
10
- description: 'Restore MCP server config from a backup (latest if no file specified)',
11
+ description: 'Restore config from a backup (latest if no file specified)',
11
12
  },
12
13
  args: {
13
14
  file: {
@@ -15,6 +16,11 @@ export default defineCommand({
15
16
  description: 'Backup filename or path (optional, defaults to latest)',
16
17
  required: false,
17
18
  },
19
+ type: {
20
+ type: 'string',
21
+ description: 'What to restore: mcp (default), plugins, or all',
22
+ default: 'mcp',
23
+ },
18
24
  json: {
19
25
  type: 'boolean',
20
26
  description: 'Output as JSON',
@@ -22,34 +28,77 @@ export default defineCommand({
22
28
  },
23
29
  },
24
30
  async run({ args }) {
25
- const backups = await list_backups();
26
- if (backups.length === 0) {
27
- error('No backups found. Run "mcpick backup" first.');
28
- }
29
- let backup_path;
30
- if (args.file) {
31
- const found = backups.find((b) => b.filename === args.file || b.path === args.file);
32
- if (!found) {
33
- error(`Backup '${args.file}' not found. Available: ${backups.map((b) => b.filename).join(', ')}`);
31
+ const restore_type = args.type;
32
+ const results = {};
33
+ if (restore_type === 'mcp' || restore_type === 'all') {
34
+ const backups = await list_backups();
35
+ if (backups.length === 0) {
36
+ if (restore_type === 'mcp') {
37
+ error('No MCP backups found. Run "mcpick backup" first.');
38
+ }
39
+ }
40
+ else {
41
+ let backup_path;
42
+ if (args.file && restore_type === 'mcp') {
43
+ const found = backups.find((b) => b.filename === args.file || b.path === args.file);
44
+ if (!found) {
45
+ error(`Backup '${args.file}' not found. Available: ${backups.map((b) => b.filename).join(', ')}`);
46
+ }
47
+ backup_path = found.path;
48
+ }
49
+ else {
50
+ backup_path = backups[0].path;
51
+ }
52
+ const content = await readFile(backup_path, 'utf-8');
53
+ const parsed = JSON.parse(content);
54
+ const config = validate_claude_config(parsed);
55
+ await write_claude_config(config);
56
+ const server_count = Object.keys(config.mcpServers || {}).length;
57
+ results.mcp = {
58
+ restored: backup_path,
59
+ servers: server_count,
60
+ };
61
+ if (!args.json) {
62
+ console.log(`Restored MCP: ${backup_path} (${server_count} servers)`);
63
+ }
34
64
  }
35
- backup_path = found.path;
36
65
  }
37
- else {
38
- backup_path = backups[0].path;
66
+ if (restore_type === 'plugins' || restore_type === 'all') {
67
+ const backups = await list_plugin_backups();
68
+ if (backups.length === 0) {
69
+ if (restore_type === 'plugins') {
70
+ error('No plugin backups found. Run "mcpick backup" first.');
71
+ }
72
+ }
73
+ else {
74
+ let backup_path;
75
+ if (args.file && restore_type === 'plugins') {
76
+ const found = backups.find((b) => b.filename === args.file || b.path === args.file);
77
+ if (!found) {
78
+ error(`Backup '${args.file}' not found. Available: ${backups.map((b) => b.filename).join(', ')}`);
79
+ }
80
+ backup_path = found.path;
81
+ }
82
+ else {
83
+ backup_path = backups[0].path;
84
+ }
85
+ const content = await readFile(backup_path, 'utf-8');
86
+ const parsed = JSON.parse(content);
87
+ await write_claude_settings({
88
+ enabledPlugins: parsed.enabledPlugins || {},
89
+ });
90
+ const plugin_count = Object.keys(parsed.enabledPlugins || {}).length;
91
+ results.plugins = {
92
+ restored: backup_path,
93
+ plugins: plugin_count,
94
+ };
95
+ if (!args.json) {
96
+ console.log(`Restored plugins: ${backup_path} (${plugin_count} plugins)`);
97
+ }
98
+ }
39
99
  }
40
- const content = await readFile(backup_path, 'utf-8');
41
- const parsed = JSON.parse(content);
42
- const config = validate_claude_config(parsed);
43
- await write_claude_config(config);
44
- const server_count = Object.keys(config.mcpServers || {}).length;
45
100
  if (args.json) {
46
- output({
47
- restored: backup_path,
48
- servers: server_count,
49
- }, true);
50
- }
51
- else {
52
- console.log(`Restored from ${backup_path} (${server_count} servers)`);
101
+ output(results, true);
53
102
  }
54
103
  },
55
104
  });