mcpick 0.0.21 → 0.0.22

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 (43) hide show
  1. package/.github/copilot-instructions.md +39 -21
  2. package/CHANGELOG.md +19 -5
  3. package/CONTEXT.md +49 -0
  4. package/README.md +146 -127
  5. package/dist/{add-BDyaBew0.js → add-Bok0qbXi.js} +4 -5
  6. package/dist/{add-json-BjgzdeG-.js → add-json-C44vy2A_.js} +3 -3
  7. package/dist/{atomic-write-BqEykHp9.js → atomic-write-4lANmzsO.js} +1 -1
  8. package/dist/{backup-DSDhHI5f.js → backup-bdg6dvsb.js} +5 -5
  9. package/dist/{cache-D6kd7qE8.js → cache-CSUcGdZP.js} +3 -3
  10. package/dist/cli-avr5R1LO.js +111 -0
  11. package/dist/clients-CSQgqHzb.js +30 -0
  12. package/dist/{clone-DYKPEsar.js → clone-CQ0skkT6.js} +6 -7
  13. package/dist/{config-DijVdEFn.js → config-BhX4eAgg.js} +4 -4
  14. package/dist/{dev-DRJRNp7y.js → dev-CTDg5g-c.js} +6 -6
  15. package/dist/{disable-xJXZfUR_.js → disable-DLlOj7sc.js} +3 -4
  16. package/dist/{enable-RrpcN6la.js → enable-CGFYYC2A.js} +3 -4
  17. package/dist/{get-Bb1eOOIZ.js → get-l-eAJhBy.js} +3 -3
  18. package/dist/{hooks-Bmn7pUZa.js → hooks-BWZ_Kgx3.js} +4 -4
  19. package/dist/index.js +1479 -312
  20. package/dist/list-By--kltj.js +100 -0
  21. package/dist/{marketplace-DcKk5dc1.js → marketplace-DdiKDDKK.js} +4 -5
  22. package/dist/output-BgN9Uuxf.js +17 -0
  23. package/dist/{paths-BPISiJi4.js → paths-6wrIM8yh.js} +1 -1
  24. package/dist/{plugin-cache-Bby9Dxm9.js → plugin-cache-DKcW8LGV.js} +3 -3
  25. package/dist/{plugins-Dc7DN6R_.js → plugins-CsXE8AH4.js} +5 -5
  26. package/dist/{profile-CX97sMGp.js → profile-DzGPsdsl.js} +5 -5
  27. package/dist/redact-Dltz2gde.js +88 -0
  28. package/dist/{reload-CYDhkCVZ.js → reload-C29-vuvy.js} +2 -2
  29. package/dist/{remove-D1owHLhG.js → remove-B5q4rQRU.js} +3 -4
  30. package/dist/{reset-project-choices-BfRSNN3m.js → reset-project-choices-Dhh4CxIC.js} +3 -3
  31. package/dist/{restore-DdMfUljI.js → restore-BI8aiszM.js} +6 -6
  32. package/dist/{settings-DEcWtzLE.js → settings-CZR8bVfh.js} +5 -5
  33. package/dist/skills-DPBDmION.js +216 -0
  34. package/dist/{validation-xMlbgGCF.js → validation-qWlF51fw.js} +1 -1
  35. package/package.json +20 -6
  36. package/dist/claude-cli-DnmBJrjg.js +0 -445
  37. package/dist/cli-CsFfnWBo.js +0 -84
  38. package/dist/hook-state-Di8lUsPr.js +0 -171
  39. package/dist/list-B8YeDWt6.js +0 -64
  40. package/dist/output-BchYq0mR.js +0 -15
  41. package/dist/profile-DkY_lBEm.js +0 -70
  42. package/dist/redact-O35tjnRD.js +0 -26
  43. package/dist/registry-CfUKT7_C.js +0 -92
package/dist/index.js CHANGED
@@ -1,218 +1,16 @@
1
1
  #!/usr/bin/env node
2
- import { m as get_plugin_backup_filename, n as get_backup_filename, r as get_backups_dir, t as ensure_directory_exists } from "./paths-BPISiJi4.js";
3
- import { n as validate_mcp_server } from "./validation-xMlbgGCF.js";
4
- import { i as list_plugin_backups, n as get_all_available_servers, o as sync_servers_to_registry, r as list_backups, t as add_server_to_registry } from "./registry-CfUKT7_C.js";
5
- import { a as install_plugin_via_cli, c as marketplace_remove_via_cli, h as update_plugin_via_cli, i as get_scope_options, l as marketplace_update_via_cli, m as uninstall_plugin_via_cli, n as check_claude_cli, o as marketplace_add_via_cli, p as remove_mcp_via_cli, r as get_scope_description, t as add_mcp_via_cli } from "./claude-cli-DnmBJrjg.js";
6
- 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-DijVdEFn.js";
7
- import { a as read_claude_settings, i as get_all_plugins, n as build_enabled_plugins, r as get_all_hooks, s as write_claude_settings } from "./settings-DEcWtzLE.js";
8
- 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-Bby9Dxm9.js";
9
- import { a as redisable_restored_hooks, i as read_disabled_hooks, n as disable_plugin_hook, r as enable_plugin_hook, t as check_restored_hooks } from "./hook-state-Di8lUsPr.js";
10
- import { n as load_profile, r as save_profile, t as list_profiles } from "./profile-DkY_lBEm.js";
2
+ import { _ as get_profiles_dir, c as get_disabled_hooks_path, f as get_marketplaces_dir, g as get_profile_path, i as get_claude_config_path, m as get_plugin_backup_filename, n as get_backup_filename, p as get_mcpick_dir, r as get_backups_dir, t as ensure_directory_exists, y as get_server_registry_path } from "./paths-6wrIM8yh.js";
3
+ import { r as validate_server_registry, t as validate_claude_config } from "./validation-qWlF51fw.js";
4
+ import { a as get_enabled_servers, c as write_claude_config, n as create_config_from_servers, o as get_enabled_servers_for_scope, s as read_claude_config } from "./config-BhX4eAgg.js";
5
+ import { a as read_claude_settings, i as get_all_plugins, n as build_enabled_plugins, r as get_all_hooks, s as write_claude_settings } from "./settings-CZR8bVfh.js";
6
+ import { a as redact_url, i as redact_text } from "./redact-Dltz2gde.js";
7
+ import { d as refresh_all_marketplaces, l as read_known_marketplaces, n as clear_plugin_caches, r as get_cached_plugins_info, t as clean_orphaned_versions, u as read_marketplace_manifest } from "./plugin-cache-DKcW8LGV.js";
11
8
  import { cancel, confirm, intro, isCancel, log, multiselect, note, outro, select, text } from "@clack/prompts";
