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.
@@ -0,0 +1,63 @@
1
+ import { defineCommand } from 'citty';
2
+ import { get_enabled_servers_for_scope } from '../../core/config.js';
3
+ import { get_all_available_servers } from '../../core/registry.js';
4
+ import { error, output } from '../output.js';
5
+ export default defineCommand({
6
+ meta: {
7
+ name: 'list',
8
+ description: 'List all MCP servers and their status',
9
+ },
10
+ args: {
11
+ scope: {
12
+ type: 'string',
13
+ description: 'Scope to check: local, project, or user',
14
+ },
15
+ json: {
16
+ type: 'boolean',
17
+ description: 'Output as JSON',
18
+ default: false,
19
+ },
20
+ },
21
+ async run({ args }) {
22
+ const scopes = args.scope
23
+ ? [args.scope]
24
+ : ['local', 'project', 'user'];
25
+ if (args.scope &&
26
+ !['local', 'project', 'user'].includes(args.scope)) {
27
+ error(`Invalid scope: ${args.scope}. Use local, project, or user.`);
28
+ }
29
+ const all_servers = await get_all_available_servers();
30
+ const enabled_by_scope = {};
31
+ for (const scope of scopes) {
32
+ enabled_by_scope[scope] =
33
+ await get_enabled_servers_for_scope(scope);
34
+ }
35
+ if (args.json) {
36
+ const data = all_servers.map((server) => {
37
+ const status = {};
38
+ for (const scope of scopes) {
39
+ status[scope] = enabled_by_scope[scope].includes(server.name);
40
+ }
41
+ const { name, ...rest } = server;
42
+ return { name, ...status, ...rest };
43
+ });
44
+ output(data, true);
45
+ }
46
+ else {
47
+ if (all_servers.length === 0) {
48
+ console.log('No servers in registry.');
49
+ return;
50
+ }
51
+ for (const server of all_servers) {
52
+ const statuses = scopes
53
+ .map((scope) => {
54
+ const enabled = enabled_by_scope[scope].includes(server.name);
55
+ return `${scope}:${enabled ? 'on' : 'off'}`;
56
+ })
57
+ .join(' ');
58
+ console.log(`${server.name} ${statuses}`);
59
+ }
60
+ }
61
+ },
62
+ });
63
+ //# sourceMappingURL=list.js.map
@@ -0,0 +1,202 @@
1
+ import { defineCommand } from 'citty';
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
+ import { error, output } from '../output.js';
5
+ const list = defineCommand({
6
+ meta: {
7
+ name: 'list',
8
+ description: 'List all plugins and their status',
9
+ },
10
+ args: {
11
+ json: {
12
+ type: 'boolean',
13
+ description: 'Output as JSON',
14
+ default: false,
15
+ },
16
+ },
17
+ async run({ args }) {
18
+ const settings = await read_claude_settings();
19
+ const plugins = get_all_plugins(settings);
20
+ if (args.json) {
21
+ output(plugins, true);
22
+ }
23
+ else {
24
+ if (plugins.length === 0) {
25
+ console.log('No plugins found.');
26
+ return;
27
+ }
28
+ for (const p of plugins) {
29
+ const status = p.enabled ? 'on' : 'off';
30
+ console.log(`${p.name}@${p.marketplace} ${status}`);
31
+ }
32
+ }
33
+ },
34
+ });
35
+ const enable = defineCommand({
36
+ meta: {
37
+ name: 'enable',
38
+ description: 'Enable a plugin',
39
+ },
40
+ args: {
41
+ plugin: {
42
+ type: 'positional',
43
+ description: 'Plugin key (name@marketplace)',
44
+ required: true,
45
+ },
46
+ },
47
+ async run({ args }) {
48
+ const settings = await read_claude_settings();
49
+ const plugins = get_all_plugins(settings);
50
+ const key = args.plugin;
51
+ const plugin = plugins.find((p) => `${p.name}@${p.marketplace}` === key);
52
+ if (!plugin) {
53
+ error(`Plugin '${key}' not found. Run 'mcpick plugins list' to see available plugins.`);
54
+ }
55
+ plugin.enabled = true;
56
+ const updated = build_enabled_plugins(plugins);
57
+ await write_claude_settings({ enabledPlugins: updated });
58
+ console.log(`Enabled plugin '${key}'`);
59
+ },
60
+ });
61
+ const disable = defineCommand({
62
+ meta: {
63
+ name: 'disable',
64
+ description: 'Disable a plugin',
65
+ },
66
+ args: {
67
+ plugin: {
68
+ type: 'positional',
69
+ description: 'Plugin key (name@marketplace)',
70
+ required: true,
71
+ },
72
+ },
73
+ async run({ args }) {
74
+ const settings = await read_claude_settings();
75
+ const plugins = get_all_plugins(settings);
76
+ const key = args.plugin;
77
+ const plugin = plugins.find((p) => `${p.name}@${p.marketplace}` === key);
78
+ if (!plugin) {
79
+ error(`Plugin '${key}' not found. Run 'mcpick plugins list' to see available plugins.`);
80
+ }
81
+ plugin.enabled = false;
82
+ const updated = build_enabled_plugins(plugins);
83
+ await write_claude_settings({ enabledPlugins: updated });
84
+ console.log(`Disabled plugin '${key}'`);
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
+ });
195
+ export default defineCommand({
196
+ meta: {
197
+ name: 'plugins',
198
+ description: 'Manage Claude Code plugins',
199
+ },
200
+ subCommands: { list, enable, disable, install, uninstall, update },
201
+ });
202
+ //# sourceMappingURL=plugins.js.map
@@ -0,0 +1,134 @@
1
+ import { defineCommand } from 'citty';
2
+ import { write_claude_config } from '../../core/config.js';
3
+ import { list_profiles, load_profile, save_profile, } from '../../core/profile.js';
4
+ import { write_claude_settings } from '../../core/settings.js';
5
+ import { error, output } from '../output.js';
6
+ const list = defineCommand({
7
+ meta: {
8
+ name: 'list',
9
+ description: 'List all saved profiles',
10
+ },
11
+ args: {
12
+ json: {
13
+ type: 'boolean',
14
+ description: 'Output as JSON',
15
+ default: false,
16
+ },
17
+ },
18
+ async run({ args }) {
19
+ const profiles = await list_profiles();
20
+ if (args.json) {
21
+ output(profiles, true);
22
+ }
23
+ else {
24
+ if (profiles.length === 0) {
25
+ console.log('No profiles found.');
26
+ return;
27
+ }
28
+ for (const p of profiles) {
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(', ')})`);
34
+ }
35
+ }
36
+ },
37
+ });
38
+ const load = defineCommand({
39
+ meta: {
40
+ name: 'load',
41
+ description: 'Load and apply a saved profile',
42
+ },
43
+ args: {
44
+ name: {
45
+ type: 'positional',
46
+ description: 'Profile name',
47
+ required: true,
48
+ },
49
+ json: {
50
+ type: 'boolean',
51
+ description: 'Output as JSON',
52
+ default: false,
53
+ },
54
+ },
55
+ async run({ args }) {
56
+ try {
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
+ }
67
+ if (args.json) {
68
+ output({
69
+ profile: args.name,
70
+ servers: server_count,
71
+ plugins: plugin_count,
72
+ }, true);
73
+ }
74
+ else {
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(', ')})`);
80
+ }
81
+ }
82
+ catch (err) {
83
+ error(err instanceof Error ? err.message : 'Failed to load profile');
84
+ }
85
+ },
86
+ });
87
+ const save = defineCommand({
88
+ meta: {
89
+ name: 'save',
90
+ description: 'Save current config as a profile',
91
+ },
92
+ args: {
93
+ name: {
94
+ type: 'positional',
95
+ description: 'Profile name',
96
+ required: true,
97
+ },
98
+ json: {
99
+ type: 'boolean',
100
+ description: 'Output as JSON',
101
+ default: false,
102
+ },
103
+ },
104
+ async run({ args }) {
105
+ try {
106
+ const counts = await save_profile(args.name);
107
+ if (args.json) {
108
+ output({
109
+ profile: args.name,
110
+ servers: counts.serverCount,
111
+ plugins: counts.pluginCount,
112
+ }, true);
113
+ }
114
+ else {
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(', ')})`);
120
+ }
121
+ }
122
+ catch (err) {
123
+ error(err instanceof Error ? err.message : 'Failed to save profile');
124
+ }
125
+ },
126
+ });
127
+ export default defineCommand({
128
+ meta: {
129
+ name: 'profile',
130
+ description: 'Manage profiles (MCP servers + plugins)',
131
+ },
132
+ subCommands: { list, load, save },
133
+ });
134
+ //# sourceMappingURL=profile.js.map
@@ -0,0 +1,31 @@
1
+ import { defineCommand } from 'citty';
2
+ import { read_server_registry, write_server_registry, } from '../../core/registry.js';
3
+ import { remove_mcp_via_cli } from '../../utils/claude-cli.js';
4
+ import { error } from '../output.js';
5
+ export default defineCommand({
6
+ meta: {
7
+ name: 'remove',
8
+ description: 'Remove an MCP server from the registry and disable it',
9
+ },
10
+ args: {
11
+ server: {
12
+ type: 'positional',
13
+ description: 'Server name to remove',
14
+ required: true,
15
+ },
16
+ },
17
+ async run({ args }) {
18
+ const registry = await read_server_registry();
19
+ 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.`);
22
+ }
23
+ // Remove from registry
24
+ registry.servers.splice(index, 1);
25
+ await write_server_registry(registry);
26
+ // Also disable via CLI (best effort)
27
+ await remove_mcp_via_cli(args.server);
28
+ console.log(`Removed '${args.server}' from registry`);
29
+ },
30
+ });
31
+ //# sourceMappingURL=remove.js.map
@@ -0,0 +1,105 @@
1
+ import { defineCommand } from 'citty';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { write_claude_config } from '../../core/config.js';
4
+ import { list_backups, list_plugin_backups, } from '../../core/registry.js';
5
+ import { write_claude_settings } from '../../core/settings.js';
6
+ import { validate_claude_config } from '../../core/validation.js';
7
+ import { error, output } from '../output.js';
8
+ export default defineCommand({
9
+ meta: {
10
+ name: 'restore',
11
+ description: 'Restore config from a backup (latest if no file specified)',
12
+ },
13
+ args: {
14
+ file: {
15
+ type: 'positional',
16
+ description: 'Backup filename or path (optional, defaults to latest)',
17
+ required: false,
18
+ },
19
+ type: {
20
+ type: 'string',
21
+ description: 'What to restore: mcp (default), plugins, or all',
22
+ default: 'mcp',
23
+ },
24
+ json: {
25
+ type: 'boolean',
26
+ description: 'Output as JSON',
27
+ default: false,
28
+ },
29
+ },
30
+ async run({ args }) {
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
+ }
64
+ }
65
+ }
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
+ }
99
+ }
100
+ if (args.json) {
101
+ output(results, true);
102
+ }
103
+ },
104
+ });
105
+ //# sourceMappingURL=restore.js.map
@@ -0,0 +1,21 @@
1
+ import { defineCommand, runMain } from 'citty';
2
+ const main = defineCommand({
3
+ meta: {
4
+ name: 'mcpick',
5
+ description: 'MCP Server Configuration Manager',
6
+ },
7
+ subCommands: {
8
+ list: () => import('./commands/list.js').then((m) => m.default),
9
+ enable: () => import('./commands/enable.js').then((m) => m.default),
10
+ disable: () => import('./commands/disable.js').then((m) => m.default),
11
+ remove: () => import('./commands/remove.js').then((m) => m.default),
12
+ add: () => import('./commands/add.js').then((m) => m.default),
13
+ backup: () => import('./commands/backup.js').then((m) => m.default),
14
+ restore: () => import('./commands/restore.js').then((m) => m.default),
15
+ profile: () => import('./commands/profile.js').then((m) => m.default),
16
+ plugins: () => import('./commands/plugins.js').then((m) => m.default),
17
+ cache: () => import('./commands/cache.js').then((m) => m.default),
18
+ },
19
+ });
20
+ export const run = () => runMain(main);
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,21 @@
1
+ export function output(data, json) {
2
+ if (json) {
3
+ console.log(JSON.stringify(data, null, 2));
4
+ }
5
+ else if (typeof data === 'string') {
6
+ console.log(data);
7
+ }
8
+ else if (Array.isArray(data)) {
9
+ for (const item of data) {
10
+ console.log(item);
11
+ }
12
+ }
13
+ else {
14
+ console.log(data);
15
+ }
16
+ }
17
+ export function error(message) {
18
+ console.error(`error: ${message}`);
19
+ process.exit(1);
20
+ }
21
+ //# sourceMappingURL=output.js.map
@@ -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 { 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
  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
- const backup_filename = get_backup_filename();
13
- const backup_path = join(backups_dir, backup_filename);
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
- const backup_content = JSON.stringify(mcp_backup, null, 2);
18
- await writeFile(backup_path, backup_content, 'utf-8');
19
- await cleanup_old_backups();
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 created:\n${backup_path}\n(${server_count} servers backed up)`);
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('mcp-servers-') && file.endsWith('.json'))
33
- .map((file) => {
34
- const timestamp_match = file.match(/mcp-servers-(\d{4})-(\d{2})-(\d{2})-(\d{2})(\d{2})(\d{2})\.json/);
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 { file } of files_to_delete) {
51
+ for (const file of files_to_delete) {
46
52
  await unlink(join(backups_dir, file));
47
53
  }
48
54
  }
49
55
  }
50
- catch (error) {
51
- console.warn('Warning: Failed to cleanup old backups:', error);
56
+ catch {
57
+ // Cleanup is best-effort
52
58
  }
53
59
  }
54
60
  //# sourceMappingURL=backup.js.map