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.
- package/CHANGELOG.md +21 -0
- package/README.md +8 -2
- package/dist/{add-Qzd8i-5k.js → add-7mhUpbrt.js} +22 -35
- package/dist/{add-json-DGmsjB0O.js → add-json-BMM2L4hv.js} +18 -34
- package/dist/{backup-C7fvikFw.js → backup-C-YJmgps.js} +4 -4
- package/dist/{cache-D3jjh5dD.js → cache-BOYZhUF6.js} +4 -3
- package/dist/{cli-CZOlaqoZ.js → cli-sOeHH4CK.js} +22 -22
- package/dist/{clients-Bh93TGP4.js → clients-D5KAuQ5U.js} +3 -3
- package/dist/{clone-MI8jJhTz.js → clone-BRJA55js.js} +5 -5
- package/dist/{config-DE58Fik_.js → config-Bzh374VP.js} +4 -13
- package/dist/{dev-51esdZG9.js → dev-B-WlQSqY.js} +4 -4
- package/dist/{disable-csYAn2Vk.js → disable-Br0aVG3u.js} +18 -37
- package/dist/{enable-B5GbmhL-.js → enable-DUolKCEH.js} +18 -37
- package/dist/{get-DacRZmwv.js → get-D-6Cl_CO.js} +3 -3
- package/dist/{hooks-C_x49qap.js → hooks-BKPmZViU.js} +3 -3
- package/dist/index.js +571 -505
- package/dist/{list-BeBtsiae.js → list-DMcaHDfM.js} +4 -4
- package/dist/{marketplace-BDC2YtvT.js → marketplace-DTW7Ys8k.js} +4 -4
- package/dist/mutation-ukRPw3qM.js +19 -0
- package/dist/{output-HtT5HCof.js → output-BS1TMOWt.js} +1 -1
- package/dist/{plugin-cache-DmLbh89d.js → plugin-cache-Dw1I2YuO.js} +10 -3
- package/dist/{plugins-Bkw-SKkZ.js → plugins-BzLD4og0.js} +4 -4
- package/dist/profile-CmIWUJH_.js +163 -0
- package/dist/{reload-Bl1mYK1I.js → reload-Di28s_rY.js} +2 -2
- package/dist/{remove-BSHgva79.js → remove-B32EuYRC.js} +18 -33
- package/dist/{reset-project-choices-BNLus9J9.js → reset-project-choices-DX4TnZ2i.js} +3 -3
- package/dist/{restore-YisgARhc.js → restore-ByS4xi0y.js} +5 -5
- package/dist/{rollback-GR1RkpXW.js → rollback-DdDJrA8y.js} +3 -3
- package/dist/{skills-rDTDqqZA.js → skills-pvyQ17XU.js} +3 -3
- package/dist/{validation-xMlbgGCF.js → validation-CfPAjPJ5.js} +21 -2
- package/package.json +1 -1
- package/dist/dry-run-XQ32fxPT.js +0 -20
- 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-
|
|
4
|
-
import {
|
|
5
|
-
import { a as redact_url, i as redact_text
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
584
|
+
return write_json_file(location.path, data);
|
|
657
585
|
},
|
|
658
|
-
async
|
|
659
|
-
const
|
|
660
|
-
|
|
661
|
-
|
|
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
|
|
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
|
|
841
|
-
if (!adapter.
|
|
842
|
-
|
|
843
|
-
|
|
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
|
|
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
|
-
|
|
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 = {
|
|
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] =
|
|
895
|
-
else registry.servers.push(
|
|
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-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2662
|
-
await write_claude_config(profile.
|
|
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.
|
|
2666
|
-
await write_claude_settings({ enabledPlugins: profile.
|
|
2667
|
-
pluginCount = Object.keys(profile.
|
|
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
|
|
2669
|
+
const files = (await readdir(profiles_dir)).filter((file) => file.endsWith(".json"));
|
|
2680
2670
|
const profiles = [];
|
|
2681
|
-
for (const file of
|
|
2671
|
+
for (const file of files) try {
|
|
2672
|
+
const name = file.replace(/\.json$/, "");
|
|
2682
2673
|
const path = get_profile_path(file);
|
|
2683
|
-
const
|
|
2684
|
-
const
|
|
2685
|
-
const
|
|
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
|
|
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.
|
|
2701
|
+
const servers = get_enabled_servers(config).map((server) => normalize_mcp_server(server.name, server));
|
|
2703
2702
|
const plugins = settings.enabledPlugins || {};
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
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
|
-
|
|
2713
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
2884
|
+
options: adapters.map((adapter) => ({
|
|
2819
2885
|
value: adapter.id,
|
|
2820
2886
|
label: adapter.label
|
|
2821
2887
|
})),
|
|
2822
|
-
initialValue:
|
|
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
|
|
2912
|
-
hint: "Apply a saved
|
|
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
|
|
2917
|
-
hint: "Save
|
|
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
|
|
3023
|
+
await handle_load_profile();
|
|
2958
3024
|
break;
|
|
2959
3025
|
case "save-profile":
|
|
2960
|
-
await
|
|
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-
|
|
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 {
|
|
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
|