mcpick 0.0.13 → 0.0.15
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 +14 -0
- package/dist/cli/commands/cache.js +118 -3
- package/dist/cli/commands/dev.js +161 -0
- package/dist/cli/commands/marketplace.js +162 -0
- package/dist/cli/commands/plugins.js +20 -0
- package/dist/cli/commands/reload.js +36 -0
- package/dist/cli/index.js +3 -0
- package/dist/core/dev-override.js +210 -0
- package/dist/core/plugin-cache.js +252 -6
- package/dist/index.js +3 -0
- package/dist/utils/claude-cli.js +95 -0
- package/dist/utils/paths.js +3 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# mcpick
|
|
2
2
|
|
|
3
|
+
## 0.0.15
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 0170ea0: Add local dev workflow: mcpick dev + cache link/unlink
|
|
8
|
+
- 2161258: Fix marketplace refresh, cache clear, uninstall detection;
|
|
9
|
+
add marketplace commands
|
|
10
|
+
|
|
11
|
+
## 0.0.14
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- 0170ea0: Add local dev workflow: mcpick dev + cache link/unlink
|
|
16
|
+
|
|
3
17
|
## 0.0.13
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineCommand } from 'citty';
|
|
2
|
-
import { clean_orphaned_versions, clear_plugin_caches, get_cached_plugins_info, read_installed_plugins, refresh_all_marketplaces, } 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];
|
|
@@ -166,6 +173,111 @@ const refresh = defineCommand({
|
|
|
166
173
|
}
|
|
167
174
|
},
|
|
168
175
|
});
|
|
176
|
+
const link = defineCommand({
|
|
177
|
+
meta: {
|
|
178
|
+
name: 'link',
|
|
179
|
+
description: 'Symlink a local directory into the plugin cache for dev',
|
|
180
|
+
},
|
|
181
|
+
args: {
|
|
182
|
+
path: {
|
|
183
|
+
type: 'positional',
|
|
184
|
+
description: 'Local path to the plugin/marketplace directory',
|
|
185
|
+
required: true,
|
|
186
|
+
},
|
|
187
|
+
as: {
|
|
188
|
+
type: 'string',
|
|
189
|
+
description: 'Plugin key (name@marketplace) for the cache entry',
|
|
190
|
+
required: true,
|
|
191
|
+
},
|
|
192
|
+
json: {
|
|
193
|
+
type: 'boolean',
|
|
194
|
+
description: 'Output as JSON',
|
|
195
|
+
default: false,
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
async run({ args }) {
|
|
199
|
+
if (!args.as) {
|
|
200
|
+
error('--as is required. Specify plugin key as name@marketplace.');
|
|
201
|
+
}
|
|
202
|
+
if (!args.as.includes('@')) {
|
|
203
|
+
error('Plugin key must be in name@marketplace format (e.g. my-plugin@my-marketplace)');
|
|
204
|
+
}
|
|
205
|
+
const result = await link_local_plugin(args.path, args.as);
|
|
206
|
+
if (args.json) {
|
|
207
|
+
output(result, true);
|
|
208
|
+
}
|
|
209
|
+
else if (result.success) {
|
|
210
|
+
console.log(`Linked: ${result.key}`);
|
|
211
|
+
console.log(` ${result.symlinkPath} → ${result.targetPath}`);
|
|
212
|
+
console.log('\nRun /reload-plugins in Claude Code or restart your session.');
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
error(result.error || 'Unknown error');
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
const unlink = defineCommand({
|
|
220
|
+
meta: {
|
|
221
|
+
name: 'unlink',
|
|
222
|
+
description: 'Remove a symlink from the plugin cache',
|
|
223
|
+
},
|
|
224
|
+
args: {
|
|
225
|
+
key: {
|
|
226
|
+
type: 'positional',
|
|
227
|
+
description: 'Plugin key (name@marketplace)',
|
|
228
|
+
required: true,
|
|
229
|
+
},
|
|
230
|
+
json: {
|
|
231
|
+
type: 'boolean',
|
|
232
|
+
description: 'Output as JSON',
|
|
233
|
+
default: false,
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
async run({ args }) {
|
|
237
|
+
const result = await unlink_local_plugin(args.key);
|
|
238
|
+
if (args.json) {
|
|
239
|
+
output(result, true);
|
|
240
|
+
}
|
|
241
|
+
else if (result.success) {
|
|
242
|
+
console.log(`Unlinked: ${args.key}`);
|
|
243
|
+
if (result.restored) {
|
|
244
|
+
console.log(' Original cache directory restored from backup.');
|
|
245
|
+
}
|
|
246
|
+
console.log('\nRun /reload-plugins in Claude Code or restart your session.');
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
error(result.error || 'Unknown error');
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
const links = defineCommand({
|
|
254
|
+
meta: {
|
|
255
|
+
name: 'links',
|
|
256
|
+
description: 'List all symlinked plugin cache entries',
|
|
257
|
+
},
|
|
258
|
+
args: {
|
|
259
|
+
json: {
|
|
260
|
+
type: 'boolean',
|
|
261
|
+
description: 'Output as JSON',
|
|
262
|
+
default: false,
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
async run({ args }) {
|
|
266
|
+
const linked = await list_linked_plugins();
|
|
267
|
+
if (args.json) {
|
|
268
|
+
output(linked, true);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (linked.length === 0) {
|
|
272
|
+
console.log('No linked plugins.');
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
for (const l of linked) {
|
|
276
|
+
console.log(`${l.key}`);
|
|
277
|
+
console.log(` ${l.symlinkPath} → ${l.targetPath}`);
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
});
|
|
169
281
|
export default defineCommand({
|
|
170
282
|
meta: {
|
|
171
283
|
name: 'cache',
|
|
@@ -176,6 +288,9 @@ export default defineCommand({
|
|
|
176
288
|
clear,
|
|
177
289
|
'clean-orphaned': clean_orphaned,
|
|
178
290
|
refresh,
|
|
291
|
+
link,
|
|
292
|
+
unlink,
|
|
293
|
+
links,
|
|
179
294
|
},
|
|
180
295
|
});
|
|
181
296
|
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { defineCommand } from 'citty';
|
|
2
|
+
import { apply_dev_override, list_dev_overrides, restore_all_dev_overrides, restore_dev_override, } from '../../core/dev-override.js';
|
|
3
|
+
import { error, output } from '../output.js';
|
|
4
|
+
const apply = defineCommand({
|
|
5
|
+
meta: {
|
|
6
|
+
name: 'apply',
|
|
7
|
+
description: 'Override an MCP server with a local dev command',
|
|
8
|
+
},
|
|
9
|
+
args: {
|
|
10
|
+
name: {
|
|
11
|
+
type: 'positional',
|
|
12
|
+
description: 'Server name to override',
|
|
13
|
+
required: true,
|
|
14
|
+
},
|
|
15
|
+
command: {
|
|
16
|
+
type: 'string',
|
|
17
|
+
description: 'Local command to run',
|
|
18
|
+
required: true,
|
|
19
|
+
},
|
|
20
|
+
args: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
description: 'Comma-separated arguments',
|
|
23
|
+
},
|
|
24
|
+
scope: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
description: 'Scope to search: local, project, or user (default: auto-detect)',
|
|
27
|
+
},
|
|
28
|
+
json: {
|
|
29
|
+
type: 'boolean',
|
|
30
|
+
description: 'Output as JSON',
|
|
31
|
+
default: false,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
async run({ args }) {
|
|
35
|
+
const scope = args.scope;
|
|
36
|
+
if (scope && !['local', 'project', 'user'].includes(scope)) {
|
|
37
|
+
error(`Invalid scope: ${scope}. Use local, project, or user.`);
|
|
38
|
+
}
|
|
39
|
+
const cmd_args = args.args ? args.args.split(',') : [];
|
|
40
|
+
const result = await apply_dev_override(args.name, args.command, cmd_args, scope);
|
|
41
|
+
if (args.json) {
|
|
42
|
+
output(result, true);
|
|
43
|
+
}
|
|
44
|
+
else if (result.success) {
|
|
45
|
+
console.log(`Dev override applied for '${args.name}' (scope: ${result.scope})`);
|
|
46
|
+
console.log(` command: ${args.command}${cmd_args.length > 0 ? ` ${cmd_args.join(' ')}` : ''}`);
|
|
47
|
+
console.log('\nRestart Claude Code or run /reload-plugins to pick up changes.');
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
error(result.error || 'Unknown error');
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
const restore = defineCommand({
|
|
55
|
+
meta: {
|
|
56
|
+
name: 'restore',
|
|
57
|
+
description: 'Restore original server config from dev override',
|
|
58
|
+
},
|
|
59
|
+
args: {
|
|
60
|
+
name: {
|
|
61
|
+
type: 'positional',
|
|
62
|
+
description: 'Server name to restore (omit with --all to restore all)',
|
|
63
|
+
required: false,
|
|
64
|
+
},
|
|
65
|
+
all: {
|
|
66
|
+
type: 'boolean',
|
|
67
|
+
description: 'Restore all dev overrides',
|
|
68
|
+
default: false,
|
|
69
|
+
},
|
|
70
|
+
json: {
|
|
71
|
+
type: 'boolean',
|
|
72
|
+
description: 'Output as JSON',
|
|
73
|
+
default: false,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
async run({ args }) {
|
|
77
|
+
if (args.all) {
|
|
78
|
+
const result = await restore_all_dev_overrides();
|
|
79
|
+
if (args.json) {
|
|
80
|
+
output(result, true);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
if (result.restored.length === 0 &&
|
|
84
|
+
result.errors.length === 0) {
|
|
85
|
+
console.log('No dev overrides to restore.');
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
for (const name of result.restored) {
|
|
89
|
+
console.log(`Restored: ${name}`);
|
|
90
|
+
}
|
|
91
|
+
for (const err of result.errors) {
|
|
92
|
+
console.error(`Error: ${err}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (!args.name) {
|
|
99
|
+
error('Specify a server name or use --all. Run "mcpick dev list" to see active overrides.');
|
|
100
|
+
}
|
|
101
|
+
const result = await restore_dev_override(args.name);
|
|
102
|
+
if (args.json) {
|
|
103
|
+
output(result, true);
|
|
104
|
+
}
|
|
105
|
+
else if (result.success) {
|
|
106
|
+
console.log(`Restored original config for '${args.name}'`);
|
|
107
|
+
console.log('\nRestart Claude Code or run /reload-plugins to pick up changes.');
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
error(result.error || 'Unknown error');
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
const list = defineCommand({
|
|
115
|
+
meta: {
|
|
116
|
+
name: 'list',
|
|
117
|
+
description: 'List active dev overrides',
|
|
118
|
+
},
|
|
119
|
+
args: {
|
|
120
|
+
json: {
|
|
121
|
+
type: 'boolean',
|
|
122
|
+
description: 'Output as JSON',
|
|
123
|
+
default: false,
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
async run({ args }) {
|
|
127
|
+
const overrides = await list_dev_overrides();
|
|
128
|
+
if (args.json) {
|
|
129
|
+
output(overrides, true);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (overrides.length === 0) {
|
|
133
|
+
console.log('No active dev overrides.');
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
for (const o of overrides) {
|
|
137
|
+
const orig = o.original;
|
|
138
|
+
const dev = o.dev;
|
|
139
|
+
const original_cmd = orig.command
|
|
140
|
+
? `${orig.command}${orig.args ? ' ' + orig.args.join(' ') : ''}`
|
|
141
|
+
: orig.url || '?';
|
|
142
|
+
const dev_cmd = `${dev.command}${dev.args ? ' ' + dev.args.join(' ') : ''}`;
|
|
143
|
+
console.log(`${o.name} (scope: ${o.scope})`);
|
|
144
|
+
console.log(` original: ${original_cmd}`);
|
|
145
|
+
console.log(` dev: ${dev_cmd}`);
|
|
146
|
+
console.log(` since: ${o.createdAt}`);
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
export default defineCommand({
|
|
151
|
+
meta: {
|
|
152
|
+
name: 'dev',
|
|
153
|
+
description: 'MCP server local development workflow',
|
|
154
|
+
},
|
|
155
|
+
subCommands: {
|
|
156
|
+
apply,
|
|
157
|
+
restore,
|
|
158
|
+
list,
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
//# sourceMappingURL=dev.js.map
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { defineCommand } from 'citty';
|
|
2
|
+
import { marketplace_add_via_cli, marketplace_list_via_cli, marketplace_remove_via_cli, marketplace_update_via_cli, } from '../../utils/claude-cli.js';
|
|
3
|
+
import { error, output } from '../output.js';
|
|
4
|
+
const list = defineCommand({
|
|
5
|
+
meta: {
|
|
6
|
+
name: 'list',
|
|
7
|
+
description: 'List configured marketplaces',
|
|
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 marketplace_list_via_cli();
|
|
18
|
+
if (!result.success) {
|
|
19
|
+
error(result.error || 'Unknown error');
|
|
20
|
+
}
|
|
21
|
+
if (args.json) {
|
|
22
|
+
// Try to parse CLI JSON output, fallback to raw
|
|
23
|
+
try {
|
|
24
|
+
const parsed = JSON.parse(result.stdout || '[]');
|
|
25
|
+
output(parsed, true);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
output({ marketplaces: result.stdout }, true);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
console.log(result.stdout || 'No marketplaces configured.');
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
const add = defineCommand({
|
|
37
|
+
meta: {
|
|
38
|
+
name: 'add',
|
|
39
|
+
description: 'Add a marketplace from URL, path, or GitHub repo',
|
|
40
|
+
},
|
|
41
|
+
args: {
|
|
42
|
+
source: {
|
|
43
|
+
type: 'positional',
|
|
44
|
+
description: 'Marketplace source (GitHub repo, URL, or local path)',
|
|
45
|
+
required: true,
|
|
46
|
+
},
|
|
47
|
+
scope: {
|
|
48
|
+
type: 'string',
|
|
49
|
+
description: 'Scope: user, project, or local (default: user)',
|
|
50
|
+
default: 'user',
|
|
51
|
+
},
|
|
52
|
+
json: {
|
|
53
|
+
type: 'boolean',
|
|
54
|
+
description: 'Output as JSON',
|
|
55
|
+
default: false,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
async run({ args }) {
|
|
59
|
+
const scope = args.scope;
|
|
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);
|
|
64
|
+
if (args.json) {
|
|
65
|
+
output({
|
|
66
|
+
added: args.source,
|
|
67
|
+
scope,
|
|
68
|
+
success: result.success,
|
|
69
|
+
error: result.error,
|
|
70
|
+
}, true);
|
|
71
|
+
}
|
|
72
|
+
else if (result.success) {
|
|
73
|
+
console.log(`Marketplace added: ${args.source} (scope: ${scope})`);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
error(result.error || 'Unknown error');
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
const remove = defineCommand({
|
|
81
|
+
meta: {
|
|
82
|
+
name: 'remove',
|
|
83
|
+
description: 'Remove a marketplace',
|
|
84
|
+
},
|
|
85
|
+
args: {
|
|
86
|
+
name: {
|
|
87
|
+
type: 'positional',
|
|
88
|
+
description: 'Marketplace name to remove',
|
|
89
|
+
required: true,
|
|
90
|
+
},
|
|
91
|
+
json: {
|
|
92
|
+
type: 'boolean',
|
|
93
|
+
description: 'Output as JSON',
|
|
94
|
+
default: false,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
async run({ args }) {
|
|
98
|
+
const result = await marketplace_remove_via_cli(args.name);
|
|
99
|
+
if (args.json) {
|
|
100
|
+
output({
|
|
101
|
+
removed: args.name,
|
|
102
|
+
success: result.success,
|
|
103
|
+
error: result.error,
|
|
104
|
+
}, true);
|
|
105
|
+
}
|
|
106
|
+
else if (result.success) {
|
|
107
|
+
console.log(`Marketplace removed: ${args.name}`);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
error(result.error || 'Unknown error');
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
const update = defineCommand({
|
|
115
|
+
meta: {
|
|
116
|
+
name: 'update',
|
|
117
|
+
description: 'Update marketplace(s) from source',
|
|
118
|
+
},
|
|
119
|
+
args: {
|
|
120
|
+
name: {
|
|
121
|
+
type: 'positional',
|
|
122
|
+
description: 'Marketplace name to update (omit to update all)',
|
|
123
|
+
required: false,
|
|
124
|
+
},
|
|
125
|
+
json: {
|
|
126
|
+
type: 'boolean',
|
|
127
|
+
description: 'Output as JSON',
|
|
128
|
+
default: false,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
async run({ args }) {
|
|
132
|
+
const result = await marketplace_update_via_cli(args.name || undefined);
|
|
133
|
+
if (args.json) {
|
|
134
|
+
output({
|
|
135
|
+
updated: args.name || 'all',
|
|
136
|
+
success: result.success,
|
|
137
|
+
error: result.error,
|
|
138
|
+
}, true);
|
|
139
|
+
}
|
|
140
|
+
else if (result.success) {
|
|
141
|
+
console.log(args.name
|
|
142
|
+
? `Marketplace updated: ${args.name}`
|
|
143
|
+
: 'All marketplaces updated.');
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
error(result.error || 'Unknown error');
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
export default defineCommand({
|
|
151
|
+
meta: {
|
|
152
|
+
name: 'marketplace',
|
|
153
|
+
description: 'Manage Claude Code plugin marketplaces',
|
|
154
|
+
},
|
|
155
|
+
subCommands: {
|
|
156
|
+
list,
|
|
157
|
+
add,
|
|
158
|
+
remove,
|
|
159
|
+
update,
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
//# sourceMappingURL=marketplace.js.map
|
|
@@ -1,4 +1,5 @@
|
|
|
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
4
|
import { install_plugin_via_cli, uninstall_plugin_via_cli, update_plugin_via_cli, } from '../../utils/claude-cli.js';
|
|
4
5
|
import { error, output } from '../output.js';
|
|
@@ -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);
|
|
@@ -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
|
package/dist/cli/index.js
CHANGED
|
@@ -15,6 +15,9 @@ const main = defineCommand({
|
|
|
15
15
|
profile: () => import('./commands/profile.js').then((m) => m.default),
|
|
16
16
|
plugins: () => import('./commands/plugins.js').then((m) => m.default),
|
|
17
17
|
cache: () => import('./commands/cache.js').then((m) => m.default),
|
|
18
|
+
dev: () => import('./commands/dev.js').then((m) => m.default),
|
|
19
|
+
marketplace: () => import('./commands/marketplace.js').then((m) => m.default),
|
|
20
|
+
reload: () => import('./commands/reload.js').then((m) => m.default),
|
|
18
21
|
},
|
|
19
22
|
});
|
|
20
23
|
export const run = () => runMain(main);
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { access, readFile } from 'node:fs/promises';
|
|
2
|
+
import { atomic_json_write } from '../utils/atomic-write.js';
|
|
3
|
+
import { get_claude_config_path, get_current_project_path, get_dev_overrides_path, get_project_mcp_json_path, } from '../utils/paths.js';
|
|
4
|
+
const EMPTY_OVERRIDES = {
|
|
5
|
+
version: 1,
|
|
6
|
+
overrides: {},
|
|
7
|
+
};
|
|
8
|
+
export async function read_dev_overrides() {
|
|
9
|
+
try {
|
|
10
|
+
const content = await readFile(get_dev_overrides_path(), 'utf-8');
|
|
11
|
+
return JSON.parse(content);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return { ...EMPTY_OVERRIDES, overrides: {} };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
async function write_dev_overrides(data) {
|
|
18
|
+
await atomic_json_write(get_dev_overrides_path(), () => data);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Read full config for a given scope and return the server entry if found.
|
|
22
|
+
* Returns { config, server, scope } or null.
|
|
23
|
+
*/
|
|
24
|
+
async function find_server_in_scope(name, scope) {
|
|
25
|
+
if (scope === 'user' || scope === 'local') {
|
|
26
|
+
const config_path = get_claude_config_path();
|
|
27
|
+
try {
|
|
28
|
+
await access(config_path);
|
|
29
|
+
const content = await readFile(config_path, 'utf-8');
|
|
30
|
+
const parsed = JSON.parse(content);
|
|
31
|
+
if (scope === 'user') {
|
|
32
|
+
const server = parsed.mcpServers?.[name];
|
|
33
|
+
if (server)
|
|
34
|
+
return { server, scope: 'user' };
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
// local scope: projects[cwd].mcpServers
|
|
38
|
+
const cwd = get_current_project_path();
|
|
39
|
+
const server = parsed.projects?.[cwd]?.mcpServers?.[name];
|
|
40
|
+
if (server)
|
|
41
|
+
return { server, scope: 'local' };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// File doesn't exist
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
else if (scope === 'project') {
|
|
49
|
+
const mcp_path = get_project_mcp_json_path();
|
|
50
|
+
try {
|
|
51
|
+
await access(mcp_path);
|
|
52
|
+
const content = await readFile(mcp_path, 'utf-8');
|
|
53
|
+
const parsed = JSON.parse(content);
|
|
54
|
+
const server = parsed.mcpServers?.[name];
|
|
55
|
+
if (server)
|
|
56
|
+
return { server, scope: 'project' };
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// File doesn't exist
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Auto-detect which scope a server lives in.
|
|
66
|
+
* Searches local → project → user.
|
|
67
|
+
*/
|
|
68
|
+
async function detect_server_scope(name) {
|
|
69
|
+
for (const scope of ['local', 'project', 'user']) {
|
|
70
|
+
const result = await find_server_in_scope(name, scope);
|
|
71
|
+
if (result)
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Write a server config into the appropriate scope config file.
|
|
78
|
+
*/
|
|
79
|
+
async function write_server_to_scope(name, server, scope) {
|
|
80
|
+
if (scope === 'user') {
|
|
81
|
+
await atomic_json_write(get_claude_config_path(), (existing) => {
|
|
82
|
+
if (!existing.mcpServers) {
|
|
83
|
+
existing.mcpServers = {};
|
|
84
|
+
}
|
|
85
|
+
existing.mcpServers[name] = server;
|
|
86
|
+
return existing;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
else if (scope === 'local') {
|
|
90
|
+
const cwd = get_current_project_path();
|
|
91
|
+
await atomic_json_write(get_claude_config_path(), (existing) => {
|
|
92
|
+
if (!existing.projects) {
|
|
93
|
+
existing.projects = {};
|
|
94
|
+
}
|
|
95
|
+
const projects = existing.projects;
|
|
96
|
+
if (!projects[cwd]) {
|
|
97
|
+
projects[cwd] = {};
|
|
98
|
+
}
|
|
99
|
+
if (!projects[cwd].mcpServers) {
|
|
100
|
+
projects[cwd].mcpServers = {};
|
|
101
|
+
}
|
|
102
|
+
projects[cwd].mcpServers[name] =
|
|
103
|
+
server;
|
|
104
|
+
return existing;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
else if (scope === 'project') {
|
|
108
|
+
await atomic_json_write(get_project_mcp_json_path(), (existing) => {
|
|
109
|
+
if (!existing.mcpServers) {
|
|
110
|
+
existing.mcpServers = {};
|
|
111
|
+
}
|
|
112
|
+
existing.mcpServers[name] =
|
|
113
|
+
server;
|
|
114
|
+
return existing;
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Apply a dev override: store original config, swap in local dev command.
|
|
120
|
+
*/
|
|
121
|
+
export async function apply_dev_override(name, command, args, scope) {
|
|
122
|
+
// Find the server
|
|
123
|
+
let found;
|
|
124
|
+
if (scope) {
|
|
125
|
+
found = await find_server_in_scope(name, scope);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
found = await detect_server_scope(name);
|
|
129
|
+
}
|
|
130
|
+
if (!found) {
|
|
131
|
+
return {
|
|
132
|
+
success: false,
|
|
133
|
+
scope: scope || 'local',
|
|
134
|
+
error: `Server '${name}' not found${scope ? ` in ${scope} scope` : ' in any scope'}`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
// Check not already overridden
|
|
138
|
+
const overrides = await read_dev_overrides();
|
|
139
|
+
if (overrides.overrides[name]) {
|
|
140
|
+
return {
|
|
141
|
+
success: false,
|
|
142
|
+
scope: found.scope,
|
|
143
|
+
error: `Server '${name}' already has a dev override. Run 'mcpick dev --restore ${name}' first.`,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
// Build dev server config
|
|
147
|
+
const dev_server = {
|
|
148
|
+
command,
|
|
149
|
+
...(args.length > 0 ? { args } : {}),
|
|
150
|
+
};
|
|
151
|
+
// Store original
|
|
152
|
+
overrides.overrides[name] = {
|
|
153
|
+
original: found.server,
|
|
154
|
+
dev: dev_server,
|
|
155
|
+
scope: found.scope,
|
|
156
|
+
createdAt: new Date().toISOString(),
|
|
157
|
+
};
|
|
158
|
+
await write_dev_overrides(overrides);
|
|
159
|
+
// Write dev config
|
|
160
|
+
await write_server_to_scope(name, dev_server, found.scope);
|
|
161
|
+
return { success: true, scope: found.scope };
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Restore original server config from dev override.
|
|
165
|
+
*/
|
|
166
|
+
export async function restore_dev_override(name) {
|
|
167
|
+
const overrides = await read_dev_overrides();
|
|
168
|
+
const entry = overrides.overrides[name];
|
|
169
|
+
if (!entry) {
|
|
170
|
+
return {
|
|
171
|
+
success: false,
|
|
172
|
+
error: `No dev override found for '${name}'`,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
// Write original config back
|
|
176
|
+
await write_server_to_scope(name, entry.original, entry.scope);
|
|
177
|
+
// Remove override entry
|
|
178
|
+
delete overrides.overrides[name];
|
|
179
|
+
await write_dev_overrides(overrides);
|
|
180
|
+
return { success: true };
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Restore all dev overrides.
|
|
184
|
+
*/
|
|
185
|
+
export async function restore_all_dev_overrides() {
|
|
186
|
+
const overrides = await read_dev_overrides();
|
|
187
|
+
const restored = [];
|
|
188
|
+
const errors = [];
|
|
189
|
+
for (const name of Object.keys(overrides.overrides)) {
|
|
190
|
+
const result = await restore_dev_override(name);
|
|
191
|
+
if (result.success) {
|
|
192
|
+
restored.push(name);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
errors.push(`${name}: ${result.error}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return { restored, errors };
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* List all active dev overrides.
|
|
202
|
+
*/
|
|
203
|
+
export async function list_dev_overrides() {
|
|
204
|
+
const overrides = await read_dev_overrides();
|
|
205
|
+
return Object.entries(overrides.overrides).map(([name, entry]) => ({
|
|
206
|
+
name,
|
|
207
|
+
...entry,
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=dev-override.js.map
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { exec } from 'node:child_process';
|
|
2
|
-
import { readdir, readFile, rm } 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';
|
|
6
|
-
import { get_installed_plugins_path, get_known_marketplaces_path, get_marketplace_manifest_path, get_plugin_cache_dir, } from '../utils/paths.js';
|
|
6
|
+
import { ensure_directory_exists, get_installed_plugins_path, get_known_marketplaces_path, get_marketplace_manifest_path, get_plugin_cache_dir, } from '../utils/paths.js';
|
|
7
7
|
const execAsync = promisify(exec);
|
|
8
8
|
const EMPTY_INSTALLED = {
|
|
9
9
|
version: 2,
|
|
@@ -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() {
|
|
@@ -99,7 +151,7 @@ async function find_orphaned_versions(marketplace, plugin_name) {
|
|
|
99
151
|
return orphaned;
|
|
100
152
|
}
|
|
101
153
|
// --- Staleness analysis ---
|
|
102
|
-
function parse_plugin_key(key) {
|
|
154
|
+
export function parse_plugin_key(key) {
|
|
103
155
|
const at_index = key.lastIndexOf('@');
|
|
104
156
|
return {
|
|
105
157
|
name: at_index > 0 ? key.substring(0, at_index) : key,
|
|
@@ -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());
|
|
@@ -257,4 +345,162 @@ export async function clean_orphaned_versions() {
|
|
|
257
345
|
}
|
|
258
346
|
return { cleaned: cleaned_paths.length, paths: cleaned_paths };
|
|
259
347
|
}
|
|
348
|
+
// --- Cache linking ---
|
|
349
|
+
/**
|
|
350
|
+
* Symlink a local directory into the plugin cache.
|
|
351
|
+
* Backs up existing cache directory if present.
|
|
352
|
+
*/
|
|
353
|
+
export async function link_local_plugin(local_path, key) {
|
|
354
|
+
const resolved_path = resolve(local_path);
|
|
355
|
+
const { name, marketplace } = parse_plugin_key(key);
|
|
356
|
+
// Validate local path exists
|
|
357
|
+
try {
|
|
358
|
+
const stat = await lstat(resolved_path);
|
|
359
|
+
if (!stat.isDirectory()) {
|
|
360
|
+
return {
|
|
361
|
+
success: false,
|
|
362
|
+
key,
|
|
363
|
+
symlinkPath: '',
|
|
364
|
+
targetPath: resolved_path,
|
|
365
|
+
error: `Path is not a directory: ${resolved_path}`,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
catch {
|
|
370
|
+
return {
|
|
371
|
+
success: false,
|
|
372
|
+
key,
|
|
373
|
+
symlinkPath: '',
|
|
374
|
+
targetPath: resolved_path,
|
|
375
|
+
error: `Path does not exist: ${resolved_path}`,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
const cache_dir = get_plugin_cache_dir();
|
|
379
|
+
const plugin_dir = join(cache_dir, marketplace, name);
|
|
380
|
+
// Ensure parent directory exists
|
|
381
|
+
await ensure_directory_exists(join(cache_dir, marketplace));
|
|
382
|
+
// If plugin_dir exists and is not a symlink, back it up
|
|
383
|
+
try {
|
|
384
|
+
const stat = await lstat(plugin_dir);
|
|
385
|
+
if (stat.isSymbolicLink()) {
|
|
386
|
+
// Already a symlink — remove it
|
|
387
|
+
await rm(plugin_dir);
|
|
388
|
+
}
|
|
389
|
+
else if (stat.isDirectory()) {
|
|
390
|
+
// Back up existing directory
|
|
391
|
+
const backup_path = `${plugin_dir}.backup`;
|
|
392
|
+
await rename(plugin_dir, backup_path);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
catch {
|
|
396
|
+
// Doesn't exist — fine
|
|
397
|
+
}
|
|
398
|
+
// Create symlink
|
|
399
|
+
try {
|
|
400
|
+
await symlink(resolved_path, plugin_dir, 'dir');
|
|
401
|
+
}
|
|
402
|
+
catch (err) {
|
|
403
|
+
const msg = err instanceof Error ? err.message : 'Unknown error';
|
|
404
|
+
return {
|
|
405
|
+
success: false,
|
|
406
|
+
key,
|
|
407
|
+
symlinkPath: plugin_dir,
|
|
408
|
+
targetPath: resolved_path,
|
|
409
|
+
error: `Failed to create symlink: ${msg}`,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
return {
|
|
413
|
+
success: true,
|
|
414
|
+
key,
|
|
415
|
+
symlinkPath: plugin_dir,
|
|
416
|
+
targetPath: resolved_path,
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Remove a symlink from the plugin cache and restore backup if present.
|
|
421
|
+
*/
|
|
422
|
+
export async function unlink_local_plugin(key) {
|
|
423
|
+
const { name, marketplace } = parse_plugin_key(key);
|
|
424
|
+
const cache_dir = get_plugin_cache_dir();
|
|
425
|
+
const plugin_dir = join(cache_dir, marketplace, name);
|
|
426
|
+
// Verify it's actually a symlink
|
|
427
|
+
try {
|
|
428
|
+
const stat = await lstat(plugin_dir);
|
|
429
|
+
if (!stat.isSymbolicLink()) {
|
|
430
|
+
return {
|
|
431
|
+
success: false,
|
|
432
|
+
key,
|
|
433
|
+
restored: false,
|
|
434
|
+
error: `'${key}' is not a symlink — nothing to unlink`,
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
catch {
|
|
439
|
+
return {
|
|
440
|
+
success: false,
|
|
441
|
+
key,
|
|
442
|
+
restored: false,
|
|
443
|
+
error: `'${key}' not found in cache`,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
// Remove symlink
|
|
447
|
+
await rm(plugin_dir);
|
|
448
|
+
// Restore backup if present
|
|
449
|
+
const backup_path = `${plugin_dir}.backup`;
|
|
450
|
+
let restored = false;
|
|
451
|
+
try {
|
|
452
|
+
await lstat(backup_path);
|
|
453
|
+
await rename(backup_path, plugin_dir);
|
|
454
|
+
restored = true;
|
|
455
|
+
}
|
|
456
|
+
catch {
|
|
457
|
+
// No backup to restore
|
|
458
|
+
}
|
|
459
|
+
return { success: true, key, restored };
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* List all symlinked entries in the plugin cache.
|
|
463
|
+
*/
|
|
464
|
+
export async function list_linked_plugins() {
|
|
465
|
+
const cache_dir = get_plugin_cache_dir();
|
|
466
|
+
const links = [];
|
|
467
|
+
try {
|
|
468
|
+
const marketplaces = await readdir(cache_dir, {
|
|
469
|
+
withFileTypes: true,
|
|
470
|
+
});
|
|
471
|
+
for (const mkt of marketplaces) {
|
|
472
|
+
if (!mkt.isDirectory() && !mkt.isSymbolicLink())
|
|
473
|
+
continue;
|
|
474
|
+
const mkt_path = join(cache_dir, mkt.name);
|
|
475
|
+
let entries;
|
|
476
|
+
try {
|
|
477
|
+
entries = await readdir(mkt_path, { withFileTypes: true });
|
|
478
|
+
}
|
|
479
|
+
catch {
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
for (const entry of entries) {
|
|
483
|
+
const entry_path = join(mkt_path, entry.name);
|
|
484
|
+
try {
|
|
485
|
+
const stat = await lstat(entry_path);
|
|
486
|
+
if (stat.isSymbolicLink()) {
|
|
487
|
+
const target = await readlink(entry_path);
|
|
488
|
+
links.push({
|
|
489
|
+
key: `${entry.name}@${mkt.name}`,
|
|
490
|
+
symlinkPath: entry_path,
|
|
491
|
+
targetPath: resolve(join(cache_dir, mkt.name), target),
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
catch {
|
|
496
|
+
// Skip on error
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
catch {
|
|
502
|
+
// Cache dir doesn't exist
|
|
503
|
+
}
|
|
504
|
+
return links;
|
|
505
|
+
}
|
|
260
506
|
//# sourceMappingURL=plugin-cache.js.map
|
package/dist/index.js
CHANGED
package/dist/utils/claude-cli.js
CHANGED
|
@@ -189,6 +189,101 @@ 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
|
+
export async function marketplace_add_via_cli(source, scope = 'user') {
|
|
196
|
+
const cli_available = await check_claude_cli();
|
|
197
|
+
if (!cli_available) {
|
|
198
|
+
return {
|
|
199
|
+
success: false,
|
|
200
|
+
error: 'Claude CLI not found. Please install Claude Code CLI.',
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
await execAsync(`claude plugin marketplace add ${shell_escape(source)} --scope ${scope}`);
|
|
205
|
+
return { success: true };
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
209
|
+
return {
|
|
210
|
+
success: false,
|
|
211
|
+
error: `Failed to add marketplace: ${message}`,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Remove a marketplace via Claude CLI
|
|
217
|
+
*/
|
|
218
|
+
export async function marketplace_remove_via_cli(name) {
|
|
219
|
+
const cli_available = await check_claude_cli();
|
|
220
|
+
if (!cli_available) {
|
|
221
|
+
return {
|
|
222
|
+
success: false,
|
|
223
|
+
error: 'Claude CLI not found. Please install Claude Code CLI.',
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
try {
|
|
227
|
+
await execAsync(`claude plugin marketplace remove ${shell_escape(name)}`);
|
|
228
|
+
return { success: true };
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
232
|
+
return {
|
|
233
|
+
success: false,
|
|
234
|
+
error: `Failed to remove marketplace: ${message}`,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Update marketplace(s) via Claude CLI
|
|
240
|
+
*/
|
|
241
|
+
export async function marketplace_update_via_cli(name) {
|
|
242
|
+
const cli_available = await check_claude_cli();
|
|
243
|
+
if (!cli_available) {
|
|
244
|
+
return {
|
|
245
|
+
success: false,
|
|
246
|
+
error: 'Claude CLI not found. Please install Claude Code CLI.',
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
try {
|
|
250
|
+
const cmd = name
|
|
251
|
+
? `claude plugin marketplace update ${shell_escape(name)}`
|
|
252
|
+
: 'claude plugin marketplace update';
|
|
253
|
+
await execAsync(cmd);
|
|
254
|
+
return { success: true };
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
258
|
+
return {
|
|
259
|
+
success: false,
|
|
260
|
+
error: `Failed to update marketplace: ${message}`,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* List marketplaces via Claude CLI
|
|
266
|
+
*/
|
|
267
|
+
export async function marketplace_list_via_cli() {
|
|
268
|
+
const cli_available = await check_claude_cli();
|
|
269
|
+
if (!cli_available) {
|
|
270
|
+
return {
|
|
271
|
+
success: false,
|
|
272
|
+
error: 'Claude CLI not found. Please install Claude Code CLI.',
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
try {
|
|
276
|
+
const { stdout } = await execAsync('claude plugin marketplace list');
|
|
277
|
+
return { success: true, stdout: stdout.trim() };
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
281
|
+
return {
|
|
282
|
+
success: false,
|
|
283
|
+
error: `Failed to list marketplaces: ${message}`,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
}
|
|
192
287
|
/**
|
|
193
288
|
* Get the scope description for display
|
|
194
289
|
*/
|
package/dist/utils/paths.js
CHANGED
|
@@ -31,6 +31,9 @@ export function get_claude_settings_path() {
|
|
|
31
31
|
export function get_mcpick_dir() {
|
|
32
32
|
return join(get_base_dir().baseDir, 'mcpick');
|
|
33
33
|
}
|
|
34
|
+
export function get_dev_overrides_path() {
|
|
35
|
+
return join(get_mcpick_dir(), 'dev-overrides.json');
|
|
36
|
+
}
|
|
34
37
|
export function get_server_registry_path() {
|
|
35
38
|
return join(get_mcpick_dir(), 'servers.json');
|
|
36
39
|
}
|