mcpick 0.0.25 → 0.0.26

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 (36) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/{add-7mhUpbrt.js → add-DwUPxD5U.js} +4 -5
  3. package/dist/{add-json-BMM2L4hv.js → add-json-Dk3C8QSR.js} +4 -4
  4. package/dist/atomic-write-9pfXia-x.js +22 -0
  5. package/dist/{backup-C-YJmgps.js → backup-Ber7st-_.js} +4 -5
  6. package/dist/{cache-BOYZhUF6.js → cache-BVNAOxqk.js} +3 -3
  7. package/dist/{cli-sOeHH4CK.js → cli-Dfein2li.js} +22 -22
  8. package/dist/{clients-D5KAuQ5U.js → clients-C1mWlnsn.js} +3 -3
  9. package/dist/{clone-BRJA55js.js → clone-BE904ik7.js} +3 -6
  10. package/dist/{dev-B-WlQSqY.js → dev-BW_2JDPM.js} +5 -6
  11. package/dist/{disable-Br0aVG3u.js → disable-CVwuJLhT.js} +4 -4
  12. package/dist/{enable-DUolKCEH.js → enable-BEi8TUd1.js} +4 -4
  13. package/dist/{get-D-6Cl_CO.js → get-eDOm6sRK.js} +3 -3
  14. package/dist/{hooks-BKPmZViU.js → hooks-DzGBYYId.js} +4 -3
  15. package/dist/index.js +306 -265
  16. package/dist/{list-DMcaHDfM.js → list-CvpW_mNZ.js} +3 -5
  17. package/dist/{marketplace-DTW7Ys8k.js → marketplace-BKGJ3UGr.js} +4 -4
  18. package/dist/{mutation-ukRPw3qM.js → mutation-BBMIFB8H.js} +2 -2
  19. package/dist/{output-BS1TMOWt.js → output-DGPi2IWN.js} +2 -2
  20. package/dist/{plugin-cache-Dw1I2YuO.js → plugin-cache-Dj-lS1mA.js} +169 -21
  21. package/dist/{plugins-BzLD4og0.js → plugins-elIQYM38.js} +4 -4
  22. package/dist/{profile-CmIWUJH_.js → profile-NVbTnJKI.js} +4 -4
  23. package/dist/{reload-Di28s_rY.js → reload-BkVtWaf0.js} +2 -2
  24. package/dist/{remove-B32EuYRC.js → remove-BC2vomi_.js} +4 -4
  25. package/dist/{reset-project-choices-DX4TnZ2i.js → reset-project-choices-DhJsWQx2.js} +3 -3
  26. package/dist/{restore-ByS4xi0y.js → restore-CeYSOFt8.js} +3 -5
  27. package/dist/{rollback-DdDJrA8y.js → rollback-g-WX6b3o.js} +3 -3
  28. package/dist/safe-apply-B5flt8QC.js +185 -0
  29. package/dist/{skills-pvyQ17XU.js → skills-zuQ5kCL1.js} +2 -2
  30. package/package.json +6 -6
  31. package/pnpm-workspace.yaml +1 -0
  32. package/dist/config-Bzh374VP.js +0 -167
  33. package/dist/paths-BPISiJi4.js +0 -124
  34. package/dist/redact-wBMtzbno.js +0 -88
  35. package/dist/rolldown-runtime-CiIaOW0V.js +0 -13
  36. package/dist/validation-CfPAjPJ5.js +0 -63
