mcpick 0.0.24 → 0.0.25

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 (33) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +8 -2
  3. package/dist/{add-Qzd8i-5k.js → add-7mhUpbrt.js} +22 -35
  4. package/dist/{add-json-DGmsjB0O.js → add-json-BMM2L4hv.js} +18 -34
  5. package/dist/{backup-C7fvikFw.js → backup-C-YJmgps.js} +4 -4
  6. package/dist/{cache-D3jjh5dD.js → cache-BOYZhUF6.js} +4 -3
  7. package/dist/{cli-CZOlaqoZ.js → cli-sOeHH4CK.js} +22 -22
  8. package/dist/{clients-Bh93TGP4.js → clients-D5KAuQ5U.js} +3 -3
  9. package/dist/{clone-MI8jJhTz.js → clone-BRJA55js.js} +5 -5
  10. package/dist/{config-DE58Fik_.js → config-Bzh374VP.js} +4 -13
  11. package/dist/{dev-51esdZG9.js → dev-B-WlQSqY.js} +4 -4
  12. package/dist/{disable-csYAn2Vk.js → disable-Br0aVG3u.js} +18 -37
  13. package/dist/{enable-B5GbmhL-.js → enable-DUolKCEH.js} +18 -37
  14. package/dist/{get-DacRZmwv.js → get-D-6Cl_CO.js} +3 -3
  15. package/dist/{hooks-C_x49qap.js → hooks-BKPmZViU.js} +3 -3
  16. package/dist/index.js +571 -505
  17. package/dist/{list-BeBtsiae.js → list-DMcaHDfM.js} +4 -4
  18. package/dist/{marketplace-BDC2YtvT.js → marketplace-DTW7Ys8k.js} +4 -4
  19. package/dist/mutation-ukRPw3qM.js +19 -0
  20. package/dist/{output-HtT5HCof.js → output-BS1TMOWt.js} +1 -1
  21. package/dist/{plugin-cache-DmLbh89d.js → plugin-cache-Dw1I2YuO.js} +10 -3
  22. package/dist/{plugins-Bkw-SKkZ.js → plugins-BzLD4og0.js} +4 -4
  23. package/dist/profile-CmIWUJH_.js +163 -0
  24. package/dist/{reload-Bl1mYK1I.js → reload-Di28s_rY.js} +2 -2
  25. package/dist/{remove-BSHgva79.js → remove-B32EuYRC.js} +18 -33
  26. package/dist/{reset-project-choices-BNLus9J9.js → reset-project-choices-DX4TnZ2i.js} +3 -3
  27. package/dist/{restore-YisgARhc.js → restore-ByS4xi0y.js} +5 -5
  28. package/dist/{rollback-GR1RkpXW.js → rollback-DdDJrA8y.js} +3 -3
  29. package/dist/{skills-rDTDqqZA.js → skills-pvyQ17XU.js} +3 -3
  30. package/dist/{validation-xMlbgGCF.js → validation-CfPAjPJ5.js} +21 -2
  31. package/package.json +1 -1
  32. package/dist/dry-run-XQ32fxPT.js +0 -20
  33. package/dist/profile-DwJTVXiz.js +0 -161
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
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";
3
+ import { n as validate_mcp_server, r as validate_server_registry, t as validate_claude_config } from "./validation-CfPAjPJ5.js";
4
+ import { i as get_enabled_servers, o as read_claude_config, s as write_claude_config } from "./config-Bzh374VP.js";
5
+ import { a as redact_url, i as redact_text } 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-Dw1I2YuO.js";
7
7
  import { cancel, confirm, intro, isCancel, log, multiselect, note, outro, select, text } from "@clack/prompts";
8
8
  import { access, mkdir, readFile, readdir, rename, rm, unlink, writeFile } from "node:fs/promises";
9
9
  import { basename, dirname, join, resolve } from "node:path";