12
- import { readFile, readdir, unlink, writeFile } from "node:fs/promises";
13
- import { join } from "node:path";
14
- //#region src/commands/add-server.ts
15
- function format_server_details(server) {
16
- const details = [`Name: ${server.name}`];
17
- if ("command" in server) details.push(`Command: ${server.command} ${(server.args || []).join(" ")}`);
18
- if ("url" in server) details.push(`URL: ${server.url}`);
19
- details.push(`Description: ${server.description || "None"}`);
20
- if (server.type) details.push(`Transport: ${server.type}`);
21
- if (server.env) details.push(`Environment: ${Object.keys(server.env).length} variables`);
22
- if ("headers" in server && server.headers) details.push(`Headers: ${Object.keys(server.headers).length} headers`);
23
- return details;
24
- }
25
- async function add_server() {
26
- try {
27
- const cli_available = await check_claude_cli();
28
- const scope = await select({
29
- message: "Where should this server be installed?",
30
- options: get_scope_options(),
31
- initialValue: "local"
32
- });
33
- if (typeof scope === "symbol") return;
34
- const config_method = await select({
35
- message: "How would you like to add the server?",
36
- options: [{
37
- value: "json",
38
- label: "Paste JSON configuration",
39
- hint: "Paste complete server config as JSON"
40
- }, {
41
- value: "form",
42
- label: "Step-by-step form",
43
- hint: "Fill out fields one by one"
44
- }],
45
- initialValue: "json"
46
- });
47
- if (typeof config_method === "symbol") return;
48
- if (config_method === "json") return await add_server_from_json(scope, cli_available);
49
- const name = await text({
50
- message: "Server name:",
51
- placeholder: "e.g., mcp-sqlite-tools",
52
- validate: (value) => {
53
- if (!value || value.trim().length === 0) return "Server name is required";
54
- }
55
- });
56
- if (typeof name === "symbol") return;
57
- const command = await text({
58
- message: "Command to run:",
59
- placeholder: "e.g., uvx, npx, node",
60
- validate: (value) => {
61
- if (!value || value.trim().length === 0) return "Command is required";
62
- }
63
- });
64
- if (typeof command === "symbol") return;
65
- const args_input = await text({
66
- message: "Arguments (comma-separated):",
67
- placeholder: "e.g., mcp-sqlite-tools, --port, 3000",
68
- defaultValue: ""
69
- });
70
- if (typeof args_input === "symbol") return;
71
- const args = args_input.split(",").map((arg) => arg.trim()).filter((arg) => arg.length > 0);
72
- const description = await text({
73
- message: "Description (optional):",
74
- placeholder: "Brief description of what this server provides"
75
- });
76
- if (typeof description === "symbol") return;
77
- const configure_advanced = await confirm({
78
- message: "Configure advanced settings (env variables, transport, etc.)?",
79
- initialValue: false
80
- });
81
- if (typeof configure_advanced === "symbol") return;
82
- let server_data = {
83
- name: name.trim(),
84
- type: "stdio",
85
- command: command.trim(),
86
- args,
87
- ...description && description.trim() && { description: description.trim() }
88
- };
89
- if (configure_advanced) {
90
- const transport_type = await select({
91
- message: "Transport type:",
92
- options: [
93
- {
94
- value: "stdio",
95
- label: "stdio (default)",
96
- hint: "Standard input/output"
97
- },
98
- {
99
- value: "sse",
100
- label: "sse",
101
- hint: "Server-sent events"
102
- },
103
- {
104
- value: "http",
105
- label: "http",
106
- hint: "HTTP transport"
107
- }
108
- ],
109
- initialValue: "stdio"
110
- });
111
- if (typeof transport_type === "symbol") return;
112
- server_data.type = transport_type;
113
- if (transport_type === "sse" || transport_type === "http") {
114
- delete server_data.command;
115
- delete server_data.args;
116
- const url = await text({
117
- message: "Server URL:",
118
- placeholder: "e.g., http://localhost:3000",
119
- validate: (value) => {
120
- if (!value || value.trim().length === 0) return "URL is required for non-stdio transport";
121
- }
122
- });
123
- if (typeof url === "symbol") return;
124
- server_data.url = url.trim();
125
- }
126
- const env_input = await text({
127
- message: "Environment variables (KEY=value, comma-separated):",
128
- placeholder: "e.g., API_KEY=abc123, TIMEOUT=30"
129
- });
130
- if (typeof env_input === "symbol") return;
131
- if (env_input && env_input.trim()) {
132
- const env = {};
133
- env_input.split(",").forEach((pair) => {
134
- const [key, ...valueParts] = pair.split("=");
135
- if (key && valueParts.length > 0) env[key.trim()] = valueParts.join("=").trim();
136
- });
137
- if (Object.keys(env).length > 0) server_data.env = env;
138
- }
139
- if (transport_type === "http") {
140
- const headers_input = await text({
141
- message: "HTTP headers (KEY=value, comma-separated):",
142
- placeholder: "e.g., Authorization=Bearer token, Content-Type=application/json"
143
- });
144
- if (typeof headers_input === "symbol") return;
145
- if (headers_input && headers_input.trim()) {
146
- const headers = {};
147
- headers_input.split(",").forEach((pair) => {
148
- const [key, ...valueParts] = pair.split("=");
149
- if (key && valueParts.length > 0) headers[key.trim()] = valueParts.join("=").trim();
150
- });
151
- if (Object.keys(headers).length > 0) server_data.headers = headers;
152
- }
153
- }
154
- }
155
- const validated_server = validate_mcp_server(server_data);
156
- const details = format_server_details(validated_server);
157
- details.push(`Scope: ${get_scope_description(scope)}`);
158
- note(`Server to add:\n${details.join("\n")}`);
159
- const should_add = await confirm({ message: "Add this server?" });
160
- if (typeof should_add === "symbol" || !should_add) return;
161
- await add_server_to_registry(validated_server);
162
- if (cli_available) {
163
- const result = await add_mcp_via_cli(validated_server, scope);
164
- if (result.success) note(`Server "${validated_server.name}" installed successfully!\nScope: ${get_scope_description(scope)}\nAlso added to mcpick registry for profile management.`);
165
- else log.warn(`CLI installation failed: ${result.error}\nServer added to registry only. Use 'claude mcp add' manually.`);
166
- } else log.warn("Claude CLI not found. Server added to registry only.\nInstall Claude Code CLI and run 'claude mcp add' to activate.");
167
- } catch (error) {
168
- throw new Error(`Failed to add server: ${error instanceof Error ? error.message : "Unknown error"}`);
169
- }
170
- }
171
- async function add_server_from_json(scope, cli_available) {
172
- const json_input = await text({
173
- message: "Paste JSON configuration:",
174
- placeholder: "{ \"name\": \"mcp-sqlite-tools\", \"command\": \"npx\", \"args\": [\"-y\", \"mcp-sqlite-tools\"] }",
175
- validate: (value) => {
176
- if (!value || value.trim().length === 0) return "JSON configuration is required";
177
- let jsonString = value.trim();
178
- if (!jsonString.startsWith("{")) jsonString = `{${jsonString}}`;
179
- try {
180
- const parsed = JSON.parse(jsonString);
181
- if (typeof parsed !== "object" || parsed === null) return "JSON must be an object";
182
- if (!parsed.command) return "Server configuration must include a \"command\" field";
183
- } catch {
184
- return "Invalid JSON format";
185
- }
186
- }
187
- });
188
- if (typeof json_input === "symbol") return;
189
- try {
190
- let jsonString = json_input.trim();
191
- if (!jsonString.startsWith("{")) jsonString = `{${jsonString}}`;
192
- const server_data = JSON.parse(jsonString);
193
- if (!server_data.type && server_data.command) server_data.type = "stdio";
194
- if (server_data.type !== "stdio") {
195
- delete server_data.command;
196
- delete server_data.args;
197
- }
198
- if (server_data.command && !server_data.args) server_data.args = [];
199
- const validated_server = validate_mcp_server(server_data);
200
- const details = format_server_details(validated_server);
201
- details.push(`Scope: ${get_scope_description(scope)}`);
202
- note(`Server to add:\n${details.join("\n")}`);
203
- const should_add = await confirm({ message: "Add this server?" });
204
- if (typeof should_add === "symbol" || !should_add) return;
205
- await add_server_to_registry(validated_server);
206
- if (cli_available) {
207
- const result = await add_mcp_via_cli(validated_server, scope);
208
- if (result.success) note(`Server "${validated_server.name}" installed successfully!\nScope: ${get_scope_description(scope)}\nAlso added to mcpick registry for profile management.`);
209
- else log.warn(`CLI installation failed: ${result.error}\nServer added to registry only. Use 'claude mcp add' manually.`);
210
- } else log.warn("Claude CLI not found. Server added to registry only.\nInstall Claude Code CLI and run 'claude mcp add' to activate.");
211
- } catch (error) {
212
- throw new Error(`Failed to parse or validate JSON: ${error instanceof Error ? error.message : "Unknown error"}`);
213
- }
214
- }
215
- //#endregion
9
+ import { access, mkdir, readFile, readdir, rename, unlink, writeFile } from "node:fs/promises";
10
+ import { dirname, join } from "node:path";
11
+ import { homedir } from "node:os";
12
+ import { execFile } from "node:child_process";
13
+ import { promisify } from "node:util";
216
14
  //#region src/commands/backup.ts
217
15
  const MAX_BACKUPS = 10;
218
16
  async function backup_config() {
@@ -251,79 +49,1024 @@ async function cleanup_old_backups(prefix) {
251
49
  } catch {}
252
50
  }
253
51
  //#endregion
