mcpick 0.0.22 → 0.0.24

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +4 -0
  3. package/dist/add-Qzd8i-5k.js +184 -0
  4. package/dist/add-json-DGmsjB0O.js +115 -0
  5. package/dist/{backup-bdg6dvsb.js → backup-C7fvikFw.js} +5 -5
  6. package/dist/{cache-CSUcGdZP.js → cache-D3jjh5dD.js} +3 -3
  7. package/dist/{cli-avr5R1LO.js → cli-CZOlaqoZ.js} +22 -21
  8. package/dist/{clients-CSQgqHzb.js → clients-Bh93TGP4.js} +3 -3
  9. package/dist/{clone-CQ0skkT6.js → clone-MI8jJhTz.js} +6 -6
  10. package/dist/{config-BhX4eAgg.js → config-DE58Fik_.js} +4 -4
  11. package/dist/{dev-CTDg5g-c.js → dev-51esdZG9.js} +6 -6
  12. package/dist/disable-csYAn2Vk.js +106 -0
  13. package/dist/dry-run-XQ32fxPT.js +20 -0
  14. package/dist/enable-B5GbmhL-.js +107 -0
  15. package/dist/{get-l-eAJhBy.js → get-DacRZmwv.js} +3 -3
  16. package/dist/{hooks-BWZ_Kgx3.js → hooks-C_x49qap.js} +3 -4
  17. package/dist/index.js +717 -116
  18. package/dist/{list-By--kltj.js → list-BeBtsiae.js} +5 -5
  19. package/dist/{marketplace-DdiKDDKK.js → marketplace-BDC2YtvT.js} +4 -4
  20. package/dist/{output-BgN9Uuxf.js → output-HtT5HCof.js} +2 -2
  21. package/dist/{paths-6wrIM8yh.js → paths-BPISiJi4.js} +1 -1
  22. package/dist/{plugin-cache-DKcW8LGV.js → plugin-cache-DmLbh89d.js} +35 -16
  23. package/dist/{plugins-CsXE8AH4.js → plugins-Bkw-SKkZ.js} +4 -5
  24. package/dist/profile-DwJTVXiz.js +161 -0
  25. package/dist/{redact-Dltz2gde.js → redact-wBMtzbno.js} +1 -1
  26. package/dist/{reload-C29-vuvy.js → reload-Bl1mYK1I.js} +2 -2
  27. package/dist/remove-BSHgva79.js +107 -0
  28. package/dist/{reset-project-choices-Dhh4CxIC.js → reset-project-choices-BNLus9J9.js} +3 -3
  29. package/dist/{restore-BI8aiszM.js → restore-YisgARhc.js} +5 -6
  30. package/dist/rollback-GR1RkpXW.js +55 -0
  31. package/dist/{skills-DPBDmION.js → skills-rDTDqqZA.js} +3 -3
  32. package/dist/{validation-qWlF51fw.js → validation-xMlbgGCF.js} +1 -1
  33. package/package.json +4 -4
  34. package/.github/copilot-instructions.md +0 -50
  35. package/.github/workflows/ci.yml +0 -26
  36. package/.vscode/settings.json +0 -5
  37. package/CONTEXT.md +0 -49
  38. package/dist/add-Bok0qbXi.js +0 -112
  39. package/dist/add-json-C44vy2A_.js +0 -58
  40. package/dist/atomic-write-4lANmzsO.js +0 -26
  41. package/dist/disable-DLlOj7sc.js +0 -38
  42. package/dist/enable-CGFYYC2A.js +0 -39
  43. package/dist/profile-DzGPsdsl.js +0 -120
  44. package/dist/remove-B5q4rQRU.js +0 -30
  45. package/dist/settings-CZR8bVfh.js +0 -201
package/dist/index.js CHANGED
@@ -1,16 +1,311 @@
1
1
  #!/usr/bin/env node
2
- import { _ as get_profiles_dir, c as get_disabled_hooks_path, f as get_marketplaces_dir, g as get_profile_path, i as get_claude_config_path, m as get_plugin_backup_filename, n as get_backup_filename, p as get_mcpick_dir, r as get_backups_dir, t as ensure_directory_exists, y as get_server_registry_path } from "./paths-6wrIM8yh.js";
3
- import { r as validate_server_registry, t as validate_claude_config } from "./validation-qWlF51fw.js";
4
- import { a as get_enabled_servers, c as write_claude_config, n as create_config_from_servers, o as get_enabled_servers_for_scope, s as read_claude_config } from "./config-BhX4eAgg.js";
5
- import { a as read_claude_settings, i as get_all_plugins, n as build_enabled_plugins, r as get_all_hooks, s as write_claude_settings } from "./settings-CZR8bVfh.js";
6
- import { a as redact_url, i as redact_text } from "./redact-Dltz2gde.js";
7
- import { d as refresh_all_marketplaces, l as read_known_marketplaces, n as clear_plugin_caches, r as get_cached_plugins_info, t as clean_orphaned_versions, u as read_marketplace_manifest } from "./plugin-cache-DKcW8LGV.js";
2
+ import { _ as get_profiles_dir, a as get_claude_settings_path, c as get_disabled_hooks_path, f as get_marketplaces_dir, g as get_profile_path, i as get_claude_config_path, m as get_plugin_backup_filename, n as get_backup_filename, p as get_mcpick_dir, r as get_backups_dir, t as ensure_directory_exists, y as get_server_registry_path } from "./paths-BPISiJi4.js";
3
+ import { r as validate_server_registry, t as validate_claude_config } from "./validation-xMlbgGCF.js";
4
+ import { a as get_enabled_servers, c as write_claude_config, n as create_config_from_servers, o as get_enabled_servers_for_scope, s as read_claude_config } from "./config-DE58Fik_.js";
5
+ import { a as redact_url, i as redact_text, o as redact_value } from "./redact-wBMtzbno.js";
6
+ import { d as refresh_all_marketplaces, l as read_known_marketplaces, n as clear_plugin_caches, r as get_cached_plugins_info, t as clean_orphaned_versions, u as read_marketplace_manifest } from "./plugin-cache-DmLbh89d.js";
8
7
  import { cancel, confirm, intro, isCancel, log, multiselect, note, outro, select, text } from "@clack/prompts";
9
- import { access, mkdir, readFile, readdir, rename, unlink, writeFile } from "node:fs/promises";
10
- import { dirname, join } from "node:path";
8
+ import { access, mkdir, readFile, readdir, rename, rm, unlink, writeFile } from "node:fs/promises";
9
+ import { basename, dirname, join, resolve } from "node:path";
10
+ import { createHash, randomUUID } from "node:crypto";
11
11
  import { homedir } from "node:os";
12
12
  import { execFile } from "node:child_process";
13
13
  import { promisify } from "node:util";
