mcpick 0.0.14 → 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 +17 -0
- package/dist/cli/commands/add-json.js +60 -0
- package/dist/cli/commands/cache.js +10 -3
- package/dist/cli/commands/dev.js +3 -4
- package/dist/cli/commands/get.js +45 -0
- package/dist/cli/commands/marketplace.js +203 -0
- package/dist/cli/commands/plugins.js +65 -2
- package/dist/cli/commands/reload.js +36 -0
- package/dist/cli/commands/reset-project-choices.js +32 -0
- package/dist/cli/index.js +5 -0
- package/dist/commands/manage-marketplace.js +275 -0
- package/dist/core/dev-override.js +3 -3
- package/dist/core/plugin-cache.js +95 -5
- package/dist/index.js +14 -0
- package/dist/utils/claude-cli.js +256 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
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
|
+
|
|
12
|
+
## 0.0.15
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- 0170ea0: Add local dev workflow: mcpick dev + cache link/unlink
|
|
17
|
+
- 2161258: Fix marketplace refresh, cache clear, uninstall detection;
|
|
18
|
+
add marketplace commands
|
|
19
|
+
|
|
3
20
|
## 0.0.14
|
|
4
21
|
|
|
5
22
|
### 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
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineCommand } from 'citty';
|
|
2
|
-
import { clean_orphaned_versions, clear_plugin_caches, get_cached_plugins_info, link_local_plugin, list_linked_plugins, read_installed_plugins, refresh_all_marketplaces, unlink_local_plugin, } from '../../core/plugin-cache.js';
|
|
2
|
+
import { clean_orphaned_versions, clear_plugin_caches, get_cached_plugins_info, link_local_plugin, list_linked_plugins, read_installed_plugins, refresh_all_marketplaces, scan_all_cache_keys, unlink_local_plugin, } from '../../core/plugin-cache.js';
|
|
3
3
|
import { error, output } from '../output.js';
|
|
4
4
|
const status = defineCommand({
|
|
5
5
|
meta: {
|
|
@@ -65,7 +65,13 @@ const clear = defineCommand({
|
|
|
65
65
|
},
|
|
66
66
|
async run({ args }) {
|
|
67
67
|
const installed = await read_installed_plugins();
|
|
68
|
-
const
|
|
68
|
+
const installed_keys = Object.keys(installed.plugins);
|
|
69
|
+
// When --all, also scan disk for marketplace-sourced caches
|
|
70
|
+
// not tracked in installed_plugins.json
|
|
71
|
+
const scanned_keys = args.all ? await scan_all_cache_keys() : [];
|
|
72
|
+
const all_keys = [
|
|
73
|
+
...new Set([...installed_keys, ...scanned_keys]),
|
|
74
|
+
];
|
|
69
75
|
if (all_keys.length === 0) {
|
|
70
76
|
if (args.json) {
|
|
71
77
|
output({ cleared: [], errors: [] }, true);
|
|
@@ -77,7 +83,8 @@ const clear = defineCommand({
|
|
|
77
83
|
}
|
|
78
84
|
let keys_to_clear;
|
|
79
85
|
if (args.plugin) {
|
|
80
|
-
if (!installed.plugins[args.plugin]
|
|
86
|
+
if (!installed.plugins[args.plugin] &&
|
|
87
|
+
!scanned_keys.includes(args.plugin)) {
|
|
81
88
|
error(`Plugin '${args.plugin}' not found in cache. Run 'mcpick cache status' to see cached plugins.`);
|
|
82
89
|
}
|
|
83
90
|
keys_to_clear = [args.plugin];
|
package/dist/cli/commands/dev.js
CHANGED
|
@@ -36,9 +36,7 @@ const apply = defineCommand({
|
|
|
36
36
|
if (scope && !['local', 'project', 'user'].includes(scope)) {
|
|
37
37
|
error(`Invalid scope: ${scope}. Use local, project, or user.`);
|
|
38
38
|
}
|
|
39
|
-
const cmd_args = args.args
|
|
40
|
-
? args.args.split(',')
|
|
41
|
-
: [];
|
|
39
|
+
const cmd_args = args.args ? args.args.split(',') : [];
|
|
42
40
|
const result = await apply_dev_override(args.name, args.command, cmd_args, scope);
|
|
43
41
|
if (args.json) {
|
|
44
42
|
output(result, true);
|
|
@@ -82,7 +80,8 @@ const restore = defineCommand({
|
|
|
82
80
|
output(result, true);
|
|
83
81
|
}
|
|
84
82
|
else {
|
|
85
|
-
if (result.restored.length === 0 &&
|
|
83
|
+
if (result.restored.length === 0 &&
|
|
84
|
+
result.errors.length === 0) {
|
|
86
85
|
console.log('No dev overrides to restore.');
|
|
87
86
|
}
|
|
88
87
|
else {
|
|
@@ -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
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { defineCommand } from 'citty';
|
|
2
|
+
import { read_marketplace_manifest } from '../../core/plugin-cache.js';
|
|
3
|
+
import { marketplace_add_via_cli, marketplace_list_via_cli, marketplace_remove_via_cli, marketplace_update_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 configured marketplaces',
|
|
9
|
+
},
|
|
10
|
+
args: {
|
|
11
|
+
json: {
|
|
12
|
+
type: 'boolean',
|
|
13
|
+
description: 'Output as JSON',
|
|
14
|
+
default: false,
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
async run({ args }) {
|
|
18
|
+
const result = await marketplace_list_via_cli();
|
|
19
|
+
if (!result.success) {
|
|
20
|
+
error(result.error || 'Unknown error');
|
|
21
|
+
}
|
|
22
|
+
if (args.json) {
|
|
23
|
+
// Try to parse CLI JSON output, fallback to raw
|
|
24
|
+
try {
|
|
25
|
+
const parsed = JSON.parse(result.stdout || '[]');
|
|
26
|
+
output(parsed, true);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
output({ marketplaces: result.stdout }, true);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
console.log(result.stdout || 'No marketplaces configured.');
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
const add = defineCommand({
|
|
38
|
+
meta: {
|
|
39
|
+
name: 'add',
|
|
40
|
+
description: 'Add a plugin marketplace (a catalog of installable plugins)',
|
|
41
|
+
},
|
|
42
|
+
args: {
|
|
43
|
+
source: {
|
|
44
|
+
type: 'positional',
|
|
45
|
+
description: 'Marketplace source (GitHub repo, URL, or local path)',
|
|
46
|
+
required: true,
|
|
47
|
+
},
|
|
48
|
+
json: {
|
|
49
|
+
type: 'boolean',
|
|
50
|
+
description: 'Output as JSON',
|
|
51
|
+
default: false,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
async run({ args }) {
|
|
55
|
+
const result = await marketplace_add_via_cli(args.source);
|
|
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
|
+
}
|
|
63
|
+
output({
|
|
64
|
+
added: args.source,
|
|
65
|
+
success: result.success,
|
|
66
|
+
error: result.error,
|
|
67
|
+
available_plugins,
|
|
68
|
+
}, true);
|
|
69
|
+
}
|
|
70
|
+
else if (result.success) {
|
|
71
|
+
console.log(`Marketplace added: ${args.source}`);
|
|
72
|
+
await show_available_plugins(args.source);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
error(result.error || 'Unknown error');
|
|
76
|
+
}
|
|
77
|
+
},
|
|
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
|
+
}
|
|
121
|
+
const remove = defineCommand({
|
|
122
|
+
meta: {
|
|
123
|
+
name: 'remove',
|
|
124
|
+
description: 'Remove a marketplace',
|
|
125
|
+
},
|
|
126
|
+
args: {
|
|
127
|
+
name: {
|
|
128
|
+
type: 'positional',
|
|
129
|
+
description: 'Marketplace name to remove',
|
|
130
|
+
required: true,
|
|
131
|
+
},
|
|
132
|
+
json: {
|
|
133
|
+
type: 'boolean',
|
|
134
|
+
description: 'Output as JSON',
|
|
135
|
+
default: false,
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
async run({ args }) {
|
|
139
|
+
const result = await marketplace_remove_via_cli(args.name);
|
|
140
|
+
if (args.json) {
|
|
141
|
+
output({
|
|
142
|
+
removed: args.name,
|
|
143
|
+
success: result.success,
|
|
144
|
+
error: result.error,
|
|
145
|
+
}, true);
|
|
146
|
+
}
|
|
147
|
+
else if (result.success) {
|
|
148
|
+
console.log(`Marketplace removed: ${args.name}`);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
error(result.error || 'Unknown error');
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
const update = defineCommand({
|
|
156
|
+
meta: {
|
|
157
|
+
name: 'update',
|
|
158
|
+
description: 'Update marketplace(s) from source',
|
|
159
|
+
},
|
|
160
|
+
args: {
|
|
161
|
+
name: {
|
|
162
|
+
type: 'positional',
|
|
163
|
+
description: 'Marketplace name to update (omit to update all)',
|
|
164
|
+
required: false,
|
|
165
|
+
},
|
|
166
|
+
json: {
|
|
167
|
+
type: 'boolean',
|
|
168
|
+
description: 'Output as JSON',
|
|
169
|
+
default: false,
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
async run({ args }) {
|
|
173
|
+
const result = await marketplace_update_via_cli(args.name || undefined);
|
|
174
|
+
if (args.json) {
|
|
175
|
+
output({
|
|
176
|
+
updated: args.name || 'all',
|
|
177
|
+
success: result.success,
|
|
178
|
+
error: result.error,
|
|
179
|
+
}, true);
|
|
180
|
+
}
|
|
181
|
+
else if (result.success) {
|
|
182
|
+
console.log(args.name
|
|
183
|
+
? `Marketplace updated: ${args.name}`
|
|
184
|
+
: 'All marketplaces updated.');
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
error(result.error || 'Unknown error');
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
export default defineCommand({
|
|
192
|
+
meta: {
|
|
193
|
+
name: 'marketplace',
|
|
194
|
+
description: 'Manage plugin marketplaces (catalogs of installable plugins). Add a marketplace first, then install plugins from it with: mcpick plugins install <name>@<marketplace>',
|
|
195
|
+
},
|
|
196
|
+
subCommands: {
|
|
197
|
+
list,
|
|
198
|
+
add,
|
|
199
|
+
remove,
|
|
200
|
+
update,
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
//# sourceMappingURL=marketplace.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defineCommand } from 'citty';
|
|
2
|
+
import { parse_plugin_key, read_known_marketplaces, } from '../../core/plugin-cache.js';
|
|
2
3
|
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 { install_plugin_via_cli, uninstall_plugin_via_cli, update_plugin_via_cli, validate_plugin_via_cli, } from '../../utils/claude-cli.js';
|
|
4
5
|
import { error, output } from '../output.js';
|
|
5
6
|
const list = defineCommand({
|
|
6
7
|
meta: {
|
|
@@ -144,6 +145,25 @@ const uninstall = defineCommand({
|
|
|
144
145
|
},
|
|
145
146
|
async run({ args }) {
|
|
146
147
|
const scope = args.scope;
|
|
148
|
+
// Check if this is a marketplace-managed plugin
|
|
149
|
+
const { marketplace } = parse_plugin_key(args.plugin);
|
|
150
|
+
const known = await read_known_marketplaces();
|
|
151
|
+
if (known[marketplace]) {
|
|
152
|
+
const msg = `'${args.plugin}' is managed by marketplace '${marketplace}'.\n` +
|
|
153
|
+
`To remove the marketplace: mcpick marketplace remove ${marketplace}\n` +
|
|
154
|
+
`Or via Claude CLI: claude plugin marketplace remove ${marketplace}`;
|
|
155
|
+
if (args.json) {
|
|
156
|
+
output({
|
|
157
|
+
success: false,
|
|
158
|
+
error: msg,
|
|
159
|
+
marketplace_managed: true,
|
|
160
|
+
}, true);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
error(msg);
|
|
164
|
+
}
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
147
167
|
const result = await uninstall_plugin_via_cli(args.plugin, scope);
|
|
148
168
|
if (args.json) {
|
|
149
169
|
output(result, true);
|
|
@@ -192,11 +212,54 @@ const update = defineCommand({
|
|
|
192
212
|
}
|
|
193
213
|
},
|
|
194
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
|
+
});
|
|
195
250
|
export default defineCommand({
|
|
196
251
|
meta: {
|
|
197
252
|
name: 'plugins',
|
|
198
253
|
description: 'Manage Claude Code plugins',
|
|
199
254
|
},
|
|
200
|
-
subCommands: {
|
|
255
|
+
subCommands: {
|
|
256
|
+
list,
|
|
257
|
+
enable,
|
|
258
|
+
disable,
|
|
259
|
+
install,
|
|
260
|
+
uninstall,
|
|
261
|
+
update,
|
|
262
|
+
validate,
|
|
263
|
+
},
|
|
201
264
|
});
|
|
202
265
|
//# sourceMappingURL=plugins.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { defineCommand } from 'citty';
|
|
2
|
+
import { output } from '../output.js';
|
|
3
|
+
export default defineCommand({
|
|
4
|
+
meta: {
|
|
5
|
+
name: 'reload',
|
|
6
|
+
description: 'Reload plugins in Claude Code',
|
|
7
|
+
},
|
|
8
|
+
args: {
|
|
9
|
+
json: {
|
|
10
|
+
type: 'boolean',
|
|
11
|
+
description: 'Output as JSON',
|
|
12
|
+
default: false,
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
async run({ args }) {
|
|
16
|
+
const info = {
|
|
17
|
+
supported: false,
|
|
18
|
+
message: 'Claude Code does not expose a programmatic reload API.',
|
|
19
|
+
instructions: [
|
|
20
|
+
'Run /reload-plugins inside an active Claude Code session',
|
|
21
|
+
'Or restart your Claude Code session to pick up changes',
|
|
22
|
+
],
|
|
23
|
+
};
|
|
24
|
+
if (args.json) {
|
|
25
|
+
output(info, true);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
console.log(info.message);
|
|
29
|
+
console.log('\nTo reload plugins:');
|
|
30
|
+
for (const instruction of info.instructions) {
|
|
31
|
+
console.log(` - ${instruction}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
//# sourceMappingURL=reload.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,12 +10,17 @@ 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),
|
|
16
19
|
plugins: () => import('./commands/plugins.js').then((m) => m.default),
|
|
17
20
|
cache: () => import('./commands/cache.js').then((m) => m.default),
|
|
18
21
|
dev: () => import('./commands/dev.js').then((m) => m.default),
|
|
22
|
+
marketplace: () => import('./commands/marketplace.js').then((m) => m.default),
|
|
23
|
+
reload: () => import('./commands/reload.js').then((m) => m.default),
|
|
19
24
|
},
|
|
20
25
|
});
|
|
21
26
|
export const run = () => runMain(main);
|
|
@@ -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
|
|
@@ -82,8 +82,7 @@ async function write_server_to_scope(name, server, scope) {
|
|
|
82
82
|
if (!existing.mcpServers) {
|
|
83
83
|
existing.mcpServers = {};
|
|
84
84
|
}
|
|
85
|
-
existing.mcpServers[name] =
|
|
86
|
-
server;
|
|
85
|
+
existing.mcpServers[name] = server;
|
|
87
86
|
return existing;
|
|
88
87
|
});
|
|
89
88
|
}
|
|
@@ -100,7 +99,8 @@ async function write_server_to_scope(name, server, scope) {
|
|
|
100
99
|
if (!projects[cwd].mcpServers) {
|
|
101
100
|
projects[cwd].mcpServers = {};
|
|
102
101
|
}
|
|
103
|
-
projects[cwd].mcpServers[name] =
|
|
102
|
+
projects[cwd].mcpServers[name] =
|
|
103
|
+
server;
|
|
104
104
|
return existing;
|
|
105
105
|
});
|
|
106
106
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { exec } from 'node:child_process';
|
|
2
|
-
import { lstat, readdir, readFile, readlink, rename, rm, symlink } from 'node:fs/promises';
|
|
2
|
+
import { lstat, readdir, readFile, readlink, rename, rm, symlink, } from 'node:fs/promises';
|
|
3
3
|
import { join, resolve } from 'node:path';
|
|
4
4
|
import { promisify } from 'node:util';
|
|
5
5
|
import { atomic_json_write } from '../utils/atomic-write.js';
|
|
@@ -50,6 +50,51 @@ async function get_marketplace_head_sha(marketplace_path) {
|
|
|
50
50
|
return null;
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Recover a marketplace clone stuck on a deleted branch.
|
|
55
|
+
* Resets the fetch refspec, fetches all branches, and checks out the default branch.
|
|
56
|
+
*/
|
|
57
|
+
async function recover_deleted_branch(dir) {
|
|
58
|
+
const q = JSON.stringify(dir);
|
|
59
|
+
try {
|
|
60
|
+
// Reset narrow refspec to fetch all branches
|
|
61
|
+
await execAsync(`git -C ${q} remote set-branches origin '*'`, {
|
|
62
|
+
timeout: 10_000,
|
|
63
|
+
});
|
|
64
|
+
await execAsync(`git -C ${q} fetch origin`, {
|
|
65
|
+
timeout: 30_000,
|
|
66
|
+
});
|
|
67
|
+
// Detect default branch
|
|
68
|
+
let default_branch = 'main';
|
|
69
|
+
try {
|
|
70
|
+
const { stdout } = await execAsync(`git -C ${q} symbolic-ref refs/remotes/origin/HEAD`, { timeout: 5_000 });
|
|
71
|
+
const match = stdout
|
|
72
|
+
.trim()
|
|
73
|
+
.match(/refs\/remotes\/origin\/(.+)/);
|
|
74
|
+
if (match)
|
|
75
|
+
default_branch = match[1];
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// symbolic-ref not set — try main, then master
|
|
79
|
+
try {
|
|
80
|
+
await execAsync(`git -C ${q} rev-parse --verify origin/main`, { timeout: 5_000 });
|
|
81
|
+
default_branch = 'main';
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
default_branch = 'master';
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
await execAsync(`git -C ${q} checkout ${default_branch}`, {
|
|
88
|
+
timeout: 10_000,
|
|
89
|
+
});
|
|
90
|
+
await execAsync(`git -C ${q} reset --hard origin/${default_branch}`, { timeout: 10_000 });
|
|
91
|
+
return { recovered: true };
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
const msg = err instanceof Error ? err.message : 'Unknown error';
|
|
95
|
+
return { recovered: false, error: msg };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
53
98
|
export async function refresh_marketplace(name, marketplace) {
|
|
54
99
|
const dir = marketplace.installLocation;
|
|
55
100
|
try {
|
|
@@ -58,9 +103,16 @@ export async function refresh_marketplace(name, marketplace) {
|
|
|
58
103
|
});
|
|
59
104
|
return { success: true };
|
|
60
105
|
}
|
|
61
|
-
catch
|
|
62
|
-
|
|
63
|
-
|
|
106
|
+
catch {
|
|
107
|
+
// Fast-forward failed — attempt recovery from deleted branch
|
|
108
|
+
const recovery = await recover_deleted_branch(dir);
|
|
109
|
+
if (recovery.recovered) {
|
|
110
|
+
return { success: true };
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
success: false,
|
|
114
|
+
error: `${name}: recovery failed: ${recovery.error}`,
|
|
115
|
+
};
|
|
64
116
|
}
|
|
65
117
|
}
|
|
66
118
|
export async function refresh_all_marketplaces() {
|
|
@@ -155,6 +207,42 @@ export async function get_cached_plugins_info() {
|
|
|
155
207
|
}
|
|
156
208
|
return results;
|
|
157
209
|
}
|
|
210
|
+
// --- Cache scanning ---
|
|
211
|
+
/**
|
|
212
|
+
* Scan the cache directory on disk to find all plugin keys,
|
|
213
|
+
* including marketplace-sourced plugins not tracked in installed_plugins.json.
|
|
214
|
+
*/
|
|
215
|
+
export async function scan_all_cache_keys() {
|
|
216
|
+
const cache_dir = get_plugin_cache_dir();
|
|
217
|
+
const keys = [];
|
|
218
|
+
try {
|
|
219
|
+
const marketplaces = await readdir(cache_dir, {
|
|
220
|
+
withFileTypes: true,
|
|
221
|
+
});
|
|
222
|
+
for (const mkt of marketplaces) {
|
|
223
|
+
if (!mkt.isDirectory() && !mkt.isSymbolicLink())
|
|
224
|
+
continue;
|
|
225
|
+
const mkt_path = join(cache_dir, mkt.name);
|
|
226
|
+
try {
|
|
227
|
+
const plugins = await readdir(mkt_path, {
|
|
228
|
+
withFileTypes: true,
|
|
229
|
+
});
|
|
230
|
+
for (const plugin of plugins) {
|
|
231
|
+
if (!plugin.isDirectory() && !plugin.isSymbolicLink())
|
|
232
|
+
continue;
|
|
233
|
+
keys.push(`${plugin.name}@${mkt.name}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
// Skip unreadable marketplace dirs
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
// Cache dir doesn't exist
|
|
243
|
+
}
|
|
244
|
+
return keys;
|
|
245
|
+
}
|
|
158
246
|
// --- Cache clearing ---
|
|
159
247
|
function is_safe_cache_path(path) {
|
|
160
248
|
const cache_dir = resolve(get_plugin_cache_dir());
|
|
@@ -377,7 +465,9 @@ export async function list_linked_plugins() {
|
|
|
377
465
|
const cache_dir = get_plugin_cache_dir();
|
|
378
466
|
const links = [];
|
|
379
467
|
try {
|
|
380
|
-
const marketplaces = await readdir(cache_dir, {
|
|
468
|
+
const marketplaces = await readdir(cache_dir, {
|
|
469
|
+
withFileTypes: true,
|
|
470
|
+
});
|
|
381
471
|
for (const mkt of marketplaces) {
|
|
382
472
|
if (!mkt.isDirectory() && !mkt.isSymbolicLink())
|
|
383
473
|
continue;
|
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,12 +285,17 @@ 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',
|
|
282
294
|
'plugins',
|
|
283
295
|
'cache',
|
|
284
296
|
'dev',
|
|
297
|
+
'marketplace',
|
|
298
|
+
'reload',
|
|
285
299
|
]);
|
|
286
300
|
const arg = process.argv[2];
|
|
287
301
|
if ((arg && SUBCOMMANDS.has(arg)) ||
|
package/dist/utils/claude-cli.js
CHANGED
|
@@ -189,6 +189,170 @@ export async function update_plugin_via_cli(key, scope = 'user') {
|
|
|
189
189
|
};
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* Add a marketplace via Claude CLI
|
|
194
|
+
*/
|
|
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) {
|
|
240
|
+
const cli_available = await check_claude_cli();
|
|
241
|
+
if (!cli_available) {
|
|
242
|
+
return {
|
|
243
|
+
success: false,
|
|
244
|
+
error: 'Claude CLI not found. Please install Claude Code CLI.',
|
|
245
|
+
};
|
|
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
|
+
}
|
|
257
|
+
try {
|
|
258
|
+
await execAsync(`claude plugin marketplace add ${shell_escape(source)}`);
|
|
259
|
+
return { success: true };
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
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
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
success: false,
|
|
280
|
+
error: `Failed to add marketplace: ${message}`,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Remove a marketplace via Claude CLI
|
|
286
|
+
*/
|
|
287
|
+
export async function marketplace_remove_via_cli(name) {
|
|
288
|
+
const cli_available = await check_claude_cli();
|
|
289
|
+
if (!cli_available) {
|
|
290
|
+
return {
|
|
291
|
+
success: false,
|
|
292
|
+
error: 'Claude CLI not found. Please install Claude Code CLI.',
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
try {
|
|
296
|
+
await execAsync(`claude plugin marketplace remove ${shell_escape(name)}`);
|
|
297
|
+
return { success: true };
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
301
|
+
return {
|
|
302
|
+
success: false,
|
|
303
|
+
error: `Failed to remove marketplace: ${message}`,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Update marketplace(s) via Claude CLI
|
|
309
|
+
*/
|
|
310
|
+
export async function marketplace_update_via_cli(name) {
|
|
311
|
+
const cli_available = await check_claude_cli();
|
|
312
|
+
if (!cli_available) {
|
|
313
|
+
return {
|
|
314
|
+
success: false,
|
|
315
|
+
error: 'Claude CLI not found. Please install Claude Code CLI.',
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
try {
|
|
319
|
+
const cmd = name
|
|
320
|
+
? `claude plugin marketplace update ${shell_escape(name)}`
|
|
321
|
+
: 'claude plugin marketplace update';
|
|
322
|
+
await execAsync(cmd);
|
|
323
|
+
return { success: true };
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
327
|
+
return {
|
|
328
|
+
success: false,
|
|
329
|
+
error: `Failed to update marketplace: ${message}`,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* List marketplaces via Claude CLI
|
|
335
|
+
*/
|
|
336
|
+
export async function marketplace_list_via_cli() {
|
|
337
|
+
const cli_available = await check_claude_cli();
|
|
338
|
+
if (!cli_available) {
|
|
339
|
+
return {
|
|
340
|
+
success: false,
|
|
341
|
+
error: 'Claude CLI not found. Please install Claude Code CLI.',
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
try {
|
|
345
|
+
const { stdout } = await execAsync('claude plugin marketplace list');
|
|
346
|
+
return { success: true, stdout: stdout.trim() };
|
|
347
|
+
}
|
|
348
|
+
catch (error) {
|
|
349
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
350
|
+
return {
|
|
351
|
+
success: false,
|
|
352
|
+
error: `Failed to list marketplaces: ${message}`,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
}
|
|
192
356
|
/**
|
|
193
357
|
* Get the scope description for display
|
|
194
358
|
*/
|
|
@@ -202,6 +366,98 @@ export function get_scope_description(scope) {
|
|
|
202
366
|
return 'Global - all projects';
|
|
203
367
|
}
|
|
204
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
|
+
}
|
|
205
461
|
/**
|
|
206
462
|
* Get scope options for select prompt
|
|
207
463
|
*/
|