52
+ //#region src/core/client-config.ts
53
+ const client_options_to_skip = new Set([
54
+ "name",
55
+ "type",
56
+ "command",
57
+ "args",
58
+ "url",
59
+ "httpUrl",
60
+ "serverUrl",
61
+ "env",
62
+ "headers",
63
+ "description",
64
+ "disabled",
65
+ "enabled",
66
+ "environment"
67
+ ]);
68
+ async function file_exists(path) {
69
+ try {
70
+ await access(path);
71
+ return true;
72
+ } catch {
73
+ return false;
74
+ }
75
+ }
76
+ async function read_json_file(path) {
77
+ try {
78
+ return parse_json_or_jsonc(await readFile(path, "utf-8"));
79
+ } catch (error) {
80
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return null;
81
+ throw error;
82
+ }
83
+ }
84
+ async function write_json_file(path, data) {
85
+ await mkdir(dirname(path), { recursive: true });
86
+ const tmp_path = join(dirname(path), `.${Date.now()}.tmp`);
87
+ await writeFile(tmp_path, JSON.stringify(data, null, 2), "utf-8");
88
+ await rename(tmp_path, path);
89
+ }
90
+ function parse_json_or_jsonc(content) {
91
+ try {
92
+ return JSON.parse(content);
93
+ } catch {
94
+ return JSON.parse(remove_jsonc_syntax(content));
95
+ }
96
+ }
97
+ function remove_jsonc_syntax(content) {
98
+ let result = "";
99
+ let in_string = false;
100
+ let quote = "";
101
+ let escaped = false;
102
+ let in_line_comment = false;
103
+ let in_block_comment = false;
104
+ for (let index = 0; index < content.length; index++) {
105
+ const char = content[index];
106
+ const next = content[index + 1];
107
+ if (in_line_comment) {
108
+ if (char === "\n" || char === "\r") {
109
+ in_line_comment = false;
110
+ result += char;
111
+ }
112
+ continue;
113
+ }
114
+ if (in_block_comment) {
115
+ if (char === "*" && next === "/") {
116
+ in_block_comment = false;
117
+ index++;
118
+ }
119
+ continue;
120
+ }
121
+ if (in_string) {
122
+ result += char;
123
+ if (escaped) escaped = false;
124
+ else if (char === "\\") escaped = true;
125
+ else if (char === quote) in_string = false;
126
+ continue;
127
+ }
128
+ if (char === "\"" || char === "'") {
129
+ in_string = true;
130
+ quote = char;
131
+ result += char;
132
+ continue;
133
+ }
134
+ if (char === "/" && next === "/") {
135
+ in_line_comment = true;
136
+ index++;
137
+ continue;
138
+ }
139
+ if (char === "/" && next === "*") {
140
+ in_block_comment = true;
141
+ index++;
142
+ continue;
143
+ }
144
+ result += char;
145
+ }
146
+ return remove_trailing_commas(result);
147
+ }
148
+ function remove_trailing_commas(content) {
149
+ let result = "";
150
+ let in_string = false;
151
+ let quote = "";
152
+ let escaped = false;
153
+ for (let index = 0; index < content.length; index++) {
154
+ const char = content[index];
155
+ if (in_string) {
156
+ result += char;
157
+ if (escaped) escaped = false;
158
+ else if (char === "\\") escaped = true;
159
+ else if (char === quote) in_string = false;
160
+ continue;
161
+ }
162
+ if (char === "\"") {
163
+ in_string = true;
164
+ quote = char;
165
+ result += char;
166
+ continue;
167
+ }
168
+ if (char === ",") {
169
+ let cursor = index + 1;
170
+ while (/\s/.test(content[cursor] ?? "")) cursor++;
171
+ if (content[cursor] === "}" || content[cursor] === "]") continue;
172
+ }
173
+ result += char;
174
+ }
175
+ return result;
176
+ }
177
+ function string_record(value) {
178
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
179
+ const result = {};
180
+ for (const [key, item] of Object.entries(value)) if (typeof item === "string") result[key] = item;
181
+ return Object.keys(result).length > 0 ? result : void 0;
182
+ }
183
+ function string_array(value) {
184
+ if (!Array.isArray(value)) return void 0;
185
+ const values = value.filter((item) => typeof item === "string");
186
+ return values.length > 0 ? values : void 0;
187
+ }
188
+ function infer_transport(config) {
189
+ if (config.type === "http" || config.type === "remote" || config.httpUrl || config.serverUrl) return "http";
190
+ if (config.type === "sse") return "sse";
191
+ if (config.url && !config.command) return "http";
192
+ return "stdio";
193
+ }
194
+ function normalize_mcp_server(name, config) {
195
+ const transport = infer_transport(config);
196
+ const url = typeof config.httpUrl === "string" ? config.httpUrl : typeof config.serverUrl === "string" ? config.serverUrl : typeof config.url === "string" ? config.url : void 0;
197
+ const clientOptions = {};
198
+ for (const [key, value] of Object.entries(config)) if (!client_options_to_skip.has(key)) clientOptions[key] = value;
199
+ const command_array = string_array(config.command);
200
+ const command = typeof config.command === "string" ? config.command : command_array?.[0];
201
+ const args = string_array(config.args) ?? command_array?.slice(1);
202
+ const env = string_record(config.env) ?? string_record(config.environment);
203
+ const disabled = typeof config.disabled === "boolean" ? config.disabled : typeof config.enabled === "boolean" ? !config.enabled : void 0;
204
+ return {
205
+ name,
206
+ transport,
207
+ ...command ? { command } : {},
208
+ ...args && args.length > 0 ? { args } : {},
209
+ ...url ? { url } : {},
210
+ ...env ? { env } : {},
211
+ ...string_record(config.headers) ? { headers: string_record(config.headers) } : {},
212
+ ...typeof config.description === "string" ? { description: config.description } : {},
213
+ ...typeof disabled === "boolean" ? { disabled } : {},
214
+ ...Object.keys(clientOptions).length > 0 ? { clientOptions } : {}
215
+ };
216
+ }
217
+ function get_server_record(data, key) {
218
+ const servers = data?.[key];
219
+ if (!servers || typeof servers !== "object" || Array.isArray(servers)) return {};
220
+ return Object.fromEntries(Object.entries(servers).filter((entry) => {
221
+ const [, value] = entry;
222
+ return !!value && typeof value === "object" && !Array.isArray(value);
223
+ }));
224
+ }
225
+ function read_server_map(data, key) {
226
+ return Object.entries(get_server_record(data, key)).map(([name, config]) => normalize_mcp_server(name, config));
227
+ }
228
+ function set_server_enabled(config, enabled, mode) {
229
+ if (mode === "enabled" || "enabled" in config) {
230
+ config.enabled = enabled;
231
+ delete config.disabled;
232
+ return;
233
+ }
234
+ config.disabled = !enabled;
235
+ }
236
+ function create_json_adapter(options) {
237
+ return {
238
+ id: options.id,
239
+ label: options.label,
240
+ locations: options.locations,
241
+ async read(scope) {
242
+ const locations = scope ? options.locations().filter((location) => location.scope === scope) : options.locations();
243
+ const result = [];
244
+ for (const location of locations) for (const server of await this.readLocation(location)) result.push(server);
245
+ return result;
246
+ },
247
+ async readLocation(location) {
248
+ return read_server_map(await read_json_file(location.path), options.serverKey);
249
+ },
250
+ async writeEnabled(location, enabledNames) {
251
+ const data = await read_json_file(location.path) ?? {};
252
+ const servers = get_server_record(data, options.serverKey);
253
+ const enabled = new Set(enabledNames);
254
+ for (const [name, config] of Object.entries(servers)) set_server_enabled(config, enabled.has(name), options.disabledMode ?? "disabled");
255
+ data[options.serverKey] = servers;
256
+ await write_json_file(location.path, data);
257
+ }
258
+ };
259
+ }
260
+ function project_path(path) {
261
+ return join(process.cwd(), path);
262
+ }
263
+ const client_adapters = [
264
+ {
265
+ id: "claude-code",
266
+ label: "Claude Code",
267
+ locations: () => [
268
+ {
269
+ scope: "local",
270
+ path: get_claude_config_path(),
271
+ description: "~/.claude.json projects[cwd].mcpServers"
272
+ },
273
+ {
274
+ scope: "project",
275
+ path: project_path(".mcp.json"),
276
+ description: ".mcp.json mcpServers"
277
+ },
278
+ {
279
+ scope: "user",
280
+ path: get_claude_config_path(),
281
+ description: "~/.claude.json mcpServers"
282
+ }
283
+ ],
284
+ async read(scope) {
285
+ const locations = scope ? this.locations().filter((location) => location.scope === scope) : this.locations();
286
+ const result = [];
287
+ for (const location of locations) result.push(...await this.readLocation(location));
288
+ return result;
289
+ },
290
+ async readLocation(location) {
291
+ if (location.scope === "project") return read_server_map(await read_json_file(location.path), "mcpServers");
292
+ const data = await read_json_file(get_claude_config_path());
293
+ if (location.scope === "user") return read_server_map(data, "mcpServers");
294
+ const projects = data?.projects;
295
+ if (!projects || typeof projects !== "object" || Array.isArray(projects)) return [];
296
+ const project_config = projects[process.cwd()];
297
+ if (!project_config || typeof project_config !== "object" || Array.isArray(project_config)) return [];
298
+ return read_server_map(project_config, "mcpServers");
299
+ }
300
+ },
301
+ create_json_adapter({
302
+ id: "gemini-cli",
303
+ label: "Gemini CLI",
304
+ serverKey: "mcpServers",
305
+ locations: () => [{
306
+ scope: "project",
307
+ path: project_path(".gemini/settings.json"),
308
+ description: ".gemini/settings.json mcpServers"
309
+ }, {
310
+ scope: "user",
311
+ path: join(homedir(), ".gemini/settings.json"),
312
+ description: "~/.gemini/settings.json mcpServers"
313
+ }]
314
+ }),
315
+ create_json_adapter({
316
+ id: "vscode",
317
+ label: "VS Code / GitHub Copilot",
318
+ serverKey: "servers",
319
+ locations: () => [{
320
+ scope: "project",
321
+ path: project_path(".vscode/mcp.json"),
322
+ description: ".vscode/mcp.json servers"
323
+ }]
324
+ }),
325
+ create_json_adapter({
326
+ id: "cursor",
327
+ label: "Cursor",
328
+ serverKey: "mcpServers",
329
+ locations: () => [{
330
+ scope: "project",
331
+ path: project_path(".cursor/mcp.json"),
332
+ description: ".cursor/mcp.json mcpServers"
333
+ }, {
334
+ scope: "user",
335
+ path: join(homedir(), ".cursor/mcp.json"),
336
+ description: "~/.cursor/mcp.json mcpServers"
337
+ }]
338
+ }),
339
+ create_json_adapter({
340
+ id: "windsurf",
341
+ label: "Windsurf",
342
+ serverKey: "mcpServers",
343
+ locations: () => [{
344
+ scope: "user",
345
+ path: join(homedir(), ".codeium/windsurf/mcp_config.json"),
346
+ description: "~/.codeium/windsurf/mcp_config.json mcpServers"
347
+ }]
348
+ }),
349
+ create_json_adapter({
350
+ id: "opencode",
351
+ label: "OpenCode",
352
+ serverKey: "mcp",
353
+ disabledMode: "enabled",
354
+ locations: () => [{
355
+ scope: "project",
356
+ path: project_path("opencode.json"),
357
+ description: "opencode.json mcp"
358
+ }, {
359
+ scope: "user",
360
+ path: join(homedir(), ".config/opencode/opencode.json"),
361
+ description: "~/.config/opencode/opencode.json mcp"
362
+ }]
363
+ }),
364
+ create_json_adapter({
365
+ id: "pi",
366
+ label: "Pi MCP Adapter",
367
+ serverKey: "mcpServers",
368
+ locations: () => [
369
+ {
370
+ scope: "user",
371
+ path: join(homedir(), ".config/mcp/mcp.json"),
372
+ description: "~/.config/mcp/mcp.json shared MCP config"
373
+ },
374
+ {
375
+ scope: "user",
376
+ path: join(homedir(), ".pi/agent/mcp.json"),
377
+ description: "~/.pi/agent/mcp.json Pi global override"
378
+ },
379
+ {
380
+ scope: "project",
381
+ path: project_path(".mcp.json"),
382
+ description: ".mcp.json shared project MCP config"
383
+ },
384
+ {
385
+ scope: "project",
386
+ path: project_path(".pi/mcp.json"),
387
+ description: ".pi/mcp.json Pi project override"
388
+ }
389
+ ]
390
+ })
391
+ ];
392
+ function get_client_adapter(id) {
393
+ return client_adapters.find((adapter) => adapter.id === id) ?? null;
394
+ }
395
+ async function list_client_locations() {
396
+ return await Promise.all(client_adapters.flatMap((adapter) => adapter.locations().map(async (location) => ({
397
+ client: adapter.id,
398
+ label: adapter.label,
399
+ ...location,
400
+ exists: await file_exists(location.path)
401
+ }))));
402
+ }
403
+ //#endregion
404
+ //#region src/core/registry.ts
405
+ async function read_server_registry() {
406
+ const registry_path = get_server_registry_path();
407
+ try {
408
+ await access(registry_path);
409
+ const registry_content = await readFile(registry_path, "utf-8");
410
+ return validate_server_registry(JSON.parse(registry_content));
411
+ } catch (error) {
412
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
413
+ await ensure_directory_exists(get_mcpick_dir());
414
+ const default_registry = { servers: [] };
415
+ await write_server_registry(default_registry);
416
+ return default_registry;
417
+ }
418
+ throw error;
419
+ }
420
+ }
421
+ async function write_server_registry(registry) {
422
+ const registry_path = get_server_registry_path();
423
+ await ensure_directory_exists(get_mcpick_dir());
424
+ await writeFile(registry_path, JSON.stringify(registry, null, 2), "utf-8");
425
+ }
426
+ async function add_server_to_registry(server) {
427
+ const registry = await read_server_registry();
428
+ const existing_index = registry.servers.findIndex((s) => s.name === server.name);
429
+ if (existing_index >= 0) registry.servers[existing_index] = server;
430
+ else registry.servers.push(server);
431
+ await write_server_registry(registry);
432
+ }
433
+ async function get_all_available_servers() {
434
+ const { get_enabled_servers, read_claude_config } = await import("./config-BhX4eAgg.js").then((n) => n.t);
435
+ const registry = await read_server_registry();
436
+ const config_servers = get_enabled_servers(await read_claude_config());
437
+ const config_by_name = new Map(config_servers.map((s) => [s.name, s]));
438
+ const known_names = /* @__PURE__ */ new Set();
439
+ let registry_updated = false;
440
+ for (let i = 0; i < registry.servers.length; i++) {
441
+ const name = registry.servers[i].name;
442
+ known_names.add(name);
443
+ const config_server = config_by_name.get(name);
444
+ if (config_server) {
445
+ registry.servers[i] = config_server;
446
+ registry_updated = true;
447
+ }
448
+ }
449
+ for (const server of config_servers) if (!known_names.has(server.name)) {
450
+ registry.servers.push(server);
451
+ registry_updated = true;
452
+ }
453
+ if (registry_updated) await write_server_registry(registry);
454
+ return registry.servers;
455
+ }
456
+ async function sync_servers_to_registry(servers) {
457
+ const registry = await read_server_registry();
458
+ servers.forEach((server) => {
459
+ const existing_index = registry.servers.findIndex((s) => s.name === server.name);
460
+ if (existing_index >= 0) registry.servers[existing_index] = server;
461
+ else registry.servers.push(server);
462
+ });
463
+ await write_server_registry(registry);
464
+ }
465
+ function parse_backups(prefix, pattern) {
466
+ return async () => {
467
+ const backups_dir = get_backups_dir();
468
+ try {
469
+ await access(backups_dir);
470
+ return (await readdir(backups_dir)).filter((file) => file.startsWith(prefix) && file.endsWith(".json")).map((file) => {
471
+ const timestamp_match = file.match(pattern);
472
+ if (!timestamp_match) return null;
473
+ const [, year, month, day, hour, minute, second] = timestamp_match;
474
+ return {
475
+ filename: file,
476
+ timestamp: new Date(parseInt(year), parseInt(month) - 1, parseInt(day), parseInt(hour), parseInt(minute), parseInt(second)),
477
+ path: join(backups_dir, file)
478
+ };
479
+ }).filter((backup) => backup !== null).sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
480
+ } catch (error) {
481
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return [];
482
+ throw error;
483
+ }
484
+ };
485
+ }
486
+ const list_backups = parse_backups("mcp-servers-", /mcp-servers-(\d{4})-(\d{2})-(\d{2})-(\d{2})(\d{2})(\d{2})\.json/);
487
+ const list_plugin_backups = parse_backups("plugins-", /plugins-(\d{4})-(\d{2})-(\d{2})-(\d{2})(\d{2})(\d{2})\.json/);
488
+ //#endregion
489
+ //#region src/utils/claude-cli.ts
490
+ const exec_file_async$1 = promisify(execFile);
491
+ /**
492
+ * Check if Claude CLI is available
493
+ */
494
+ async function check_claude_cli() {
495
+ try {
496
+ await exec_file_async$1("claude", ["--version"]);
497
+ return true;
498
+ } catch {
499
+ return false;
500
+ }
501
+ }
502
+ /**
503
+ * Validate environment variable key.
504
+ * Must start with letter or underscore, contain only alphanumeric and underscores.
505
+ */
506
+ function is_valid_env_key(key) {
507
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(key);
508
+ }
509
+ /**
510
+ * Build args array for claude mcp add command.
511
+ * Returns raw args — no shell escaping needed since we use execFile.
512
+ */
513
+ function build_add_args(server, scope) {
514
+ const args = ["mcp", "add"];
515
+ args.push(server.name);
516
+ const transport = server.type || "stdio";
517
+ if (transport !== "stdio") args.push("--transport", transport);
518
+ args.push("--scope", scope);
519
+ if (transport === "stdio") {
520
+ if (server.env) {
521
+ for (const [key, value] of Object.entries(server.env)) if (is_valid_env_key(key)) args.push("-e", `${key}=${value}`);
522
+ }
523
+ if ("command" in server && server.command) {
524
+ args.push("--");
525
+ args.push(server.command);
526
+ if (server.args && server.args.length > 0) args.push(...server.args);
527
+ }
528
+ } else {
529
+ if ("url" in server && server.url) args.push(server.url);
530
+ if ("headers" in server && server.headers) for (const [key, value] of Object.entries(server.headers)) args.push("-H", `${key}: ${value}`);
531
+ }
532
+ return args;
533
+ }
534
+ /**
535
+ * Run a claude CLI command using execFile (no shell).
536
+ * This avoids all shell escaping issues on every platform.
537
+ */
538
+ async function run_claude(args) {
539
+ const result = await exec_file_async$1("claude", args);
540
+ return {
541
+ stdout: redact_text(result.stdout),
542
+ stderr: redact_text(result.stderr)
543
+ };
544
+ }
545
+ function get_redacted_error_message(error) {
546
+ return redact_text(error instanceof Error ? error.message : "Unknown error");
547
+ }
548
+ /**
549
+ * Add an MCP server using Claude CLI
550
+ */
551
+ async function add_mcp_via_cli(server, scope) {
552
+ if (!await check_claude_cli()) return {
553
+ success: false,
554
+ error: "Claude CLI not found. Please install Claude Code CLI."
555
+ };
556
+ try {
557
+ await run_claude(build_add_args(server, scope));
558
+ return { success: true };
559
+ } catch (error) {
560
+ return {
561
+ success: false,
562
+ error: `Failed to add server via CLI: ${get_redacted_error_message(error)}`
563
+ };
564
+ }
565
+ }
566
+ /**
567
+ * Remove an MCP server using Claude CLI
568
+ */
569
+ async function remove_mcp_via_cli(name) {
570
+ if (!await check_claude_cli()) return {
571
+ success: false,
572
+ error: "Claude CLI not found. Please install Claude Code CLI."
573
+ };
574
+ try {
575
+ await run_claude([
576
+ "mcp",
577
+ "remove",
578
+ name
579
+ ]);
580
+ return { success: true };
581
+ } catch (error) {
582
+ return {
583
+ success: false,
584
+ error: `Failed to remove server via CLI: ${get_redacted_error_message(error)}`
585
+ };
586
+ }
587
+ }
588
+ /**
589
+ * Install a plugin via Claude CLI
590
+ */
591
+ async function install_plugin_via_cli(key, scope = "user") {
592
+ if (!await check_claude_cli()) return {
593
+ success: false,
594
+ error: "Claude CLI not found. Please install Claude Code CLI."
595
+ };
596
+ try {
597
+ await run_claude([
598
+ "plugin",
599
+ "install",
600
+ key,
601
+ "--scope",
602
+ scope
603
+ ]);
604
+ return { success: true };
605
+ } catch (error) {
606
+ return {
607
+ success: false,
608
+ error: `Failed to install plugin: ${get_redacted_error_message(error)}`
609
+ };
610
+ }
611
+ }
612
+ /**
613
+ * Uninstall a plugin via Claude CLI
614
+ */
615
+ async function uninstall_plugin_via_cli(key, scope = "user") {
616
+ if (!await check_claude_cli()) return {
617
+ success: false,
618
+ error: "Claude CLI not found. Please install Claude Code CLI."
619
+ };
620
+ try {
621
+ await run_claude([
622
+ "plugin",
623
+ "uninstall",
624
+ key,
625
+ "--scope",
626
+ scope
627
+ ]);
628
+ return { success: true };
629
+ } catch (error) {
630
+ return {
631
+ success: false,
632
+ error: `Failed to uninstall plugin: ${get_redacted_error_message(error)}`
633
+ };
634
+ }
635
+ }
636
+ /**
637
+ * Update a plugin via Claude CLI
638
+ */
639
+ async function update_plugin_via_cli(key, scope = "user") {
640
+ if (!await check_claude_cli()) return {
641
+ success: false,
642
+ error: "Claude CLI not found. Please install Claude Code CLI."
643
+ };
644
+ try {
645
+ await run_claude([
646
+ "plugin",
647
+ "update",
648
+ key,
649
+ "--scope",
650
+ scope
651
+ ]);
652
+ return { success: true };
653
+ } catch (error) {
654
+ return {
655
+ success: false,
656
+ error: `Failed to update plugin: ${get_redacted_error_message(error)}`
657
+ };
658
+ }
659
+ }
660
+ /**
661
+ * Extract GitHub owner/repo from various source formats.
662
+ * Returns null if not a recognizable GitHub reference.
663
+ */
664
+ function parse_github_repo(source) {
665
+ const https_match = source.match(/^https?:\/\/github\.com\/([^/]+)\/([^/.]+)(?:\.git)?$/);
666
+ if (https_match) return {
667
+ owner: https_match[1],
668
+ repo: https_match[2]
669
+ };
670
+ const ssh_match = source.match(/^git@github\.com:([^/]+)\/([^/.]+)(?:\.git)?$/);
671
+ if (ssh_match) return {
672
+ owner: ssh_match[1],
673
+ repo: ssh_match[2]
674
+ };
675
+ const shorthand_match = source.match(/^([^/\s]+)\/([^/\s]+)$/);
676
+ if (shorthand_match) return {
677
+ owner: shorthand_match[1],
678
+ repo: shorthand_match[2]
679
+ };
680
+ return null;
681
+ }
682
+ /**
683
+ * Validate that a GitHub repository exists and is accessible.
684
+ * Returns an error message if validation fails, null if OK.
685
+ */
686
+ async function validate_github_repo(owner, repo) {
687
+ try {
688
+ const response = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
689
+ method: "GET",
690
+ headers: { Accept: "application/vnd.github.v3+json" }
691
+ });
692
+ if (response.status === 200) return null;
693
+ if (response.status === 404) return `Repository '${owner}/${repo}' not found on GitHub. Check the name or ensure it's not private.`;
694
+ if (response.status === 403) return `Access denied for '${owner}/${repo}'. The repository may be private — configure a GitHub token or use SSH.`;
695
+ return `GitHub API returned status ${response.status} for '${owner}/${repo}'.`;
696
+ } catch {
697
+ return null;
698
+ }
699
+ }
700
+ async function marketplace_add_via_cli(source) {
701
+ if (!await check_claude_cli()) return {
702
+ success: false,
703
+ error: "Claude CLI not found. Please install Claude Code CLI."
704
+ };
705
+ const gh = parse_github_repo(source);
706
+ const is_shorthand = gh && !source.startsWith("http") && !source.startsWith("git@");
707
+ if (gh && is_shorthand) {
708
+ const validation_error = await validate_github_repo(gh.owner, gh.repo);
709
+ if (validation_error) return {
710
+ success: false,
711
+ error: validation_error
712
+ };
713
+ }
714
+ try {
715
+ await run_claude([
716
+ "plugin",
717
+ "marketplace",
718
+ "add",
719
+ source
720
+ ]);
721
+ return { success: true };
722
+ } catch (error) {
723
+ const message = get_redacted_error_message(error);
724
+ if (message.includes("SSH") || message.includes("Permission denied (publickey)")) return {
725
+ success: false,
726
+ error: `SSH authentication failed for '${source}'. Either:\n - Configure SSH keys: https://docs.github.com/en/authentication/connecting-to-github-with-ssh\n - Use HTTPS URL instead: https://github.com/${gh ? `${gh.owner}/${gh.repo}` : source}`
727
+ };
728
+ if (message.includes("not found") || message.includes("does not exist")) return {
729
+ success: false,
730
+ error: `Repository '${source}' not found. Check the name and your access permissions.`
731
+ };
732
+ return {
733
+ success: false,
734
+ error: `Failed to add marketplace: ${message}`
735
+ };
736
+ }
737
+ }
738
+ /**
739
+ * Remove a marketplace via Claude CLI
740
+ */
741
+ async function marketplace_remove_via_cli(name) {
742
+ if (!await check_claude_cli()) return {
743
+ success: false,
744
+ error: "Claude CLI not found. Please install Claude Code CLI."
745
+ };
746
+ try {
747
+ await run_claude([
748
+ "plugin",
749
+ "marketplace",
750
+ "remove",
751
+ name
752
+ ]);
753
+ return { success: true };
754
+ } catch (error) {
755
+ return {
756
+ success: false,
757
+ error: `Failed to remove marketplace: ${get_redacted_error_message(error)}`
758
+ };
759
+ }
760
+ }
761
+ /**
762
+ * Update marketplace(s) via Claude CLI
763
+ */
764
+ async function marketplace_update_via_cli(name) {
765
+ if (!await check_claude_cli()) return {
766
+ success: false,
767
+ error: "Claude CLI not found. Please install Claude Code CLI."
768
+ };
769
+ try {
770
+ const args = [
771
+ "plugin",
772
+ "marketplace",
773
+ "update"
774
+ ];
775
+ if (name) args.push(name);
776
+ await run_claude(args);
777
+ return { success: true };
778
+ } catch (error) {
779
+ return {
780
+ success: false,
781
+ error: `Failed to update marketplace: ${get_redacted_error_message(error)}`
782
+ };
783
+ }
784
+ }
785
+ /**
786
+ * List marketplaces via Claude CLI
787
+ */
788
+ async function marketplace_list_via_cli() {
789
+ if (!await check_claude_cli()) return {
790
+ success: false,
791
+ error: "Claude CLI not found. Please install Claude Code CLI."
792
+ };
793
+ try {
794
+ const { stdout } = await run_claude([
795
+ "plugin",
796
+ "marketplace",
797
+ "list"
798
+ ]);
799
+ return {
800
+ success: true,
801
+ stdout: stdout.trim()
802
+ };
803
+ } catch (error) {
804
+ return {
805
+ success: false,
806
+ error: `Failed to list marketplaces: ${get_redacted_error_message(error)}`
807
+ };
808
+ }
809
+ }
810
+ /**
811
+ * Get the scope description for display
812
+ */
813
+ function get_scope_description(scope) {
814
+ switch (scope) {
815
+ case "local": return "This project only (default)";
816
+ case "project": return "Shared via .mcp.json (version controlled)";
817
+ case "user": return "Global - all projects";
818
+ }
819
+ }
820
+ /**
821
+ * Validate a plugin or marketplace manifest via Claude CLI
822
+ */
823
+ async function validate_plugin_via_cli(path) {
824
+ if (!await check_claude_cli()) return {
825
+ success: false,
826
+ error: "Claude CLI not found. Please install Claude Code CLI."
827
+ };
828
+ try {
829
+ const { stdout } = await run_claude([
830
+ "plugin",
831
+ "validate",
832
+ path
833
+ ]);
834
+ return {
835
+ success: true,
836
+ stdout: stdout.trim()
837
+ };
838
+ } catch (error) {
839
+ return {
840
+ success: false,
841
+ error: `Validation failed: ${get_redacted_error_message(error)}`
842
+ };
843
+ }
844
+ }
845
+ /**
846
+ * Get details about an MCP server via Claude CLI
847
+ */
848
+ async function mcp_get_via_cli(name) {
849
+ if (!await check_claude_cli()) return {
850
+ success: false,
851
+ error: "Claude CLI not found. Please install Claude Code CLI."
852
+ };
853
+ try {
854
+ const { stdout } = await run_claude([
855
+ "mcp",
856
+ "get",
857
+ name
858
+ ]);
859
+ return {
860
+ success: true,
861
+ stdout: stdout.trim()
862
+ };
863
+ } catch (error) {
864
+ return {
865
+ success: false,
866
+ error: `Failed to get server details: ${get_redacted_error_message(error)}`
867
+ };
868
+ }
869
+ }
870
+ /**
871
+ * Add an MCP server from raw JSON via Claude CLI
872
+ */
873
+ async function mcp_add_json_via_cli(name, json, scope = "local") {
874
+ if (!await check_claude_cli()) return {
875
+ success: false,
876
+ error: "Claude CLI not found. Please install Claude Code CLI."
877
+ };
878
+ try {
879
+ await run_claude([
880
+ "mcp",
881
+ "add-json",
882
+ name,
883
+ json,
884
+ "--scope",
885
+ scope
886
+ ]);
887
+ return { success: true };
888
+ } catch (error) {
889
+ return {
890
+ success: false,
891
+ error: `Failed to add server from JSON: ${get_redacted_error_message(error)}`
892
+ };
893
+ }
894
+ }
895
+ /**
896
+ * Reset project-scoped MCP server choices via Claude CLI
897
+ */
898
+ async function mcp_reset_project_choices_via_cli() {
899
+ if (!await check_claude_cli()) return {
900
+ success: false,
901
+ error: "Claude CLI not found. Please install Claude Code CLI."
902
+ };
903
+ try {
904
+ await run_claude(["mcp", "reset-project-choices"]);
905
+ return { success: true };
906
+ } catch (error) {
907
+ return {
908
+ success: false,
909
+ error: `Failed to reset project choices: ${get_redacted_error_message(error)}`
910
+ };
911
+ }
912
+ }
913
+ /**
914
+ * Get scope options for select prompt
915
+ */
916
+ function get_scope_options() {
917
+ return [
918
+ {
919
+ value: "local",
920
+ label: "Local",
921
+ hint: "This project only (default)"
922
+ },
923
+ {
924
+ value: "project",
925
+ label: "Project",
926
+ hint: "Shared via .mcp.json (git)"
927
+ },
928
+ {
929
+ value: "user",
930
+ label: "User (Global)",
931
+ hint: "Available in all projects"
932
+ }
933
+ ];
934
+ }
935
+ //#endregion
254
936
  //#region src/commands/edit-config.ts