14
+ //#region src/utils/safe-apply.ts
15
+ async function file_exists$1(path) {
16
+ try {
17
+ await access(path);
18
+ return true;
19
+ } catch {
20
+ return false;
21
+ }
22
+ }
23
+ function backup_name(path) {
24
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
25
+ const hash = createHash("sha256").update(path).digest("hex").slice(0, 10);
26
+ return `config-${basename(path).replace(/[^A-Za-z0-9._-]/g, "_")}-${stamp}-${hash}.json`;
27
+ }
28
+ async function create_backup(path, content) {
29
+ const backups_dir = get_backups_dir();
30
+ await ensure_directory_exists(backups_dir);
31
+ const backup_path = join(backups_dir, backup_name(path));
32
+ await writeFile(backup_path, content, "utf-8");
33
+ await writeFile(`${backup_path}.meta.json`, JSON.stringify({
34
+ original_path: path,
35
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
36
+ }, null, 2), "utf-8");
37
+ return backup_path;
38
+ }
39
+ /**
40
+ * Safely replace a JSON file: backup existing content, write via temp+rename,
41
+ * verify the result parses, and restore the original content on failure.
42
+ */
43
+ async function safe_json_write(path, data, indent = 2) {
44
+ await mkdir(dirname(path), { recursive: true });
45
+ const original_content = await file_exists$1(path) ? await readFile(path, "utf-8") : void 0;
46
+ const backup_path = original_content !== void 0 ? await create_backup(path, original_content) : void 0;
47
+ const tmp_path = join(dirname(path), `.${basename(path)}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`);
48
+ const next_content = JSON.stringify(data, null, indent);
49
+ try {
50
+ await writeFile(tmp_path, next_content, "utf-8");
51
+ await rename(tmp_path, path);
52
+ const written = await readFile(path, "utf-8");
53
+ JSON.parse(written);
54
+ return {
55
+ path,
56
+ ...backup_path ? { backup_path } : {}
57
+ };
58
+ } catch (error) {
59
+ await rm(tmp_path, { force: true }).catch(() => void 0);
60
+ if (original_content !== void 0) await writeFile(path, original_content, "utf-8");
61
+ else await rm(path, { force: true }).catch(() => void 0);
62
+ throw error;
63
+ }
64
+ }
65
+ async function list_config_backups() {
66
+ const backups_dir = get_backups_dir();
67
+ try {
68
+ const files = await readdir(backups_dir);
69
+ const backups = [];
70
+ for (const file of files) {
71
+ if (!file.startsWith("config-") || !file.endsWith(".json")) continue;
72
+ if (file.endsWith(".meta.json")) continue;
73
+ const backup_path = join(backups_dir, file);
74
+ try {
75
+ const meta = JSON.parse(await readFile(`${backup_path}.meta.json`, "utf-8"));
76
+ if (typeof meta.original_path !== "string" || typeof meta.created_at !== "string") continue;
77
+ backups.push({
78
+ path: backup_path,
79
+ original_path: meta.original_path,
80
+ created_at: meta.created_at
81
+ });
82
+ } catch {}
83
+ }
84
+ return backups.sort((a, b) => b.created_at.localeCompare(a.created_at));
85
+ } catch {
86
+ return [];
87
+ }
88
+ }
89
+ async function restore_config_backup(backup_path) {
90
+ const backup = (await list_config_backups()).find((candidate) => candidate.path === backup_path || basename(candidate.path) === backup_path);
91
+ if (!backup) throw new Error(`Config backup '${backup_path}' not found.`);
92
+ const content = await readFile(backup.path, "utf-8");
93
+ const parsed = JSON.parse(content);
94
+ await safe_json_write(backup.original_path, parsed);
95
+ return backup;
96
+ }
97
+ //#endregion
98
+ //#region src/utils/atomic-write.ts
99
+ /**
100
+ * Atomically write a JSON file with fresh-read merging.
101
+ *
102
+ * 1. Re-reads the file right before writing to pick up concurrent changes
103
+ * 2. Applies the merge function to the freshest data
104
+ * 3. Writes to a temp file, then renames (atomic on same filesystem)
105
+ */
106
+ async function atomic_json_write(file_path, merge) {
107
+ let existing = {};
108
+ try {
109
+ const content = await readFile(file_path, "utf-8");
110
+ existing = JSON.parse(content);
111
+ } catch {}
112
+ await safe_json_write(file_path, merge(existing), 2);
113
+ }
114
+ //#endregion
115
+ //#region src/core/settings.ts
116
+ async function read_claude_settings() {
117
+ const settings_path = get_claude_settings_path();
118
+ try {
119
+ await access(settings_path);
120
+ const content = await readFile(settings_path, "utf-8");
121
+ return JSON.parse(content);
122
+ } catch (error) {
123
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return {};
124
+ throw error;
125
+ }
126
+ }
127
+ async function write_claude_settings(updates) {
128
+ await atomic_json_write(get_claude_settings_path(), (existing) => {
129
+ for (const [key, value] of Object.entries(updates)) existing[key] = value;
130
+ return existing;
131
+ });
132
+ }
133
+ /**
134
+ * Parse enabledPlugins into structured list.
135
+ * Keys are in format "plugin-name@marketplace-name"
136
+ */
137
+ function get_all_plugins(settings) {
138
+ const enabled_plugins = settings.enabledPlugins || {};
139
+ return Object.entries(enabled_plugins).map(([key, enabled]) => {
140
+ const at_index = key.lastIndexOf("@");
141
+ return {
142
+ name: at_index > 0 ? key.substring(0, at_index) : key,
143
+ marketplace: at_index > 0 ? key.substring(at_index + 1) : "unknown",
144
+ enabled
145
+ };
146
+ });
147
+ }
148
+ /**
149
+ * Build the enabledPlugins record from a list of PluginInfo
150
+ */
151
+ function build_enabled_plugins(plugins) {
152
+ const result = {};
153
+ for (const plugin of plugins) {
154
+ const key = `${plugin.name}@${plugin.marketplace}`;
155
+ result[key] = plugin.enabled;
156
+ }
157
+ return result;
158
+ }
159
+ async function read_settings_file(path) {
160
+ try {
161
+ await access(path);
162
+ const content = await readFile(path, "utf-8");
163
+ return JSON.parse(content);
164
+ } catch {
165
+ return {};
166
+ }
167
+ }
168
+ function get_settings_paths() {
169
+ return [
170
+ {
171
+ scope: "user",
172
+ path: resolve(process.env.HOME || process.env.USERPROFILE || "", ".claude", "settings.json")
173
+ },
174
+ {
175
+ scope: "project",
176
+ path: resolve(process.cwd(), ".claude", "settings.json")
177
+ },
178
+ {
179
+ scope: "project-local",
180
+ path: resolve(process.cwd(), ".claude", "settings.local.json")
181
+ }
182
+ ];
183
+ }
184
+ /**
185
+ * Read all hooks across all scopes (settings + plugins), flattened for display.
186
+ */
187
+ async function get_all_hooks() {
188
+ const entries = [];
189
+ for (const { scope, path } of get_settings_paths()) {
190
+ const hooks = (await read_settings_file(path)).hooks;
191
+ if (!hooks) continue;
192
+ for (const [event, matchers] of Object.entries(hooks)) {
193
+ if (!Array.isArray(matchers)) continue;
194
+ for (let mi = 0; mi < matchers.length; mi++) {
195
+ const m = matchers[mi];
196
+ if (!m.hooks?.length) continue;
197
+ for (let hi = 0; hi < m.hooks.length; hi++) entries.push({
198
+ event,
199
+ matcher: m.matcher,
200
+ handler: m.hooks[hi],
201
+ scope,
202
+ source: scope,
203
+ matcher_index: mi,
204
+ hook_index: hi
205
+ });
206
+ }
207
+ }
208
+ }
209
+ const plugin_hooks = await get_all_plugin_hooks();
210
+ entries.push(...plugin_hooks);
211
+ return entries;
212
+ }
213
+ /**
214
+ * Scan all installed plugins for hooks.json and return flattened hook entries.
215
+ * Checks both cache and marketplace source paths since Claude Code reads from both.
216
+ */
217
+ async function get_all_plugin_hooks() {
218
+ const { read_installed_plugins } = await import("./plugin-cache-DmLbh89d.js").then((n) => n.s);
219
+ const { get_marketplaces_dir } = await import("./paths-BPISiJi4.js").then((n) => n.b);
220
+ const installed = await read_installed_plugins();
221
+ const entries = [];
222
+ const seen_hooks = /* @__PURE__ */ new Set();
223
+ for (const [plugin_key, installs] of Object.entries(installed.plugins)) {
224
+ if (!installs?.length) continue;
225
+ const install = installs[0];
226
+ const at_index = plugin_key.lastIndexOf("@");
227
+ const plugin_name = at_index > 0 ? plugin_key.substring(0, at_index) : plugin_key;
228
+ const marketplace_name = at_index > 0 ? plugin_key.substring(at_index + 1) : "";
229
+ const hooks_paths = [join(install.installPath, "hooks", "hooks.json")];
230
+ if (marketplace_name) hooks_paths.push(join(get_marketplaces_dir(), marketplace_name, "plugins", plugin_name, "hooks", "hooks.json"));
231
+ for (const hooks_path of hooks_paths) {
232
+ let hooks_data;
233
+ try {
234
+ const content = await readFile(hooks_path, "utf-8");
235
+ hooks_data = JSON.parse(content);
236
+ } catch {
237
+ continue;
238
+ }
239
+ const hooks = hooks_data.hooks || hooks_data;
240
+ for (const [event, matchers] of Object.entries(hooks)) {
241
+ if (!Array.isArray(matchers)) continue;
242
+ for (let mi = 0; mi < matchers.length; mi++) {
243
+ const m = matchers[mi];
244
+ if (!m.hooks?.length) continue;
245
+ for (let hi = 0; hi < m.hooks.length; hi++) {
246
+ const h = m.hooks[hi];
247
+ const dedup_key = `${plugin_key}:${event}:${h.type}:${h.command || h.url || h.prompt}`;
248
+ if (seen_hooks.has(dedup_key)) continue;
249
+ seen_hooks.add(dedup_key);
250
+ entries.push({
251
+ event,
252
+ matcher: m.matcher,
253
+ handler: h,
254
+ scope: "user",
255
+ source: "plugin",
256
+ matcher_index: mi,
257
+ hook_index: hi,
258
+ plugin_key,
259
+ hooks_json_path: hooks_path
260
+ });
261
+ }
262
+ }
263
+ }
264
+ }
265
+ }
266
+ return entries;
267
+ }
268
+ /**
269
+ * Remove a specific hook entry by scope/event/indices.
270
+ */
271
+ async function remove_hook(entry) {
272
+ const scope_path = get_settings_paths().find((s) => s.scope === entry.scope);
273
+ if (!scope_path) throw new Error(`Unknown scope: ${entry.scope}`);
274
+ await atomic_json_write(scope_path.path, (existing) => {
275
+ const hooks = existing.hooks;
276
+ if (!hooks) return existing;
277
+ const matchers = hooks[entry.event];
278
+ if (!matchers?.[entry.matcher_index]) return existing;
279
+ const matcher = matchers[entry.matcher_index];
280
+ matcher.hooks.splice(entry.hook_index, 1);
281
+ if (matcher.hooks.length === 0) matchers.splice(entry.matcher_index, 1);
282
+ if (matchers.length === 0) delete hooks[entry.event];
283
+ if (Object.keys(hooks).length === 0) delete existing.hooks;
284
+ return existing;
285
+ });
286
+ }
287
+ /**
288
+ * Add a hook to a specific scope.
289
+ */
290
+ async function add_hook(scope, event, matcher, handler) {
291
+ const scope_path = get_settings_paths().find((s) => s.scope === scope);
292
+ if (!scope_path) throw new Error(`Unknown scope: ${scope}`);
293
+ await atomic_json_write(scope_path.path, (existing) => {
294
+ if (!existing.hooks) existing.hooks = {};
295
+ const hooks = existing.hooks;
296
+ if (!hooks[event]) hooks[event] = [];
297
+ const matchers = hooks[event];
298
+ const existing_matcher = matchers.find((m) => (m.matcher || void 0) === matcher);
299
+ if (existing_matcher) existing_matcher.hooks.push(handler);
300
+ else {
301
+ const new_matcher = { hooks: [handler] };
302
+ if (matcher) new_matcher.matcher = matcher;
303
+ matchers.push(new_matcher);
304
+ }
305
+ return existing;
306
+ });
307
+ }
308
+ //#endregion
14
309
  //#region src/commands/backup.ts
