mcpick 0.0.15 → 0.0.16
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 +9 -0
- package/dist/cli/commands/add-json.js +60 -0
- package/dist/cli/commands/get.js +45 -0
- package/dist/cli/commands/marketplace.js +55 -14
- package/dist/cli/commands/plugins.js +45 -2
- package/dist/cli/commands/reset-project-choices.js +32 -0
- package/dist/cli/index.js +3 -0
- package/dist/commands/manage-marketplace.js +275 -0
- package/dist/index.js +12 -0
- package/dist/utils/claude-cli.js +163 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# mcpick
|
|
2
2
|
|
|
3
|
+
## 0.0.16
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- b671135: fix: validate marketplace repos upfront, remove unsupported
|
|
8
|
+
--scope flag
|
|
9
|
+
- fed8311: feat: TUI marketplace management, plugin browse, CLI parity
|
|
10
|
+
commands
|
|
11
|
+
|
|
3
12
|
## 0.0.15
|
|
4
13
|
|
|
5
14
|
### Patch Changes
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { defineCommand } from 'citty';
|
|
2
|
+
import { mcp_add_json_via_cli } from '../../utils/claude-cli.js';
|
|
3
|
+
import { error, output } from '../output.js';
|
|
4
|
+
export default defineCommand({
|
|
5
|
+
meta: {
|
|
6
|
+
name: 'add-json',
|
|
7
|
+
description: 'Add an MCP server from a JSON configuration string',
|
|
8
|
+
},
|
|
9
|
+
args: {
|
|
10
|
+
name: {
|
|
11
|
+
type: 'positional',
|
|
12
|
+
description: 'Server name',
|
|
13
|
+
required: true,
|
|
14
|
+
},
|
|
15
|
+
config: {
|
|
16
|
+
type: 'positional',
|
|
17
|
+
description: 'JSON configuration string',
|
|
18
|
+
required: true,
|
|
19
|
+
},
|
|
20
|
+
scope: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
description: 'Scope: local, project, or user (default: local)',
|
|
23
|
+
default: 'local',
|
|
24
|
+
},
|
|
25
|
+
json: {
|
|
26
|
+
type: 'boolean',
|
|
27
|
+
description: 'Output as JSON',
|
|
28
|
+
default: false,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
async run({ args }) {
|
|
32
|
+
const scope = args.scope;
|
|
33
|
+
if (!['local', 'project', 'user'].includes(scope)) {
|
|
34
|
+
error(`Invalid scope: ${scope}. Use local, project, or user.`);
|
|
35
|
+
}
|
|
36
|
+
// Validate the JSON is parseable
|
|
37
|
+
try {
|
|
38
|
+
JSON.parse(args.config);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
error('Invalid JSON configuration. Provide a valid JSON string.');
|
|
42
|
+
}
|
|
43
|
+
const result = await mcp_add_json_via_cli(args.name, args.config, scope);
|
|
44
|
+
if (args.json) {
|
|
45
|
+
output({
|
|
46
|
+
added: args.name,
|
|
47
|
+
scope,
|
|
48
|
+
success: result.success,
|
|
49
|
+
error: result.error,
|
|
50
|
+
}, true);
|
|
51
|
+
}
|
|
52
|
+
else if (result.success) {
|
|
53
|
+
console.log(`Added '${args.name}' from JSON (scope: ${scope})`);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
error(result.error || 'Unknown error');
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
//# sourceMappingURL=add-json.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { defineCommand } from 'citty';
|
|
2
|
+
import { mcp_get_via_cli } from '../../utils/claude-cli.js';
|
|
3
|
+
import { error, output } from '../output.js';
|
|
4
|
+
export default defineCommand({
|
|
5
|
+
meta: {
|
|
6
|
+
name: 'get',
|
|
7
|
+
description: 'Get details about an MCP server',
|
|
8
|
+
},
|
|
9
|
+
args: {
|
|
10
|
+
name: {
|
|
11
|
+
type: 'positional',
|
|
12
|
+
description: 'Server name',
|
|
13
|
+
required: true,
|
|
14
|
+
},
|
|
15
|
+
json: {
|
|
16
|
+
type: 'boolean',
|
|
17
|
+
description: 'Output as JSON',
|
|
18
|
+
default: false,
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
async run({ args }) {
|
|
22
|
+
const result = await mcp_get_via_cli(args.name);
|
|
23
|
+
if (args.json) {
|
|
24
|
+
try {
|
|
25
|
+
const parsed = JSON.parse(result.stdout || '{}');
|
|
26
|
+
output(parsed, true);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
output({
|
|
30
|
+
name: args.name,
|
|
31
|
+
success: result.success,
|
|
32
|
+
output: result.stdout,
|
|
33
|
+
error: result.error,
|
|
34
|
+
}, true);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else if (result.success) {
|
|
38
|
+
console.log(result.stdout || 'No details available.');
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
error(result.error || 'Unknown error');
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
//# sourceMappingURL=get.js.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { defineCommand } from 'citty';
|
|
2
|
+
import { read_marketplace_manifest } from '../../core/plugin-cache.js';
|
|
2
3
|
import { marketplace_add_via_cli, marketplace_list_via_cli, marketplace_remove_via_cli, marketplace_update_via_cli, } from '../../utils/claude-cli.js';
|
|
3
4
|
import { error, output } from '../output.js';
|
|
4
5
|
const list = defineCommand({
|
|
@@ -36,7 +37,7 @@ const list = defineCommand({
|
|
|
36
37
|
const add = defineCommand({
|
|
37
38
|
meta: {
|
|
38
39
|
name: 'add',
|
|
39
|
-
description: 'Add a marketplace
|
|
40
|
+
description: 'Add a plugin marketplace (a catalog of installable plugins)',
|
|
40
41
|
},
|
|
41
42
|
args: {
|
|
42
43
|
source: {
|
|
@@ -44,11 +45,6 @@ const add = defineCommand({
|
|
|
44
45
|
description: 'Marketplace source (GitHub repo, URL, or local path)',
|
|
45
46
|
required: true,
|
|
46
47
|
},
|
|
47
|
-
scope: {
|
|
48
|
-
type: 'string',
|
|
49
|
-
description: 'Scope: user, project, or local (default: user)',
|
|
50
|
-
default: 'user',
|
|
51
|
-
},
|
|
52
48
|
json: {
|
|
53
49
|
type: 'boolean',
|
|
54
50
|
description: 'Output as JSON',
|
|
@@ -56,27 +52,72 @@ const add = defineCommand({
|
|
|
56
52
|
},
|
|
57
53
|
},
|
|
58
54
|
async run({ args }) {
|
|
59
|
-
const
|
|
60
|
-
if (!['user', 'project', 'local'].includes(scope)) {
|
|
61
|
-
error(`Invalid scope: ${scope}. Use user, project, or local.`);
|
|
62
|
-
}
|
|
63
|
-
const result = await marketplace_add_via_cli(args.source, scope);
|
|
55
|
+
const result = await marketplace_add_via_cli(args.source);
|
|
64
56
|
if (args.json) {
|
|
57
|
+
// Try to include available plugins in JSON output
|
|
58
|
+
let available_plugins = [];
|
|
59
|
+
if (result.success) {
|
|
60
|
+
const manifests = await find_marketplace_plugins(args.source);
|
|
61
|
+
available_plugins = manifests;
|
|
62
|
+
}
|
|
65
63
|
output({
|
|
66
64
|
added: args.source,
|
|
67
|
-
scope,
|
|
68
65
|
success: result.success,
|
|
69
66
|
error: result.error,
|
|
67
|
+
available_plugins,
|
|
70
68
|
}, true);
|
|
71
69
|
}
|
|
72
70
|
else if (result.success) {
|
|
73
|
-
console.log(`Marketplace added: ${args.source}
|
|
71
|
+
console.log(`Marketplace added: ${args.source}`);
|
|
72
|
+
await show_available_plugins(args.source);
|
|
74
73
|
}
|
|
75
74
|
else {
|
|
76
75
|
error(result.error || 'Unknown error');
|
|
77
76
|
}
|
|
78
77
|
},
|
|
79
78
|
});
|
|
79
|
+
/**
|
|
80
|
+
* Try to find and display available plugins from a newly added marketplace.
|
|
81
|
+
* The marketplace name in the filesystem may differ from the source string,
|
|
82
|
+
* so we try common derivations.
|
|
83
|
+
*/
|
|
84
|
+
async function find_marketplace_plugins(source) {
|
|
85
|
+
// Try the source as-is, then extract repo name from various formats
|
|
86
|
+
const candidates = [];
|
|
87
|
+
// Extract repo name from owner/repo, URLs, etc.
|
|
88
|
+
const repo_match = source.match(/([^/]+?)(?:\.git)?$/);
|
|
89
|
+
if (repo_match) {
|
|
90
|
+
candidates.push(repo_match[1].toLowerCase());
|
|
91
|
+
candidates.push(repo_match[1]);
|
|
92
|
+
}
|
|
93
|
+
// Try the full source as a name
|
|
94
|
+
candidates.push(source);
|
|
95
|
+
for (const name of candidates) {
|
|
96
|
+
const manifest = await read_marketplace_manifest(name);
|
|
97
|
+
if (manifest?.plugins?.length) {
|
|
98
|
+
return manifest.plugins.map((p) => {
|
|
99
|
+
const desc = p.description
|
|
100
|
+
? ` - ${p.description}`
|
|
101
|
+
: '';
|
|
102
|
+
return `${p.name}${desc}`;
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
async function show_available_plugins(source) {
|
|
109
|
+
const plugins = await find_marketplace_plugins(source);
|
|
110
|
+
if (plugins.length > 0) {
|
|
111
|
+
console.log(`\nAvailable plugins (${plugins.length}):`);
|
|
112
|
+
for (const p of plugins) {
|
|
113
|
+
console.log(` - ${p}`);
|
|
114
|
+
}
|
|
115
|
+
console.log('\nInstall with: mcpick plugins install <name>@<marketplace>');
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
console.log('\nTo browse and install plugins: mcpick plugins install <name>@<marketplace>');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
80
121
|
const remove = defineCommand({
|
|
81
122
|
meta: {
|
|
82
123
|
name: 'remove',
|
|
@@ -150,7 +191,7 @@ const update = defineCommand({
|
|
|
150
191
|
export default defineCommand({
|
|
151
192
|
meta: {
|
|
152
193
|
name: 'marketplace',
|
|
153
|
-
description: 'Manage
|
|
194
|
+
description: 'Manage plugin marketplaces (catalogs of installable plugins). Add a marketplace first, then install plugins from it with: mcpick plugins install <name>@<marketplace>',
|
|
154
195
|
},
|
|
155
196
|
subCommands: {
|
|
156
197
|
list,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineCommand } from 'citty';
|
|
2
2
|
import { parse_plugin_key, read_known_marketplaces, } from '../../core/plugin-cache.js';
|
|
3
3
|
import { build_enabled_plugins, get_all_plugins, read_claude_settings, write_claude_settings, } from '../../core/settings.js';
|
|
4
|
-
import { install_plugin_via_cli, uninstall_plugin_via_cli, update_plugin_via_cli, } from '../../utils/claude-cli.js';
|
|
4
|
+
import { install_plugin_via_cli, uninstall_plugin_via_cli, update_plugin_via_cli, validate_plugin_via_cli, } from '../../utils/claude-cli.js';
|
|
5
5
|
import { error, output } from '../output.js';
|
|
6
6
|
const list = defineCommand({
|
|
7
7
|
meta: {
|
|
@@ -212,11 +212,54 @@ const update = defineCommand({
|
|
|
212
212
|
}
|
|
213
213
|
},
|
|
214
214
|
});
|
|
215
|
+
const validate = defineCommand({
|
|
216
|
+
meta: {
|
|
217
|
+
name: 'validate',
|
|
218
|
+
description: 'Validate a plugin or marketplace manifest',
|
|
219
|
+
},
|
|
220
|
+
args: {
|
|
221
|
+
path: {
|
|
222
|
+
type: 'positional',
|
|
223
|
+
description: 'Path to plugin or marketplace manifest',
|
|
224
|
+
required: true,
|
|
225
|
+
},
|
|
226
|
+
json: {
|
|
227
|
+
type: 'boolean',
|
|
228
|
+
description: 'Output as JSON',
|
|
229
|
+
default: false,
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
async run({ args }) {
|
|
233
|
+
const result = await validate_plugin_via_cli(args.path);
|
|
234
|
+
if (args.json) {
|
|
235
|
+
output({
|
|
236
|
+
path: args.path,
|
|
237
|
+
valid: result.success,
|
|
238
|
+
output: result.stdout,
|
|
239
|
+
error: result.error,
|
|
240
|
+
}, true);
|
|
241
|
+
}
|
|
242
|
+
else if (result.success) {
|
|
243
|
+
console.log(result.stdout || 'Validation passed.');
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
error(result.error || 'Validation failed');
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
});
|
|
215
250
|
export default defineCommand({
|
|
216
251
|
meta: {
|
|
217
252
|
name: 'plugins',
|
|
218
253
|
description: 'Manage Claude Code plugins',
|
|
219
254
|
},
|
|
220
|
-
subCommands: {
|
|
255
|
+
subCommands: {
|
|
256
|
+
list,
|
|
257
|
+
enable,
|
|
258
|
+
disable,
|
|
259
|
+
install,
|
|
260
|
+
uninstall,
|
|
261
|
+
update,
|
|
262
|
+
validate,
|
|
263
|
+
},
|
|
221
264
|
});
|
|
222
265
|
//# sourceMappingURL=plugins.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { defineCommand } from 'citty';
|
|
2
|
+
import { mcp_reset_project_choices_via_cli } from '../../utils/claude-cli.js';
|
|
3
|
+
import { error, output } from '../output.js';
|
|
4
|
+
export default defineCommand({
|
|
5
|
+
meta: {
|
|
6
|
+
name: 'reset-project-choices',
|
|
7
|
+
description: 'Reset all approved and rejected project-scoped MCP servers',
|
|
8
|
+
},
|
|
9
|
+
args: {
|
|
10
|
+
json: {
|
|
11
|
+
type: 'boolean',
|
|
12
|
+
description: 'Output as JSON',
|
|
13
|
+
default: false,
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
async run({ args }) {
|
|
17
|
+
const result = await mcp_reset_project_choices_via_cli();
|
|
18
|
+
if (args.json) {
|
|
19
|
+
output({
|
|
20
|
+
success: result.success,
|
|
21
|
+
error: result.error,
|
|
22
|
+
}, true);
|
|
23
|
+
}
|
|
24
|
+
else if (result.success) {
|
|
25
|
+
console.log('Project MCP server choices have been reset.');
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
error(result.error || 'Unknown error');
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
//# sourceMappingURL=reset-project-choices.js.map
|
package/dist/cli/index.js
CHANGED
|
@@ -10,6 +10,9 @@ const main = defineCommand({
|
|
|
10
10
|
disable: () => import('./commands/disable.js').then((m) => m.default),
|
|
11
11
|
remove: () => import('./commands/remove.js').then((m) => m.default),
|
|
12
12
|
add: () => import('./commands/add.js').then((m) => m.default),
|
|
13
|
+
'add-json': () => import('./commands/add-json.js').then((m) => m.default),
|
|
14
|
+
get: () => import('./commands/get.js').then((m) => m.default),
|
|
15
|
+
'reset-project-choices': () => import('./commands/reset-project-choices.js').then((m) => m.default),
|
|
13
16
|
backup: () => import('./commands/backup.js').then((m) => m.default),
|
|
14
17
|
restore: () => import('./commands/restore.js').then((m) => m.default),
|
|
15
18
|
profile: () => import('./commands/profile.js').then((m) => m.default),
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { confirm, isCancel, log, multiselect, note, select, text, } from '@clack/prompts';
|
|
2
|
+
import { read_known_marketplaces, read_marketplace_manifest, } from '../core/plugin-cache.js';
|
|
3
|
+
import { get_all_plugins, read_claude_settings, } from '../core/settings.js';
|
|
4
|
+
import { install_plugin_via_cli, marketplace_add_via_cli, marketplace_remove_via_cli, marketplace_update_via_cli, uninstall_plugin_via_cli, } from '../utils/claude-cli.js';
|
|
5
|
+
/**
|
|
6
|
+
* Browse all available plugins across all marketplaces.
|
|
7
|
+
* Shows a multiselect with installed plugins pre-selected — toggle to install/uninstall.
|
|
8
|
+
*/
|
|
9
|
+
async function handle_browse() {
|
|
10
|
+
const known = await read_known_marketplaces();
|
|
11
|
+
const marketplace_names = Object.keys(known);
|
|
12
|
+
if (marketplace_names.length === 0) {
|
|
13
|
+
note('No marketplaces configured.\nAdd one first to browse plugins.');
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
// Build list of all available plugins across marketplaces
|
|
17
|
+
const all_available = [];
|
|
18
|
+
for (const mkt_name of marketplace_names) {
|
|
19
|
+
const manifest = await read_marketplace_manifest(mkt_name);
|
|
20
|
+
if (!manifest?.plugins?.length)
|
|
21
|
+
continue;
|
|
22
|
+
for (const p of manifest.plugins) {
|
|
23
|
+
all_available.push({
|
|
24
|
+
key: `${p.name}@${mkt_name}`,
|
|
25
|
+
name: p.name,
|
|
26
|
+
marketplace: mkt_name,
|
|
27
|
+
description: p.description,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (all_available.length === 0) {
|
|
32
|
+
note('No plugins found in any marketplace.');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
// Get currently installed plugins
|
|
36
|
+
const settings = await read_claude_settings();
|
|
37
|
+
const installed = get_all_plugins(settings);
|
|
38
|
+
const installed_keys = new Set(installed.map((p) => `${p.name}@${p.marketplace}`));
|
|
39
|
+
const selected = await multiselect({
|
|
40
|
+
message: `Available plugins (${all_available.length}) — toggle to install/uninstall:`,
|
|
41
|
+
options: all_available.map((p) => ({
|
|
42
|
+
value: p.key,
|
|
43
|
+
label: p.name,
|
|
44
|
+
hint: `${p.marketplace}${p.description ? ` · ${p.description}` : ''}`,
|
|
45
|
+
})),
|
|
46
|
+
initialValues: all_available
|
|
47
|
+
.filter((p) => installed_keys.has(p.key))
|
|
48
|
+
.map((p) => p.key),
|
|
49
|
+
required: false,
|
|
50
|
+
});
|
|
51
|
+
if (isCancel(selected))
|
|
52
|
+
return;
|
|
53
|
+
const selected_set = new Set(selected);
|
|
54
|
+
// Determine what to install and uninstall
|
|
55
|
+
const to_install = all_available.filter((p) => selected_set.has(p.key) && !installed_keys.has(p.key));
|
|
56
|
+
const to_uninstall = all_available.filter((p) => !selected_set.has(p.key) && installed_keys.has(p.key));
|
|
57
|
+
if (to_install.length === 0 && to_uninstall.length === 0) {
|
|
58
|
+
log.info('No changes.');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
// Install new plugins
|
|
62
|
+
for (const p of to_install) {
|
|
63
|
+
log.info(`Installing ${p.key}...`);
|
|
64
|
+
const result = await install_plugin_via_cli(p.key);
|
|
65
|
+
if (result.success) {
|
|
66
|
+
log.success(`Installed: ${p.key}`);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
log.error(`Failed: ${p.key} - ${result.error}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Uninstall deselected plugins
|
|
73
|
+
for (const p of to_uninstall) {
|
|
74
|
+
log.info(`Uninstalling ${p.key}...`);
|
|
75
|
+
const result = await uninstall_plugin_via_cli(p.key);
|
|
76
|
+
if (result.success) {
|
|
77
|
+
log.success(`Uninstalled: ${p.key}`);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
log.error(`Failed: ${p.key} - ${result.error}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const parts = [];
|
|
84
|
+
if (to_install.length > 0)
|
|
85
|
+
parts.push(`${to_install.length} installed`);
|
|
86
|
+
if (to_uninstall.length > 0)
|
|
87
|
+
parts.push(`${to_uninstall.length} uninstalled`);
|
|
88
|
+
note(parts.join(', '), 'Plugins updated');
|
|
89
|
+
}
|
|
90
|
+
async function handle_add() {
|
|
91
|
+
const source = await text({
|
|
92
|
+
message: 'Marketplace source:',
|
|
93
|
+
placeholder: 'e.g. owner/repo or https://github.com/owner/repo',
|
|
94
|
+
validate: (value) => {
|
|
95
|
+
if (!value || value.trim().length === 0) {
|
|
96
|
+
return 'Marketplace source is required';
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
if (isCancel(source))
|
|
101
|
+
return;
|
|
102
|
+
log.info(`Adding marketplace: ${source}`);
|
|
103
|
+
const result = await marketplace_add_via_cli(source);
|
|
104
|
+
if (!result.success) {
|
|
105
|
+
log.error(result.error || 'Unknown error');
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
log.success(`Marketplace added: ${source}`);
|
|
109
|
+
// Try to find and offer available plugins
|
|
110
|
+
const marketplace_name = derive_marketplace_name(source);
|
|
111
|
+
const manifest = marketplace_name
|
|
112
|
+
? await read_marketplace_manifest(marketplace_name)
|
|
113
|
+
: null;
|
|
114
|
+
if (!manifest?.plugins?.length) {
|
|
115
|
+
log.info('Install plugins with: mcpick plugins install <name>@<marketplace>');
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const should_install = await confirm({
|
|
119
|
+
message: `${manifest.plugins.length} plugins available. Install now?`,
|
|
120
|
+
});
|
|
121
|
+
if (isCancel(should_install) || !should_install)
|
|
122
|
+
return;
|
|
123
|
+
const to_install = await multiselect({
|
|
124
|
+
message: 'Select plugins to install:',
|
|
125
|
+
options: manifest.plugins.map((p) => ({
|
|
126
|
+
value: `${p.name}@${marketplace_name}`,
|
|
127
|
+
label: p.name,
|
|
128
|
+
hint: p.description,
|
|
129
|
+
})),
|
|
130
|
+
required: false,
|
|
131
|
+
});
|
|
132
|
+
if (isCancel(to_install) || to_install.length === 0)
|
|
133
|
+
return;
|
|
134
|
+
for (const key of to_install) {
|
|
135
|
+
log.info(`Installing ${key}...`);
|
|
136
|
+
const install_result = await install_plugin_via_cli(key);
|
|
137
|
+
if (install_result.success) {
|
|
138
|
+
log.success(`Installed: ${key}`);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
log.error(`Failed: ${key} - ${install_result.error}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function derive_marketplace_name(source) {
|
|
146
|
+
const match = source.match(/([^/]+?)(?:\.git)?$/);
|
|
147
|
+
return match ? match[1].toLowerCase() : null;
|
|
148
|
+
}
|
|
149
|
+
async function handle_remove() {
|
|
150
|
+
const known = await read_known_marketplaces();
|
|
151
|
+
const names = Object.keys(known);
|
|
152
|
+
if (names.length === 0) {
|
|
153
|
+
note('No marketplaces configured.');
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const name = await select({
|
|
157
|
+
message: 'Select marketplace to remove:',
|
|
158
|
+
options: names.map((n) => ({
|
|
159
|
+
value: n,
|
|
160
|
+
label: n,
|
|
161
|
+
hint: known[n].source.repo || known[n].source.url,
|
|
162
|
+
})),
|
|
163
|
+
});
|
|
164
|
+
if (isCancel(name))
|
|
165
|
+
return;
|
|
166
|
+
const should_remove = await confirm({
|
|
167
|
+
message: `Remove marketplace '${name}'? Its plugins will also be removed.`,
|
|
168
|
+
});
|
|
169
|
+
if (isCancel(should_remove) || !should_remove)
|
|
170
|
+
return;
|
|
171
|
+
const remove_result = await marketplace_remove_via_cli(name);
|
|
172
|
+
if (remove_result.success) {
|
|
173
|
+
log.success(`Marketplace removed: ${name}`);
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
log.error(remove_result.error || 'Unknown error');
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async function handle_update() {
|
|
180
|
+
const known = await read_known_marketplaces();
|
|
181
|
+
const names = Object.keys(known);
|
|
182
|
+
if (names.length === 0) {
|
|
183
|
+
note('No marketplaces configured.');
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const choice = await select({
|
|
187
|
+
message: 'What to update:',
|
|
188
|
+
options: [
|
|
189
|
+
{ value: '__all__', label: 'All marketplaces' },
|
|
190
|
+
...names.map((n) => ({
|
|
191
|
+
value: n,
|
|
192
|
+
label: n,
|
|
193
|
+
hint: known[n].source.repo || known[n].source.url,
|
|
194
|
+
})),
|
|
195
|
+
],
|
|
196
|
+
});
|
|
197
|
+
if (isCancel(choice))
|
|
198
|
+
return;
|
|
199
|
+
if (choice === '__all__') {
|
|
200
|
+
log.info('Updating all marketplaces...');
|
|
201
|
+
const result = await marketplace_update_via_cli();
|
|
202
|
+
if (result.success) {
|
|
203
|
+
log.success('All marketplaces updated.');
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
log.error(result.error || 'Unknown error');
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
log.info(`Updating ${choice}...`);
|
|
211
|
+
const result = await marketplace_update_via_cli(choice);
|
|
212
|
+
if (result.success) {
|
|
213
|
+
log.success(`Marketplace updated: ${choice}`);
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
log.error(result.error || 'Unknown error');
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
export async function manage_marketplace() {
|
|
221
|
+
while (true) {
|
|
222
|
+
const action = await select({
|
|
223
|
+
message: 'Marketplace & plugins:',
|
|
224
|
+
options: [
|
|
225
|
+
{
|
|
226
|
+
value: 'browse',
|
|
227
|
+
label: 'Browse & install plugins',
|
|
228
|
+
hint: 'Toggle plugins on/off across all marketplaces',
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
value: 'add',
|
|
232
|
+
label: 'Add marketplace',
|
|
233
|
+
hint: 'Add a plugin catalog, then install plugins from it',
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
value: 'remove',
|
|
237
|
+
label: 'Remove marketplace',
|
|
238
|
+
hint: 'Remove a marketplace and its plugins',
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
value: 'update',
|
|
242
|
+
label: 'Update marketplace(s)',
|
|
243
|
+
hint: 'Pull latest from source',
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
value: 'back',
|
|
247
|
+
label: 'Back',
|
|
248
|
+
hint: 'Return to main menu',
|
|
249
|
+
},
|
|
250
|
+
],
|
|
251
|
+
});
|
|
252
|
+
if (isCancel(action) || action === 'back')
|
|
253
|
+
return;
|
|
254
|
+
try {
|
|
255
|
+
switch (action) {
|
|
256
|
+
case 'browse':
|
|
257
|
+
await handle_browse();
|
|
258
|
+
break;
|
|
259
|
+
case 'add':
|
|
260
|
+
await handle_add();
|
|
261
|
+
break;
|
|
262
|
+
case 'remove':
|
|
263
|
+
await handle_remove();
|
|
264
|
+
break;
|
|
265
|
+
case 'update':
|
|
266
|
+
await handle_update();
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
catch (err) {
|
|
271
|
+
log.error(err instanceof Error ? err.message : 'Unknown error');
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
//# sourceMappingURL=manage-marketplace.js.map
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ 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
7
|
import { manage_cache } from './commands/manage-cache.js';
|
|
8
|
+
import { manage_marketplace } from './commands/manage-marketplace.js';
|
|
8
9
|
import { restore_config } from './commands/restore.js';
|
|
9
10
|
import { write_claude_config } from './core/config.js';
|
|
10
11
|
import { list_profiles, load_profile, save_profile, } from './core/profile.js';
|
|
@@ -178,6 +179,11 @@ async function main() {
|
|
|
178
179
|
label: 'Manage plugins',
|
|
179
180
|
hint: 'Toggle, install, uninstall, or update plugins',
|
|
180
181
|
},
|
|
182
|
+
{
|
|
183
|
+
value: 'manage-marketplace',
|
|
184
|
+
label: 'Manage marketplaces',
|
|
185
|
+
hint: 'Add, remove, or update plugin marketplaces',
|
|
186
|
+
},
|
|
181
187
|
{
|
|
182
188
|
value: 'manage-cache',
|
|
183
189
|
label: 'Manage plugin cache',
|
|
@@ -226,6 +232,9 @@ async function main() {
|
|
|
226
232
|
case 'edit-plugins':
|
|
227
233
|
await edit_plugins();
|
|
228
234
|
break;
|
|
235
|
+
case 'manage-marketplace':
|
|
236
|
+
await manage_marketplace();
|
|
237
|
+
break;
|
|
229
238
|
case 'manage-cache':
|
|
230
239
|
await manage_cache();
|
|
231
240
|
break;
|
|
@@ -276,6 +285,9 @@ const SUBCOMMANDS = new Set([
|
|
|
276
285
|
'disable',
|
|
277
286
|
'remove',
|
|
278
287
|
'add',
|
|
288
|
+
'add-json',
|
|
289
|
+
'get',
|
|
290
|
+
'reset-project-choices',
|
|
279
291
|
'backup',
|
|
280
292
|
'restore',
|
|
281
293
|
'profile',
|
package/dist/utils/claude-cli.js
CHANGED
|
@@ -192,7 +192,51 @@ export async function update_plugin_via_cli(key, scope = 'user') {
|
|
|
192
192
|
/**
|
|
193
193
|
* Add a marketplace via Claude CLI
|
|
194
194
|
*/
|
|
195
|
-
|
|
195
|
+
/**
|
|
196
|
+
* Extract GitHub owner/repo from various source formats.
|
|
197
|
+
* Returns null if not a recognizable GitHub reference.
|
|
198
|
+
*/
|
|
199
|
+
function parse_github_repo(source) {
|
|
200
|
+
// HTTPS URL: https://github.com/owner/repo[.git]
|
|
201
|
+
const https_match = source.match(/^https?:\/\/github\.com\/([^/]+)\/([^/.]+)(?:\.git)?$/);
|
|
202
|
+
if (https_match)
|
|
203
|
+
return { owner: https_match[1], repo: https_match[2] };
|
|
204
|
+
// SSH URL: git@github.com:owner/repo[.git]
|
|
205
|
+
const ssh_match = source.match(/^git@github\.com:([^/]+)\/([^/.]+)(?:\.git)?$/);
|
|
206
|
+
if (ssh_match)
|
|
207
|
+
return { owner: ssh_match[1], repo: ssh_match[2] };
|
|
208
|
+
// Shorthand: owner/repo (no slashes beyond the one separator)
|
|
209
|
+
const shorthand_match = source.match(/^([^/\s]+)\/([^/\s]+)$/);
|
|
210
|
+
if (shorthand_match)
|
|
211
|
+
return { owner: shorthand_match[1], repo: shorthand_match[2] };
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Validate that a GitHub repository exists and is accessible.
|
|
216
|
+
* Returns an error message if validation fails, null if OK.
|
|
217
|
+
*/
|
|
218
|
+
async function validate_github_repo(owner, repo) {
|
|
219
|
+
try {
|
|
220
|
+
const response = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
|
|
221
|
+
method: 'GET',
|
|
222
|
+
headers: { Accept: 'application/vnd.github.v3+json' },
|
|
223
|
+
});
|
|
224
|
+
if (response.status === 200)
|
|
225
|
+
return null;
|
|
226
|
+
if (response.status === 404) {
|
|
227
|
+
return `Repository '${owner}/${repo}' not found on GitHub. Check the name or ensure it's not private.`;
|
|
228
|
+
}
|
|
229
|
+
if (response.status === 403) {
|
|
230
|
+
return `Access denied for '${owner}/${repo}'. The repository may be private — configure a GitHub token or use SSH.`;
|
|
231
|
+
}
|
|
232
|
+
return `GitHub API returned status ${response.status} for '${owner}/${repo}'.`;
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
// Network error — skip validation and let the CLI attempt the clone
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
export async function marketplace_add_via_cli(source) {
|
|
196
240
|
const cli_available = await check_claude_cli();
|
|
197
241
|
if (!cli_available) {
|
|
198
242
|
return {
|
|
@@ -200,12 +244,37 @@ export async function marketplace_add_via_cli(source, scope = 'user') {
|
|
|
200
244
|
error: 'Claude CLI not found. Please install Claude Code CLI.',
|
|
201
245
|
};
|
|
202
246
|
}
|
|
247
|
+
// Validate GitHub repo exists before attempting clone
|
|
248
|
+
// Only validate shorthand (owner/repo) — explicit URLs imply the user knows the repo
|
|
249
|
+
const gh = parse_github_repo(source);
|
|
250
|
+
const is_shorthand = gh && !source.startsWith('http') && !source.startsWith('git@');
|
|
251
|
+
if (gh && is_shorthand) {
|
|
252
|
+
const validation_error = await validate_github_repo(gh.owner, gh.repo);
|
|
253
|
+
if (validation_error) {
|
|
254
|
+
return { success: false, error: validation_error };
|
|
255
|
+
}
|
|
256
|
+
}
|
|
203
257
|
try {
|
|
204
|
-
await execAsync(`claude plugin marketplace add ${shell_escape(source)}
|
|
258
|
+
await execAsync(`claude plugin marketplace add ${shell_escape(source)}`);
|
|
205
259
|
return { success: true };
|
|
206
260
|
}
|
|
207
261
|
catch (error) {
|
|
208
262
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
263
|
+
// Provide clearer error messages for common failures
|
|
264
|
+
if (message.includes('SSH') ||
|
|
265
|
+
message.includes('Permission denied (publickey)')) {
|
|
266
|
+
return {
|
|
267
|
+
success: false,
|
|
268
|
+
error: `SSH authentication failed for '${source}'. Either:\n - Configure SSH keys: https://docs.github.com/en/authentication/connecting-to-github-with-ssh\n - Use HTTPS URL instead: https://github.com/${gh ? `${gh.owner}/${gh.repo}` : source}`,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
if (message.includes('not found') ||
|
|
272
|
+
message.includes('does not exist')) {
|
|
273
|
+
return {
|
|
274
|
+
success: false,
|
|
275
|
+
error: `Repository '${source}' not found. Check the name and your access permissions.`,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
209
278
|
return {
|
|
210
279
|
success: false,
|
|
211
280
|
error: `Failed to add marketplace: ${message}`,
|
|
@@ -297,6 +366,98 @@ export function get_scope_description(scope) {
|
|
|
297
366
|
return 'Global - all projects';
|
|
298
367
|
}
|
|
299
368
|
}
|
|
369
|
+
/**
|
|
370
|
+
* Validate a plugin or marketplace manifest via Claude CLI
|
|
371
|
+
*/
|
|
372
|
+
export async function validate_plugin_via_cli(path) {
|
|
373
|
+
const cli_available = await check_claude_cli();
|
|
374
|
+
if (!cli_available) {
|
|
375
|
+
return {
|
|
376
|
+
success: false,
|
|
377
|
+
error: 'Claude CLI not found. Please install Claude Code CLI.',
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
try {
|
|
381
|
+
const { stdout } = await execAsync(`claude plugin validate ${shell_escape(path)}`);
|
|
382
|
+
return { success: true, stdout: stdout.trim() };
|
|
383
|
+
}
|
|
384
|
+
catch (error) {
|
|
385
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
386
|
+
return {
|
|
387
|
+
success: false,
|
|
388
|
+
error: `Validation failed: ${message}`,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Get details about an MCP server via Claude CLI
|
|
394
|
+
*/
|
|
395
|
+
export async function mcp_get_via_cli(name) {
|
|
396
|
+
const cli_available = await check_claude_cli();
|
|
397
|
+
if (!cli_available) {
|
|
398
|
+
return {
|
|
399
|
+
success: false,
|
|
400
|
+
error: 'Claude CLI not found. Please install Claude Code CLI.',
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
try {
|
|
404
|
+
const { stdout } = await execAsync(`claude mcp get ${shell_escape(name)}`);
|
|
405
|
+
return { success: true, stdout: stdout.trim() };
|
|
406
|
+
}
|
|
407
|
+
catch (error) {
|
|
408
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
409
|
+
return {
|
|
410
|
+
success: false,
|
|
411
|
+
error: `Failed to get server details: ${message}`,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Add an MCP server from raw JSON via Claude CLI
|
|
417
|
+
*/
|
|
418
|
+
export async function mcp_add_json_via_cli(name, json, scope = 'local') {
|
|
419
|
+
const cli_available = await check_claude_cli();
|
|
420
|
+
if (!cli_available) {
|
|
421
|
+
return {
|
|
422
|
+
success: false,
|
|
423
|
+
error: 'Claude CLI not found. Please install Claude Code CLI.',
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
try {
|
|
427
|
+
await execAsync(`claude mcp add-json ${shell_escape(name)} ${shell_escape(json)} --scope ${scope}`);
|
|
428
|
+
return { success: true };
|
|
429
|
+
}
|
|
430
|
+
catch (error) {
|
|
431
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
432
|
+
return {
|
|
433
|
+
success: false,
|
|
434
|
+
error: `Failed to add server from JSON: ${message}`,
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Reset project-scoped MCP server choices via Claude CLI
|
|
440
|
+
*/
|
|
441
|
+
export async function mcp_reset_project_choices_via_cli() {
|
|
442
|
+
const cli_available = await check_claude_cli();
|
|
443
|
+
if (!cli_available) {
|
|
444
|
+
return {
|
|
445
|
+
success: false,
|
|
446
|
+
error: 'Claude CLI not found. Please install Claude Code CLI.',
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
try {
|
|
450
|
+
await execAsync('claude mcp reset-project-choices');
|
|
451
|
+
return { success: true };
|
|
452
|
+
}
|
|
453
|
+
catch (error) {
|
|
454
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
455
|
+
return {
|
|
456
|
+
success: false,
|
|
457
|
+
error: `Failed to reset project choices: ${message}`,
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
}
|
|
300
461
|
/**
|
|
301
462
|
* Get scope options for select prompt
|
|
302
463
|
*/
|