package/dist/index.js CHANGED
@@ -1,115 +1,222 @@
1
1
  #!/usr/bin/env node
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 { 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";
2
+ import { S as get_server_registry_path, _ as get_plugin_backup_filename, a as get_backup_filename, b as get_profiles_dir, c as get_claude_settings_path, g as get_mcpick_dir, h as get_marketplaces_dir, i as ensure_directory_exists, l as get_current_project_path, o as get_backups_dir, r as safe_json_write, s as get_claude_config_path, x as get_project_mcp_json_path, y as get_profile_path } from "./safe-apply-B5flt8QC.js";
3
+ import { t as atomic_json_write } from "./atomic-write-9pfXia-x.js";
4
+ import { _ as redisable_restored_hooks, c as read_known_marketplaces, g as read_disabled_hooks, h as enable_plugin_hook, l as read_marketplace_manifest, m as disable_plugin_hook, n as clear_plugin_caches, p as check_restored_hooks, r as get_cached_plugins_info, s as read_installed_plugins, t as clean_orphaned_versions, u as refresh_all_marketplaces } from "./plugin-cache-Dj-lS1mA.js";
7
5
  import { cancel, confirm, intro, isCancel, log, multiselect, note, outro, select, text } from "@clack/prompts";
8
- import { access, mkdir, readFile, readdir, rename, rm, unlink, writeFile } from "node:fs/promises";
9
- import { basename, dirname, join, resolve } from "node:path";
10
- import { createHash, randomUUID } from "node:crypto";
6
+ import { access, readFile, readdir, unlink, writeFile } from "node:fs/promises";
7
+ import { join, resolve } from "node:path";
11
8
  import { homedir } from "node:os";
9
+ import * as v from "valibot";
12
10
  import { execFile } from "node:child_process";
13
11
  import { promisify } from "node:util";
14
- //#region src/utils/safe-apply.ts
15
- async function file_exists$1(path) {
12
+ //#region src/core/validation.ts
13
+ const mcp_server_schema_stdio = v.object({
14
+ type: v.optional(v.literal("stdio")),
15
+ command: v.pipe(v.string(), v.minLength(1)),
16
+ args: v.optional(v.array(v.string())),
17
+ env: v.optional(v.record(v.string(), v.string())),
18
+ description: v.optional(v.string())
19
+ });
20
+ const mcp_server_schema_sse = v.object({
21
+ type: v.literal("sse"),
22
+ env: v.optional(v.record(v.string(), v.string())),
23
+ url: v.pipe(v.string(), v.minLength(1)),
24
+ headers: v.optional(v.record(v.string(), v.string())),
25
+ description: v.optional(v.string())
26
+ });
27
+ const mcp_server_schema_http = v.object({
28
+ type: v.literal("http"),
29
+ env: v.optional(v.record(v.string(), v.string())),
30
+ url: v.pipe(v.string(), v.minLength(1)),
31
+ headers: v.optional(v.record(v.string(), v.string())),
32
+ description: v.optional(v.string())
33
+ });
34
+ const mcp_server_schema_base = v.union([
35
+ mcp_server_schema_stdio,
36
+ mcp_server_schema_sse,
37
+ mcp_server_schema_http
38
+ ]);
39
+ const mcp_server_schema = v.intersect([v.object({ name: v.pipe(v.string(), v.minLength(1)) }), mcp_server_schema_base]);
40
+ const claude_config_schema = v.object({ mcpServers: v.optional(v.record(v.string(), mcp_server_schema_base)) });
41
+ const portable_mcp_server_schema = v.object({
42
+ name: v.pipe(v.string(), v.minLength(1)),
43
+ transport: v.union([
44
+ v.literal("stdio"),
45
+ v.literal("http"),
46
+ v.literal("sse")
47
+ ]),
48
+ command: v.optional(v.string()),
49
+ args: v.optional(v.array(v.string())),
50
+ url: v.optional(v.string()),
51
+ env: v.optional(v.record(v.string(), v.string())),
52
+ headers: v.optional(v.record(v.string(), v.string())),
53
+ description: v.optional(v.string()),
54
+ disabled: v.optional(v.boolean()),
55
+ client_options: v.optional(v.record(v.string(), v.unknown()))
56
+ });
57
+ const server_registry_schema = v.object({
58
+ version: v.literal(3),
59
+ servers: v.array(portable_mcp_server_schema)
60
+ });
61
+ function validate_mcp_server(data) {
62
+ return v.parse(mcp_server_schema, data);
63
+ }
64
+ function validate_claude_config(data) {
65
+ return v.parse(claude_config_schema, data);
66
+ }
67
+ function validate_server_registry(data) {
68
+ return v.parse(server_registry_schema, data);
69
+ }
70
+ //#endregion
71
+ //#region src/core/config.ts
72
+ async function read_claude_config() {
73
+ const config_path = get_claude_config_path();
16
74
  try {
17
- await access(path);
18
- return true;
19
- } catch {
20
- return false;
75
+ await access(config_path);
76
+ const config_content = await readFile(config_path, "utf-8");
77
+ return validate_claude_config(JSON.parse(config_content));
78
+ } catch (error) {
79
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return { mcpServers: {} };
80
+ throw error;
21
81
  }
22
82
  }
23
- function backup_name(path) {
24
- const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
25
- const hash = createHash("sha256").update(path).digest("hex").slice(0, 10);
26
- return `config-${basename(path).replace(/[^A-Za-z0-9._-]/g, "_")}-${stamp}-${hash}.json`;
83
+ async function write_claude_config(config) {
84
+ await atomic_json_write(get_claude_config_path(), (existing) => {
85
+ existing.mcpServers = config.mcpServers;
86
+ return existing;
87
+ });
27
88
  }
28
- async function create_backup(path, content) {
29
- const backups_dir = get_backups_dir();
30
- await ensure_directory_exists(backups_dir);
31
- const backup_path = join(backups_dir, backup_name(path));
32
- await writeFile(backup_path, content, "utf-8");
33
- await writeFile(`${backup_path}.meta.json`, JSON.stringify({
34
- original_path: path,
35
- created_at: (/* @__PURE__ */ new Date()).toISOString()
36
- }, null, 2), "utf-8");
37
- return backup_path;
89
+ function get_enabled_servers(config) {
90
+ if (!config.mcpServers) return [];
91
+ return Object.entries(config.mcpServers).map(([name, server]) => ({
92
+ ...server,
93
+ name
94
+ }));
38
95
  }
39
96
  /**
40
- * Safely replace a JSON file: backup existing content, write via temp+rename,
41
- * verify the result parses, and restore the original content on failure.
97
+ * Find a server's full config in a specific scope.
42
98
  */
43
- async function safe_json_write(path, data, indent = 2) {
44
- await mkdir(dirname(path), { recursive: true });
45
- const original_content = await file_exists$1(path) ? await readFile(path, "utf-8") : void 0;
46
- const backup_path = original_content !== void 0 ? await create_backup(path, original_content) : void 0;
47
- const tmp_path = join(dirname(path), `.${basename(path)}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`);
48
- const next_content = JSON.stringify(data, null, indent);
49
- try {
50
- await writeFile(tmp_path, next_content, "utf-8");
51
- await rename(tmp_path, path);
52
- const written = await readFile(path, "utf-8");
53
- JSON.parse(written);
54
- return {
55
- path,
56
- ...backup_path ? { backup_path } : {}
57
- };
58
- } catch (error) {
59
- await rm(tmp_path, { force: true }).catch(() => void 0);
60
- if (original_content !== void 0) await writeFile(path, original_content, "utf-8");
61
- else await rm(path, { force: true }).catch(() => void 0);
62
- throw error;
99
+ async function find_server_in_scope(name, scope) {
100
+ if (scope === "user" || scope === "local") {
101
+ const config_path = get_claude_config_path();
102
+ try {
103
+ await access(config_path);
104
+ const content = await readFile(config_path, "utf-8");
105
+ const parsed = JSON.parse(content);
106
+ if (scope === "user") {
107
+ const server = parsed.mcpServers?.[name];
108
+ if (server) return {
109
+ server,
110
+ scope: "user"
111
+ };
112
+ } else {
113
+ const cwd = get_current_project_path();
114
+ const server = parsed.projects?.[cwd]?.mcpServers?.[name];
115
+ if (server) return {
116
+ server,
117
+ scope: "local"
118
+ };
119
+ }
120
+ } catch {}
121
+ } else if (scope === "project") {
122
+ const mcp_path = get_project_mcp_json_path();
123
+ try {
124
+ await access(mcp_path);
125
+ const content = await readFile(mcp_path, "utf-8");
126
+ const server = JSON.parse(content).mcpServers?.[name];
127
+ if (server) return {
128
+ server,
129
+ scope: "project"
130
+ };
131
+ } catch {}
63
132
  }
133
+ return null;
64
134
  }
65
- async function list_config_backups() {
66
- const backups_dir = get_backups_dir();
135
+ /**
136
+ * Auto-detect which scope a server lives in.
137
+ * Searches local → project → user.
138
+ */
139
+ async function detect_server_scope(name) {
140
+ for (const scope of [
141
+ "local",
142
+ "project",
143
+ "user"
144
+ ]) {
145
+ const result = await find_server_in_scope(name, scope);
146
+ if (result) return result;
147
+ }
148
+ return null;
149
+ }
150
+ /**
151
+ * Read full Claude config including projects section
152
+ */
153
+ async function read_claude_config_full() {
154
+ const config_path = get_claude_config_path();
67
155
  try {
68
- const files = await readdir(backups_dir);
69
- const backups = [];
70
- for (const file of files) {
71
- if (!file.startsWith("config-") || !file.endsWith(".json")) continue;
72
- if (file.endsWith(".meta.json")) continue;
73
- const backup_path = join(backups_dir, file);
74
- try {
75
- const meta = JSON.parse(await readFile(`${backup_path}.meta.json`, "utf-8"));
76
- if (typeof meta.original_path !== "string" || typeof meta.created_at !== "string") continue;
77
- backups.push({
78
- path: backup_path,
79
- original_path: meta.original_path,
80
- created_at: meta.created_at
81
- });
82
- } catch {}
83
- }
84
- return backups.sort((a, b) => b.created_at.localeCompare(a.created_at));
156
+ await access(config_path);
157
+ const config_content = await readFile(config_path, "utf-8");
158
+ return JSON.parse(config_content);
85
159
  } catch {
86
- return [];
160
+ return {
161
+ mcpServers: {},
162
+ projects: {}
163
+ };
87
164
  }
88
165
  }
89
- async function restore_config_backup(backup_path) {
90
- const backup = (await list_config_backups()).find((candidate) => candidate.path === backup_path || basename(candidate.path) === backup_path);
91
- if (!backup) throw new Error(`Config backup '${backup_path}' not found.`);
92
- const content = await readFile(backup.path, "utf-8");
93
- const parsed = JSON.parse(content);
94
- await safe_json_write(backup.original_path, parsed);
95
- return backup;
166
+ /**
167
+ * Read MCP servers for local scope (current project)
168
+ * Stored in ~/.claude.json -> projects[cwd].mcpServers
169
+ * Also searches parent directories since Claude CLI may store config at parent level
170
+ */
171
+ async function read_local_mcp_servers() {
172
+ const { dirname } = await import("node:path");
173
+ const { homedir } = await import("node:os");
174
+ const full_config = await read_claude_config_full();
175
+ const home = homedir();
176
+ let current_dir = get_current_project_path();
177
+ while (current_dir && current_dir !== "/" && current_dir.length >= home.length) {
178
+ const project_config = full_config.projects?.[current_dir];
179
+ if (project_config?.mcpServers && Object.keys(project_config.mcpServers).length > 0) return Object.keys(project_config.mcpServers);
180
+ current_dir = dirname(current_dir);
181
+ }
182
+ return [];
96
183
  }
97
- //#endregion
98
- //#region src/utils/atomic-write.ts
99
184
  /**
100
- * Atomically write a JSON file with fresh-read merging.
101
- *
102
- * 1. Re-reads the file right before writing to pick up concurrent changes
103
- * 2. Applies the merge function to the freshest data
104
- * 3. Writes to a temp file, then renames (atomic on same filesystem)
185
+ * Read MCP servers from ~/.claude.json -> mcpServers (user scope)
105
186
  */
106
- async function atomic_json_write(file_path, merge) {
107
- let existing = {};
108
- try {
109
- const content = await readFile(file_path, "utf-8");
110
- existing = JSON.parse(content);
111
- } catch {}
112
- await safe_json_write(file_path, merge(existing), 2);
187
+ async function read_user_mcp_servers() {
188
+ const config = await read_claude_config();
189
+ return Object.keys(config.mcpServers || {});
190
+ }
191
+ /**
192
+ * Read MCP servers from .mcp.json files (project scope)
193
+ * Searches current directory and parents for .mcp.json
194
+ */
195
+ async function find_and_read_project_mcp_json() {
196
+ const { dirname } = await import("node:path");
197
+ let current_dir = get_current_project_path();
198
+ const home = (await import("node:os")).homedir();
199
+ while (current_dir && current_dir !== "/" && current_dir.length >= home.length) {
200
+ const mcp_path = `${current_dir}/.mcp.json`;
201
+ try {
202
+ await access(mcp_path);
203
+ const content = await readFile(mcp_path, "utf-8");
204
+ const servers = JSON.parse(content).mcpServers || {};
205
+ return Object.keys(servers);
206
+ } catch {}
207
+ current_dir = dirname(current_dir);
208
+ }
209
+ return [];
210
+ }
211
+ /**
212
+ * Get currently enabled server names for a specific scope
213
+ */
214
+ async function get_enabled_servers_for_scope(scope) {
215
+ switch (scope) {
216
+ case "local": return read_local_mcp_servers();
217
+ case "project": return find_and_read_project_mcp_json();
218
+ case "user": return read_user_mcp_servers();
219
+ }
113
220
  }
114
221
  //#endregion
115
222
  //#region src/core/settings.ts
@@ -215,8 +322,6 @@ async function get_all_hooks() {
215
322
  * Checks both cache and marketplace source paths since Claude Code reads from both.
216
323
  */
217
324
  async function get_all_plugin_hooks() {
218
- const { read_installed_plugins } = await import("./plugin-cache-Dw1I2YuO.js").then((n) => n.s);
219
- const { get_marketplaces_dir } = await import("./paths-BPISiJi4.js").then((n) => n.b);
220
325
  const installed = await read_installed_plugins();
221
326
  const entries = [];
222
327
  const seen_hooks = /* @__PURE__ */ new Set();
@@ -537,8 +642,24 @@ function portable_to_json(server, mode) {
537
642
  if (typeof server.disabled === "boolean") set_server_enabled(result, !server.disabled, mode);
538
643
  return result;
539
644
  }
540
- function portable_server_map(servers, mode) {
541
- return Object.fromEntries(servers.map((server) => [server.name, portable_to_json(server, mode)]));
645
+ function opencode_to_json(server) {
646
+ const result = {};
647
+ const allowed_options = server.transport === "stdio" ? new Set(["cwd", "timeout"]) : new Set(["oauth", "timeout"]);
648
+ for (const [key, value] of Object.entries(server.client_options ?? {})) if (allowed_options.has(key)) result[key] = value;
649
+ if (server.transport === "stdio") {
650
+ result.type = "local";
651
+ if (server.command) result.command = [server.command, ...server.args ?? []];
652
+ if (server.env) result.environment = server.env;
653
+ } else {
654
+ result.type = "remote";
655
+ if (server.url) result.url = server.url;
656
+ if (server.headers) result.headers = server.headers;
657
+ }
658
+ if (typeof server.disabled === "boolean") result.enabled = !server.disabled;
659
+ return result;
660
+ }
661
+ function portable_server_map(servers, mode, serialize = portable_to_json) {
662
+ return Object.fromEntries(servers.map((server) => [server.name, serialize(server, mode)]));
542
663
  }
543
664
  function create_json_adapter(options) {
544
665
  return {
@@ -565,7 +686,7 @@ function create_json_adapter(options) {
565
686
  async write_server(location, server) {
566
687
  const data = await read_json_file(location.path) ?? {};
567
688
  const servers = get_server_record(data, options.serverKey);
568
- servers[server.name] = portable_to_json(server, options.disabledMode ?? "disabled");
689
+ servers[server.name] = (options.serialize ?? portable_to_json)(server, options.disabledMode ?? "disabled");
569
690
  data[options.serverKey] = servers;
570
691
  return write_json_file(location.path, data);
571
692
  },
@@ -585,7 +706,7 @@ function create_json_adapter(options) {
585
706
  },
586
707
  async write_servers(location, servers) {
587
708
  const data = await read_json_file(location.path) ?? {};
588
- data[options.serverKey] = portable_server_map(servers, options.disabledMode ?? "disabled");
709
+ data[options.serverKey] = portable_server_map(servers, options.disabledMode ?? "disabled", options.serialize);
589
710
  return write_json_file(location.path, data);
590
711
  }
591
712
  };
@@ -702,6 +823,7 @@ const client_adapters = [
702
823
  label: "OpenCode",
703
824
  serverKey: "mcp",
704
825
  disabledMode: "enabled",
826
+ serialize: opencode_to_json,
705
827
  locations: () => [{
706
828
  scope: "project",
707
829
  path: project_path("opencode.json"),
@@ -883,7 +1005,6 @@ async function add_server_to_registry(server) {
883
1005
  await write_server_registry(registry);
884
1006
  }
885
1007
  async function get_all_available_servers() {
886
- const { get_enabled_servers, read_claude_config } = await import("./config-Bzh374VP.js").then((n) => n.t);
887
1008
  const registry = await read_server_registry();
888
1009
  const config_servers = get_enabled_servers(await read_claude_config());
889
1010
  const config_by_name = new Map(config_servers.map((s) => [s.name, s]));
@@ -929,6 +1050,91 @@ function parse_backups(prefix, pattern) {
929
1050
  const list_backups = parse_backups("mcp-servers-", /mcp-servers-(\d{4})-(\d{2})-(\d{2})-(\d{2})(\d{2})(\d{2})\.json/);
930
1051
  const list_plugin_backups = parse_backups("plugins-", /plugins-(\d{4})-(\d{2})-(\d{2})-(\d{2})(\d{2})(\d{2})\.json/);
931
1052
  //#endregion
1053
+ //#region src/utils/redact.ts
1054
+ const SENSITIVE_KEY_PATTERN = /(api[_-]?key|token|secret|password|passwd|authorization|client[_-]?secret|access[_-]?token|refresh[_-]?token|private[_-]?key)/i;
1055
+ const ANSI_PATTERN = new RegExp(String.raw`[\u001B\u009B][[\]()#;?]*(?:(?:(?:[a-zA-Z\d]*(?:;[a-zA-Z\d]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))`, "g");
1056
+ const TEXT_REDACTIONS = [
1057
+ [/AKIA[A-Z0-9]{16}/g, "[REDACTED:AWS_ACCESS_KEY]"],
1058
+ [/\bAIza[0-9A-Za-z_-]{35}\b/g, "[REDACTED:GOOGLE_API_KEY]"],
1059
+ [/\bya29\.[0-9A-Za-z_-]{20,}\b/g, "[REDACTED:GOOGLE_OAUTH_TOKEN]"],
1060
+ [/\bsk-(?:live|test)?_?[a-zA-Z0-9._-]{20,}\b/g, "[REDACTED:API_KEY]"],
1061
+ [/\b(?:pk|rk)_(?:live|test)_[a-zA-Z0-9]{20,}\b/g, "[REDACTED:API_KEY]"],
1062
+ [/\bBearer\s+[a-zA-Z0-9._~+/-]+=*/gi, "Bearer [REDACTED]"],
1063
+ [/(:\/\/[^:\s/@]+):([^@\s]+)@/g, "$1:[REDACTED]@"],
1064
+ [/([?&][^=&#\s]*(?:token|key|secret|password|passwd)[^=&#\s]*=)[^&#\s]*/gi, "$1[REDACTED]"],
1065
+ [/\b(Authorization\s*:\s*)(?:Bearer\s+)?[^\s,}\]]+/gi, "$1[REDACTED]"],
1066
+ [/\b((?:api[_-]?key|token|secret|password|passwd|client[_-]?secret|access[_-]?token|refresh[_-]?token)\s*[:=]\s*["']?)[^"'\s,}\]]{8,}/gi, "$1[REDACTED]"],
1067
+ [/\b((?:SecretAccessKey|aws_secret_access_key)\s*[:=]\s*)[A-Za-z0-9/+=]{20,}/gi, "$1[REDACTED]"],
1068
+ [/-----BEGIN\s+[\w\s]*PRIVATE\s+KEY-----[\s\S]*?-----END\s+[\w\s]*PRIVATE\s+KEY-----/g, "[REDACTED:PRIVATE_KEY]"]
1069
+ ];
1070
+ /**
1071
+ * Redact known secret patterns from arbitrary text. This is a safety net for
1072
+ * CLI stdout/stderr; structured MCP config redaction should happen first.
1073
+ */
1074
+ function redact_text(text) {
1075
+ let redacted = text;
1076
+ for (const [pattern, replacement] of TEXT_REDACTIONS) redacted = redacted.replace(pattern, replacement);
1077
+ return redacted.replace(ANSI_PATTERN, "");
1078
+ }
1079
+ function redact_value(value, key = "") {
1080
+ if (typeof value === "string") return SENSITIVE_KEY_PATTERN.test(key) ? "***" : redact_text(value);
1081
+ if (Array.isArray(value)) return value.map((item) => redact_value(item));
1082
+ if (value && typeof value === "object") {
1083
+ const redacted = {};
1084
+ for (const [child_key, child_value] of Object.entries(value)) redacted[child_key] = child_key === "env" || child_key === "headers" ? redact_unknown_record(child_value) : SENSITIVE_KEY_PATTERN.test(child_key) ? "***" : redact_value(child_value, child_key);
1085
+ return redacted;
1086
+ }
1087
+ return value;
1088
+ }
1089
+ /**
1090
+ * Redact sensitive values from a server config.
1091
+ * Shows env/header key names but replaces values with "***".
1092
+ */
1093
+ function redact_server_base(server) {
1094
+ const redacted = redact_value(server);
1095
+ if ("env" in redacted && redacted.env) redacted.env = redact_record(redacted.env);
1096
+ if ("headers" in redacted && redacted.headers) redacted.headers = redact_record(redacted.headers);
1097
+ if ("url" in redacted && redacted.url) redacted.url = redact_url(redacted.url);
1098
+ return redacted;
1099
+ }
1100
+ function redact_server(server) {
1101
+ return {
1102
+ ...redact_server_base(server),
1103
+ name: server.name
1104
+ };
1105
+ }
1106
+ function redact_portable_server(server) {
1107
+ return {
1108
+ ...redact_value(server),
1109
+ ...server.env ? { env: redact_record(server.env) } : {},
1110
+ ...server.headers ? { headers: redact_record(server.headers) } : {},
1111
+ ...server.url ? { url: redact_url(server.url) } : {}
1112
+ };
1113
+ }
1114
+ function redact_url(url) {
1115
+ try {
1116
+ const parsed = new URL(url);
1117
+ parsed.username = "";
1118
+ parsed.password = "";
1119
+ parsed.search = parsed.search ? "?redacted" : "";
1120
+ parsed.hash = "";
1121
+ return parsed.toString();
1122
+ } catch {
1123
+ return redact_text(url.replace(/[?#].*$/, "?redacted"));
1124
+ }
1125
+ }
1126
+ function redact_unknown_record(value) {
1127
+ if (!value || typeof value !== "object" || Array.isArray(value)) return redact_value(value);
1128
+ const redacted = {};
1129
+ for (const key of Object.keys(value)) redacted[key] = "***";
1130
+ return redacted;
1131
+ }
1132
+ function redact_record(record) {
1133
+ const redacted = {};
1134
+ for (const key of Object.keys(record)) redacted[key] = "***";
1135
+ return redacted;
1136
+ }
1137
+ //#endregion
932
1138
  //#region src/commands/edit-config.ts
933
1139
  async function edit_config() {
934
1140
  try {
@@ -1719,171 +1925,6 @@ async function edit_plugins() {
1719
1925
  }
1720
1926
  }
1721
1927
  //#endregion
1722
- //#region src/core/hook-state.ts
1723
- async function read_disabled_hooks() {
1724
- try {
1725
- const content = await readFile(get_disabled_hooks_path(), "utf-8");
1726
- return JSON.parse(content);
1727
- } catch {
1728
- return [];
1729
- }
1730
- }
1731
- async function write_disabled_hooks(entries) {
1732
- await ensure_directory_exists(get_mcpick_dir());
1733
- await safe_json_write(get_disabled_hooks_path(), entries, " ");
1734
- }
1735
- /**
1736
- * Remove a specific hook handler from a hooks.json file by matching the handler.
1737
- * Returns true if the hook was found and removed.
1738
- */
1739
- async function remove_hook_from_file(hooks_path, event, handler) {
1740
- let content;
1741
- try {
1742
- content = await readFile(hooks_path, "utf-8");
1743
- } catch {
1744
- return false;
1745
- }
1746
- const hooks_data = JSON.parse(content);
1747
- const hooks_obj = hooks_data.hooks || hooks_data;
1748
- const matchers = hooks_obj[event];
1749
- if (!matchers) return false;
1750
- let removed = false;
1751
- for (const m of matchers) {
1752
- const idx = m.hooks?.findIndex((h) => h.type === handler.type && h.command === handler.command && h.url === handler.url && h.prompt === handler.prompt);
1753
- if (idx !== void 0 && idx >= 0) {
1754
- m.hooks.splice(idx, 1);
1755
- removed = true;
1756
- if (m.hooks.length === 0) matchers.splice(matchers.indexOf(m), 1);
1757
- break;
1758
- }
1759
- }
1760
- if (!removed) return false;
1761
- if (matchers.length === 0) delete hooks_obj[event];
1762
- await safe_json_write(hooks_path, hooks_data, " ");
1763
- return true;
1764
- }
1765
- /**
1766
- * Get all hooks.json paths for a plugin (cache + marketplace source).
1767
- */
1768
- function get_all_hooks_paths(plugin_key, primary_path) {
1769
- const paths = [primary_path];
1770
- const at_index = plugin_key.lastIndexOf("@");
1771
- if (at_index > 0) {
1772
- const plugin_name = plugin_key.substring(0, at_index);
1773
- const marketplace_name = plugin_key.substring(at_index + 1);
1774
- paths.push(join(get_marketplaces_dir(), marketplace_name, "plugins", plugin_name, "hooks", "hooks.json"));
1775
- }
1776
- return [...new Set(paths)];
1777
- }
1778
- /**
1779
- * Disable a specific hook from a plugin.
1780
- * Removes from both cache and marketplace source hooks.json files.
1781
- */
1782
- async function disable_plugin_hook(entry) {
1783
- if (!entry.hooks_json_path || !entry.plugin_key) throw new Error("Not a plugin hook");
1784
- const disabled = await read_disabled_hooks();
1785
- disabled.push({
1786
- plugin_key: entry.plugin_key,
1787
- hooks_json_path: entry.hooks_json_path,
1788
- event: entry.event,
1789
- matcher: entry.matcher,
1790
- matcher_index: entry.matcher_index,
1791
- hook_index: entry.hook_index,
1792
- original_handler: entry.handler,
1793
- disabled_at: (/* @__PURE__ */ new Date()).toISOString()
1794
- });
1795
- await write_disabled_hooks(disabled);
1796
- const all_paths = get_all_hooks_paths(entry.plugin_key, entry.hooks_json_path);
1797
- for (const hooks_path of all_paths) await remove_hook_from_file(hooks_path, entry.event, entry.handler);
1798
- }
1799
- /**
1800
- * Add a hook handler back into a hooks.json file.
1801
- */
1802
- async function add_hook_to_file(hooks_path, event, matcher_pattern, handler) {
1803
- let hooks_data;
1804
- try {
1805
- const content = await readFile(hooks_path, "utf-8");
1806
- hooks_data = JSON.parse(content);
1807
- } catch {
1808
- hooks_data = { hooks: {} };
1809
- }
1810
- const hooks_obj = hooks_data.hooks || (hooks_data.hooks = {});
1811
- if (!hooks_obj[event]) hooks_obj[event] = [];
1812
- const matchers = hooks_obj[event];
1813
- let matcher = matchers.find((m) => (m.matcher || void 0) === matcher_pattern);
1814
- if (!matcher) {
1815
- matcher = { hooks: [] };
1816
- if (matcher_pattern) matcher.matcher = matcher_pattern;
1817
- matchers.push(matcher);
1818
- }
1819
- if (matcher.hooks.some((h) => h.type === handler.type && h.command === handler.command && h.url === handler.url && h.prompt === handler.prompt)) return;
1820
- matcher.hooks.push(handler);
1821
- await safe_json_write(hooks_path, hooks_data, " ");
1822
- }
1823
- /**
1824
- * Re-enable a previously disabled plugin hook.
1825
- * Restores to both cache and marketplace source hooks.json files.
1826
- */
1827
- async function enable_plugin_hook(disabled_entry) {
1828
- const all_paths = get_all_hooks_paths(disabled_entry.plugin_key, disabled_entry.hooks_json_path);
1829
- for (const hooks_path of all_paths) await add_hook_to_file(hooks_path, disabled_entry.event, disabled_entry.matcher, disabled_entry.original_handler);
1830
- 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)));
1831
- }
1832
- /**
1833
- * Check if any previously disabled hooks have been restored (e.g. by marketplace update).
1834
- * Returns entries that were re-added and need to be re-disabled.
1835
- */
1836
- async function check_restored_hooks() {
1837
- const disabled = await read_disabled_hooks();
1838
- if (disabled.length === 0) return [];
1839
- const restored = [];
1840
- for (const entry of disabled) {
1841
- const all_paths = get_all_hooks_paths(entry.plugin_key, entry.hooks_json_path);
1842
- let found = false;
1843
- for (const hooks_path of all_paths) {
1844
- let hooks_data;
1845
- try {
1846
- const content = await readFile(hooks_path, "utf-8");
1847
- hooks_data = JSON.parse(content);
1848
- } catch {
1849
- continue;
1850
- }
1851
- const matchers = (hooks_data.hooks || hooks_data)[entry.event];
1852
- if (!matchers) continue;
1853
- for (const m of matchers) {
1854
- if ((m.matcher || void 0) !== entry.matcher) continue;
1855
- 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))) {
1856
- found = true;
1857
- break;
1858
- }
1859
- }
1860
- if (found) break;
1861
- }
1862
- if (found) restored.push(entry);
1863
- }
1864
- return restored;
1865
- }
1866
- /**
1867
- * Re-disable hooks that were restored by a marketplace update.
1868
- */
1869
- async function redisable_restored_hooks(restored) {
1870
- let success = 0;
1871
- let failed = 0;
1872
- for (const entry of restored) try {
1873
- const all_paths = get_all_hooks_paths(entry.plugin_key, entry.hooks_json_path);
1874
- let any_removed = false;
1875
- for (const hooks_path of all_paths) if (await remove_hook_from_file(hooks_path, entry.event, entry.original_handler)) any_removed = true;
1876
- if (any_removed) success++;
1877
- else failed++;
1878
- } catch {
1879
- failed++;
1880
- }
1881
- return {
1882
- success,
1883
- failed
1884
- };
1885
- }
1886
- //#endregion
1887
1928
  //#region src/commands/manage-cache.ts
1888
1929
  function format_status_line(p) {
1889
1930
  const markers = [];
@@ -3074,12 +3115,12 @@ const SUBCOMMANDS = new Set([
3074
3115
  const arg = process.argv[2];
3075
3116
  if (arg && SUBCOMMANDS.has(arg) || arg === "--help" || arg === "-h" || !process.stdout.isTTY) {
3076
3117
  if (!arg && !process.stdout.isTTY) process.argv.push("--help");
3077
- import("./cli-sOeHH4CK.js").then((m) => m.run());
3118
+ import("./cli-Dfein2li.js").then((m) => m.run());
3078
3119
  } else main().catch((error) => {
3079
3120
  console.error("Fatal error:", error);
3080
3121
  process.exit(1);
3081
3122
  });
3082
3123
  //#endregion
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 };
3124
+ export { validate_mcp_server as $, list_plugin_backups as A, add_hook as B, redact_server_base as C, add_server_to_registry as D, redact_value as E, get_client_adapter as F, remove_hook as G, get_all_hooks as H, list_client_locations as I, find_server_in_scope as J, write_claude_settings as K, remove_client_server as L, write_server_registry as M, add_client_server as N, get_all_available_servers as O, add_client_server_config as P, validate_claude_config as Q, resolve_client_location as R, redact_server as S, redact_url as T, get_all_plugins as U, build_enabled_plugins as V, read_claude_settings as W, read_claude_config as X, get_enabled_servers_for_scope as Y, write_claude_config as Z, remove_mcp_via_cli as _, save_profile_for_client as a, validate_plugin_via_cli as b, add_mcp_via_cli as c, marketplace_list_via_cli as d, marketplace_remove_via_cli as f, mcp_reset_project_choices_via_cli as g, mcp_get_via_cli as h, save_current_claude_profile as i, read_server_registry as j, list_backups as k, install_plugin_via_cli as l, mcp_add_json_via_cli as m, apply_profile_to_client as n, run_skills_cli as o, marketplace_update_via_cli as p, detect_server_scope as q, list_profiles as r, split_cli_list as s, apply_profile_to_claude as t, marketplace_add_via_cli as u, uninstall_plugin_via_cli as v, redact_text as w, redact_portable_server as x, update_plugin_via_cli as y, set_client_server_enabled as z };
3084
3125
 
3085
3126
  //# sourceMappingURL=index.js.map