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 +17 -0
- package/README.md +114 -4
- package/dist/cli/commands/backup.js +50 -22
- package/dist/cli/commands/plugins.js +110 -1
- package/dist/cli/commands/profile.js +31 -9
- package/dist/cli/commands/remove.js +12 -8
- package/dist/cli/commands/restore.js +75 -26
- package/dist/commands/backup.js +28 -22
- package/dist/commands/edit-plugins.js +192 -47
- package/dist/commands/restore.js +85 -34
- package/dist/core/config.js +6 -15
- package/dist/core/plugin-cache.js +3 -2
- package/dist/core/profile.js +32 -8
- package/dist/core/registry.js +44 -30
- package/dist/core/settings.js +8 -15
- package/dist/index.js +48 -19
- package/dist/utils/atomic-write.js +27 -0
- package/dist/utils/claude-cli.js +69 -0
- package/dist/utils/paths.js +8 -2
- package/package.json +1 -1
package/dist/commands/backup.js
CHANGED
|
@@ -2,53 +2,59 @@ import { note } from '@clack/prompts';
|
|
|
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 {
|
|
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
|
const MAX_BACKUPS = 10;
|
|
7
8
|
export async function backup_config() {
|
|
8
9
|
try {
|
|
9
10
|
const current_config = await read_claude_config();
|
|
11
|
+
const current_settings = await read_claude_settings();
|
|
10
12
|
const backups_dir = get_backups_dir();
|
|
11
13
|
await ensure_directory_exists(backups_dir);
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
+
// Backup MCP servers
|
|
15
|
+
const mcp_filename = get_backup_filename();
|
|
16
|
+
const mcp_path = join(backups_dir, mcp_filename);
|
|
14
17
|
const mcp_backup = {
|
|
15
18
|
mcpServers: current_config.mcpServers || {},
|
|
16
19
|
};
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
await writeFile(mcp_path, JSON.stringify(mcp_backup, null, 2), 'utf-8');
|
|
21
|
+
// Backup plugins
|
|
22
|
+
const plugins = current_settings.enabledPlugins || {};
|
|
23
|
+
const plugin_count = Object.keys(plugins).length;
|
|
24
|
+
let plugin_msg = '';
|
|
25
|
+
if (plugin_count > 0) {
|
|
26
|
+
const plugin_filename = get_plugin_backup_filename();
|
|
27
|
+
const plugin_path = join(backups_dir, plugin_filename);
|
|
28
|
+
const plugin_backup = { enabledPlugins: plugins };
|
|
29
|
+
await writeFile(plugin_path, JSON.stringify(plugin_backup, null, 2), 'utf-8');
|
|
30
|
+
plugin_msg = `\nPlugins backup: ${plugin_path}\n(${plugin_count} plugins backed up)`;
|
|
31
|
+
}
|
|
32
|
+
await cleanup_old_backups('mcp-servers-');
|
|
33
|
+
await cleanup_old_backups('plugins-');
|
|
20
34
|
const server_count = Object.keys(current_config.mcpServers || {}).length;
|
|
21
|
-
note(`MCP servers backup
|
|
35
|
+
note(`MCP servers backup: ${mcp_path}\n(${server_count} servers backed up)${plugin_msg}`);
|
|
22
36
|
}
|
|
23
37
|
catch (error) {
|
|
24
38
|
throw new Error(`Failed to create backup: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
25
39
|
}
|
|
26
40
|
}
|
|
27
|
-
async function cleanup_old_backups() {
|
|
41
|
+
async function cleanup_old_backups(prefix) {
|
|
28
42
|
try {
|
|
29
43
|
const backups_dir = get_backups_dir();
|
|
30
44
|
const files = await readdir(backups_dir);
|
|
31
45
|
const backup_files = files
|
|
32
|
-
.filter((file) => file.startsWith(
|
|
33
|
-
.
|
|
34
|
-
|
|
35
|
-
if (!timestamp_match)
|
|
36
|
-
return null;
|
|
37
|
-
const [, year, month, day, hour, minute, second] = timestamp_match;
|
|
38
|
-
const timestamp = new Date(parseInt(year), parseInt(month) - 1, parseInt(day), parseInt(hour), parseInt(minute), parseInt(second));
|
|
39
|
-
return { file, timestamp };
|
|
40
|
-
})
|
|
41
|
-
.filter((backup) => backup !== null)
|
|
42
|
-
.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
|
|
46
|
+
.filter((file) => file.startsWith(prefix) && file.endsWith('.json'))
|
|
47
|
+
.sort()
|
|
48
|
+
.reverse();
|
|
43
49
|
if (backup_files.length > MAX_BACKUPS) {
|
|
44
50
|
const files_to_delete = backup_files.slice(MAX_BACKUPS);
|
|
45
|
-
for (const
|
|
51
|
+
for (const file of files_to_delete) {
|
|
46
52
|
await unlink(join(backups_dir, file));
|
|
47
53
|
}
|
|
48
54
|
}
|
|
49
55
|
}
|
|
50
|
-
catch
|
|
51
|
-
|
|
56
|
+
catch {
|
|
57
|
+
// Cleanup is best-effort
|
|
52
58
|
}
|
|
53
59
|
}
|
|
54
60
|
//# sourceMappingURL=backup.js.map
|
|
@@ -1,56 +1,201 @@
|
|
|
1
|
-
import { log, multiselect, note } from '@clack/prompts';
|
|
1
|
+
import { confirm, isCancel, log, multiselect, note, select, text, } from '@clack/prompts';
|
|
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';
|
|
4
|
+
async function handle_toggle() {
|
|
5
|
+
const settings = await read_claude_settings();
|
|
6
|
+
const plugins = get_all_plugins(settings);
|
|
7
|
+
if (plugins.length === 0) {
|
|
8
|
+
note('No plugins found in ~/.claude/settings.json.\n' +
|
|
9
|
+
'Install plugins via Claude Code: /plugin');
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const plugin_choices = plugins.map((plugin) => ({
|
|
13
|
+
value: `${plugin.name}@${plugin.marketplace}`,
|
|
14
|
+
label: plugin.name,
|
|
15
|
+
hint: plugin.marketplace,
|
|
16
|
+
}));
|
|
17
|
+
const currently_enabled = plugins
|
|
18
|
+
.filter((p) => p.enabled)
|
|
19
|
+
.map((p) => `${p.name}@${p.marketplace}`);
|
|
20
|
+
const selected = await multiselect({
|
|
21
|
+
message: 'Select plugins to enable:',
|
|
22
|
+
options: plugin_choices,
|
|
23
|
+
initialValues: currently_enabled,
|
|
24
|
+
required: false,
|
|
25
|
+
});
|
|
26
|
+
if (typeof selected === 'symbol')
|
|
27
|
+
return;
|
|
28
|
+
const selected_set = new Set(selected);
|
|
29
|
+
const updated_plugins = plugins.map((plugin) => ({
|
|
30
|
+
...plugin,
|
|
31
|
+
enabled: selected_set.has(`${plugin.name}@${plugin.marketplace}`),
|
|
32
|
+
}));
|
|
33
|
+
const enabled_plugins = build_enabled_plugins(updated_plugins);
|
|
34
|
+
await write_claude_settings({ enabledPlugins: enabled_plugins });
|
|
35
|
+
const enabled_count = updated_plugins.filter((p) => p.enabled).length;
|
|
36
|
+
const disabled_count = updated_plugins.filter((p) => !p.enabled).length;
|
|
37
|
+
note(`Plugins updated!\n` +
|
|
38
|
+
`Enabled: ${enabled_count}, Disabled: ${disabled_count}`);
|
|
39
|
+
const newly_enabled = updated_plugins.filter((p) => p.enabled &&
|
|
40
|
+
!currently_enabled.includes(`${p.name}@${p.marketplace}`));
|
|
41
|
+
const newly_disabled = updated_plugins.filter((p) => !p.enabled &&
|
|
42
|
+
currently_enabled.includes(`${p.name}@${p.marketplace}`));
|
|
43
|
+
if (newly_enabled.length > 0) {
|
|
44
|
+
log.success(`Enabled: ${newly_enabled.map((p) => p.name).join(', ')}`);
|
|
45
|
+
}
|
|
46
|
+
if (newly_disabled.length > 0) {
|
|
47
|
+
log.warn(`Disabled: ${newly_disabled.map((p) => p.name).join(', ')}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function handle_install() {
|
|
51
|
+
const key = await text({
|
|
52
|
+
message: 'Plugin to install (name@marketplace):',
|
|
53
|
+
placeholder: 'e.g. commit-commands@claude-plugins-official',
|
|
54
|
+
validate: (value) => {
|
|
55
|
+
if (!value || value.trim().length === 0) {
|
|
56
|
+
return 'Plugin key is required';
|
|
57
|
+
}
|
|
58
|
+
if (!value.includes('@')) {
|
|
59
|
+
return 'Format: plugin-name@marketplace-name';
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
if (isCancel(key))
|
|
64
|
+
return;
|
|
65
|
+
const scope = await select({
|
|
66
|
+
message: 'Installation scope:',
|
|
67
|
+
options: [
|
|
68
|
+
{ value: 'user', label: 'User', hint: 'Global (default)' },
|
|
69
|
+
{
|
|
70
|
+
value: 'project',
|
|
71
|
+
label: 'Project',
|
|
72
|
+
hint: 'Shared with team',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
value: 'local',
|
|
76
|
+
label: 'Local',
|
|
77
|
+
hint: 'This project only (gitignored)',
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
});
|
|
81
|
+
if (isCancel(scope))
|
|
82
|
+
return;
|
|
83
|
+
const result = await install_plugin_via_cli(key, scope);
|
|
84
|
+
if (result.success) {
|
|
85
|
+
log.success(`Installed '${key}' (scope: ${scope})`);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
log.error(result.error ?? 'Unknown error');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function handle_uninstall() {
|
|
92
|
+
const settings = await read_claude_settings();
|
|
93
|
+
const plugins = get_all_plugins(settings);
|
|
94
|
+
if (plugins.length === 0) {
|
|
95
|
+
log.info('No plugins installed.');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const selected = await select({
|
|
99
|
+
message: 'Select plugin to uninstall:',
|
|
100
|
+
options: plugins.map((p) => ({
|
|
101
|
+
value: `${p.name}@${p.marketplace}`,
|
|
102
|
+
label: p.name,
|
|
103
|
+
hint: p.marketplace,
|
|
104
|
+
})),
|
|
105
|
+
});
|
|
106
|
+
if (isCancel(selected))
|
|
107
|
+
return;
|
|
108
|
+
const should_uninstall = await confirm({
|
|
109
|
+
message: `Uninstall '${selected}'?`,
|
|
110
|
+
});
|
|
111
|
+
if (isCancel(should_uninstall) || !should_uninstall)
|
|
112
|
+
return;
|
|
113
|
+
const result = await uninstall_plugin_via_cli(selected);
|
|
114
|
+
if (result.success) {
|
|
115
|
+
log.success(`Uninstalled '${selected}'`);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
log.error(result.error ?? 'Unknown error');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async function handle_update() {
|
|
122
|
+
const settings = await read_claude_settings();
|
|
123
|
+
const plugins = get_all_plugins(settings);
|
|
124
|
+
if (plugins.length === 0) {
|
|
125
|
+
log.info('No plugins installed.');
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const selected = await select({
|
|
129
|
+
message: 'Select plugin to update:',
|
|
130
|
+
options: plugins.map((p) => ({
|
|
131
|
+
value: `${p.name}@${p.marketplace}`,
|
|
132
|
+
label: p.name,
|
|
133
|
+
hint: p.marketplace,
|
|
134
|
+
})),
|
|
135
|
+
});
|
|
136
|
+
if (isCancel(selected))
|
|
137
|
+
return;
|
|
138
|
+
const result = await update_plugin_via_cli(selected);
|
|
139
|
+
if (result.success) {
|
|
140
|
+
log.success(`Updated '${selected}'`);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
log.error(result.error ?? 'Unknown error');
|
|
144
|
+
}
|
|
145
|
+
}
|
|
3
146
|
export async function edit_plugins() {
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
147
|
+
while (true) {
|
|
148
|
+
const action = await select({
|
|
149
|
+
message: 'Plugin management:',
|
|
150
|
+
options: [
|
|
151
|
+
{
|
|
152
|
+
value: 'toggle',
|
|
153
|
+
label: 'Enable / Disable plugins',
|
|
154
|
+
hint: 'Toggle plugins on/off',
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
value: 'install',
|
|
158
|
+
label: 'Install plugin',
|
|
159
|
+
hint: 'Install from a marketplace',
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
value: 'uninstall',
|
|
163
|
+
label: 'Uninstall plugin',
|
|
164
|
+
hint: 'Remove a plugin entirely',
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
value: 'update',
|
|
168
|
+
label: 'Update plugin',
|
|
169
|
+
hint: 'Update to latest version',
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
value: 'back',
|
|
173
|
+
label: 'Back',
|
|
174
|
+
hint: 'Return to main menu',
|
|
175
|
+
},
|
|
176
|
+
],
|
|
25
177
|
});
|
|
26
|
-
if (
|
|
178
|
+
if (isCancel(action) || action === 'back')
|
|
27
179
|
return;
|
|
180
|
+
try {
|
|
181
|
+
switch (action) {
|
|
182
|
+
case 'toggle':
|
|
183
|
+
await handle_toggle();
|
|
184
|
+
break;
|
|
185
|
+
case 'install':
|
|
186
|
+
await handle_install();
|
|
187
|
+
break;
|
|
188
|
+
case 'uninstall':
|
|
189
|
+
await handle_uninstall();
|
|
190
|
+
break;
|
|
191
|
+
case 'update':
|
|
192
|
+
await handle_update();
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
28
195
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
...plugin,
|
|
32
|
-
enabled: selected_set.has(`${plugin.name}@${plugin.marketplace}`),
|
|
33
|
-
}));
|
|
34
|
-
const enabled_plugins = build_enabled_plugins(updated_plugins);
|
|
35
|
-
await write_claude_settings({ enabledPlugins: enabled_plugins });
|
|
36
|
-
const enabled_count = updated_plugins.filter((p) => p.enabled).length;
|
|
37
|
-
const disabled_count = updated_plugins.filter((p) => !p.enabled).length;
|
|
38
|
-
note(`Plugins updated!\n` +
|
|
39
|
-
`Enabled: ${enabled_count}, Disabled: ${disabled_count}`);
|
|
40
|
-
// Show what changed
|
|
41
|
-
const newly_enabled = updated_plugins.filter((p) => p.enabled &&
|
|
42
|
-
!currently_enabled.includes(`${p.name}@${p.marketplace}`));
|
|
43
|
-
const newly_disabled = updated_plugins.filter((p) => !p.enabled &&
|
|
44
|
-
currently_enabled.includes(`${p.name}@${p.marketplace}`));
|
|
45
|
-
if (newly_enabled.length > 0) {
|
|
46
|
-
log.success(`Enabled: ${newly_enabled.map((p) => p.name).join(', ')}`);
|
|
196
|
+
catch (err) {
|
|
197
|
+
log.error(err instanceof Error ? err.message : 'Unknown error');
|
|
47
198
|
}
|
|
48
|
-
if (newly_disabled.length > 0) {
|
|
49
|
-
log.warn(`Disabled: ${newly_disabled.map((p) => p.name).join(', ')}`);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
catch (error) {
|
|
53
|
-
throw new Error(`Failed to edit plugins: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
54
199
|
}
|
|
55
200
|
}
|
|
56
201
|
//# sourceMappingURL=edit-plugins.js.map
|
package/dist/commands/restore.js
CHANGED
|
@@ -1,50 +1,101 @@
|
|
|
1
|
-
import { confirm, note, select } from '@clack/prompts';
|
|
1
|
+
import { confirm, isCancel, log, note, select } from '@clack/prompts';
|
|
2
2
|
import { readFile } from 'node:fs/promises';
|
|
3
3
|
import { read_claude_config, 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
|
export async function restore_config() {
|
|
6
7
|
try {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
8
|
+
const restore_type = await select({
|
|
9
|
+
message: 'What would you like to restore?',
|
|
10
|
+
options: [
|
|
11
|
+
{
|
|
12
|
+
value: 'mcp',
|
|
13
|
+
label: 'MCP servers',
|
|
14
|
+
hint: 'Restore server configuration',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
value: 'plugins',
|
|
18
|
+
label: 'Plugins',
|
|
19
|
+
hint: 'Restore plugin enabled/disabled state',
|
|
20
|
+
},
|
|
21
|
+
],
|
|
20
22
|
});
|
|
21
|
-
if (
|
|
23
|
+
if (isCancel(restore_type))
|
|
22
24
|
return;
|
|
25
|
+
if (restore_type === 'mcp') {
|
|
26
|
+
await restore_mcp();
|
|
23
27
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
});
|
|
27
|
-
if (typeof should_restore === 'symbol' || !should_restore) {
|
|
28
|
-
return;
|
|
28
|
+
else {
|
|
29
|
+
await restore_plugins();
|
|
29
30
|
}
|
|
30
|
-
// Read the backup file
|
|
31
|
-
const backup_content = await readFile(selected_backup_path, 'utf-8');
|
|
32
|
-
const backup_data = JSON.parse(backup_content);
|
|
33
|
-
// Read current config and merge
|
|
34
|
-
const current_config = await read_claude_config();
|
|
35
|
-
const updated_config = {
|
|
36
|
-
...current_config,
|
|
37
|
-
mcpServers: backup_data.mcpServers || {},
|
|
38
|
-
};
|
|
39
|
-
// Write back only the updated config
|
|
40
|
-
await write_claude_config(updated_config);
|
|
41
|
-
const server_count = Object.keys(backup_data.mcpServers || {}).length;
|
|
42
|
-
note(`MCP servers configuration restored successfully!\n(${server_count} servers restored)`);
|
|
43
31
|
}
|
|
44
32
|
catch (error) {
|
|
45
33
|
throw new Error(`Failed to restore configuration: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
46
34
|
}
|
|
47
35
|
}
|
|
36
|
+
async function restore_mcp() {
|
|
37
|
+
const backups = await list_backups();
|
|
38
|
+
if (backups.length === 0) {
|
|
39
|
+
note('No MCP server backups found.');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const backup_choices = backups.map((backup) => ({
|
|
43
|
+
value: backup.path,
|
|
44
|
+
label: `${backup.filename} (${backup.timestamp.toLocaleString()})`,
|
|
45
|
+
hint: format_time_ago(backup.timestamp),
|
|
46
|
+
}));
|
|
47
|
+
const selected_backup_path = await select({
|
|
48
|
+
message: 'Select backup to restore:',
|
|
49
|
+
options: backup_choices,
|
|
50
|
+
});
|
|
51
|
+
if (isCancel(selected_backup_path))
|
|
52
|
+
return;
|
|
53
|
+
const should_restore = await confirm({
|
|
54
|
+
message: 'This will replace your current MCP servers configuration. Continue?',
|
|
55
|
+
});
|
|
56
|
+
if (isCancel(should_restore) || !should_restore)
|
|
57
|
+
return;
|
|
58
|
+
const backup_content = await readFile(selected_backup_path, 'utf-8');
|
|
59
|
+
const backup_data = JSON.parse(backup_content);
|
|
60
|
+
const current_config = await read_claude_config();
|
|
61
|
+
const updated_config = {
|
|
62
|
+
...current_config,
|
|
63
|
+
mcpServers: backup_data.mcpServers || {},
|
|
64
|
+
};
|
|
65
|
+
await write_claude_config(updated_config);
|
|
66
|
+
const server_count = Object.keys(backup_data.mcpServers || {}).length;
|
|
67
|
+
log.success(`MCP servers restored (${server_count} servers)`);
|
|
68
|
+
}
|
|
69
|
+
async function restore_plugins() {
|
|
70
|
+
const backups = await list_plugin_backups();
|
|
71
|
+
if (backups.length === 0) {
|
|
72
|
+
note('No plugin backups found.');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const backup_choices = backups.map((backup) => ({
|
|
76
|
+
value: backup.path,
|
|
77
|
+
label: `${backup.filename} (${backup.timestamp.toLocaleString()})`,
|
|
78
|
+
hint: format_time_ago(backup.timestamp),
|
|
79
|
+
}));
|
|
80
|
+
const selected_backup_path = await select({
|
|
81
|
+
message: 'Select plugin backup to restore:',
|
|
82
|
+
options: backup_choices,
|
|
83
|
+
});
|
|
84
|
+
if (isCancel(selected_backup_path))
|
|
85
|
+
return;
|
|
86
|
+
const should_restore = await confirm({
|
|
87
|
+
message: 'This will replace your current plugin configuration. Continue?',
|
|
88
|
+
});
|
|
89
|
+
if (isCancel(should_restore) || !should_restore)
|
|
90
|
+
return;
|
|
91
|
+
const backup_content = await readFile(selected_backup_path, 'utf-8');
|
|
92
|
+
const backup_data = JSON.parse(backup_content);
|
|
93
|
+
await write_claude_settings({
|
|
94
|
+
enabledPlugins: backup_data.enabledPlugins || {},
|
|
95
|
+
});
|
|
96
|
+
const plugin_count = Object.keys(backup_data.enabledPlugins || {}).length;
|
|
97
|
+
log.success(`Plugins restored (${plugin_count} plugins)`);
|
|
98
|
+
}
|
|
48
99
|
function format_time_ago(date) {
|
|
49
100
|
const now = new Date();
|
|
50
101
|
const diff = now.getTime() - date.getTime();
|
package/dist/core/config.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { access, readFile
|
|
1
|
+
import { access, readFile } from 'node:fs/promises';
|
|
2
|
+
import { atomic_json_write } from '../utils/atomic-write.js';
|
|
2
3
|
import { get_claude_config_path, get_current_project_path, get_project_mcp_json_path, } from '../utils/paths.js';
|
|
3
4
|
import { validate_claude_config } from './validation.js';
|
|
4
5
|
export async function read_claude_config() {
|
|
@@ -19,20 +20,10 @@ export async function read_claude_config() {
|
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
export async function write_claude_config(config) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const existing_content = await readFile(config_path, 'utf-8');
|
|
27
|
-
existing_config = JSON.parse(existing_content);
|
|
28
|
-
}
|
|
29
|
-
catch (error) {
|
|
30
|
-
// If file doesn't exist or is invalid, start with empty object
|
|
31
|
-
}
|
|
32
|
-
// Only update the mcpServers section, preserve everything else
|
|
33
|
-
existing_config.mcpServers = config.mcpServers;
|
|
34
|
-
const config_content = JSON.stringify(existing_config, null, 2);
|
|
35
|
-
await writeFile(config_path, config_content, 'utf-8');
|
|
23
|
+
await atomic_json_write(get_claude_config_path(), (existing) => {
|
|
24
|
+
existing.mcpServers = config.mcpServers;
|
|
25
|
+
return existing;
|
|
26
|
+
});
|
|
36
27
|
}
|
|
37
28
|
export function get_enabled_servers(config) {
|
|
38
29
|
if (!config.mcpServers) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { exec } from 'node:child_process';
|
|
2
|
-
import { readdir, readFile, rm
|
|
2
|
+
import { readdir, readFile, rm } from 'node:fs/promises';
|
|
3
3
|
import { join, resolve } from 'node:path';
|
|
4
4
|
import { promisify } from 'node:util';
|
|
5
|
+
import { atomic_json_write } from '../utils/atomic-write.js';
|
|
5
6
|
import { get_installed_plugins_path, get_known_marketplaces_path, get_marketplace_manifest_path, get_plugin_cache_dir, } from '../utils/paths.js';
|
|
6
7
|
const execAsync = promisify(exec);
|
|
7
8
|
const EMPTY_INSTALLED = {
|
|
@@ -19,7 +20,7 @@ export async function read_installed_plugins() {
|
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
export async function write_installed_plugins(data) {
|
|
22
|
-
await
|
|
23
|
+
await atomic_json_write(get_installed_plugins_path(), () => data);
|
|
23
24
|
}
|
|
24
25
|
export async function read_known_marketplaces() {
|
|
25
26
|
try {
|
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
|