15
310
  const MAX_BACKUPS = 10;
16
311
  async function backup_config() {
@@ -49,6 +344,43 @@ async function cleanup_old_backups(prefix) {
49
344
  } catch {}
50
345
  }
51
346
  //#endregion
347
+ //#region src/utils/config-preview.ts
348
+ function build_json_change_preview(input) {
349
+ const before = redact_value(input.before);
350
+ const after = redact_value(input.after);
351
+ return {
352
+ dryRun: true,
353
+ operation: input.operation,
354
+ client: input.client,
355
+ scope: input.scope,
356
+ location: input.location,
357
+ before,
358
+ after,
359
+ diff: create_json_diff(before, after)
360
+ };
361
+ }
362
+ function build_command_preview(input) {
363
+ return {
364
+ dryRun: true,
365
+ operation: input.operation,
366
+ client: input.client,
367
+ scope: input.scope,
368
+ location: input.location,
369
+ command: input.command.map((part) => redact_text(part))
370
+ };
371
+ }
372
+ function create_json_diff(before, after) {
373
+ const before_lines = JSON.stringify(before, null, 2).split("\n");
374
+ const after_lines = JSON.stringify(after, null, 2).split("\n");
375
+ if (before_lines.join("\n") === after_lines.join("\n")) return "";
376
+ return [
377
+ "--- before",
378
+ "+++ after",
379
+ ...before_lines.map((line) => `- ${line}`),
380
+ ...after_lines.map((line) => `+ ${line}`)
381
+ ].join("\n");
382
+ }
383
+ //#endregion
52
384
  //#region src/core/client-config.ts