@@ -215,7 +215,7 @@ async function get_all_hooks() {
215
215
  * Checks both cache and marketplace source paths since Claude Code reads from both.
216
216
  */
217
217
  async function get_all_plugin_hooks() {
218
- const { read_installed_plugins } = await import("./plugin-cache-DmLbh89d.js").then((n) => n.s);
218
+ const { read_installed_plugins } = await import("./plugin-cache-Dw1I2YuO.js").then((n) => n.s);
219
219
  const { get_marketplaces_dir } = await import("./paths-BPISiJi4.js").then((n) => n.b);
220
220
  const installed = await read_installed_plugins();
221
221
  const entries = [];
@@ -344,43 +344,6 @@ async function cleanup_old_backups(prefix) {
344
344
  } catch {}
345
345
  }
346
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
384
347
  //#region src/core/client-config.ts
385
348
  const client_options_to_skip = new Set([
386
349
  "name",
@@ -414,7 +377,7 @@ async function read_json_file(path) {
414
377
  }
415
378
  }
416
379
  async function write_json_file(path, data) {
417
- await safe_json_write(path, data, 2);
380
+ return safe_json_write(path, data, 2);
418
381
  }
419
382
  function parse_json_or_jsonc(content) {
420
383
  try {
@@ -503,13 +466,13 @@ function remove_trailing_commas(content) {
503
466
  }
504
467
  return result;
505
468
  }
506
- function string_record(value) {
469
+ function string_record$1(value) {
507
470
  if (!value || typeof value !== "object" || Array.isArray(value)) return;
508
471
  const result = {};
509
472
  for (const [key, item] of Object.entries(value)) if (typeof item === "string") result[key] = item;
510
473
  return Object.keys(result).length > 0 ? result : void 0;
511
474
  }
512
- function string_array(value) {
475
+ function string_array$1(value) {
513
476
  if (!Array.isArray(value)) return void 0;
514
477
  const values = value.filter((item) => typeof item === "string");
515
478
  return values.length > 0 ? values : void 0;
@@ -525,10 +488,10 @@ function normalize_mcp_server(name, config) {
525
488
  const url = typeof config.httpUrl === "string" ? config.httpUrl : typeof config.serverUrl === "string" ? config.serverUrl : typeof config.url === "string" ? config.url : void 0;
526
489
  const client_options = {};
527
490
  for (const [key, value] of Object.entries(config)) if (!client_options_to_skip.has(key)) client_options[key] = value;
528
- const command_array = string_array(config.command);
491
+ const command_array = string_array$1(config.command);
529
492
  const command = typeof config.command === "string" ? config.command : command_array?.[0];
530
- const args = string_array(config.args) ?? command_array?.slice(1);
531
- const env = string_record(config.env) ?? string_record(config.environment);
493
+ const args = string_array$1(config.args) ?? command_array?.slice(1);
494
+ const env = string_record$1(config.env) ?? string_record$1(config.environment);
532
495
  const disabled = typeof config.disabled === "boolean" ? config.disabled : typeof config.enabled === "boolean" ? !config.enabled : void 0;
533
496
  return {
534
497
  name,
@@ -537,7 +500,7 @@ function normalize_mcp_server(name, config) {
537
500
  ...args && args.length > 0 ? { args } : {},
538
501
  ...url ? { url } : {},
539
502
  ...env ? { env } : {},
540
- ...string_record(config.headers) ? { headers: string_record(config.headers) } : {},
503
+ ...string_record$1(config.headers) ? { headers: string_record$1(config.headers) } : {},
541
504
  ...typeof config.description === "string" ? { description: config.description } : {},
542
505
  ...typeof disabled === "boolean" ? { disabled } : {},
543
506
  ...Object.keys(client_options).length > 0 ? { client_options } : {}
@@ -574,20 +537,10 @@ function portable_to_json(server, mode) {
574
537
  if (typeof server.disabled === "boolean") set_server_enabled(result, !server.disabled, mode);
575
538
  return result;
576
539
  }
540
+ function portable_server_map(servers, mode) {
541
+ return Object.fromEntries(servers.map((server) => [server.name, portable_to_json(server, mode)]));
542
+ }
577
543
  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
- }
591
544
  return {
592
545
  id: options.id,
593
546
  label: options.label,
@@ -602,66 +555,38 @@ function create_json_adapter(options) {
602
555
  return read_server_map(await read_json_file(location.path), options.serverKey);
603
556
  },
604
557
  async writeEnabled(location, enabled_names) {
605
- const data = await read_data(location);
558
+ const data = await read_json_file(location.path) ?? {};
606
559
  const servers = get_server_record(data, options.serverKey);
607
560
  const enabled = new Set(enabled_names);
608
561
  for (const [name, config] of Object.entries(servers)) set_server_enabled(config, enabled.has(name), options.disabledMode ?? "disabled");
609
562
  data[options.serverKey] = servers;
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);
563
+ return write_json_file(location.path, data);
620
564
  },
621
565
  async write_server(location, server) {
622
- const data = await read_data(location);
566
+ const data = await read_json_file(location.path) ?? {};
623
567
  const servers = get_server_record(data, options.serverKey);
624
568
  servers[server.name] = portable_to_json(server, options.disabledMode ?? "disabled");
625
569
  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);
570
+ return write_json_file(location.path, data);
635
571
  },
636
572
  async write_server_config(location, name, config) {
637
- const data = await read_data(location);
573
+ const data = await read_json_file(location.path) ?? {};
638
574
  const servers = get_server_record(data, options.serverKey);
639
575
  servers[name] = config;
640
576
  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);
577
+ return write_json_file(location.path, data);
650
578
  },
651
579
  async remove_server(location, name) {
652
- const data = await read_data(location);
580
+ const data = await read_json_file(location.path) ?? {};
653
581
  const servers = get_server_record(data, options.serverKey);
654
582
  delete servers[name];
655
583
  data[options.serverKey] = servers;
656
- await write_json_file(location.path, data);
584
+ return write_json_file(location.path, data);
657
585
  },
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);
586
+ async write_servers(location, servers) {
587
+ const data = await read_json_file(location.path) ?? {};
588
+ data[options.serverKey] = portable_server_map(servers, options.disabledMode ?? "disabled");
589
+ return write_json_file(location.path, data);
665
590
  }
666
591
  };
667
592
  }
@@ -704,6 +629,24 @@ const client_adapters = [
704
629
  const project_config = projects[process.cwd()];
705
630
  if (!project_config || typeof project_config !== "object" || Array.isArray(project_config)) return [];
706
631
  return read_server_map(project_config, "mcpServers");
632
+ },
633
+ async write_servers(location, servers) {
634
+ const data = await read_json_file(location.path) ?? (location.scope === "project" ? {} : { projects: {} });
635
+ const mcp_servers = portable_server_map(servers, "disabled");
636
+ if (location.scope === "project") {
637
+ data.mcpServers = mcp_servers;
638
+ return write_json_file(location.path, data);
639
+ }
640
+ if (location.scope === "user") {
641
+ data.mcpServers = mcp_servers;
642
+ return write_json_file(get_claude_config_path(), data);
643
+ }
644
+ const projects = data.projects && typeof data.projects === "object" && !Array.isArray(data.projects) ? data.projects : {};
645
+ const project_config = projects[process.cwd()] && typeof projects[process.cwd()] === "object" && !Array.isArray(projects[process.cwd()]) ? projects[process.cwd()] : {};
646
+ project_config.mcpServers = mcp_servers;
647
+ projects[process.cwd()] = project_config;
648
+ data.projects = projects;
649
+ return write_json_file(get_claude_config_path(), data);
707
650
  }
708
651
  },
709
652
  create_json_adapter({
@@ -800,6 +743,17 @@ const client_adapters = [
800
743
  function get_client_adapter(id) {
801
744
  return client_adapters.find((adapter) => adapter.id === id) ?? null;
802
745
  }
746
+ function mutation_result(adapter, location, operation, servers, write_result, enabledCount) {
747
+ return {
748
+ operation,
749
+ client: adapter.id,
750
+ scope: location.scope,
751
+ location: write_result.path,
752
+ servers,
753
+ ...enabledCount !== void 0 ? { enabledCount } : {},
754
+ ...write_result.backup_path ? { backup_path: write_result.backup_path } : {}
755
+ };
756
+ }
803
757
  function resolve_client_location(adapter, scope, path) {
804
758
  let locations = adapter.locations();
805
759
  if (path) locations = locations.filter((location) => location.path === path);
@@ -810,43 +764,31 @@ function resolve_client_location(adapter, scope, path) {
810
764
  }
811
765
  async function add_client_server(adapter, location, server) {
812
766
  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);
767
+ const write_result = await adapter.write_server(location, server);
768
+ return mutation_result(adapter, location, "add", [server.name], write_result);
818
769
  }
819
770
  async function add_client_server_config(adapter, location, name, config) {
820
771
  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);
772
+ const write_result = await adapter.write_server_config(location, name, config);
773
+ return mutation_result(adapter, location, "add", [name], write_result);
826
774
  }
827
775
  async function remove_client_server(adapter, location, server_name) {
828
776
  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);
777
+ const write_result = await adapter.remove_server(location, server_name);
778
+ return mutation_result(adapter, location, "remove", [server_name], write_result);
834
779
  }
835
- function assert_enabled_server_names(location, servers, enabled_names) {
780
+ async function set_client_enabled_servers(adapter, location, enabled_names) {
781
+ if (!adapter.writeEnabled) throw new Error(`${adapter.label} support is read-only.`);
782
+ const servers = await adapter.readLocation(location);
836
783
  const known_names = new Set(servers.map((server) => server.name));
837
784
  const unknown_names = enabled_names.filter((name) => !known_names.has(name));
838
785
  if (unknown_names.length > 0) throw new Error(`Server '${unknown_names[0]}' not found at ${location.path}.`);
786
+ return mutation_result(adapter, location, "set-enabled", enabled_names, await adapter.writeEnabled(location, enabled_names), enabled_names.length);
839
787
  }
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);
788
+ async function replace_client_servers(adapter, location, servers) {
789
+ if (!adapter.write_servers) throw new Error(`${adapter.label} support cannot replace server profiles yet.`);
790
+ const write_result = await adapter.write_servers(location, servers);
791
+ return mutation_result(adapter, location, "replace", servers.map((server) => server.name), write_result, servers.filter((server) => server.disabled !== true).length);
850
792
  }
851
793
  async function set_client_server_enabled(adapter, location, server_name, enabled) {
852
794
  const servers = await adapter.readLocation(location);
@@ -855,7 +797,11 @@ async function set_client_server_enabled(adapter, location, server_name, enabled
855
797
  const enabled_names = new Set(servers.filter((candidate) => candidate.disabled !== true).map((candidate) => candidate.name));
856
798
  if (enabled) enabled_names.add(server.name);
857
799
  else enabled_names.delete(server.name);
858
- return set_client_enabled_servers(adapter, location, [...enabled_names]);
800
+ return {
801
+ ...await set_client_enabled_servers(adapter, location, [...enabled_names]),
802
+ operation: enabled ? "enable" : "disable",
803
+ servers: [server_name]
804
+ };
859
805
  }
860
806
  async function list_client_locations() {
861
807
  return await Promise.all(client_adapters.flatMap((adapter) => adapter.locations().map(async (location) => ({
@@ -867,16 +813,56 @@ async function list_client_locations() {
867
813
  }
868
814
  //#endregion
869
815
  //#region src/core/registry.ts
816
+ function is_object$1(value) {
817
+ return !!value && typeof value === "object" && !Array.isArray(value);
818
+ }
819
+ function portable_to_mcp_server(server) {
820
+ return validate_mcp_server({
821
+ name: server.name,
822
+ ...server.transport !== "stdio" ? { type: server.transport } : {},
823
+ ...server.command ? { command: server.command } : {},
824
+ ...server.args ? { args: server.args } : {},
825
+ ...server.url ? { url: server.url } : {},
826
+ ...server.env ? { env: server.env } : {},
827
+ ...server.headers ? { headers: server.headers } : {},
828
+ ...server.description ? { description: server.description } : {}
829
+ });
830
+ }
831
+ function mcp_server_to_portable(server) {
832
+ const { name, ...config } = server;
833
+ return normalize_mcp_server(name, config);
834
+ }
835
+ function parse_portable_registry(data) {
836
+ if (!is_object$1(data) || !Array.isArray(data.servers)) throw new Error("Invalid server registry: expected servers array");
837
+ if (data.version === 3) return validate_server_registry(data);
838
+ return validate_server_registry({
839
+ version: 3,
840
+ servers: data.servers.map((server) => {
841
+ if (!is_object$1(server)) throw new Error("Invalid server registry entry");
842
+ if (typeof server.transport === "string") {
843
+ if (typeof server.name !== "string") throw new Error("Invalid server registry entry");
844
+ return normalize_mcp_server(server.name, server);
845
+ }
846
+ return mcp_server_to_portable(validate_mcp_server(server));
847
+ })
848
+ });
849
+ }
870
850
  async function read_server_registry() {
871
851
  const registry_path = get_server_registry_path();
872
852
  try {
873
853
  await access(registry_path);
874
854
  const registry_content = await readFile(registry_path, "utf-8");
875
- return validate_server_registry(JSON.parse(registry_content));
855
+ const parsed_registry = JSON.parse(registry_content);
856
+ const registry = parse_portable_registry(parsed_registry);
857
+ if (parsed_registry.version !== 3) await write_server_registry(registry);
858
+ return registry;
876
859
  } catch (error) {
877
860
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
878
861
  await ensure_directory_exists(get_mcpick_dir());
879
- const default_registry = { servers: [] };
862
+ const default_registry = {
863
+ version: 3,
864
+ servers: []
865
+ };
880
866
  await write_server_registry(default_registry);
881
867
  return default_registry;
882
868
  }
@@ -890,13 +876,14 @@ async function write_server_registry(registry) {
890
876
  }
891
877
  async function add_server_to_registry(server) {
892
878
  const registry = await read_server_registry();
879
+ const portable = mcp_server_to_portable(server);
893
880
  const existing_index = registry.servers.findIndex((s) => s.name === server.name);
894
- if (existing_index >= 0) registry.servers[existing_index] = server;
895
- else registry.servers.push(server);
881
+ if (existing_index >= 0) registry.servers[existing_index] = portable;
882
+ else registry.servers.push(portable);
896
883
  await write_server_registry(registry);
897
884
  }
898
885
  async function get_all_available_servers() {
899
- const { get_enabled_servers, read_claude_config } = await import("./config-DE58Fik_.js").then((n) => n.t);
886
+ const { get_enabled_servers, read_claude_config } = await import("./config-Bzh374VP.js").then((n) => n.t);
900
887
  const registry = await read_server_registry();
901
888
  const config_servers = get_enabled_servers(await read_claude_config());
902
889
  const config_by_name = new Map(config_servers.map((s) => [s.name, s]));
@@ -907,25 +894,16 @@ async function get_all_available_servers() {
907
894
  known_names.add(name);
908
895
  const config_server = config_by_name.get(name);
909
896
  if (config_server) {
910
- registry.servers[i] = config_server;
897
+ registry.servers[i] = mcp_server_to_portable(config_server);
911
898
  registry_updated = true;
912
899
  }
913
900
  }
914
901
  for (const server of config_servers) if (!known_names.has(server.name)) {
915
- registry.servers.push(server);
902
+ registry.servers.push(mcp_server_to_portable(server));
916
903
  registry_updated = true;
917
904
  }
918
905
  if (registry_updated) await write_server_registry(registry);
919
- return registry.servers;
920
- }
921
- async function sync_servers_to_registry(servers) {
922
- const registry = await read_server_registry();
923
- servers.forEach((server) => {
924
- const existing_index = registry.servers.findIndex((s) => s.name === server.name);
925
- if (existing_index >= 0) registry.servers[existing_index] = server;
926
- else registry.servers.push(server);
927
- });
928
- await write_server_registry(registry);
906
+ return registry.servers.map(portable_to_mcp_server);
929
907
  }
930
908
  function parse_backups(prefix, pattern) {
931
909
  return async () => {
@@ -951,6 +929,110 @@ function parse_backups(prefix, pattern) {
951
929
  const list_backups = parse_backups("mcp-servers-", /mcp-servers-(\d{4})-(\d{2})-(\d{2})-(\d{2})(\d{2})(\d{2})\.json/);
952
930
  const list_plugin_backups = parse_backups("plugins-", /plugins-(\d{4})-(\d{2})-(\d{2})-(\d{2})(\d{2})(\d{2})\.json/);
953
931
  //#endregion
932
+ //#region src/commands/edit-config.ts
933
+ async function edit_config() {
934
+ try {
935
+ const sorted_adapters = [...client_adapters].sort((a, b) => b.label.localeCompare(a.label));
936
+ const client_id = await select({
937
+ message: "Which MCP client do you want to edit?",
938
+ options: sorted_adapters.map((adapter) => ({
939
+ value: adapter.id,
940
+ label: adapter.label
941
+ })),
942
+ initialValue: sorted_adapters[0]?.id
943
+ });
944
+ if (typeof client_id === "symbol") return;
945
+ const adapter = client_adapters.find((candidate) => candidate.id === client_id);
946
+ if (!adapter) return;
947
+ if (adapter.id === "claude-code") {
948
+ await edit_claude_config(adapter);
949
+ return;
950
+ }
951
+ await edit_client_config(adapter);
952
+ } catch (error) {
953
+ throw new Error(`Failed to edit configuration: ${error instanceof Error ? error.message : "Unknown error"}`);
954
+ }
955
+ }
956
+ async function edit_client_config(adapter) {
957
+ if (!adapter.writeEnabled) {
958
+ note(`${adapter.label} support is read-only for now.`);
959
+ return;
960
+ }
961
+ const location = await select_config_location(adapter);
962
+ if (!location) return;
963
+ const servers = await adapter.readLocation(location);
964
+ if (servers.length === 0) {
965
+ note(`No MCP servers found at ${location.path}.`);
966
+ return;
967
+ }
968
+ const selected_names = await multiselect({
969
+ message: `Toggle MCP servers for ${adapter.label}:`,
970
+ options: servers.map((server) => ({
971
+ value: server.name,
972
+ label: server.name,
973
+ hint: server_hint(server)
974
+ })),
975
+ initialValues: servers.filter((server) => server.disabled !== true).map((server) => server.name),
976
+ required: false
977
+ });
978
+ if (typeof selected_names === "symbol") return;
979
+ const mutation = await set_client_enabled_servers(adapter, location, selected_names);
980
+ note(`Configuration updated!\nClient: ${adapter.label}\nConfig: ${mutation.location}\n` + (mutation.backup_path ? `Backup: ${mutation.backup_path}\n` : "") + `Enabled servers: ${selected_names.length}`);
981
+ }
982
+ async function select_config_location(adapter) {
983
+ const locations = adapter.locations();
984
+ if (locations.length === 1) return locations[0];
985
+ const location_path = await select({
986
+ message: `Which ${adapter.label} configuration do you want to edit?`,
987
+ options: locations.map((location) => ({
988
+ value: location.path,
989
+ label: `${location.scope} — ${location.description}`,
990
+ hint: location.path
991
+ }))
992
+ });
993
+ if (typeof location_path === "symbol") return null;
994
+ return locations.find((location) => location.path === location_path) ?? null;
995
+ }
996
+ function server_hint(server) {
997
+ return [
998
+ server.disabled === true ? "off" : "on",
999
+ server.command ? [server.command, ...server.args ?? []].join(" ") : server.url ? redact_url(server.url) : server.transport,
1000
+ server.description
1001
+ ].filter(Boolean).join(" · ");
1002
+ }
1003
+ async function edit_claude_config(adapter) {
1004
+ const location = await select_config_location(adapter);
1005
+ if (!location) return;
1006
+ const registry_servers = (await get_all_available_servers()).map((server) => {
1007
+ const { name, ...config } = server;
1008
+ return normalize_mcp_server(name, config);
1009
+ });
1010
+ const current_servers = await adapter.readLocation(location);
1011
+ const servers_by_name = /* @__PURE__ */ new Map();
1012
+ for (const server of registry_servers) servers_by_name.set(server.name, server);
1013
+ for (const server of current_servers) if (!servers_by_name.has(server.name)) servers_by_name.set(server.name, server);
1014
+ const all_servers = [...servers_by_name.values()];
1015
+ if (all_servers.length === 0) {
1016
+ note("No MCP servers found in this Claude Code config or registry. Add servers with the CLI first.");
1017
+ return;
1018
+ }
1019
+ const currently_enabled = current_servers.filter((server) => server.disabled !== true).map((server) => server.name);
1020
+ const selected_server_names = await multiselect({
1021
+ message: `Select MCP servers for ${location.description}:`,
1022
+ options: all_servers.map((server) => ({
1023
+ value: server.name,
1024
+ label: server.name,
1025
+ hint: server_hint(server)
1026
+ })),
1027
+ initialValues: currently_enabled,
1028
+ required: false
1029
+ });
1030
+ if (typeof selected_server_names === "symbol") return;
1031
+ const selected_servers = all_servers.filter((server) => selected_server_names.includes(server.name));
1032
+ const mutation = await replace_client_servers(adapter, location, selected_servers);
1033
+ note(`Configuration updated!\nClient: ${adapter.label}\nConfig: ${mutation.location}\n` + (mutation.backup_path ? `Backup: ${mutation.backup_path}\n` : "") + `Enabled servers: ${selected_servers.length}`);
1034
+ }
1035
+ //#endregion
954
1036
  //#region src/utils/claude-cli.ts
955
1037
  const exec_file_async$1 = promisify(execFile);
956
1038
  /**
@@ -1381,16 +1463,6 @@ async function marketplace_list_via_cli() {
1381
1463
  }
1382
1464
  }
1383
1465
  /**
1384
- * Get the scope description for display
1385
- */
1386
- function get_scope_description(scope) {
1387
- switch (scope) {
1388
- case "local": return "This project only (default)";
1389
- case "project": return "Shared via .mcp.json (version controlled)";
1390
- case "user": return "Global - all projects";
1391
- }
1392
- }
1393
- /**
1394
1466
  * Validate a plugin or marketplace manifest via Claude CLI
1395
1467
  */
1396
1468
  async function validate_plugin_via_cli(path) {
@@ -1443,23 +1515,20 @@ async function mcp_get_via_cli(name) {
1443
1515
  /**
1444
1516
  * Add an MCP server from raw JSON via Claude CLI
1445
1517
  */
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
- }
1456
1518
  async function mcp_add_json_via_cli(name, json, scope = "local") {
1457
1519
  if (!await check_claude_cli()) return {
1458
1520
  success: false,
1459
1521
  error: "Claude CLI not found. Please install Claude Code CLI."
1460
1522
  };
1461
1523
  try {
1462
- await run_claude(build_add_json_args(name, json, scope));
1524
+ await run_claude([
1525
+ "mcp",
1526
+ "add-json",
1527
+ name,
1528
+ json,
1529
+ "--scope",
1530
+ scope
1531
+ ]);
1463
1532
  return { success: true };
1464
1533
  } catch (error) {
1465
1534
  return {
@@ -1486,174 +1555,6 @@ async function mcp_reset_project_choices_via_cli() {
1486
1555
  };
1487
1556
  }
1488
1557
  }
1489
- /**
1490
- * Get scope options for select prompt
1491
- */
1492
- function get_scope_options() {
1493
- return [
1494
- {
1495
- value: "local",
1496
- label: "Local",
1497
- hint: "This project only (default)"
1498
- },
1499
- {
1500
- value: "project",
1501
- label: "Project",
1502
- hint: "Shared via .mcp.json (git)"
1503
- },
1504
- {
1505
- value: "user",
1506
- label: "User (Global)",
1507
- hint: "Available in all projects"
1508
- }
1509
- ];
1510
- }
1511
- //#endregion
1512
- //#region src/commands/edit-config.ts
1513
- async function edit_config() {
1514
- try {
1515
- const client_id = await select({
1516
- message: "Which MCP client do you want to edit?",
1517
- options: client_adapters.map((adapter) => ({
1518
- value: adapter.id,
1519
- label: adapter.label
1520
- })),
1521
- initialValue: "claude-code"
1522
- });
1523
- if (typeof client_id === "symbol") return;
1524
- const adapter = client_adapters.find((candidate) => candidate.id === client_id);
1525
- if (!adapter) return;
1526
- if (adapter.id === "claude-code") {
1527
- await edit_claude_config();
1528
- return;
1529
- }
1530
- await edit_client_config(adapter);
1531
- } catch (error) {
1532
- throw new Error(`Failed to edit configuration: ${error instanceof Error ? error.message : "Unknown error"}`);
1533
- }
1534
- }
1535
- async function edit_client_config(adapter) {
1536
- if (!adapter.writeEnabled) {
1537
- note(`${adapter.label} support is read-only for now.`);
1538
- return;
1539
- }
1540
- const location = await select_config_location(adapter);
1541
- if (!location) return;
1542
- const servers = await adapter.readLocation(location);
1543
- if (servers.length === 0) {
1544
- note(`No MCP servers found at ${location.path}.`);
1545
- return;
1546
- }
1547
- const selected_names = await multiselect({
1548
- message: `Toggle MCP servers for ${adapter.label}:`,
1549
- options: servers.map((server) => ({
1550
- value: server.name,
1551
- label: server.name,
1552
- hint: server_hint(server)
1553
- })),
1554
- initialValues: servers.filter((server) => server.disabled !== true).map((server) => server.name),
1555
- required: false
1556
- });
1557
- if (typeof selected_names === "symbol") return;
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);
1568
- note(`Configuration updated!\nClient: ${adapter.label}\nConfig: ${location.path}\nEnabled servers: ${selected_names.length}`);
1569
- }
1570
- async function select_config_location(adapter) {
1571
- const locations = adapter.locations();
1572
- if (locations.length === 1) return locations[0];
1573
- const location_path = await select({
1574
- message: `Which ${adapter.label} configuration do you want to edit?`,
1575
- options: locations.map((location) => ({
1576
- value: location.path,
1577
- label: `${location.scope} — ${location.description}`,
1578
- hint: location.path
1579
- }))
1580
- });
1581
- if (typeof location_path === "symbol") return null;
1582
- return locations.find((location) => location.path === location_path) ?? null;
1583
- }
1584
- function server_hint(server) {
1585
- return [
1586
- server.disabled === true ? "off" : "on",
1587
- server.command ? [server.command, ...server.args ?? []].join(" ") : server.url ? redact_url(server.url) : server.transport,
1588
- server.description
1589
- ].filter(Boolean).join(" · ");
1590
- }
1591
- async function edit_claude_config() {
1592
- const cli_available = await check_claude_cli();
1593
- const scope = await select({
1594
- message: "Which Claude Code configuration do you want to edit?",
1595
- options: get_scope_options(),
1596
- initialValue: "local"
1597
- });
1598
- if (typeof scope === "symbol") return;
1599
- const current_config = await read_claude_config();
1600
- let all_servers = await get_all_available_servers();
1601
- if (all_servers.length === 0 && current_config.mcpServers) {
1602
- const current_servers = get_enabled_servers(current_config);
1603
- if (current_servers.length > 0) {
1604
- await sync_servers_to_registry(current_servers);
1605
- all_servers = current_servers;
1606
- note(`Imported ${current_servers.length} servers from your .claude.json file into registry.`);
1607
- }
1608
- }
1609
- if (all_servers.length === 0) {
1610
- note("No MCP servers found in .claude.json or registry. Add servers with the CLI first.");
1611
- return;
1612
- }
1613
- const currently_enabled = await get_enabled_servers_for_scope(scope);
1614
- const selected_server_names = await multiselect({
1615
- message: `Select MCP servers for ${get_scope_description(scope)}:`,
1616
- options: all_servers.map((server) => ({
1617
- value: server.name,
1618
- label: server.name,
1619
- hint: server.description || ""
1620
- })),
1621
- initialValues: currently_enabled,
1622
- required: false
1623
- });
1624
- if (typeof selected_server_names === "symbol") return;
1625
- const selected_servers = all_servers.filter((server) => selected_server_names.includes(server.name));
1626
- const servers_to_add = selected_server_names.filter((name) => !currently_enabled.includes(name));
1627
- const servers_to_remove = currently_enabled.filter((name) => !selected_server_names.includes(name));
1628
- if (cli_available && (scope === "local" || scope === "project")) {
1629
- let error_count = 0;
1630
- for (const name of servers_to_add) {
1631
- const server = all_servers.find((s) => s.name === name);
1632
- if (server) {
1633
- const result = await add_mcp_via_cli(server, scope);
1634
- if (!result.success) {
1635
- error_count++;
1636
- log.warn(`Failed to add ${name}: ${result.error}`);
1637
- }
1638
- }
1639
- }
1640
- for (const name of servers_to_remove) {
1641
- const result = await remove_mcp_via_cli(name, scope);
1642
- if (!result.success) {
1643
- error_count++;
1644
- log.warn(`Failed to remove ${name}: ${result.error}`);
1645
- }
1646
- }
1647
- await sync_servers_to_registry(selected_servers);
1648
- if (error_count > 0) note(`Configuration updated with ${error_count} errors.\nScope: ${get_scope_description(scope)}\nAdded: ${servers_to_add.length}, Removed: ${servers_to_remove.length}`);
1649
- else note(`Configuration updated!\nScope: ${get_scope_description(scope)}\nEnabled servers: ${selected_servers.length}`);
1650
- } else {
1651
- await write_claude_config(create_config_from_servers(selected_servers));
1652
- await sync_servers_to_registry(selected_servers);
1653
- if (!cli_available && scope !== "user") log.warn(`Claude CLI not available. Changes written to ~/.claude.json (user scope) instead of ${scope} scope.`);
1654
- note(`Configuration updated!\nEnabled servers: ${selected_servers.length}`);
1655
- }
1656
- }
1657
1558
  //#endregion
1658
1559
  //#region src/commands/edit-plugins.ts
1659
1560
  async function handle_toggle() {
@@ -1818,120 +1719,6 @@ async function edit_plugins() {
1818
1719
  }
1819
1720
  }
1820
1721
  //#endregion
1821
- //#region src/commands/manage-cache.ts
1822
- function format_status_line(p) {
1823
- const markers = [];
1824
- if (p.isVersionStale) markers.push(`version: ${p.installedVersion} → ${p.latestVersion}`);
1825
- if (p.isShaStale) markers.push("commits behind");
1826
- if (p.orphanedVersions.length > 0) markers.push(`${p.orphanedVersions.length} orphaned`);
1827
- const status = markers.length > 0 ? `[stale: ${markers.join(", ")}]` : "[up to date]";
1828
- return `${p.name}@${p.marketplace} v${p.installedVersion} ${status}`;
1829
- }
1830
- async function handle_status() {
1831
- const plugins = await get_cached_plugins_info();
1832
- if (plugins.length === 0) {
1833
- log.info("No cached plugins found.");
1834
- return;
1835
- }
1836
- note(plugins.map(format_status_line).join("\n"), "Plugin Cache Status");
1837
- }
1838
- async function handle_clear() {
1839
- const plugins = await get_cached_plugins_info();
1840
- if (plugins.length === 0) {
1841
- log.info("No cached plugins to clear.");
1842
- return;
1843
- }
1844
- const selected = await multiselect({
1845
- message: "Select plugins to clear cache for:",
1846
- options: plugins.map((p) => {
1847
- const stale = p.isVersionStale || p.isShaStale;
1848
- return {
1849
- value: p.key,
1850
- label: `${p.name}@${p.marketplace}`,
1851
- hint: stale ? `v${p.installedVersion} → ${p.latestVersion ?? "unknown"} (stale)` : `v${p.installedVersion}`
1852
- };
1853
- }),
1854
- initialValues: plugins.filter((p) => p.isVersionStale || p.isShaStale).map((p) => p.key)
1855
- });
1856
- if (isCancel(selected) || selected.length === 0) return;
1857
- const should_clear = await confirm({ message: `Clear cache for ${selected.length} plugin(s)? This will also refresh the marketplace.` });
1858
- if (isCancel(should_clear) || !should_clear) return;
1859
- const result = await clear_plugin_caches(selected);
1860
- for (const key of result.cleared) log.success(`Cleared: ${key}`);
1861
- for (const err of result.errors) log.error(`Error: ${err}`);
1862
- if (result.cleared.length > 0) note("Run /reload-plugins in Claude Code or restart your session to apply changes.", "Next Steps");
1863
- }
1864
- async function handle_clean_orphaned() {
1865
- const should_clean = await confirm({ message: "Remove all orphaned plugin version directories?" });
1866
- if (isCancel(should_clean) || !should_clean) return;
1867
- const result = await clean_orphaned_versions();
1868
- if (result.cleaned === 0) log.info("No orphaned versions found.");
1869
- else {
1870
- for (const p of result.paths) log.success(`Removed: ${p}`);
1871
- log.info(`Cleaned ${result.cleaned} orphaned version(s).`);
1872
- }
1873
- }
1874
- async function handle_refresh() {
1875
- const should_refresh = await confirm({ message: "Refresh all marketplace clones (git pull)?" });
1876
- if (isCancel(should_refresh) || !should_refresh) return;
1877
- const results = await refresh_all_marketplaces();
1878
- if (results.size === 0) {
1879
- log.info("No marketplaces configured.");
1880
- return;
1881
- }
1882
- for (const [name, result] of results) if (result.success) log.success(`${name}: refreshed`);
1883
- else log.error(`${name}: ${result.error}`);
1884
- }
1885
- async function manage_cache() {
1886
- while (true) {
1887
- const action = await select({
1888
- message: "Plugin cache management:",
1889
- options: [
1890
- {
1891
- value: "status",
1892
- label: "View cache status",
1893
- hint: "Show plugins with staleness info"
1894
- },
1895
- {
1896
- value: "clear",
1897
- label: "Clear plugin caches",
1898
- hint: "Refresh marketplace + clear selected caches"
1899
- },
1900
- {
1901
- value: "clean-orphaned",
1902
- label: "Clean orphaned versions",
1903
- hint: "Remove old version directories"
1904
- },
1905
- {
1906
- value: "refresh",
1907
- label: "Refresh marketplaces",
1908
- hint: "Git pull all marketplace clones"
1909
- },
1910
- {
1911
- value: "back",
1912
- label: "Back",
1913
- hint: "Return to main menu"
1914
- }
1915
- ]
1916
- });
1917
- if (isCancel(action) || action === "back") return;
1918
- switch (action) {
1919
- case "status":
1920
- await handle_status();
1921
- break;
1922
- case "clear":
1923
- await handle_clear();
1924
- break;
1925
- case "clean-orphaned":
1926
- await handle_clean_orphaned();
1927
- break;
1928
- case "refresh":
1929
- await handle_refresh();
1930
- break;
1931
- }
1932
- }
1933
- }
1934
- //#endregion
1935
1722
  //#region src/core/hook-state.ts
1936
1723
  async function read_disabled_hooks() {
1937
1724
  try {
@@ -2097,6 +1884,121 @@ async function redisable_restored_hooks(restored) {
2097
1884
  };
2098
1885
  }
2099
1886
  //#endregion
1887
+ //#region src/commands/manage-cache.ts
1888
+ function format_status_line(p) {
1889
+ const markers = [];
1890
+ if (p.isVersionStale) markers.push(`version: ${p.installedVersion} → ${p.latestVersion}`);
1891
+ if (p.isShaStale) markers.push("commits behind");
1892
+ if (p.orphanedVersions.length > 0) markers.push(`${p.orphanedVersions.length} orphaned`);
1893
+ const status = markers.length > 0 ? `[stale: ${markers.join(", ")}]` : "[up to date]";
1894
+ return `${p.name}@${p.marketplace} v${p.installedVersion} ${status}`;
1895
+ }
1896
+ async function handle_status() {
1897
+ const plugins = await get_cached_plugins_info();
1898
+ if (plugins.length === 0) {
1899
+ log.info("No cached plugins found.");
1900
+ return;
1901
+ }
1902
+ note(plugins.map(format_status_line).join("\n"), "Plugin Cache Status");
1903
+ }
1904
+ async function handle_clear() {
1905
+ const plugins = await get_cached_plugins_info();
1906
+ if (plugins.length === 0) {
1907
+ log.info("No cached plugins to clear.");
1908
+ return;
1909
+ }
1910
+ const selected = await multiselect({
1911
+ message: "Select plugins to clear cache for:",
1912
+ options: plugins.map((p) => {
1913
+ const stale = p.isVersionStale || p.isShaStale;
1914
+ return {
1915
+ value: p.key,
1916
+ label: `${p.name}@${p.marketplace}`,
1917
+ hint: stale ? `v${p.installedVersion} → ${p.latestVersion ?? "unknown"} (stale)` : `v${p.installedVersion}`
1918
+ };
1919
+ }),
1920
+ initialValues: plugins.filter((p) => p.isVersionStale || p.isShaStale).map((p) => p.key)
1921
+ });
1922
+ if (isCancel(selected) || selected.length === 0) return;
1923
+ const should_clear = await confirm({ message: `Clear cache for ${selected.length} plugin(s)? This will also refresh the marketplace.` });
1924
+ if (isCancel(should_clear) || !should_clear) return;
1925
+ const result = await clear_plugin_caches(selected);
1926
+ for (const key of result.cleared) log.success(`Cleared: ${key}`);
1927
+ if (result.redisabledHooks?.success) log.success(`Re-disabled restored hooks: ${result.redisabledHooks.success}`);
1928
+ for (const err of result.errors) log.error(`Error: ${err}`);
1929
+ if (result.cleared.length > 0) note("Run /reload-plugins in Claude Code or restart your session to apply changes.", "Next Steps");
1930
+ }
1931
+ async function handle_clean_orphaned() {
1932
+ const should_clean = await confirm({ message: "Remove all orphaned plugin version directories?" });
1933
+ if (isCancel(should_clean) || !should_clean) return;
1934
+ const result = await clean_orphaned_versions();
1935
+ if (result.cleaned === 0) log.info("No orphaned versions found.");
1936
+ else {
1937
+ for (const p of result.paths) log.success(`Removed: ${p}`);
1938
+ log.info(`Cleaned ${result.cleaned} orphaned version(s).`);
1939
+ }
1940
+ }
1941
+ async function handle_refresh() {
1942
+ const should_refresh = await confirm({ message: "Refresh all marketplace clones (git pull)?" });
1943
+ if (isCancel(should_refresh) || !should_refresh) return;
1944
+ const results = await refresh_all_marketplaces();
1945
+ if (results.size === 0) {
1946
+ log.info("No marketplaces configured.");
1947
+ return;
1948
+ }
1949
+ for (const [name, result] of results) if (result.success) log.success(`${name}: refreshed`);
1950
+ else log.error(`${name}: ${result.error}`);
1951
+ }
1952
+ async function manage_cache() {
1953
+ while (true) {
1954
+ const action = await select({
1955
+ message: "Plugin cache management:",
1956
+ options: [
1957
+ {
1958
+ value: "status",
1959
+ label: "View cache status",
1960
+ hint: "Show plugins with staleness info"
1961
+ },
1962
+ {
1963
+ value: "clear",
1964
+ label: "Clear plugin caches",
1965
+ hint: "Refresh marketplace + clear selected caches"
1966
+ },
1967
+ {
1968
+ value: "clean-orphaned",
1969
+ label: "Clean orphaned versions",
1970
+ hint: "Remove old version directories"
1971
+ },
1972
+ {
1973
+ value: "refresh",
1974
+ label: "Refresh marketplaces",
1975
+ hint: "Git pull all marketplace clones"
1976
+ },
1977
+ {
1978
+ value: "back",
1979
+ label: "Back",
1980
+ hint: "Return to main menu"
1981
+ }
1982
+ ]
1983
+ });
1984
+ if (isCancel(action) || action === "back") return;
1985
+ switch (action) {
1986
+ case "status":
1987
+ await handle_status();
1988
+ break;
1989
+ case "clear":
1990
+ await handle_clear();
1991
+ break;
1992
+ case "clean-orphaned":
1993
+ await handle_clean_orphaned();
1994
+ break;
1995
+ case "refresh":
1996
+ await handle_refresh();
1997
+ break;
1998
+ }
1999
+ }
2000
+ }
2001
+ //#endregion
2100
2002
  //#region src/commands/manage-hooks.ts
2101
2003
  function format_hook(entry) {
2102
2004
  const detail = entry.handler.command || entry.handler.url || entry.handler.prompt || "(unknown)";
@@ -2638,56 +2540,144 @@ function format_time_ago(date) {
2638
2540
  }
2639
2541
  //#endregion
2640
2542
  //#region src/core/profile.ts
2641
- async function load_profile(name) {
2543
+ function is_object(value) {
2544
+ return !!value && typeof value === "object" && !Array.isArray(value);
2545
+ }
2546
+ function string_record(value) {
2547
+ if (!is_object(value)) return void 0;
2548
+ const result = {};
2549
+ for (const [key, item] of Object.entries(value)) if (typeof item === "string") result[key] = item;
2550
+ return Object.keys(result).length > 0 ? result : void 0;
2551
+ }
2552
+ function boolean_record(value) {
2553
+ if (!is_object(value)) return void 0;
2554
+ const result = {};
2555
+ for (const [key, item] of Object.entries(value)) if (typeof item === "boolean") result[key] = item;
2556
+ return Object.keys(result).length > 0 ? result : void 0;
2557
+ }
2558
+ function string_array(value) {
2559
+ if (!Array.isArray(value)) return void 0;
2560
+ const result = value.filter((item) => typeof item === "string");
2561
+ return result.length > 0 ? result : void 0;
2562
+ }
2563
+ function parse_portable_server(value) {
2564
+ if (!is_object(value) || typeof value.name !== "string") return null;
2565
+ const transport = value.transport === "http" || value.transport === "sse" || value.transport === "stdio" ? value.transport : "stdio";
2566
+ const client_options = is_object(value.client_options) ? value.client_options : void 0;
2567
+ return {
2568
+ name: value.name,
2569
+ transport,
2570
+ ...typeof value.command === "string" ? { command: value.command } : {},
2571
+ ...string_array(value.args) ? { args: string_array(value.args) } : {},
2572
+ ...typeof value.url === "string" ? { url: value.url } : {},
2573
+ ...string_record(value.env) ? { env: string_record(value.env) } : {},
2574
+ ...string_record(value.headers) ? { headers: string_record(value.headers) } : {},
2575
+ ...typeof value.description === "string" ? { description: value.description } : {},
2576
+ ...typeof value.disabled === "boolean" ? { disabled: value.disabled } : {},
2577
+ ...client_options ? { client_options } : {}
2578
+ };
2579
+ }
2580
+ async function read_profile_json(name) {
2642
2581
  const profile_path = get_profile_path(name);
2643
2582
  try {
2644
2583
  await access(profile_path);
2645
- const content = await readFile(profile_path, "utf-8");
2646
- const parsed = JSON.parse(content);
2647
- let config;
2648
- if (parsed.mcpServers) config = validate_claude_config(parsed);
2649
- else if (!parsed.enabledPlugins) config = validate_claude_config({ mcpServers: parsed });
2650
- else config = validate_claude_config({ mcpServers: parsed.mcpServers || {} });
2651
- return {
2652
- config,
2653
- enabledPlugins: parsed.enabledPlugins
2654
- };
2584
+ return JSON.parse(await readFile(profile_path, "utf-8"));
2655
2585
  } catch (error) {
2656
2586
  if (error instanceof Error && "code" in error && error.code === "ENOENT") throw new Error(`Profile '${name}' not found at ${profile_path}`);
2657
2587
  throw error;
2658
2588
  }
2659
2589
  }
2590
+ function legacy_profile_to_claude_config(parsed) {
2591
+ if (parsed.mcpServers) return validate_claude_config(parsed);
2592
+ if (!parsed.enabledPlugins && !parsed.plugins) return validate_claude_config({ mcpServers: parsed });
2593
+ return validate_claude_config({ mcpServers: parsed.mcpServers || {} });
2594
+ }
2595
+ function claude_config_to_portable(config) {
2596
+ return Object.entries(config.mcpServers || {}).map(([name, server]) => normalize_mcp_server(name, server));
2597
+ }
2598
+ function portable_to_claude_config(servers) {
2599
+ const mcpServers = {};
2600
+ for (const server of servers) {
2601
+ const config = {};
2602
+ if (server.transport !== "stdio") config.type = server.transport;
2603
+ if (server.command) config.command = server.command;
2604
+ if (server.args) config.args = server.args;
2605
+ if (server.url) config.url = server.url;
2606
+ if (server.env) config.env = server.env;
2607
+ if (server.headers) config.headers = server.headers;
2608
+ if (server.description) config.description = server.description;
2609
+ mcpServers[server.name] = config;
2610
+ }
2611
+ return validate_claude_config({ mcpServers });
2612
+ }
2613
+ async function load_portable_profile(name) {
2614
+ const parsed = await read_profile_json(name);
2615
+ const plugins = boolean_record(parsed.plugins) ?? boolean_record(parsed.enabledPlugins);
2616
+ if (Array.isArray(parsed.servers)) return {
2617
+ version: 2,
2618
+ servers: parsed.servers.map(parse_portable_server).filter((server) => !!server),
2619
+ ...plugins ? { plugins } : {},
2620
+ ...is_object(parsed.client_overrides) ? { client_overrides: parsed.client_overrides } : {}
2621
+ };
2622
+ return {
2623
+ version: 2,
2624
+ servers: claude_config_to_portable(legacy_profile_to_claude_config(parsed)),
2625
+ ...plugins ? { plugins } : {}
2626
+ };
2627
+ }
2660
2628
  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;
2629
+ const profile = await load_portable_profile(name);
2630
+ await write_claude_config(portable_to_claude_config(profile.servers));
2664
2631
  let pluginCount = 0;
2665
- if (profile.enabledPlugins) {
2666
- await write_claude_settings({ enabledPlugins: profile.enabledPlugins });
2667
- pluginCount = Object.keys(profile.enabledPlugins).length;
2632
+ if (profile.plugins) {
2633
+ await write_claude_settings({ enabledPlugins: profile.plugins });
2634
+ pluginCount = Object.keys(profile.plugins).length;
2668
2635
  }
2669
2636
  return {
2670
2637
  profile: name,
2671
- serverCount,
2672
- pluginCount
2638
+ serverCount: profile.servers.length,
2639
+ pluginCount,
2640
+ client: "claude-code",
2641
+ scope: "user"
2642
+ };
2643
+ }
2644
+ async function apply_profile_to_client(input) {
2645
+ const adapter = get_client_adapter(input.client);
2646
+ if (!adapter) throw new Error(`Invalid client: ${input.client}`);
2647
+ const location = resolve_client_location(adapter, input.scope, input.location);
2648
+ const profile = await load_portable_profile(input.name);
2649
+ const mutation = await replace_client_servers(adapter, location, profile.servers);
2650
+ let pluginCount = 0;
2651
+ if (adapter.id === "claude-code" && profile.plugins) {
2652
+ await write_claude_settings({ enabledPlugins: profile.plugins });
2653
+ pluginCount = Object.keys(profile.plugins).length;
2654
+ }
2655
+ return {
2656
+ profile: input.name,
2657
+ serverCount: profile.servers.length,
2658
+ pluginCount,
2659
+ client: adapter.id,
2660
+ scope: location.scope,
2661
+ location: mutation.location,
2662
+ ...mutation.backup_path ? { backup_path: mutation.backup_path } : {}
2673
2663
  };
2674
2664
  }
2675
2665
  async function list_profiles() {
2676
2666
  const profiles_dir = get_profiles_dir();
2677
2667
  try {
2678
2668
  await access(profiles_dir);
2679
- const json_files = (await readdir(profiles_dir)).filter((f) => f.endsWith(".json"));
2669
+ const files = (await readdir(profiles_dir)).filter((file) => file.endsWith(".json"));
2680
2670
  const profiles = [];
2681
- for (const file of json_files) try {
2671
+ for (const file of files) try {
2672
+ const name = file.replace(/\.json$/, "");
2682
2673
  const path = get_profile_path(file);
2683
- const content = await readFile(path, "utf-8");
2684
- const parsed = JSON.parse(content);
2685
- const servers = parsed.mcpServers || parsed;
2686
- const plugins = parsed.enabledPlugins || {};
2674
+ const parsed = JSON.parse(await readFile(path, "utf-8"));
2675
+ const servers = Array.isArray(parsed.servers) ? parsed.servers : parsed.mcpServers || parsed;
2676
+ const plugins = parsed.plugins || parsed.enabledPlugins || {};
2687
2677
  profiles.push({
2688
- name: file.replace(".json", ""),
2678
+ name,
2689
2679
  path,
2690
- serverCount: Object.keys(servers).length,
2680
+ serverCount: Array.isArray(servers) ? servers.length : Object.keys(servers).length,
2691
2681
  pluginCount: Object.keys(plugins).length
2692
2682
  });
2693
2683
  } catch {}
@@ -2696,21 +2686,43 @@ async function list_profiles() {
2696
2686
  return [];
2697
2687
  }
2698
2688
  }
2689
+ async function save_portable_profile(name, servers, plugins) {
2690
+ await ensure_directory_exists(get_profiles_dir());
2691
+ const profile_data = {
2692
+ version: 2,
2693
+ servers,
2694
+ ...plugins && Object.keys(plugins).length > 0 ? { plugins } : {}
2695
+ };
2696
+ await safe_json_write(get_profile_path(name), profile_data, 2);
2697
+ }
2699
2698
  async function save_profile(name) {
2700
2699
  const config = await read_claude_config();
2701
2700
  const settings = await read_claude_settings();
2702
- const servers = config.mcpServers || {};
2701
+ const servers = get_enabled_servers(config).map((server) => normalize_mcp_server(server.name, server));
2703
2702
  const plugins = settings.enabledPlugins || {};
2704
- const server_count = Object.keys(servers).length;
2705
- const plugin_count = Object.keys(plugins).length;
2706
- if (server_count === 0 && plugin_count === 0) throw new Error("No MCP servers or plugins configured to save");
2707
- await ensure_directory_exists(get_profiles_dir());
2708
- const profile_data = { mcpServers: servers };
2709
- if (plugin_count > 0) profile_data.enabledPlugins = plugins;
2710
- await safe_json_write(get_profile_path(name), profile_data, 2);
2703
+ if (servers.length === 0 && Object.keys(plugins).length === 0) throw new Error("No MCP servers or plugins configured to save");
2704
+ await save_portable_profile(name, servers, plugins);
2705
+ return {
2706
+ serverCount: servers.length,
2707
+ pluginCount: Object.keys(plugins).length
2708
+ };
2709
+ }
2710
+ async function save_profile_for_client(input) {
2711
+ const adapter = get_client_adapter(input.client);
2712
+ if (!adapter) throw new Error(`Invalid client: ${input.client}`);
2713
+ const location = resolve_client_location(adapter, input.scope, input.location);
2714
+ const servers = await adapter.readLocation(location);
2715
+ let plugins;
2716
+ if (adapter.id === "claude-code") plugins = (await read_claude_settings()).enabledPlugins;
2717
+ if (servers.length === 0 && !plugins) throw new Error("No MCP servers or plugins configured to save");
2718
+ await save_portable_profile(input.name, servers, plugins);
2711
2719
  return {
2712
- serverCount: server_count,
2713
- pluginCount: plugin_count
2720
+ profile: input.name,
2721
+ serverCount: servers.length,
2722
+ pluginCount: Object.keys(plugins || {}).length,
2723
+ client: adapter.id,
2724
+ scope: location.scope,
2725
+ location: location.path
2714
2726
  };
2715
2727
  }
2716
2728
  async function save_current_claude_profile(name) {
@@ -2718,7 +2730,9 @@ async function save_current_claude_profile(name) {
2718
2730
  return {
2719
2731
  profile: name,
2720
2732
  serverCount: counts.serverCount,
2721
- pluginCount: counts.pluginCount
2733
+ pluginCount: counts.pluginCount,
2734
+ client: "claude-code",
2735
+ scope: "user"
2722
2736
  };
2723
2737
  }
2724
2738
  //#endregion
@@ -2772,7 +2786,37 @@ async function create_claude_profile(name) {
2772
2786
  process.exit(1);
2773
2787
  }
2774
2788
  }
2775
- async function handle_load_claude_profile() {
2789
+ function sorted_client_adapters() {
2790
+ return [...client_adapters].sort((a, b) => b.label.localeCompare(a.label));
2791
+ }
2792
+ async function select_client_adapter() {
2793
+ const adapters = sorted_client_adapters();
2794
+ const client_id = await select({
2795
+ message: "Which MCP client?",
2796
+ options: adapters.map((adapter) => ({
2797
+ value: adapter.id,
2798
+ label: adapter.label
2799
+ })),
2800
+ initialValue: adapters[0]?.id
2801
+ });
2802
+ if (isCancel(client_id)) return null;
2803
+ return client_adapters.find((adapter) => adapter.id === client_id) ?? null;
2804
+ }
2805
+ async function select_client_location(adapter) {
2806
+ const locations = adapter.locations();
2807
+ if (locations.length === 1) return locations[0];
2808
+ const location_path = await select({
2809
+ message: `Which ${adapter.label} configuration?`,
2810
+ options: locations.map((location) => ({
2811
+ value: location.path,
2812
+ label: `${location.scope} — ${location.description}`,
2813
+ hint: location.path
2814
+ }))
2815
+ });
2816
+ if (isCancel(location_path)) return null;
2817
+ return locations.find((location) => location.path === location_path) ?? null;
2818
+ }
2819
+ async function handle_load_profile() {
2776
2820
  const profiles = await list_profiles();
2777
2821
  if (profiles.length === 0) {
2778
2822
  log.warn("No profiles found");
@@ -2792,12 +2836,23 @@ async function handle_load_claude_profile() {
2792
2836
  })
2793
2837
  });
2794
2838
  if (isCancel(profile_name)) return;
2795
- const result = await apply_profile_to_claude(profile_name);
2839
+ const adapter = await select_client_adapter();
2840
+ if (!adapter) return;
2841
+ const location = await select_client_location(adapter);
2842
+ if (!location) return;
2843
+ const result = await apply_profile_to_client({
2844
+ name: profile_name,
2845
+ client: adapter.id,
2846
+ scope: location.scope,
2847
+ location: location.path
2848
+ });
2796
2849
  const parts = [`${result.serverCount} servers`];
2797
- if (result.pluginCount > 0) parts.push(`${result.pluginCount} plugins`);
2798
- log.success(`Profile '${result.profile}' applied (${parts.join(", ")})`);
2850
+ if (result.pluginCount > 0) parts.push(`${result.pluginCount} Claude plugins`);
2851
+ log.success(`Profile '${result.profile}' applied to ${adapter.label}:${location.scope} (${parts.join(", ")})`);
2852
+ if (result.location) log.info(`Config: ${result.location}`);
2853
+ if (result.backup_path) log.info(`Backup: ${result.backup_path}`);
2799
2854
  }
2800
- async function handle_save_claude_profile() {
2855
+ async function handle_save_profile() {
2801
2856
  const name = await text({
2802
2857
  message: "Profile name:",
2803
2858
  placeholder: "e.g. database, web-dev, minimal",
@@ -2807,19 +2862,30 @@ async function handle_save_claude_profile() {
2807
2862
  }
2808
2863
  });
2809
2864
  if (isCancel(name)) return;
2810
- const result = await save_current_claude_profile(name);
2865
+ const adapter = await select_client_adapter();
2866
+ if (!adapter) return;
2867
+ const location = await select_client_location(adapter);
2868
+ if (!location) return;
2869
+ const result = await save_profile_for_client({
2870
+ name,
2871
+ client: adapter.id,
2872
+ scope: location.scope,
2873
+ location: location.path
2874
+ });
2811
2875
  const parts = [`${result.serverCount} servers`];
2812
- if (result.pluginCount > 0) parts.push(`${result.pluginCount} plugins`);
2813
- log.success(`Profile '${result.profile}' saved (${parts.join(", ")})`);
2876
+ if (result.pluginCount > 0) parts.push(`${result.pluginCount} Claude plugins`);
2877
+ log.success(`Profile '${result.profile}' saved from ${adapter.label}:${location.scope} (${parts.join(", ")})`);
2878
+ if (result.location) log.info(`Config: ${result.location}`);
2814
2879
  }
2815
2880
  async function handle_client_tools() {
2881
+ const adapters = sorted_client_adapters();
2816
2882
  const client_id = await select({
2817
2883
  message: "Which client?",
2818
- options: client_adapters.map((adapter) => ({
2884
+ options: adapters.map((adapter) => ({
2819
2885
  value: adapter.id,
2820
2886
  label: adapter.label
2821
2887
  })),
2822
- initialValue: "claude-code"
2888
+ initialValue: adapters[0]?.id
2823
2889
  });
2824
2890
  if (isCancel(client_id)) return;
2825
2891
  if (client_id !== "claude-code") {
@@ -2908,13 +2974,13 @@ async function main() {
2908
2974
  },
2909
2975
  {
2910
2976
  value: "load-profile",
2911
- label: "Load Claude Code profile",
2912
- hint: "Apply a saved Claude Code profile"
2977
+ label: "Load profile",
2978
+ hint: "Apply a saved profile to a selected client"
2913
2979
  },
2914
2980
  {
2915
2981
  value: "save-profile",
2916
- label: "Save Claude Code profile",
2917
- hint: "Save current Claude Code config as profile"
2982
+ label: "Save profile",
2983
+ hint: "Save selected client config as a portable profile"
2918
2984
  },
2919
2985
  {
2920
2986
  value: "backup",
@@ -2954,10 +3020,10 @@ async function main() {
2954
3020
  await restore_config();
2955
3021
  break;
2956
3022
  case "load-profile":
2957
- await handle_load_claude_profile();
3023
+ await handle_load_profile();
2958
3024
  break;
2959
3025
  case "save-profile":
2960
- await handle_save_claude_profile();
3026
+ await handle_save_profile();
2961
3027
  break;
2962
3028
  case "exit":
2963
3029
  outro("Goodbye!");
@@ -3008,12 +3074,12 @@ const SUBCOMMANDS = new Set([
3008
3074
  const arg = process.argv[2];
3009
3075
  if (arg && SUBCOMMANDS.has(arg) || arg === "--help" || arg === "-h" || !process.stdout.isTTY) {
3010
3076
  if (!arg && !process.stdout.isTTY) process.argv.push("--help");
3011
- import("./cli-CZOlaqoZ.js").then((m) => m.run());
3077
+ import("./cli-sOeHH4CK.js").then((m) => m.run());
3012
3078
  } else main().catch((error) => {
3013
3079
  console.error("Fatal error:", error);
3014
3080
  process.exit(1);
3015
3081
  });
3016
3082
  //#endregion
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 };
3083
+ export { read_server_registry as A, build_enabled_plugins as B, uninstall_plugin_via_cli as C, get_all_available_servers as D, add_server_to_registry as E, list_client_locations as F, write_claude_settings as G, get_all_plugins as H, remove_client_server as I, restore_config_backup as J, atomic_json_write as K, resolve_client_location as L, add_client_server as M, add_client_server_config as N, list_backups as O, get_client_adapter as P, set_client_server_enabled as R, remove_mcp_via_cli as S, validate_plugin_via_cli as T, read_claude_settings as U, get_all_hooks as V, remove_hook as W, marketplace_remove_via_cli as _, save_profile_for_client as a, mcp_get_via_cli as b, check_restored_hooks as c, read_disabled_hooks as d, redisable_restored_hooks as f, marketplace_list_via_cli as g, marketplace_add_via_cli as h, save_current_claude_profile as i, write_server_registry as j, list_plugin_backups as k, disable_plugin_hook as l, install_plugin_via_cli as m, apply_profile_to_client as n, run_skills_cli as o, add_mcp_via_cli as p, list_config_backups as q, list_profiles as r, split_cli_list as s, apply_profile_to_claude as t, enable_plugin_hook as u, marketplace_update_via_cli as v, update_plugin_via_cli as w, mcp_reset_project_choices_via_cli as x, mcp_add_json_via_cli as y, add_hook as z };
3018
3084
 
3019
3085
  //# sourceMappingURL=index.js.map