mcpick 0.0.14 → 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 CHANGED
@@ -1,5 +1,13 @@
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
+
3
11
  ## 0.0.14
4
12
 
5
13
  ### Patch Changes
@@ -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 all_keys = Object.keys(installed.plugins);
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];
@@ -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 && result.errors.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,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
@@ -16,6 +16,8 @@ const main = defineCommand({
16
16
  plugins: () => import('./commands/plugins.js').then((m) => m.default),
17
17
  cache: () => import('./commands/cache.js').then((m) => m.default),
18
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),
19
21
  },
20
22
  });
21
23
  export const run = () => runMain(main);
@@ -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] = server;
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 (err) {
62
- const message = err instanceof Error ? err.message : 'Unknown error';
63
- return { success: false, error: `${name}: ${message}` };
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, { withFileTypes: true });
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
@@ -282,6 +282,8 @@ const SUBCOMMANDS = new Set([
282
282
  'plugins',
283
283
  'cache',
284
284
  'dev',
285
+ 'marketplace',
286
+ 'reload',
285
287
  ]);
286
288
  const arg = process.argv[2];
287
289
  if ((arg && SUBCOMMANDS.has(arg)) ||
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcpick",
3
- "version": "0.0.14",
3
+ "version": "0.0.15",
4
4
  "description": "Dynamic MCP server and plugin configuration manager for Claude Code",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",