53
385
  const client_options_to_skip = new Set([
54
386
  "name",
@@ -82,10 +414,7 @@ async function read_json_file(path) {
82
414
  }
83
415
  }
84
416
  async function write_json_file(path, data) {
85
- await mkdir(dirname(path), { recursive: true });
86
- const tmp_path = join(dirname(path), `.${Date.now()}.tmp`);
87
- await writeFile(tmp_path, JSON.stringify(data, null, 2), "utf-8");
88
- await rename(tmp_path, path);
417
+ await safe_json_write(path, data, 2);
89
418
  }
90
419
  function parse_json_or_jsonc(content) {
91
420
  try {
@@ -194,8 +523,8 @@ function infer_transport(config) {
194
523
  function normalize_mcp_server(name, config) {
195
524
  const transport = infer_transport(config);
196
525
  const url = typeof config.httpUrl === "string" ? config.httpUrl : typeof config.serverUrl === "string" ? config.serverUrl : typeof config.url === "string" ? config.url : void 0;
197
- const clientOptions = {};
198
- for (const [key, value] of Object.entries(config)) if (!client_options_to_skip.has(key)) clientOptions[key] = value;
526
+ const client_options = {};
527
+ for (const [key, value] of Object.entries(config)) if (!client_options_to_skip.has(key)) client_options[key] = value;
199
528
  const command_array = string_array(config.command);
200
529
  const command = typeof config.command === "string" ? config.command : command_array?.[0];
201
530
  const args = string_array(config.args) ?? command_array?.slice(1);
@@ -211,7 +540,7 @@ function normalize_mcp_server(name, config) {
211
540
  ...string_record(config.headers) ? { headers: string_record(config.headers) } : {},
212
541
  ...typeof config.description === "string" ? { description: config.description } : {},
213
542
  ...typeof disabled === "boolean" ? { disabled } : {},
214
- ...Object.keys(clientOptions).length > 0 ? { clientOptions } : {}
543
+ ...Object.keys(client_options).length > 0 ? { client_options } : {}
215
544
  };
216
545
  }
217
546
  function get_server_record(data, key) {
@@ -233,7 +562,32 @@ function set_server_enabled(config, enabled, mode) {
233
562
  }
234
563
  config.disabled = !enabled;
235
564
  }
565
+ function portable_to_json(server, mode) {
566
+ const result = { ...server.client_options };
567
+ if (server.transport !== "stdio") result.type = server.transport;
568
+ if (server.command) result.command = server.command;
569
+ if (server.args && server.args.length > 0) result.args = server.args;
570
+ if (server.url) result.url = server.url;
571
+ if (server.env) result.env = server.env;
572
+ if (server.headers) result.headers = server.headers;
573
+ if (server.description) result.description = server.description;
574
+ if (typeof server.disabled === "boolean") set_server_enabled(result, !server.disabled, mode);
575
+ return result;
576
+ }
236
577
  function create_json_adapter(options) {
578
+ async function read_data(location) {
579
+ return await read_json_file(location.path) ?? {};
580
+ }
581
+ function preview(location, operation, before, after) {
582
+ return build_json_change_preview({
583
+ operation,
584
+ client: options.id,
585
+ scope: location.scope,
586
+ location: location.path,
587
+ before,
588
+ after
589
+ });
590
+ }
237
591
  return {
238
592
  id: options.id,
239
593
  label: options.label,
@@ -247,13 +601,67 @@ function create_json_adapter(options) {
247
601
  async readLocation(location) {
248
602
  return read_server_map(await read_json_file(location.path), options.serverKey);
249
603
  },
250
- async writeEnabled(location, enabledNames) {
251
- const data = await read_json_file(location.path) ?? {};
604
+ async writeEnabled(location, enabled_names) {
605
+ const data = await read_data(location);
252
606
  const servers = get_server_record(data, options.serverKey);
253
- const enabled = new Set(enabledNames);
607
+ const enabled = new Set(enabled_names);
254
608
  for (const [name, config] of Object.entries(servers)) set_server_enabled(config, enabled.has(name), options.disabledMode ?? "disabled");
255
609
  data[options.serverKey] = servers;
256
610
  await write_json_file(location.path, data);
611
+ },
612
+ async previewEnabled(location, enabled_names) {
613
+ const before = await read_data(location);
614
+ const after = structuredClone(before);
615
+ const servers = get_server_record(after, options.serverKey);
616
+ const enabled = new Set(enabled_names);
617
+ for (const [name, config] of Object.entries(servers)) set_server_enabled(config, enabled.has(name), options.disabledMode ?? "disabled");
618
+ after[options.serverKey] = servers;
619
+ return preview(location, "set-enabled", before, after);
620
+ },
621
+ async write_server(location, server) {
622
+ const data = await read_data(location);
623
+ const servers = get_server_record(data, options.serverKey);
624
+ servers[server.name] = portable_to_json(server, options.disabledMode ?? "disabled");
625
+ data[options.serverKey] = servers;
626
+ await write_json_file(location.path, data);
627
+ },
628
+ async preview_write_server(location, server) {
629
+ const before = await read_data(location);
630
+ const after = structuredClone(before);
631
+ const servers = get_server_record(after, options.serverKey);
632
+ servers[server.name] = portable_to_json(server, options.disabledMode ?? "disabled");
633
+ after[options.serverKey] = servers;
634
+ return preview(location, "add-server", before, after);
635
+ },
636
+ async write_server_config(location, name, config) {
637
+ const data = await read_data(location);
638
+ const servers = get_server_record(data, options.serverKey);
639
+ servers[name] = config;
640
+ data[options.serverKey] = servers;
641
+ await write_json_file(location.path, data);
642
+ },
643
+ async preview_write_server_config(location, name, config) {
644
+ const before = await read_data(location);
645
+ const after = structuredClone(before);
646
+ const servers = get_server_record(after, options.serverKey);
647
+ servers[name] = config;
648
+ after[options.serverKey] = servers;
649
+ return preview(location, "add-json", before, after);
650
+ },
651
+ async remove_server(location, name) {
652
+ const data = await read_data(location);
653
+ const servers = get_server_record(data, options.serverKey);
654
+ delete servers[name];
655
+ data[options.serverKey] = servers;
656
+ await write_json_file(location.path, data);
657
+ },
658
+ async preview_remove_server(location, name) {
659
+ const before = await read_data(location);
660
+ const after = structuredClone(before);
661
+ const servers = get_server_record(after, options.serverKey);
662
+ delete servers[name];
663
+ after[options.serverKey] = servers;
664
+ return preview(location, "remove-server", before, after);
257
665
  }
258
666
  };
259
667
  }
@@ -392,6 +800,63 @@ const client_adapters = [
392
800
  function get_client_adapter(id) {
393
801
  return client_adapters.find((adapter) => adapter.id === id) ?? null;
394
802
  }
803
+ function resolve_client_location(adapter, scope, path) {
804
+ let locations = adapter.locations();
805
+ if (path) locations = locations.filter((location) => location.path === path);
806
+ else if (scope) locations = locations.filter((location) => location.scope === scope);
807
+ if (locations.length === 1) return locations[0];
808
+ if (locations.length === 0) throw new Error(`No ${adapter.label} config location matches${scope ? ` scope '${scope}'` : ""}${path ? ` path '${path}'` : ""}.`);
809
+ throw new Error(`${adapter.label} has multiple matching config locations. Pass --location with one of: ${locations.map((location) => location.path).join(", ")}`);
810
+ }
811
+ async function add_client_server(adapter, location, server) {
812
+ if (!adapter.write_server) throw new Error(`${adapter.label} support cannot add servers yet.`);
813
+ await adapter.write_server(location, server);
814
+ }
815
+ async function preview_add_client_server(adapter, location, server) {
816
+ if (!adapter.preview_write_server) throw new Error(`${adapter.label} support cannot preview server additions yet.`);
817
+ return adapter.preview_write_server(location, server);
818
+ }
819
+ async function add_client_server_config(adapter, location, name, config) {
820
+ if (!adapter.write_server_config) throw new Error(`${adapter.label} support cannot add servers yet.`);
821
+ await adapter.write_server_config(location, name, config);
822
+ }
823
+ async function preview_add_client_server_config(adapter, location, name, config) {
824
+ if (!adapter.preview_write_server_config) throw new Error(`${adapter.label} support cannot preview server additions yet.`);
825
+ return adapter.preview_write_server_config(location, name, config);
826
+ }
827
+ async function remove_client_server(adapter, location, server_name) {
828
+ if (!adapter.remove_server) throw new Error(`${adapter.label} support cannot remove servers yet.`);
829
+ await adapter.remove_server(location, server_name);
830
+ }
831
+ async function preview_remove_client_server(adapter, location, server_name) {
832
+ if (!adapter.preview_remove_server) throw new Error(`${adapter.label} support cannot preview server removals yet.`);
833
+ return adapter.preview_remove_server(location, server_name);
834
+ }
835
+ function assert_enabled_server_names(location, servers, enabled_names) {
836
+ const known_names = new Set(servers.map((server) => server.name));
837
+ const unknown_names = enabled_names.filter((name) => !known_names.has(name));
838
+ if (unknown_names.length > 0) throw new Error(`Server '${unknown_names[0]}' not found at ${location.path}.`);
839
+ }
840
+ async function set_client_enabled_servers(adapter, location, enabled_names) {
841
+ if (!adapter.writeEnabled) throw new Error(`${adapter.label} support is read-only.`);
842
+ assert_enabled_server_names(location, await adapter.readLocation(location), enabled_names);
843
+ await adapter.writeEnabled(location, enabled_names);
844
+ return enabled_names.length;
845
+ }
846
+ async function preview_set_client_enabled_servers(adapter, location, enabled_names) {
847
+ if (!adapter.previewEnabled) throw new Error(`${adapter.label} support cannot preview toggles yet.`);
848
+ assert_enabled_server_names(location, await adapter.readLocation(location), enabled_names);
849
+ return adapter.previewEnabled(location, enabled_names);
850
+ }
851
+ async function set_client_server_enabled(adapter, location, server_name, enabled) {
852
+ const servers = await adapter.readLocation(location);
853
+ const server = servers.find((candidate) => candidate.name === server_name);
854
+ if (!server) throw new Error(`Server '${server_name}' not found at ${location.path}.`);
855
+ const enabled_names = new Set(servers.filter((candidate) => candidate.disabled !== true).map((candidate) => candidate.name));
856
+ if (enabled) enabled_names.add(server.name);
857
+ else enabled_names.delete(server.name);
858
+ return set_client_enabled_servers(adapter, location, [...enabled_names]);
859
+ }
395
860
  async function list_client_locations() {
396
861
  return await Promise.all(client_adapters.flatMap((adapter) => adapter.locations().map(async (location) => ({
397
862
  client: adapter.id,
@@ -421,7 +886,7 @@ async function read_server_registry() {
421
886
  async function write_server_registry(registry) {
422
887
  const registry_path = get_server_registry_path();
423
888
  await ensure_directory_exists(get_mcpick_dir());
424
- await writeFile(registry_path, JSON.stringify(registry, null, 2), "utf-8");
889
+ await atomic_json_write(registry_path, () => registry);
425
890
  }
426
891
  async function add_server_to_registry(server) {
427
892
  const registry = await read_server_registry();
@@ -431,7 +896,7 @@ async function add_server_to_registry(server) {
431
896
  await write_server_registry(registry);
432
897
  }
433
898
  async function get_all_available_servers() {
434
- const { get_enabled_servers, read_claude_config } = await import("./config-BhX4eAgg.js").then((n) => n.t);
899
+ const { get_enabled_servers, read_claude_config } = await import("./config-DE58Fik_.js").then((n) => n.t);
435
900
  const registry = await read_server_registry();
436
901
  const config_servers = get_enabled_servers(await read_claude_config());
437
902
  const config_by_name = new Map(config_servers.map((s) => [s.name, s]));
@@ -566,17 +1031,26 @@ async function add_mcp_via_cli(server, scope) {
566
1031
  /**
567
1032
  * Remove an MCP server using Claude CLI
568
1033
  */
569
- async function remove_mcp_via_cli(name) {
1034
+ function build_remove_args(name, scope) {
1035
+ return scope ? [
1036
+ "mcp",
1037
+ "remove",
1038
+ name,
1039
+ "--scope",
1040
+ scope
1041
+ ] : [
1042
+ "mcp",
1043
+ "remove",
1044
+ name
1045
+ ];
1046
+ }
1047
+ async function remove_mcp_via_cli(name, scope) {
570
1048
  if (!await check_claude_cli()) return {
571
1049
  success: false,
572
1050
  error: "Claude CLI not found. Please install Claude Code CLI."
573
1051
  };
574
1052
  try {
575
- await run_claude([
576
- "mcp",
577
- "remove",
578
- name
579
- ]);
1053
+ await run_claude(build_remove_args(name, scope));
580
1054
  return { success: true };
581
1055
  } catch (error) {
582
1056
  return {
@@ -657,77 +1131,176 @@ async function update_plugin_via_cli(key, scope = "user") {
657
1131
  };
658
1132
  }
659
1133
  }
1134
+ const GITHUB_OWNER_PATTERN = "[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?";
1135
+ const GITHUB_REPO_PATTERN = "[A-Za-z0-9._-]+";
660
1136
  /**
661
1137
  * Extract GitHub owner/repo from various source formats.
662
1138
  * Returns null if not a recognizable GitHub reference.
663
1139
  */
664
1140
  function parse_github_repo(source) {
665
- const https_match = source.match(/^https?:\/\/github\.com\/([^/]+)\/([^/.]+)(?:\.git)?$/);
666
- if (https_match) return {
667
- owner: https_match[1],
668
- repo: https_match[2]
669
- };
670
- const ssh_match = source.match(/^git@github\.com:([^/]+)\/([^/.]+)(?:\.git)?$/);
1141
+ const trimmed = source.trim();
1142
+ try {
1143
+ const url = new URL(trimmed);
1144
+ if (url.hostname !== "github.com") return null;
1145
+ const [owner, raw_repo, ...rest] = url.pathname.split("/").filter(Boolean);
1146
+ if (!owner || !raw_repo || rest.length > 0) return null;
1147
+ const repo = raw_repo.replace(/\.git$/, "");
1148
+ if (!is_valid_github_repo(owner, repo)) return null;
1149
+ return {
1150
+ owner,
1151
+ repo,
1152
+ kind: "https"
1153
+ };
1154
+ } catch {}
1155
+ const ssh_match = trimmed.match(new RegExp(`^git@github\\.com:(${GITHUB_OWNER_PATTERN})/(${GITHUB_REPO_PATTERN})(?:\\.git)?$`));
671
1156
  if (ssh_match) return {
672
1157
  owner: ssh_match[1],
673
- repo: ssh_match[2]
1158
+ repo: ssh_match[2].replace(/\.git$/, ""),
1159
+ kind: "ssh"
674
1160
  };
675
- const shorthand_match = source.match(/^([^/\s]+)\/([^/\s]+)$/);
1161
+ const shorthand_match = trimmed.match(new RegExp(`^(${GITHUB_OWNER_PATTERN})/(${GITHUB_REPO_PATTERN})$`));
676
1162
  if (shorthand_match) return {
677
1163
  owner: shorthand_match[1],
678
- repo: shorthand_match[2]
1164
+ repo: shorthand_match[2],
1165
+ kind: "shorthand"
679
1166
  };
680
1167
  return null;
681
1168
  }
682
- /**
683
- * Validate that a GitHub repository exists and is accessible.
684
- * Returns an error message if validation fails, null if OK.
685
- */
686
- async function validate_github_repo(owner, repo) {
1169
+ function is_valid_github_repo(owner, repo) {
1170
+ return new RegExp(`^${GITHUB_OWNER_PATTERN}$`).test(owner) && new RegExp(`^${GITHUB_REPO_PATTERN}$`).test(repo);
1171
+ }
1172
+ function build_marketplace_add_args(source) {
1173
+ return [
1174
+ "plugin",
1175
+ "marketplace",
1176
+ "add",
1177
+ source
1178
+ ];
1179
+ }
1180
+ function get_github_repo_id(ref) {
1181
+ return `${ref.owner}/${ref.repo}`;
1182
+ }
1183
+ function get_github_clone_urls(ref) {
1184
+ const repo_id = get_github_repo_id(ref);
1185
+ const https_url = `https://github.com/${repo_id}.git`;
1186
+ const ssh_url = `git@github.com:${repo_id}.git`;
1187
+ if (ref.kind === "https") return [https_url];
1188
+ if (ref.kind === "ssh") return [ssh_url];
1189
+ return [https_url, ssh_url];
1190
+ }
1191
+ function get_process_error_output(error) {
1192
+ const process_error = error;
1193
+ return redact_text([
1194
+ process_error.message,
1195
+ process_error.stderr?.toString(),
1196
+ process_error.stdout?.toString()
1197
+ ].filter(Boolean).join("\n"));
1198
+ }
1199
+ async function can_access_with_git(url) {
687
1200
  try {
688
- const response = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
1201
+ await exec_file_async$1("git", [
1202
+ "ls-remote",
1203
+ "--exit-code",
1204
+ url,
1205
+ "HEAD"
1206
+ ], {
1207
+ env: {
1208
+ ...process.env,
1209
+ GIT_TERMINAL_PROMPT: "0",
1210
+ GIT_SSH_COMMAND: "ssh -o BatchMode=yes"
1211
+ },
1212
+ timeout: 15e3
1213
+ });
1214
+ return null;
1215
+ } catch (error) {
1216
+ return get_process_error_output(error);
1217
+ }
1218
+ }
1219
+ async function can_view_with_gh(repo_id) {
1220
+ try {
1221
+ await exec_file_async$1("gh", [
1222
+ "repo",
1223
+ "view",
1224
+ repo_id,
1225
+ "--json",
1226
+ "nameWithOwner"
1227
+ ], { timeout: 15e3 });
1228
+ return true;
1229
+ } catch (error) {
1230
+ const message = get_process_error_output(error).toLowerCase();
1231
+ if (message.includes("could not resolve") || message.includes("not found") || message.includes("repository not found")) return false;
1232
+ return null;
1233
+ }
1234
+ }
1235
+ async function get_github_api_status(repo_id) {
1236
+ try {
1237
+ return (await fetch(`https://api.github.com/repos/${repo_id}`, {
689
1238
  method: "GET",
690
1239
  headers: { Accept: "application/vnd.github.v3+json" }
691
- });
692
- if (response.status === 200) return null;
693
- if (response.status === 404) return `Repository '${owner}/${repo}' not found on GitHub. Check the name or ensure it's not private.`;
694
- if (response.status === 403) return `Access denied for '${owner}/${repo}'. The repository may be private — configure a GitHub token or use SSH.`;
695
- return `GitHub API returned status ${response.status} for '${owner}/${repo}'.`;
1240
+ })).status;
696
1241
  } catch {
697
1242
  return null;
698
1243
  }
699
1244
  }
1245
+ /**
1246
+ * Validate that a GitHub repository can be accessed by git without cloning it.
1247
+ * Returns an error message if validation fails, null if OK.
1248
+ */
1249
+ async function validate_github_repo(ref) {
1250
+ const repo_id = get_github_repo_id(ref);
1251
+ const git_errors = [];
1252
+ for (const url of get_github_clone_urls(ref)) {
1253
+ const git_error = await can_access_with_git(url);
1254
+ if (!git_error) return null;
1255
+ git_errors.push(git_error);
1256
+ }
1257
+ const gh_visible = await can_view_with_gh(repo_id);
1258
+ if (gh_visible === false) return `Repository '${repo_id}' not found or not accessible with your GitHub account.`;
1259
+ const api_status = await get_github_api_status(repo_id);
1260
+ if (api_status === 404 && gh_visible !== true) return `Repository '${repo_id}' not found or private/inaccessible. Check the name, sign in with 'gh auth login', or use an SSH URL with access.`;
1261
+ if (api_status === 403 && gh_visible !== true) return `Unable to validate '${repo_id}' because GitHub API access was denied or rate-limited. Git access also failed; check your credentials.`;
1262
+ return format_git_access_error(ref, git_errors.join("\n"));
1263
+ }
1264
+ function format_git_access_error(ref, message) {
1265
+ const repo_id = get_github_repo_id(ref);
1266
+ const lower = message.toLowerCase();
1267
+ if (ref.kind === "shorthand" && (lower.includes("could not read username") || lower.includes("authentication failed")) && (lower.includes("permission denied (publickey)") || lower.includes("ssh authentication"))) return `Repository '${repo_id}' exists, but Git cannot access it over HTTPS or SSH. Run 'gh auth login' and 'gh auth setup-git', or configure an SSH key with GitHub.`;
1268
+ if (ref.kind === "https" || lower.includes("https authentication failed") || lower.includes("could not read username") || lower.includes("authentication failed")) return `Repository '${repo_id}' exists, but HTTPS Git authentication failed. Run 'gh auth login' and 'gh auth setup-git', configure a Git credential helper, or use git@github.com:${repo_id}.git.`;
1269
+ if (ref.kind === "ssh" || lower.includes("permission denied (publickey)") || lower.includes("ssh authentication")) return `SSH authentication failed for '${repo_id}'. Configure an SSH key with GitHub or use https://github.com/${repo_id}.git with a configured Git credential helper.`;
1270
+ return `Unable to access GitHub repository '${repo_id}' with git. Check that the repository exists and that your HTTPS or SSH credentials can clone it.`;
1271
+ }
700
1272
  async function marketplace_add_via_cli(source) {
701
1273
  if (!await check_claude_cli()) return {
702
1274
  success: false,
703
1275
  error: "Claude CLI not found. Please install Claude Code CLI."
704
1276
  };
705
1277
  const gh = parse_github_repo(source);
706
- const is_shorthand = gh && !source.startsWith("http") && !source.startsWith("git@");
707
- if (gh && is_shorthand) {
708
- const validation_error = await validate_github_repo(gh.owner, gh.repo);
1278
+ if (gh) {
1279
+ const validation_error = await validate_github_repo(gh);
709
1280
  if (validation_error) return {
710
1281
  success: false,
711
1282
  error: validation_error
712
1283
  };
713
1284
  }
714
1285
  try {
715
- await run_claude([
716
- "plugin",
717
- "marketplace",
718
- "add",
719
- source
720
- ]);
1286
+ await run_claude(build_marketplace_add_args(source));
721
1287
  return { success: true };
722
1288
  } catch (error) {
723
1289
  const message = get_redacted_error_message(error);
724
- if (message.includes("SSH") || message.includes("Permission denied (publickey)")) return {
725
- success: false,
726
- 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}`
727
- };
728
- if (message.includes("not found") || message.includes("does not exist")) return {
1290
+ const lower_message = message.toLowerCase();
1291
+ if (gh) {
1292
+ if (lower_message.includes("https authentication failed") || lower_message.includes("could not read username") || lower_message.includes("authentication failed")) return {
1293
+ success: false,
1294
+ error: format_git_access_error(gh, message)
1295
+ };
1296
+ if (message.includes("SSH") || message.includes("Permission denied (publickey)")) return {
1297
+ success: false,
1298
+ error: format_git_access_error(gh, message)
1299
+ };
1300
+ }
1301
+ if (lower_message.includes("not found") || lower_message.includes("does not exist")) return {
729
1302
  success: false,
730
- error: `Repository '${source}' not found. Check the name and your access permissions.`
1303
+ error: gh ? `Repository '${get_github_repo_id(gh)}' not found or not accessible with your GitHub account.` : `Repository '${source}' not found. Check the name and your access permissions.`
731
1304
  };
732
1305
  return {
733
1306
  success: false,
@@ -870,20 +1443,23 @@ async function mcp_get_via_cli(name) {
870
1443
  /**
871
1444
  * Add an MCP server from raw JSON via Claude CLI
872
1445
  */
1446
+ function build_add_json_args(name, json, scope = "local") {
1447
+ return [
1448
+ "mcp",
1449
+ "add-json",
1450
+ name,
1451
+ json,
1452
+ "--scope",
1453
+ scope
1454
+ ];
1455
+ }
873
1456
  async function mcp_add_json_via_cli(name, json, scope = "local") {
874
1457
  if (!await check_claude_cli()) return {
875
1458
  success: false,
876
1459
  error: "Claude CLI not found. Please install Claude Code CLI."
877
1460
  };
878
1461
  try {
879
- await run_claude([
880
- "mcp",
881
- "add-json",
882
- name,
883
- json,
884
- "--scope",
885
- scope
886
- ]);
1462
+ await run_claude(build_add_json_args(name, json, scope));
887
1463
  return { success: true };
888
1464
  } catch (error) {
889
1465
  return {
@@ -979,7 +1555,16 @@ async function edit_client_config(adapter) {
979
1555
  required: false
980
1556
  });
981
1557
  if (typeof selected_names === "symbol") return;
982
- await adapter.writeEnabled(location, selected_names);
1558
+ const preview = await preview_set_client_enabled_servers(adapter, location, selected_names);
1559
+ if (preview.diff) {
1560
+ note(preview.diff, "Preview");
1561
+ const should_apply = await confirm({
1562
+ message: "Apply these changes?",
1563
+ initialValue: true
1564
+ });
1565
+ if (typeof should_apply === "symbol" || !should_apply) return;
1566
+ }
1567
+ await set_client_enabled_servers(adapter, location, selected_names);
983
1568
  note(`Configuration updated!\nClient: ${adapter.label}\nConfig: ${location.path}\nEnabled servers: ${selected_names.length}`);
984
1569
  }
985
1570
  async function select_config_location(adapter) {
@@ -1053,7 +1638,7 @@ async function edit_claude_config() {
1053
1638
  }
1054
1639
  }
1055
1640
  for (const name of servers_to_remove) {
1056
- const result = await remove_mcp_via_cli(name);
1641
+ const result = await remove_mcp_via_cli(name, scope);
1057
1642
  if (!result.success) {
1058
1643
  error_count++;
1059
1644
  log.warn(`Failed to remove ${name}: ${result.error}`);
@@ -1358,7 +1943,7 @@ async function read_disabled_hooks() {
1358
1943
  }
1359
1944
  async function write_disabled_hooks(entries) {
1360
1945
  await ensure_directory_exists(get_mcpick_dir());
1361
- await writeFile(get_disabled_hooks_path(), JSON.stringify(entries, null, " "), "utf-8");
1946
+ await safe_json_write(get_disabled_hooks_path(), entries, " ");
1362
1947
  }
1363
1948
  /**
1364
1949
  * Remove a specific hook handler from a hooks.json file by matching the handler.
@@ -1387,7 +1972,7 @@ async function remove_hook_from_file(hooks_path, event, handler) {
1387
1972
  }
1388
1973
  if (!removed) return false;
1389
1974
  if (matchers.length === 0) delete hooks_obj[event];
1390
- await writeFile(hooks_path, JSON.stringify(hooks_data, null, " "), "utf-8");
1975
+ await safe_json_write(hooks_path, hooks_data, " ");
1391
1976
  return true;
1392
1977
  }
1393
1978
  /**
@@ -1446,7 +2031,7 @@ async function add_hook_to_file(hooks_path, event, matcher_pattern, handler) {
1446
2031
  }
1447
2032
  if (matcher.hooks.some((h) => h.type === handler.type && h.command === handler.command && h.url === handler.url && h.prompt === handler.prompt)) return;
1448
2033
  matcher.hooks.push(handler);
1449
- await writeFile(hooks_path, JSON.stringify(hooks_data, null, " "), "utf-8");
2034
+ await safe_json_write(hooks_path, hooks_data, " ");
1450
2035
  }
1451
2036
  /**
1452
2037
  * Re-enable a previously disabled plugin hook.
@@ -2072,6 +2657,21 @@ async function load_profile(name) {
2072
2657
  throw error;
2073
2658
  }
2074
2659
  }
2660
+ async function apply_profile_to_claude(name) {
2661
+ const profile = await load_profile(name);
2662
+ await write_claude_config(profile.config);
2663
+ const serverCount = Object.keys(profile.config.mcpServers || {}).length;
2664
+ let pluginCount = 0;
2665
+ if (profile.enabledPlugins) {
2666
+ await write_claude_settings({ enabledPlugins: profile.enabledPlugins });
2667
+ pluginCount = Object.keys(profile.enabledPlugins).length;
2668
+ }
2669
+ return {
2670
+ profile: name,
2671
+ serverCount,
2672
+ pluginCount
2673
+ };
2674
+ }
2075
2675
  async function list_profiles() {
2076
2676
  const profiles_dir = get_profiles_dir();
2077
2677
  try {
@@ -2107,12 +2707,20 @@ async function save_profile(name) {
2107
2707
  await ensure_directory_exists(get_profiles_dir());
2108
2708
  const profile_data = { mcpServers: servers };
2109
2709
  if (plugin_count > 0) profile_data.enabledPlugins = plugins;
2110
- await writeFile(get_profile_path(name), JSON.stringify(profile_data, null, 2), "utf-8");
2710
+ await safe_json_write(get_profile_path(name), profile_data, 2);
2111
2711
  return {
2112
2712
  serverCount: server_count,
2113
2713
  pluginCount: plugin_count
2114
2714
  };
2115
2715
  }
2716
+ async function save_current_claude_profile(name) {
2717
+ const counts = await save_profile(name);
2718
+ return {
2719
+ profile: name,
2720
+ serverCount: counts.serverCount,
2721
+ pluginCount: counts.pluginCount
2722
+ };
2723
+ }
2116
2724
  //#endregion
2117
2725
  //#region src/index.ts
2118
2726
  function parse_args() {
@@ -2127,17 +2735,13 @@ function parse_args() {
2127
2735
  } else if (args[i] === "--list-profiles" || args[i] === "-l") result.listProfiles = true;
2128
2736
  return result;
2129
2737
  }
2130
- async function apply_profile(name) {
2131
- intro(`MCPick - Loading profile: ${name}`);
2738
+ async function apply_claude_profile(name) {
2739
+ intro(`MCPick - Loading Claude Code profile: ${name}`);
2132
2740
  try {
2133
- const profile = await load_profile(name);
2134
- await write_claude_config(profile.config);
2135
- const parts = [`${Object.keys(profile.config.mcpServers || {}).length} servers`];
2136
- if (profile.enabledPlugins) {
2137
- await write_claude_settings({ enabledPlugins: profile.enabledPlugins });
2138
- parts.push(`${Object.keys(profile.enabledPlugins).length} plugins`);
2139
- }
2140
- log.success(`Profile '${name}' applied (${parts.join(", ")})`);
2741
+ const result = await apply_profile_to_claude(name);
2742
+ const parts = [`${result.serverCount} servers`];
2743
+ if (result.pluginCount > 0) parts.push(`${result.pluginCount} plugins`);
2744
+ log.success(`Profile '${result.profile}' applied (${parts.join(", ")})`);
2141
2745
  outro("Done!");
2142
2746
  } catch (error) {
2143
2747
  if (error instanceof Error) cancel(error.message);
@@ -2154,13 +2758,13 @@ async function show_profiles() {
2154
2758
  } else for (const p of profiles) log.info(`${p.name} (${p.serverCount} servers)`);
2155
2759
  outro("");
2156
2760
  }
2157
- async function create_profile(name) {
2158
- intro(`MCPick - Saving profile: ${name}`);
2761
+ async function create_claude_profile(name) {
2762
+ intro(`MCPick - Saving Claude Code profile: ${name}`);
2159
2763
  try {
2160
- const counts = await save_profile(name);
2161
- const parts = [`${counts.serverCount} servers`];
2162
- if (counts.pluginCount > 0) parts.push(`${counts.pluginCount} plugins`);
2163
- log.success(`Profile '${name}' saved (${parts.join(", ")})`);
2764
+ const result = await save_current_claude_profile(name);
2765
+ const parts = [`${result.serverCount} servers`];
2766
+ if (result.pluginCount > 0) parts.push(`${result.pluginCount} plugins`);
2767
+ log.success(`Profile '${result.profile}' saved (${parts.join(", ")})`);
2164
2768
  outro("Done!");
2165
2769
  } catch (error) {
2166
2770
  if (error instanceof Error) cancel(error.message);
@@ -2168,7 +2772,7 @@ async function create_profile(name) {
2168
2772
  process.exit(1);
2169
2773
  }
2170
2774
  }
2171
- async function handle_load_profile() {
2775
+ async function handle_load_claude_profile() {
2172
2776
  const profiles = await list_profiles();
2173
2777
  if (profiles.length === 0) {
2174
2778
  log.warn("No profiles found");
@@ -2188,16 +2792,12 @@ async function handle_load_profile() {
2188
2792
  })
2189
2793
  });
2190
2794
  if (isCancel(profile_name)) return;
2191
- const profile = await load_profile(profile_name);
2192
- await write_claude_config(profile.config);
2193
- const parts = [`${Object.keys(profile.config.mcpServers || {}).length} servers`];
2194
- if (profile.enabledPlugins) {
2195
- await write_claude_settings({ enabledPlugins: profile.enabledPlugins });
2196
- parts.push(`${Object.keys(profile.enabledPlugins).length} plugins`);
2197
- }
2198
- log.success(`Profile '${profile_name}' applied (${parts.join(", ")})`);
2795
+ const result = await apply_profile_to_claude(profile_name);
2796
+ const parts = [`${result.serverCount} servers`];
2797
+ if (result.pluginCount > 0) parts.push(`${result.pluginCount} plugins`);
2798
+ log.success(`Profile '${result.profile}' applied (${parts.join(", ")})`);
2199
2799
  }
2200
- async function handle_save_profile() {
2800
+ async function handle_save_claude_profile() {
2201
2801
  const name = await text({
2202
2802
  message: "Profile name:",
2203
2803
  placeholder: "e.g. database, web-dev, minimal",
@@ -2207,10 +2807,10 @@ async function handle_save_profile() {
2207
2807
  }
2208
2808
  });
2209
2809
  if (isCancel(name)) return;
2210
- const counts = await save_profile(name);
2211
- const parts = [`${counts.serverCount} servers`];
2212
- if (counts.pluginCount > 0) parts.push(`${counts.pluginCount} plugins`);
2213
- log.success(`Profile '${name}' saved (${parts.join(", ")})`);
2810
+ const result = await save_current_claude_profile(name);
2811
+ const parts = [`${result.serverCount} servers`];
2812
+ if (result.pluginCount > 0) parts.push(`${result.pluginCount} plugins`);
2813
+ log.success(`Profile '${result.profile}' saved (${parts.join(", ")})`);
2214
2814
  }
2215
2815
  async function handle_client_tools() {
2216
2816
  const client_id = await select({
@@ -2278,11 +2878,11 @@ async function main() {
2278
2878
  return;
2279
2879
  }
2280
2880
  if (args.saveProfile) {
2281
- await create_profile(args.saveProfile);
2881
+ await create_claude_profile(args.saveProfile);
2282
2882
  return;
2283
2883
  }
2284
2884
  if (args.profile) {
2285
- await apply_profile(args.profile);
2885
+ await apply_claude_profile(args.profile);
2286
2886
  return;
2287
2887
  }
2288
2888
  intro("MCPick - MCP Configuration Manager");
@@ -2308,13 +2908,13 @@ async function main() {
2308
2908
  },
2309
2909
  {
2310
2910
  value: "load-profile",
2311
- label: "Load profile",
2312
- hint: "Apply a saved profile"
2911
+ label: "Load Claude Code profile",
2912
+ hint: "Apply a saved Claude Code profile"
2313
2913
  },
2314
2914
  {
2315
2915
  value: "save-profile",
2316
- label: "Save profile",
2317
- hint: "Save current config as profile"
2916
+ label: "Save Claude Code profile",
2917
+ hint: "Save current Claude Code config as profile"
2318
2918
  },
2319
2919
  {
2320
2920
  value: "backup",
@@ -2354,10 +2954,10 @@ async function main() {
2354
2954
  await restore_config();
2355
2955
  break;
2356
2956
  case "load-profile":
2357
- await handle_load_profile();
2957
+ await handle_load_claude_profile();
2358
2958
  break;
2359
2959
  case "save-profile":
2360
- await handle_save_profile();
2960
+ await handle_save_claude_profile();
2361
2961
  break;
2362
2962
  case "exit":
2363
2963
  outro("Goodbye!");
@@ -2402,17 +3002,18 @@ const SUBCOMMANDS = new Set([
2402
3002
  "cache",
2403
3003
  "dev",
2404
3004
  "marketplace",
2405
- "reload"
3005
+ "reload",
3006
+ "rollback"
2406
3007
  ]);
2407
3008
  const arg = process.argv[2];
2408
3009
  if (arg && SUBCOMMANDS.has(arg) || arg === "--help" || arg === "-h" || !process.stdout.isTTY) {
2409
3010
  if (!arg && !process.stdout.isTTY) process.argv.push("--help");
2410
- import("./cli-avr5R1LO.js").then((m) => m.run());
3011
+ import("./cli-CZOlaqoZ.js").then((m) => m.run());
2411
3012
  } else main().catch((error) => {
2412
3013
  console.error("Fatal error:", error);
2413
3014
  process.exit(1);
2414
3015
  });
2415
3016
  //#endregion
2416
- export { get_client_adapter as A, validate_plugin_via_cli as C, list_plugin_backups as D, list_backups as E, read_server_registry as O, update_plugin_via_cli as S, get_all_available_servers as T, mcp_add_json_via_cli as _, split_cli_list as a, remove_mcp_via_cli as b, enable_plugin_hook as c, add_mcp_via_cli as d, install_plugin_via_cli as f, marketplace_update_via_cli as g, marketplace_remove_via_cli as h, run_skills_cli as i, list_client_locations as j, write_server_registry as k, read_disabled_hooks as l, marketplace_list_via_cli as m, load_profile as n, check_restored_hooks as o, marketplace_add_via_cli as p, save_profile as r, disable_plugin_hook as s, list_profiles as t, redisable_restored_hooks as u, mcp_get_via_cli as v, add_server_to_registry as w, uninstall_plugin_via_cli as x, mcp_reset_project_choices_via_cli as y };
3017
+ export { write_claude_settings as $, list_backups as A, preview_remove_client_server as B, mcp_reset_project_choices_via_cli as C, validate_plugin_via_cli as D, update_plugin_via_cli as E, add_client_server_config as F, build_command_preview as G, remove_client_server as H, get_client_adapter as I, build_enabled_plugins as J, build_json_change_preview as K, list_client_locations as L, read_server_registry as M, write_server_registry as N, add_server_to_registry as O, add_client_server as P, remove_hook as Q, preview_add_client_server as R, mcp_get_via_cli as S, uninstall_plugin_via_cli as T, resolve_client_location as U, preview_set_client_enabled_servers as V, set_client_server_enabled as W, get_all_plugins as X, get_all_hooks as Y, read_claude_settings as Z, marketplace_add_via_cli as _, run_skills_cli as a, marketplace_update_via_cli as b, disable_plugin_hook as c, redisable_restored_hooks as d, atomic_json_write as et, add_mcp_via_cli as f, install_plugin_via_cli as g, build_remove_args as h, save_current_claude_profile as i, list_plugin_backups as j, get_all_available_servers as k, enable_plugin_hook as l, build_add_json_args as m, list_profiles as n, restore_config_backup as nt, split_cli_list as o, build_add_args as p, add_hook as q, load_profile as r, check_restored_hooks as s, apply_profile_to_claude as t, list_config_backups as tt, read_disabled_hooks as u, marketplace_list_via_cli as v, remove_mcp_via_cli as w, mcp_add_json_via_cli as x, marketplace_remove_via_cli as y, preview_add_client_server_config as z };
2417
3018
 
2418
3019
  //# sourceMappingURL=index.js.map