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.
- package/CHANGELOG.md +7 -0
- package/dist/{add-7mhUpbrt.js → add-DwUPxD5U.js} +4 -5
- package/dist/{add-json-BMM2L4hv.js → add-json-Dk3C8QSR.js} +4 -4
- package/dist/atomic-write-9pfXia-x.js +22 -0
- package/dist/{backup-C-YJmgps.js → backup-Ber7st-_.js} +4 -5
- package/dist/{cache-BOYZhUF6.js → cache-BVNAOxqk.js} +3 -3
- package/dist/{cli-sOeHH4CK.js → cli-Dfein2li.js} +22 -22
- package/dist/{clients-D5KAuQ5U.js → clients-C1mWlnsn.js} +3 -3
- package/dist/{clone-BRJA55js.js → clone-BE904ik7.js} +3 -6
- package/dist/{dev-B-WlQSqY.js → dev-BW_2JDPM.js} +5 -6
- package/dist/{disable-Br0aVG3u.js → disable-CVwuJLhT.js} +4 -4
- package/dist/{enable-DUolKCEH.js → enable-BEi8TUd1.js} +4 -4
- package/dist/{get-D-6Cl_CO.js → get-eDOm6sRK.js} +3 -3
- package/dist/{hooks-BKPmZViU.js → hooks-DzGBYYId.js} +4 -3
- package/dist/index.js +306 -265
- package/dist/{list-DMcaHDfM.js → list-CvpW_mNZ.js} +3 -5
- package/dist/{marketplace-DTW7Ys8k.js → marketplace-BKGJ3UGr.js} +4 -4
- package/dist/{mutation-ukRPw3qM.js → mutation-BBMIFB8H.js} +2 -2
- package/dist/{output-BS1TMOWt.js → output-DGPi2IWN.js} +2 -2
- package/dist/{plugin-cache-Dw1I2YuO.js → plugin-cache-Dj-lS1mA.js} +169 -21
- package/dist/{plugins-BzLD4og0.js → plugins-elIQYM38.js} +4 -4
- package/dist/{profile-CmIWUJH_.js → profile-NVbTnJKI.js} +4 -4
- package/dist/{reload-Di28s_rY.js → reload-BkVtWaf0.js} +2 -2
- package/dist/{remove-B32EuYRC.js → remove-BC2vomi_.js} +4 -4
- package/dist/{reset-project-choices-DX4TnZ2i.js → reset-project-choices-DhJsWQx2.js} +3 -3
- package/dist/{restore-ByS4xi0y.js → restore-CeYSOFt8.js} +3 -5
- package/dist/{rollback-DdDJrA8y.js → rollback-g-WX6b3o.js} +3 -3
- package/dist/safe-apply-B5flt8QC.js +185 -0
- package/dist/{skills-pvyQ17XU.js → skills-zuQ5kCL1.js} +2 -2
- package/package.json +6 -6
- package/pnpm-workspace.yaml +1 -0
- package/dist/config-Bzh374VP.js +0 -167
- package/dist/paths-BPISiJi4.js +0 -124
- package/dist/redact-wBMtzbno.js +0 -88
- package/dist/rolldown-runtime-CiIaOW0V.js +0 -13
- 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
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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,
|
|
9
|
-
import {
|
|
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/
|
|
15
|
-
|
|
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(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
*
|
|
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
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
66
|
-
|
|
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
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
*
|
|
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
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
|
541
|
-
|
|
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-
|
|
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 {
|
|
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
|