255
937
  async function edit_config() {
256
938
  try {
257
- const cli_available = await check_claude_cli();
258
- const scope = await select({
259
- message: "Which configuration do you want to edit?",
260
- options: get_scope_options(),
261
- initialValue: "local"
939
+ const client_id = await select({
940
+ message: "Which MCP client do you want to edit?",
941
+ options: client_adapters.map((adapter) => ({
942
+ value: adapter.id,
943
+ label: adapter.label
944
+ })),
945
+ initialValue: "claude-code"
262
946
  });
263
- if (typeof scope === "symbol") return;
264
- const current_config = await read_claude_config();
265
- let all_servers = await get_all_available_servers();
266
- if (all_servers.length === 0 && current_config.mcpServers) {
267
- const current_servers = get_enabled_servers(current_config);
268
- if (current_servers.length > 0) {
269
- await sync_servers_to_registry(current_servers);
270
- all_servers = current_servers;
271
- note(`Imported ${current_servers.length} servers from your .claude.json file into registry.`);
272
- }
273
- }
274
- if (all_servers.length === 0) {
275
- note("No MCP servers found in .claude.json or registry. Add servers first.");
947
+ if (typeof client_id === "symbol") return;
948
+ const adapter = client_adapters.find((candidate) => candidate.id === client_id);
949
+ if (!adapter) return;
950
+ if (adapter.id === "claude-code") {
951
+ await edit_claude_config();
276
952
  return;
277
953
  }
278
- const currently_enabled = await get_enabled_servers_for_scope(scope);
279
- const server_choices = all_servers.map((server) => ({
954
+ await edit_client_config(adapter);
955
+ } catch (error) {
956
+ throw new Error(`Failed to edit configuration: ${error instanceof Error ? error.message : "Unknown error"}`);
957
+ }
958
+ }
959
+ async function edit_client_config(adapter) {
960
+ if (!adapter.writeEnabled) {
961
+ note(`${adapter.label} support is read-only for now.`);
962
+ return;
963
+ }
964
+ const location = await select_config_location(adapter);
965
+ if (!location) return;
966
+ const servers = await adapter.readLocation(location);
967
+ if (servers.length === 0) {
968
+ note(`No MCP servers found at ${location.path}.`);
969
+ return;
970
+ }
971
+ const selected_names = await multiselect({
972
+ message: `Toggle MCP servers for ${adapter.label}:`,
973
+ options: servers.map((server) => ({
974
+ value: server.name,
975
+ label: server.name,
976
+ hint: server_hint(server)
977
+ })),
978
+ initialValues: servers.filter((server) => server.disabled !== true).map((server) => server.name),
979
+ required: false
980
+ });
981
+ if (typeof selected_names === "symbol") return;
982
+ await adapter.writeEnabled(location, selected_names);
983
+ note(`Configuration updated!\nClient: ${adapter.label}\nConfig: ${location.path}\nEnabled servers: ${selected_names.length}`);
984
+ }
985
+ async function select_config_location(adapter) {
986
+ const locations = adapter.locations();
987
+ if (locations.length === 1) return locations[0];
988
+ const location_path = await select({
989
+ message: `Which ${adapter.label} configuration do you want to edit?`,
990
+ options: locations.map((location) => ({
991
+ value: location.path,
992
+ label: `${location.scope} — ${location.description}`,
993
+ hint: location.path
994
+ }))
995
+ });
996
+ if (typeof location_path === "symbol") return null;
997
+ return locations.find((location) => location.path === location_path) ?? null;
998
+ }
999
+ function server_hint(server) {
1000
+ return [
1001
+ server.disabled === true ? "off" : "on",
1002
+ server.command ? [server.command, ...server.args ?? []].join(" ") : server.url ? redact_url(server.url) : server.transport,
1003
+ server.description
1004
+ ].filter(Boolean).join(" · ");
1005
+ }
1006
+ async function edit_claude_config() {
1007
+ const cli_available = await check_claude_cli();
1008
+ const scope = await select({
1009
+ message: "Which Claude Code configuration do you want to edit?",
1010
+ options: get_scope_options(),
1011
+ initialValue: "local"
1012
+ });
1013
+ if (typeof scope === "symbol") return;
1014
+ const current_config = await read_claude_config();
1015
+ let all_servers = await get_all_available_servers();
1016
+ if (all_servers.length === 0 && current_config.mcpServers) {
1017
+ const current_servers = get_enabled_servers(current_config);
1018
+ if (current_servers.length > 0) {
1019
+ await sync_servers_to_registry(current_servers);
1020
+ all_servers = current_servers;
1021
+ note(`Imported ${current_servers.length} servers from your .claude.json file into registry.`);
1022
+ }
1023
+ }
1024
+ if (all_servers.length === 0) {
1025
+ note("No MCP servers found in .claude.json or registry. Add servers with the CLI first.");
1026
+ return;
1027
+ }
1028
+ const currently_enabled = await get_enabled_servers_for_scope(scope);
1029
+ const selected_server_names = await multiselect({
1030
+ message: `Select MCP servers for ${get_scope_description(scope)}:`,
1031
+ options: all_servers.map((server) => ({
280
1032
  value: server.name,
281
1033
  label: server.name,
282
1034
  hint: server.description || ""
283
- }));
284
- const selected_server_names = await multiselect({
285
- message: `Select MCP servers for ${get_scope_description(scope)}:`,
286
- options: server_choices,
287
- initialValues: currently_enabled,
288
- required: false
289
- });
290
- if (typeof selected_server_names === "symbol") return;
291
- const selected_servers = all_servers.filter((server) => selected_server_names.includes(server.name));
292
- const servers_to_add = selected_server_names.filter((name) => !currently_enabled.includes(name));
293
- const servers_to_remove = currently_enabled.filter((name) => !selected_server_names.includes(name));
294
- if (cli_available && (scope === "local" || scope === "project")) {
295
- let success_count = 0;
296
- let error_count = 0;
297
- for (const name of servers_to_add) {
298
- const server = all_servers.find((s) => s.name === name);
299
- if (server) {
300
- const result = await add_mcp_via_cli(server, scope);
301
- if (result.success) success_count++;
302
- else {
303
- error_count++;
304
- log.warn(`Failed to add ${name}: ${result.error}`);
305
- }
306
- }
307
- }
308
- for (const name of servers_to_remove) {
309
- const result = await remove_mcp_via_cli(name);
310
- if (result.success) success_count++;
311
- else {
1035
+ })),
1036
+ initialValues: currently_enabled,
1037
+ required: false
1038
+ });
1039
+ if (typeof selected_server_names === "symbol") return;
1040
+ const selected_servers = all_servers.filter((server) => selected_server_names.includes(server.name));
1041
+ const servers_to_add = selected_server_names.filter((name) => !currently_enabled.includes(name));
1042
+ const servers_to_remove = currently_enabled.filter((name) => !selected_server_names.includes(name));
1043
+ if (cli_available && (scope === "local" || scope === "project")) {
1044
+ let error_count = 0;
1045
+ for (const name of servers_to_add) {
1046
+ const server = all_servers.find((s) => s.name === name);
1047
+ if (server) {
1048
+ const result = await add_mcp_via_cli(server, scope);
1049
+ if (!result.success) {
312
1050
  error_count++;
313
- log.warn(`Failed to remove ${name}: ${result.error}`);
1051
+ log.warn(`Failed to add ${name}: ${result.error}`);
314
1052
  }
315
1053
  }
316
- await sync_servers_to_registry(selected_servers);
317
- 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}`);
318
- else note(`Configuration updated!\nScope: ${get_scope_description(scope)}\nEnabled servers: ${selected_servers.length}`);
319
- } else {
320
- await write_claude_config(create_config_from_servers(selected_servers));
321
- await sync_servers_to_registry(selected_servers);
322
- if (!cli_available && scope !== "user") log.warn(`Claude CLI not available. Changes written to ~/.claude.json (user scope) instead of ${scope} scope.`);
323
- note(`Configuration updated!\nEnabled servers: ${selected_servers.length}`);
324
1054
  }
325
- } catch (error) {
326
- throw new Error(`Failed to edit configuration: ${error instanceof Error ? error.message : "Unknown error"}`);
1055
+ for (const name of servers_to_remove) {
1056
+ const result = await remove_mcp_via_cli(name);
1057
+ if (!result.success) {
1058
+ error_count++;
1059
+ log.warn(`Failed to remove ${name}: ${result.error}`);
1060
+ }
1061
+ }
1062
+ await sync_servers_to_registry(selected_servers);
1063
+ 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}`);
1064
+ else note(`Configuration updated!\nScope: ${get_scope_description(scope)}\nEnabled servers: ${selected_servers.length}`);
1065
+ } else {
1066
+ await write_claude_config(create_config_from_servers(selected_servers));
1067
+ await sync_servers_to_registry(selected_servers);
1068
+ if (!cli_available && scope !== "user") log.warn(`Claude CLI not available. Changes written to ~/.claude.json (user scope) instead of ${scope} scope.`);
1069
+ note(`Configuration updated!\nEnabled servers: ${selected_servers.length}`);
327
1070
  }
328
1071
  }
329
1072
  //#endregion
@@ -604,6 +1347,171 @@ async function manage_cache() {
604
1347
  }
605
1348
  }
606
1349
  //#endregion
1350
+ //#region src/core/hook-state.ts
1351
+ async function read_disabled_hooks() {
1352
+ try {
1353
+ const content = await readFile(get_disabled_hooks_path(), "utf-8");
1354
+ return JSON.parse(content);
1355
+ } catch {
1356
+ return [];
1357
+ }
1358
+ }
1359
+ async function write_disabled_hooks(entries) {
1360
+ await ensure_directory_exists(get_mcpick_dir());
1361
+ await writeFile(get_disabled_hooks_path(), JSON.stringify(entries, null, " "), "utf-8");
1362
+ }
1363
+ /**
1364
+ * Remove a specific hook handler from a hooks.json file by matching the handler.
1365
+ * Returns true if the hook was found and removed.
1366
+ */
1367
+ async function remove_hook_from_file(hooks_path, event, handler) {
1368
+ let content;
1369
+ try {
1370
+ content = await readFile(hooks_path, "utf-8");
1371
+ } catch {
1372
+ return false;
1373
+ }
1374
+ const hooks_data = JSON.parse(content);
1375
+ const hooks_obj = hooks_data.hooks || hooks_data;
1376
+ const matchers = hooks_obj[event];
1377
+ if (!matchers) return false;
1378
+ let removed = false;
1379
+ for (const m of matchers) {
1380
+ const idx = m.hooks?.findIndex((h) => h.type === handler.type && h.command === handler.command && h.url === handler.url && h.prompt === handler.prompt);
1381
+ if (idx !== void 0 && idx >= 0) {
1382
+ m.hooks.splice(idx, 1);
1383
+ removed = true;
1384
+ if (m.hooks.length === 0) matchers.splice(matchers.indexOf(m), 1);
1385
+ break;
1386
+ }
1387
+ }
1388
+ if (!removed) return false;
1389
+ if (matchers.length === 0) delete hooks_obj[event];
1390
+ await writeFile(hooks_path, JSON.stringify(hooks_data, null, " "), "utf-8");
1391
+ return true;
1392
+ }
1393
+ /**
1394
+ * Get all hooks.json paths for a plugin (cache + marketplace source).
1395
+ */
1396
+ function get_all_hooks_paths(plugin_key, primary_path) {
1397
+ const paths = [primary_path];
1398
+ const at_index = plugin_key.lastIndexOf("@");
1399
+ if (at_index > 0) {
1400
+ const plugin_name = plugin_key.substring(0, at_index);
1401
+ const marketplace_name = plugin_key.substring(at_index + 1);
1402
+ paths.push(join(get_marketplaces_dir(), marketplace_name, "plugins", plugin_name, "hooks", "hooks.json"));
1403
+ }
1404
+ return [...new Set(paths)];
1405
+ }
1406
+ /**
1407
+ * Disable a specific hook from a plugin.
1408
+ * Removes from both cache and marketplace source hooks.json files.
1409
+ */
1410
+ async function disable_plugin_hook(entry) {
1411
+ if (!entry.hooks_json_path || !entry.plugin_key) throw new Error("Not a plugin hook");
1412
+ const disabled = await read_disabled_hooks();
1413
+ disabled.push({
1414
+ plugin_key: entry.plugin_key,
1415
+ hooks_json_path: entry.hooks_json_path,
1416
+ event: entry.event,
1417
+ matcher: entry.matcher,
1418
+ matcher_index: entry.matcher_index,
1419
+ hook_index: entry.hook_index,
1420
+ original_handler: entry.handler,
1421
+ disabled_at: (/* @__PURE__ */ new Date()).toISOString()
1422
+ });
1423
+ await write_disabled_hooks(disabled);
1424
+ const all_paths = get_all_hooks_paths(entry.plugin_key, entry.hooks_json_path);
1425
+ for (const hooks_path of all_paths) await remove_hook_from_file(hooks_path, entry.event, entry.handler);
1426
+ }
1427
+ /**
1428
+ * Add a hook handler back into a hooks.json file.
1429
+ */
1430
+ async function add_hook_to_file(hooks_path, event, matcher_pattern, handler) {
1431
+ let hooks_data;
1432
+ try {
1433
+ const content = await readFile(hooks_path, "utf-8");
1434
+ hooks_data = JSON.parse(content);
1435
+ } catch {
1436
+ hooks_data = { hooks: {} };
1437
+ }
1438
+ const hooks_obj = hooks_data.hooks || (hooks_data.hooks = {});
1439
+ if (!hooks_obj[event]) hooks_obj[event] = [];
1440
+ const matchers = hooks_obj[event];
1441
+ let matcher = matchers.find((m) => (m.matcher || void 0) === matcher_pattern);
1442
+ if (!matcher) {
1443
+ matcher = { hooks: [] };
1444
+ if (matcher_pattern) matcher.matcher = matcher_pattern;
1445
+ matchers.push(matcher);
1446
+ }
1447
+ if (matcher.hooks.some((h) => h.type === handler.type && h.command === handler.command && h.url === handler.url && h.prompt === handler.prompt)) return;
1448
+ matcher.hooks.push(handler);
1449
+ await writeFile(hooks_path, JSON.stringify(hooks_data, null, " "), "utf-8");
1450
+ }
1451
+ /**
1452
+ * Re-enable a previously disabled plugin hook.
1453
+ * Restores to both cache and marketplace source hooks.json files.
1454
+ */
1455
+ async function enable_plugin_hook(disabled_entry) {
1456
+ const all_paths = get_all_hooks_paths(disabled_entry.plugin_key, disabled_entry.hooks_json_path);
1457
+ for (const hooks_path of all_paths) await add_hook_to_file(hooks_path, disabled_entry.event, disabled_entry.matcher, disabled_entry.original_handler);
1458
+ await write_disabled_hooks((await read_disabled_hooks()).filter((d) => !(d.plugin_key === disabled_entry.plugin_key && d.event === disabled_entry.event && d.disabled_at === disabled_entry.disabled_at)));
1459
+ }
1460
+ /**
1461
+ * Check if any previously disabled hooks have been restored (e.g. by marketplace update).
1462
+ * Returns entries that were re-added and need to be re-disabled.
1463
+ */
1464
+ async function check_restored_hooks() {
1465
+ const disabled = await read_disabled_hooks();
1466
+ if (disabled.length === 0) return [];
1467
+ const restored = [];
1468
+ for (const entry of disabled) {
1469
+ const all_paths = get_all_hooks_paths(entry.plugin_key, entry.hooks_json_path);
1470
+ let found = false;
1471
+ for (const hooks_path of all_paths) {
1472
+ let hooks_data;
1473
+ try {
1474
+ const content = await readFile(hooks_path, "utf-8");
1475
+ hooks_data = JSON.parse(content);
1476
+ } catch {
1477
+ continue;
1478
+ }
1479
+ const matchers = (hooks_data.hooks || hooks_data)[entry.event];
1480
+ if (!matchers) continue;
1481
+ for (const m of matchers) {
1482
+ if ((m.matcher || void 0) !== entry.matcher) continue;
1483
+ if (m.hooks?.some((h) => h.type === entry.original_handler.type && (h.command === entry.original_handler.command || h.url === entry.original_handler.url || h.prompt === entry.original_handler.prompt))) {
1484
+ found = true;
1485
+ break;
1486
+ }
1487
+ }
1488
+ if (found) break;
1489
+ }
1490
+ if (found) restored.push(entry);
1491
+ }
1492
+ return restored;
1493
+ }
1494
+ /**
1495
+ * Re-disable hooks that were restored by a marketplace update.
1496
+ */
1497
+ async function redisable_restored_hooks(restored) {
1498
+ let success = 0;
1499
+ let failed = 0;
1500
+ for (const entry of restored) try {
1501
+ const all_paths = get_all_hooks_paths(entry.plugin_key, entry.hooks_json_path);
1502
+ let any_removed = false;
1503
+ for (const hooks_path of all_paths) if (await remove_hook_from_file(hooks_path, entry.event, entry.original_handler)) any_removed = true;
1504
+ if (any_removed) success++;
1505
+ else failed++;
1506
+ } catch {
1507
+ failed++;
1508
+ }
1509
+ return {
1510
+ success,
1511
+ failed
1512
+ };
1513
+ }
1514
+ //#endregion
607
1515
  //#region src/commands/manage-hooks.ts
608
1516
  function format_hook(entry) {
609
1517
  const detail = entry.handler.command || entry.handler.url || entry.handler.prompt || "(unknown)";
@@ -902,6 +1810,166 @@ async function manage_marketplace() {
902
1810
  }
903
1811
  }
904
1812
  //#endregion
1813
+ //#region src/utils/skills-cli.ts
1814
+ const exec_file_async = promisify(execFile);
1815
+ function split_cli_list(value) {
1816
+ return (value ?? "").split(",").map((item) => item.trim()).filter(Boolean);
1817
+ }
1818
+ async function run_skills_cli(args) {
1819
+ try {
1820
+ const result = await exec_file_async("npx", [
1821
+ "-y",
1822
+ "skills@latest",
1823
+ ...args
1824
+ ], { env: {
1825
+ ...process.env,
1826
+ CI: "1",
1827
+ NO_COLOR: "1",
1828
+ FORCE_COLOR: "0",
1829
+ TERM: "dumb"
1830
+ } });
1831
+ return {
1832
+ success: true,
1833
+ stdout: redact_text(result.stdout.trim()),
1834
+ stderr: redact_text(result.stderr.trim())
1835
+ };
1836
+ } catch (error) {
1837
+ const err = error;
1838
+ return {
1839
+ success: false,
1840
+ stdout: err.stdout ? redact_text(err.stdout.trim()) : void 0,
1841
+ stderr: err.stderr ? redact_text(err.stderr.trim()) : void 0,
1842
+ error: redact_text(err.message)
1843
+ };
1844
+ }
1845
+ }
1846
+ //#endregion
1847
+ //#region src/commands/manage-skills.ts
1848
+ const SKILL_AGENTS = [
1849
+ {
1850
+ value: "claude-code",
1851
+ label: "Claude Code"
1852
+ },
1853
+ {
1854
+ value: "pi",
1855
+ label: "Pi"
1856
+ },
1857
+ {
1858
+ value: "opencode",
1859
+ label: "OpenCode"
1860
+ },
1861
+ {
1862
+ value: "codex",
1863
+ label: "Codex"
1864
+ },
1865
+ {
1866
+ value: "cursor",
1867
+ label: "Cursor"
1868
+ },
1869
+ {
1870
+ value: "windsurf",
1871
+ label: "Windsurf"
1872
+ }
1873
+ ];
1874
+ async function manage_skills() {
1875
+ const action = await select({
1876
+ message: "Portable skills",
1877
+ options: [
1878
+ {
1879
+ value: "list",
1880
+ label: "List installed skills"
1881
+ },
1882
+ {
1883
+ value: "available",
1884
+ label: "List skills available from source"
1885
+ },
1886
+ {
1887
+ value: "install",
1888
+ label: "Install skills"
1889
+ },
1890
+ {
1891
+ value: "update",
1892
+ label: "Update skills"
1893
+ },
1894
+ {
1895
+ value: "back",
1896
+ label: "Back"
1897
+ }
1898
+ ]
1899
+ });
1900
+ if (typeof action === "symbol" || action === "back") return;
1901
+ if (action === "list") {
1902
+ const agent = await select_agent();
1903
+ if (!agent) return;
1904
+ await show_result(await run_skills_cli([
1905
+ "list",
1906
+ "--agent",
1907
+ agent
1908
+ ]));
1909
+ return;
1910
+ }
1911
+ if (action === "available") {
1912
+ const source = await prompt_source();
1913
+ if (!source) return;
1914
+ await show_result(await run_skills_cli([
1915
+ "add",
1916
+ source,
1917
+ "--list"
1918
+ ]));
1919
+ return;
1920
+ }
1921
+ if (action === "install") {
1922
+ const source = await prompt_source();
1923
+ if (!source) return;
1924
+ const agent = await select_agent();
1925
+ if (!agent) return;
1926
+ const skill = await text({
1927
+ message: "Skill name or * for all skills:",
1928
+ placeholder: "svelte-runes",
1929
+ defaultValue: "*"
1930
+ });
1931
+ if (typeof skill === "symbol") return;
1932
+ await show_result(await run_skills_cli([
1933
+ "add",
1934
+ source,
1935
+ "--agent",
1936
+ agent,
1937
+ "--skill",
1938
+ skill,
1939
+ "--yes"
1940
+ ]));
1941
+ return;
1942
+ }
1943
+ if (action === "update") await show_result(await run_skills_cli(["update", "--yes"]));
1944
+ }
1945
+ async function select_agent() {
1946
+ const agent = await select({
1947
+ message: "Which agent/client?",
1948
+ options: [...SKILL_AGENTS, {
1949
+ value: "*",
1950
+ label: "All supported agents"
1951
+ }],
1952
+ initialValue: "pi"
1953
+ });
1954
+ return typeof agent === "symbol" ? null : agent;
1955
+ }
1956
+ async function prompt_source() {
1957
+ const source = await text({
1958
+ message: "Skills source:",
1959
+ placeholder: "spences10/skills",
1960
+ defaultValue: "spences10/skills"
1961
+ });
1962
+ return typeof source === "symbol" ? null : source;
1963
+ }
1964
+ async function show_result(result) {
1965
+ if (result.success) {
1966
+ if (result.stdout) log.info(result.stdout);
1967
+ note("Done.");
1968
+ return;
1969
+ }
1970
+ log.error(result.stderr || result.error || "skills CLI failed");
1971
+ }
1972
+ //#endregion
905
1973
  //#region src/commands/restore.ts
906
1974
  async function restore_config() {
907
1975
  try {
@@ -984,6 +2052,68 @@ function format_time_ago(date) {
984
2052
  else return "just now";
985
2053
  }
986
2054
  //#endregion
2055
+ //#region src/core/profile.ts
2056
+ async function load_profile(name) {
2057
+ const profile_path = get_profile_path(name);
2058
+ try {
2059
+ await access(profile_path);
2060
+ const content = await readFile(profile_path, "utf-8");
2061
+ const parsed = JSON.parse(content);
2062
+ let config;
2063
+ if (parsed.mcpServers) config = validate_claude_config(parsed);
2064
+ else if (!parsed.enabledPlugins) config = validate_claude_config({ mcpServers: parsed });
2065
+ else config = validate_claude_config({ mcpServers: parsed.mcpServers || {} });
2066
+ return {
2067
+ config,
2068
+ enabledPlugins: parsed.enabledPlugins
2069
+ };
2070
+ } catch (error) {
2071
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") throw new Error(`Profile '${name}' not found at ${profile_path}`);
2072
+ throw error;
2073
+ }
2074
+ }
2075
+ async function list_profiles() {
2076
+ const profiles_dir = get_profiles_dir();
2077
+ try {
2078
+ await access(profiles_dir);
2079
+ const json_files = (await readdir(profiles_dir)).filter((f) => f.endsWith(".json"));
2080
+ const profiles = [];
2081
+ for (const file of json_files) try {
2082
+ const path = get_profile_path(file);
2083
+ const content = await readFile(path, "utf-8");
2084
+ const parsed = JSON.parse(content);
2085
+ const servers = parsed.mcpServers || parsed;
2086
+ const plugins = parsed.enabledPlugins || {};
2087
+ profiles.push({
2088
+ name: file.replace(".json", ""),
2089
+ path,
2090
+ serverCount: Object.keys(servers).length,
2091
+ pluginCount: Object.keys(plugins).length
2092
+ });
2093
+ } catch {}
2094
+ return profiles;
2095
+ } catch {
2096
+ return [];
2097
+ }
2098
+ }
2099
+ async function save_profile(name) {
2100
+ const config = await read_claude_config();
2101
+ const settings = await read_claude_settings();
2102
+ const servers = config.mcpServers || {};
2103
+ const plugins = settings.enabledPlugins || {};
2104
+ const server_count = Object.keys(servers).length;
2105
+ const plugin_count = Object.keys(plugins).length;
2106
+ if (server_count === 0 && plugin_count === 0) throw new Error("No MCP servers or plugins configured to save");
2107
+ await ensure_directory_exists(get_profiles_dir());
2108
+ const profile_data = { mcpServers: servers };
2109
+ if (plugin_count > 0) profile_data.enabledPlugins = plugins;
2110
+ await writeFile(get_profile_path(name), JSON.stringify(profile_data, null, 2), "utf-8");
2111
+ return {
2112
+ serverCount: server_count,
2113
+ pluginCount: plugin_count
2114
+ };
2115
+ }
2116
+ //#endregion
987
2117
  //#region src/index.ts
988
2118
  function parse_args() {
989
2119
  const args = process.argv.slice(2);
@@ -1082,6 +2212,65 @@ async function handle_save_profile() {
1082
2212
  if (counts.pluginCount > 0) parts.push(`${counts.pluginCount} plugins`);
1083
2213
  log.success(`Profile '${name}' saved (${parts.join(", ")})`);
1084
2214
  }
2215
+ async function handle_client_tools() {
2216
+ const client_id = await select({
2217
+ message: "Which client?",
2218
+ options: client_adapters.map((adapter) => ({
2219
+ value: adapter.id,
2220
+ label: adapter.label
2221
+ })),
2222
+ initialValue: "claude-code"
2223
+ });
2224
+ if (isCancel(client_id)) return;
2225
+ if (client_id !== "claude-code") {
2226
+ note(`${client_adapters.find((adapter) => adapter.id === client_id)?.label ?? client_id} currently has MCP server toggling only.\nUse “Enable / Disable MCP servers” from the main menu.`);
2227
+ return;
2228
+ }
2229
+ const action = await select({
2230
+ message: "Claude Code tools",
2231
+ options: [
2232
+ {
2233
+ value: "plugins",
2234
+ label: "Plugins",
2235
+ hint: "Claude Code plugin enable/install/update"
2236
+ },
2237
+ {
2238
+ value: "marketplaces",
2239
+ label: "Marketplaces",
2240
+ hint: "Claude Code plugin marketplaces"
2241
+ },
2242
+ {
2243
+ value: "hooks",
2244
+ label: "Hooks",
2245
+ hint: "Claude Code settings/plugin hooks"
2246
+ },
2247
+ {
2248
+ value: "cache",
2249
+ label: "Plugin cache",
2250
+ hint: "Claude Code plugin cache maintenance"
2251
+ },
2252
+ {
2253
+ value: "back",
2254
+ label: "Back"
2255
+ }
2256
+ ]
2257
+ });
2258
+ if (isCancel(action) || action === "back") return;
2259
+ switch (action) {
2260
+ case "plugins":
2261
+ await edit_plugins();
2262
+ break;
2263
+ case "marketplaces":
2264
+ await manage_marketplace();
2265
+ break;
2266
+ case "hooks":
2267
+ await manage_hooks();
2268
+ break;
2269
+ case "cache":
2270
+ await manage_cache();
2271
+ break;
2272
+ }
2273
+ }
1085
2274
  async function main() {
1086
2275
  const args = parse_args();
1087
2276
  if (args.listProfiles) {
@@ -1096,8 +2285,8 @@ async function main() {
1096
2285
  await apply_profile(args.profile);
1097
2286
  return;
1098
2287
  }
1099
- intro("MCPick - Claude Code Extension Manager (MCP servers, plugins, skills, marketplaces)");
1100
- log.info("CLI: mcpick --help | Commands: list, add, enable, disable, plugins, marketplace, hooks, profile, backup, restore");
2288
+ intro("MCPick - MCP Configuration Manager");
2289
+ log.info("Primary flow: choose a client, then toggle its MCP servers. Use CLI commands for adding/editing server definitions.");
1101
2290
  while (true) try {
1102
2291
  const action = await select({
1103
2292
  message: "What would you like to do?",
@@ -1105,32 +2294,17 @@ async function main() {
1105
2294
  {
1106
2295
  value: "edit-config",
1107
2296
  label: "Enable / Disable MCP servers",
1108
- hint: "Toggle MCP servers on/off"
2297
+ hint: "Choose client, then toggle servers"
1109
2298
  },
1110
2299
  {
1111
- value: "add-server",
1112
- label: "Add MCP server",
1113
- hint: "Register a new MCP server"
2300
+ value: "skills",
2301
+ label: "Skills",
2302
+ hint: "Install/list portable SKILL.md packs via skills CLI"
1114
2303
  },
1115
2304
  {
1116
- value: "manage-marketplace",
1117
- label: "Manage marketplaces",
1118
- hint: "Add a marketplace, then install plugins from it"
1119
- },
1120
- {
1121
- value: "edit-plugins",
1122
- label: "Manage plugins",
1123
- hint: "Toggle, install, uninstall, or update plugins"
1124
- },
1125
- {
1126
- value: "manage-hooks",
1127
- label: "Manage hooks",
1128
- hint: "List, add, or remove event hooks"
1129
- },
1130
- {
1131
- value: "manage-cache",
1132
- label: "Manage plugin cache",
1133
- hint: "View, clear, or refresh plugin caches"
2305
+ value: "client-tools",
2306
+ label: "Client-specific tools",
2307
+ hint: "Plugins, hooks, marketplaces, cache where supported"
1134
2308
  },
1135
2309
  {
1136
2310
  value: "load-profile",
@@ -1167,24 +2341,15 @@ async function main() {
1167
2341
  case "edit-config":
1168
2342
  await edit_config();
1169
2343
  break;
1170
- case "edit-plugins":
1171
- await edit_plugins();
2344
+ case "skills":
2345
+ await manage_skills();
1172
2346
  break;
1173
- case "manage-marketplace":
1174
- await manage_marketplace();
1175
- break;
1176
- case "manage-hooks":
1177
- await manage_hooks();
1178
- break;
1179
- case "manage-cache":
1180
- await manage_cache();
2347
+ case "client-tools":
2348
+ await handle_client_tools();
1181
2349
  break;
1182
2350
  case "backup":
1183
2351
  await backup_config();
1184
2352
  break;
1185
- case "add-server":
1186
- await add_server();
1187
- break;
1188
2353
  case "restore":
1189
2354
  await restore_config();
1190
2355
  break;
@@ -1218,6 +2383,7 @@ async function main() {
1218
2383
  }
1219
2384
  }
1220
2385
  const SUBCOMMANDS = new Set([
2386
+ "clients",
1221
2387
  "list",
1222
2388
  "enable",
1223
2389
  "disable",
@@ -1231,6 +2397,7 @@ const SUBCOMMANDS = new Set([
1231
2397
  "backup",
1232
2398
  "restore",
1233
2399
  "profile",
2400
+ "skills",
1234
2401
  "plugins",
1235
2402
  "cache",
1236
2403
  "dev",
@@ -1240,12 +2407,12 @@ const SUBCOMMANDS = new Set([
1240
2407
  const arg = process.argv[2];
1241
2408
  if (arg && SUBCOMMANDS.has(arg) || arg === "--help" || arg === "-h" || !process.stdout.isTTY) {
1242
2409
  if (!arg && !process.stdout.isTTY) process.argv.push("--help");
1243
- import("./cli-CsFfnWBo.js").then((m) => m.run());
2410
+ import("./cli-avr5R1LO.js").then((m) => m.run());
1244
2411
  } else main().catch((error) => {
1245
2412
  console.error("Fatal error:", error);
1246
2413
  process.exit(1);
1247
2414
  });
1248
2415
  //#endregion
1249
- export {};
2416
+ export { get_client_adapter as A, validate_plugin_via_cli as C, list_plugin_backups as D, list_backups as E, read_server_registry as O, update_plugin_via_cli as S, get_all_available_servers as T, mcp_add_json_via_cli as _, split_cli_list as a, remove_mcp_via_cli as b, enable_plugin_hook as c, add_mcp_via_cli as d, install_plugin_via_cli as f, marketplace_update_via_cli as g, marketplace_remove_via_cli as h, run_skills_cli as i, list_client_locations as j, write_server_registry as k, read_disabled_hooks as l, marketplace_list_via_cli as m, load_profile as n, check_restored_hooks as o, marketplace_add_via_cli as p, save_profile as r, disable_plugin_hook as s, list_profiles as t, redisable_restored_hooks as u, mcp_get_via_cli as v, add_server_to_registry as w, uninstall_plugin_via_cli as x, mcp_reset_project_choices_via_cli as y };
1250
2417
 
1251
2418
  //# sourceMappingURL=index.js.map