mcpick 0.0.22 → 0.0.23
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 +11 -0
- package/README.md +4 -0
- package/dist/add-LJQa2my2.js +164 -0
- package/dist/add-json-TEdYweZ5.js +95 -0
- package/dist/{backup-bdg6dvsb.js → backup-kyS5IVIr.js} +5 -5
- package/dist/{cache-CSUcGdZP.js → cache-DTfzTsEE.js} +3 -3
- package/dist/{cli-avr5R1LO.js → cli-By-0nYNQ.js} +22 -21
- package/dist/{clients-CSQgqHzb.js → clients-qMozizys.js} +3 -3
- package/dist/{clone-CQ0skkT6.js → clone-BVhYjRGO.js} +5 -5
- package/dist/{config-BhX4eAgg.js → config-DzMmTJYL.js} +4 -4
- package/dist/{dev-CTDg5g-c.js → dev-Cst8WkQ-.js} +6 -6
- package/dist/disable-BaOs9lrm.js +83 -0
- package/dist/enable--3mjSmTq.js +84 -0
- package/dist/{get-l-eAJhBy.js → get-CjhNWyRj.js} +2 -2
- package/dist/{hooks-BWZ_Kgx3.js → hooks-DFmxgD0t.js} +3 -4
- package/dist/index.js +533 -68
- package/dist/{list-By--kltj.js → list-D5CkCXpP.js} +5 -5
- package/dist/{marketplace-DdiKDDKK.js → marketplace-C3EGyIG0.js} +3 -3
- package/dist/{output-BgN9Uuxf.js → output-HtT5HCof.js} +2 -2
- package/dist/{paths-6wrIM8yh.js → paths-BPISiJi4.js} +1 -1
- package/dist/{plugin-cache-DKcW8LGV.js → plugin-cache-BSgB42wa.js} +35 -16
- package/dist/{plugins-CsXE8AH4.js → plugins-Dn2mPFKm.js} +4 -5
- package/dist/{profile-DzGPsdsl.js → profile-Dq3ORPil.js} +4 -5
- package/dist/{redact-Dltz2gde.js → redact-wBMtzbno.js} +1 -1
- package/dist/{reload-C29-vuvy.js → reload-257iU7Z7.js} +2 -2
- package/dist/remove-26XFzkPd.js +87 -0
- package/dist/{reset-project-choices-Dhh4CxIC.js → reset-project-choices-D2F04LfC.js} +2 -2
- package/dist/{restore-BI8aiszM.js → restore-BYYsoNqF.js} +5 -6
- package/dist/rollback-CPdaME91.js +55 -0
- package/dist/{skills-DPBDmION.js → skills-DfWk9mpk.js} +2 -2
- package/dist/{validation-qWlF51fw.js → validation-xMlbgGCF.js} +1 -1
- package/package.json +4 -4
- package/.github/copilot-instructions.md +0 -50
- package/.github/workflows/ci.yml +0 -26
- package/.vscode/settings.json +0 -5
- package/CONTEXT.md +0 -49
- package/dist/add-Bok0qbXi.js +0 -112
- package/dist/add-json-C44vy2A_.js +0 -58
- package/dist/atomic-write-4lANmzsO.js +0 -26
- package/dist/disable-DLlOj7sc.js +0 -38
- package/dist/enable-CGFYYC2A.js +0 -39
- package/dist/remove-B5q4rQRU.js +0 -30
- package/dist/settings-CZR8bVfh.js +0 -201
package/dist/index.js
CHANGED
|
@@ -1,16 +1,311 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
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-
|
|
3
|
-
import { r as validate_server_registry, t as validate_claude_config } from "./validation-
|
|
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-
|
|
5
|
-
import { a as
|
|
6
|
-
import {
|
|
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";
|
|
2
|
+
import { _ as get_profiles_dir, a as get_claude_settings_path, c as get_disabled_hooks_path, f as get_marketplaces_dir, g as get_profile_path, i as get_claude_config_path, m as get_plugin_backup_filename, n as get_backup_filename, p as get_mcpick_dir, r as get_backups_dir, t as ensure_directory_exists, y as get_server_registry_path } from "./paths-BPISiJi4.js";
|
|
3
|
+
import { r as validate_server_registry, t as validate_claude_config } from "./validation-xMlbgGCF.js";
|
|
4
|
+
import { a as get_enabled_servers, c as write_claude_config, n as create_config_from_servers, o as get_enabled_servers_for_scope, s as read_claude_config } from "./config-DzMmTJYL.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-BSgB42wa.js";
|
|
8
7
|
import { cancel, confirm, intro, isCancel, log, multiselect, note, outro, select, text } from "@clack/prompts";
|
|
9
|
-
import { access, mkdir, readFile, readdir, rename, unlink, writeFile } from "node:fs/promises";
|
|
10
|
-
import { dirname, join } from "node:path";
|
|
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";
|
|
11
11
|
import { homedir } from "node:os";
|
|
12
12
|
import { execFile } from "node:child_process";
|
|
13
13
|
import { promisify } from "node:util";
|
|
14
|
+
//#region src/utils/safe-apply.ts
|
|
15
|
+
async function file_exists$1(path) {
|
|
16
|
+
try {
|
|
17
|
+
await access(path);
|
|
18
|
+
return true;
|
|
19
|
+
} catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
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`;
|
|
27
|
+
}
|
|
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;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
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.
|
|
42
|
+
*/
|
|
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;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async function list_config_backups() {
|
|
66
|
+
const backups_dir = get_backups_dir();
|
|
67
|
+
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));
|
|
85
|
+
} catch {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
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;
|
|
96
|
+
}
|
|
97
|
+
//#endregion
|
|
98
|
+
//#region src/utils/atomic-write.ts
|
|
99
|
+
/**
|
|
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)
|
|
105
|
+
*/
|
|
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);
|
|
113
|
+
}
|
|
114
|
+
//#endregion
|
|
115
|
+
//#region src/core/settings.ts
|
|
116
|
+
async function read_claude_settings() {
|
|
117
|
+
const settings_path = get_claude_settings_path();
|
|
118
|
+
try {
|
|
119
|
+
await access(settings_path);
|
|
120
|
+
const content = await readFile(settings_path, "utf-8");
|
|
121
|
+
return JSON.parse(content);
|
|
122
|
+
} catch (error) {
|
|
123
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return {};
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async function write_claude_settings(updates) {
|
|
128
|
+
await atomic_json_write(get_claude_settings_path(), (existing) => {
|
|
129
|
+
for (const [key, value] of Object.entries(updates)) existing[key] = value;
|
|
130
|
+
return existing;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Parse enabledPlugins into structured list.
|
|
135
|
+
* Keys are in format "plugin-name@marketplace-name"
|
|
136
|
+
*/
|
|
137
|
+
function get_all_plugins(settings) {
|
|
138
|
+
const enabled_plugins = settings.enabledPlugins || {};
|
|
139
|
+
return Object.entries(enabled_plugins).map(([key, enabled]) => {
|
|
140
|
+
const at_index = key.lastIndexOf("@");
|
|
141
|
+
return {
|
|
142
|
+
name: at_index > 0 ? key.substring(0, at_index) : key,
|
|
143
|
+
marketplace: at_index > 0 ? key.substring(at_index + 1) : "unknown",
|
|
144
|
+
enabled
|
|
145
|
+
};
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Build the enabledPlugins record from a list of PluginInfo
|
|
150
|
+
*/
|
|
151
|
+
function build_enabled_plugins(plugins) {
|
|
152
|
+
const result = {};
|
|
153
|
+
for (const plugin of plugins) {
|
|
154
|
+
const key = `${plugin.name}@${plugin.marketplace}`;
|
|
155
|
+
result[key] = plugin.enabled;
|
|
156
|
+
}
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
async function read_settings_file(path) {
|
|
160
|
+
try {
|
|
161
|
+
await access(path);
|
|
162
|
+
const content = await readFile(path, "utf-8");
|
|
163
|
+
return JSON.parse(content);
|
|
164
|
+
} catch {
|
|
165
|
+
return {};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function get_settings_paths() {
|
|
169
|
+
return [
|
|
170
|
+
{
|
|
171
|
+
scope: "user",
|
|
172
|
+
path: resolve(process.env.HOME || process.env.USERPROFILE || "", ".claude", "settings.json")
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
scope: "project",
|
|
176
|
+
path: resolve(process.cwd(), ".claude", "settings.json")
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
scope: "project-local",
|
|
180
|
+
path: resolve(process.cwd(), ".claude", "settings.local.json")
|
|
181
|
+
}
|
|
182
|
+
];
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Read all hooks across all scopes (settings + plugins), flattened for display.
|
|
186
|
+
*/
|
|
187
|
+
async function get_all_hooks() {
|
|
188
|
+
const entries = [];
|
|
189
|
+
for (const { scope, path } of get_settings_paths()) {
|
|
190
|
+
const hooks = (await read_settings_file(path)).hooks;
|
|
191
|
+
if (!hooks) continue;
|
|
192
|
+
for (const [event, matchers] of Object.entries(hooks)) {
|
|
193
|
+
if (!Array.isArray(matchers)) continue;
|
|
194
|
+
for (let mi = 0; mi < matchers.length; mi++) {
|
|
195
|
+
const m = matchers[mi];
|
|
196
|
+
if (!m.hooks?.length) continue;
|
|
197
|
+
for (let hi = 0; hi < m.hooks.length; hi++) entries.push({
|
|
198
|
+
event,
|
|
199
|
+
matcher: m.matcher,
|
|
200
|
+
handler: m.hooks[hi],
|
|
201
|
+
scope,
|
|
202
|
+
source: scope,
|
|
203
|
+
matcher_index: mi,
|
|
204
|
+
hook_index: hi
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const plugin_hooks = await get_all_plugin_hooks();
|
|
210
|
+
entries.push(...plugin_hooks);
|
|
211
|
+
return entries;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Scan all installed plugins for hooks.json and return flattened hook entries.
|
|
215
|
+
* Checks both cache and marketplace source paths since Claude Code reads from both.
|
|
216
|
+
*/
|
|
217
|
+
async function get_all_plugin_hooks() {
|
|
218
|
+
const { read_installed_plugins } = await import("./plugin-cache-BSgB42wa.js").then((n) => n.s);
|
|
219
|
+
const { get_marketplaces_dir } = await import("./paths-BPISiJi4.js").then((n) => n.b);
|
|
220
|
+
const installed = await read_installed_plugins();
|
|
221
|
+
const entries = [];
|
|
222
|
+
const seen_hooks = /* @__PURE__ */ new Set();
|
|
223
|
+
for (const [plugin_key, installs] of Object.entries(installed.plugins)) {
|
|
224
|
+
if (!installs?.length) continue;
|
|
225
|
+
const install = installs[0];
|
|
226
|
+
const at_index = plugin_key.lastIndexOf("@");
|
|
227
|
+
const plugin_name = at_index > 0 ? plugin_key.substring(0, at_index) : plugin_key;
|
|
228
|
+
const marketplace_name = at_index > 0 ? plugin_key.substring(at_index + 1) : "";
|
|
229
|
+
const hooks_paths = [join(install.installPath, "hooks", "hooks.json")];
|
|
230
|
+
if (marketplace_name) hooks_paths.push(join(get_marketplaces_dir(), marketplace_name, "plugins", plugin_name, "hooks", "hooks.json"));
|
|
231
|
+
for (const hooks_path of hooks_paths) {
|
|
232
|
+
let hooks_data;
|
|
233
|
+
try {
|
|
234
|
+
const content = await readFile(hooks_path, "utf-8");
|
|
235
|
+
hooks_data = JSON.parse(content);
|
|
236
|
+
} catch {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
const hooks = hooks_data.hooks || hooks_data;
|
|
240
|
+
for (const [event, matchers] of Object.entries(hooks)) {
|
|
241
|
+
if (!Array.isArray(matchers)) continue;
|
|
242
|
+
for (let mi = 0; mi < matchers.length; mi++) {
|
|
243
|
+
const m = matchers[mi];
|
|
244
|
+
if (!m.hooks?.length) continue;
|
|
245
|
+
for (let hi = 0; hi < m.hooks.length; hi++) {
|
|
246
|
+
const h = m.hooks[hi];
|
|
247
|
+
const dedup_key = `${plugin_key}:${event}:${h.type}:${h.command || h.url || h.prompt}`;
|
|
248
|
+
if (seen_hooks.has(dedup_key)) continue;
|
|
249
|
+
seen_hooks.add(dedup_key);
|
|
250
|
+
entries.push({
|
|
251
|
+
event,
|
|
252
|
+
matcher: m.matcher,
|
|
253
|
+
handler: h,
|
|
254
|
+
scope: "user",
|
|
255
|
+
source: "plugin",
|
|
256
|
+
matcher_index: mi,
|
|
257
|
+
hook_index: hi,
|
|
258
|
+
plugin_key,
|
|
259
|
+
hooks_json_path: hooks_path
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return entries;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Remove a specific hook entry by scope/event/indices.
|
|
270
|
+
*/
|
|
271
|
+
async function remove_hook(entry) {
|
|
272
|
+
const scope_path = get_settings_paths().find((s) => s.scope === entry.scope);
|
|
273
|
+
if (!scope_path) throw new Error(`Unknown scope: ${entry.scope}`);
|
|
274
|
+
await atomic_json_write(scope_path.path, (existing) => {
|
|
275
|
+
const hooks = existing.hooks;
|
|
276
|
+
if (!hooks) return existing;
|
|
277
|
+
const matchers = hooks[entry.event];
|
|
278
|
+
if (!matchers?.[entry.matcher_index]) return existing;
|
|
279
|
+
const matcher = matchers[entry.matcher_index];
|
|
280
|
+
matcher.hooks.splice(entry.hook_index, 1);
|
|
281
|
+
if (matcher.hooks.length === 0) matchers.splice(entry.matcher_index, 1);
|
|
282
|
+
if (matchers.length === 0) delete hooks[entry.event];
|
|
283
|
+
if (Object.keys(hooks).length === 0) delete existing.hooks;
|
|
284
|
+
return existing;
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Add a hook to a specific scope.
|
|
289
|
+
*/
|
|
290
|
+
async function add_hook(scope, event, matcher, handler) {
|
|
291
|
+
const scope_path = get_settings_paths().find((s) => s.scope === scope);
|
|
292
|
+
if (!scope_path) throw new Error(`Unknown scope: ${scope}`);
|
|
293
|
+
await atomic_json_write(scope_path.path, (existing) => {
|
|
294
|
+
if (!existing.hooks) existing.hooks = {};
|
|
295
|
+
const hooks = existing.hooks;
|
|
296
|
+
if (!hooks[event]) hooks[event] = [];
|
|
297
|
+
const matchers = hooks[event];
|
|
298
|
+
const existing_matcher = matchers.find((m) => (m.matcher || void 0) === matcher);
|
|
299
|
+
if (existing_matcher) existing_matcher.hooks.push(handler);
|
|
300
|
+
else {
|
|
301
|
+
const new_matcher = { hooks: [handler] };
|
|
302
|
+
if (matcher) new_matcher.matcher = matcher;
|
|
303
|
+
matchers.push(new_matcher);
|
|
304
|
+
}
|
|
305
|
+
return existing;
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
//#endregion
|
|
14
309
|
//#region src/commands/backup.ts
|
|
15
310
|
const MAX_BACKUPS = 10;
|
|
16
311
|
async function backup_config() {
|
|
@@ -82,10 +377,7 @@ async function read_json_file(path) {
|
|
|
82
377
|
}
|
|
83
378
|
}
|
|
84
379
|
async function write_json_file(path, data) {
|
|
85
|
-
await
|
|
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);
|
|
380
|
+
await safe_json_write(path, data, 2);
|
|
89
381
|
}
|
|
90
382
|
function parse_json_or_jsonc(content) {
|
|
91
383
|
try {
|
|
@@ -194,8 +486,8 @@ function infer_transport(config) {
|
|
|
194
486
|
function normalize_mcp_server(name, config) {
|
|
195
487
|
const transport = infer_transport(config);
|
|
196
488
|
const url = typeof config.httpUrl === "string" ? config.httpUrl : typeof config.serverUrl === "string" ? config.serverUrl : typeof config.url === "string" ? config.url : void 0;
|
|
197
|
-
const
|
|
198
|
-
for (const [key, value] of Object.entries(config)) if (!client_options_to_skip.has(key))
|
|
489
|
+
const client_options = {};
|
|
490
|
+
for (const [key, value] of Object.entries(config)) if (!client_options_to_skip.has(key)) client_options[key] = value;
|
|
199
491
|
const command_array = string_array(config.command);
|
|
200
492
|
const command = typeof config.command === "string" ? config.command : command_array?.[0];
|
|
201
493
|
const args = string_array(config.args) ?? command_array?.slice(1);
|
|
@@ -211,7 +503,7 @@ function normalize_mcp_server(name, config) {
|
|
|
211
503
|
...string_record(config.headers) ? { headers: string_record(config.headers) } : {},
|
|
212
504
|
...typeof config.description === "string" ? { description: config.description } : {},
|
|
213
505
|
...typeof disabled === "boolean" ? { disabled } : {},
|
|
214
|
-
...Object.keys(
|
|
506
|
+
...Object.keys(client_options).length > 0 ? { client_options } : {}
|
|
215
507
|
};
|
|
216
508
|
}
|
|
217
509
|
function get_server_record(data, key) {
|
|
@@ -233,6 +525,18 @@ function set_server_enabled(config, enabled, mode) {
|
|
|
233
525
|
}
|
|
234
526
|
config.disabled = !enabled;
|
|
235
527
|
}
|
|
528
|
+
function portable_to_json(server, mode) {
|
|
529
|
+
const result = { ...server.client_options };
|
|
530
|
+
if (server.transport !== "stdio") result.type = server.transport;
|
|
531
|
+
if (server.command) result.command = server.command;
|
|
532
|
+
if (server.args && server.args.length > 0) result.args = server.args;
|
|
533
|
+
if (server.url) result.url = server.url;
|
|
534
|
+
if (server.env) result.env = server.env;
|
|
535
|
+
if (server.headers) result.headers = server.headers;
|
|
536
|
+
if (server.description) result.description = server.description;
|
|
537
|
+
if (typeof server.disabled === "boolean") set_server_enabled(result, !server.disabled, mode);
|
|
538
|
+
return result;
|
|
539
|
+
}
|
|
236
540
|
function create_json_adapter(options) {
|
|
237
541
|
return {
|
|
238
542
|
id: options.id,
|
|
@@ -247,13 +551,34 @@ function create_json_adapter(options) {
|
|
|
247
551
|
async readLocation(location) {
|
|
248
552
|
return read_server_map(await read_json_file(location.path), options.serverKey);
|
|
249
553
|
},
|
|
250
|
-
async writeEnabled(location,
|
|
554
|
+
async writeEnabled(location, enabled_names) {
|
|
251
555
|
const data = await read_json_file(location.path) ?? {};
|
|
252
556
|
const servers = get_server_record(data, options.serverKey);
|
|
253
|
-
const enabled = new Set(
|
|
557
|
+
const enabled = new Set(enabled_names);
|
|
254
558
|
for (const [name, config] of Object.entries(servers)) set_server_enabled(config, enabled.has(name), options.disabledMode ?? "disabled");
|
|
255
559
|
data[options.serverKey] = servers;
|
|
256
560
|
await write_json_file(location.path, data);
|
|
561
|
+
},
|
|
562
|
+
async write_server(location, server) {
|
|
563
|
+
const data = await read_json_file(location.path) ?? {};
|
|
564
|
+
const servers = get_server_record(data, options.serverKey);
|
|
565
|
+
servers[server.name] = portable_to_json(server, options.disabledMode ?? "disabled");
|
|
566
|
+
data[options.serverKey] = servers;
|
|
567
|
+
await write_json_file(location.path, data);
|
|
568
|
+
},
|
|
569
|
+
async write_server_config(location, name, config) {
|
|
570
|
+
const data = await read_json_file(location.path) ?? {};
|
|
571
|
+
const servers = get_server_record(data, options.serverKey);
|
|
572
|
+
servers[name] = config;
|
|
573
|
+
data[options.serverKey] = servers;
|
|
574
|
+
await write_json_file(location.path, data);
|
|
575
|
+
},
|
|
576
|
+
async remove_server(location, name) {
|
|
577
|
+
const data = await read_json_file(location.path) ?? {};
|
|
578
|
+
const servers = get_server_record(data, options.serverKey);
|
|
579
|
+
delete servers[name];
|
|
580
|
+
data[options.serverKey] = servers;
|
|
581
|
+
await write_json_file(location.path, data);
|
|
257
582
|
}
|
|
258
583
|
};
|
|
259
584
|
}
|
|
@@ -392,6 +717,37 @@ const client_adapters = [
|
|
|
392
717
|
function get_client_adapter(id) {
|
|
393
718
|
return client_adapters.find((adapter) => adapter.id === id) ?? null;
|
|
394
719
|
}
|
|
720
|
+
function resolve_client_location(adapter, scope, path) {
|
|
721
|
+
let locations = adapter.locations();
|
|
722
|
+
if (path) locations = locations.filter((location) => location.path === path);
|
|
723
|
+
else if (scope) locations = locations.filter((location) => location.scope === scope);
|
|
724
|
+
if (locations.length === 1) return locations[0];
|
|
725
|
+
if (locations.length === 0) throw new Error(`No ${adapter.label} config location matches${scope ? ` scope '${scope}'` : ""}${path ? ` path '${path}'` : ""}.`);
|
|
726
|
+
throw new Error(`${adapter.label} has multiple matching config locations. Pass --location with one of: ${locations.map((location) => location.path).join(", ")}`);
|
|
727
|
+
}
|
|
728
|
+
async function add_client_server(adapter, location, server) {
|
|
729
|
+
if (!adapter.write_server) throw new Error(`${adapter.label} support cannot add servers yet.`);
|
|
730
|
+
await adapter.write_server(location, server);
|
|
731
|
+
}
|
|
732
|
+
async function add_client_server_config(adapter, location, name, config) {
|
|
733
|
+
if (!adapter.write_server_config) throw new Error(`${adapter.label} support cannot add servers yet.`);
|
|
734
|
+
await adapter.write_server_config(location, name, config);
|
|
735
|
+
}
|
|
736
|
+
async function remove_client_server(adapter, location, server_name) {
|
|
737
|
+
if (!adapter.remove_server) throw new Error(`${adapter.label} support cannot remove servers yet.`);
|
|
738
|
+
await adapter.remove_server(location, server_name);
|
|
739
|
+
}
|
|
740
|
+
async function set_client_server_enabled(adapter, location, server_name, enabled) {
|
|
741
|
+
if (!adapter.writeEnabled) throw new Error(`${adapter.label} support is read-only.`);
|
|
742
|
+
const servers = await adapter.readLocation(location);
|
|
743
|
+
const server = servers.find((candidate) => candidate.name === server_name);
|
|
744
|
+
if (!server) throw new Error(`Server '${server_name}' not found at ${location.path}.`);
|
|
745
|
+
const enabled_names = new Set(servers.filter((candidate) => candidate.disabled !== true).map((candidate) => candidate.name));
|
|
746
|
+
if (enabled) enabled_names.add(server.name);
|
|
747
|
+
else enabled_names.delete(server.name);
|
|
748
|
+
await adapter.writeEnabled(location, [...enabled_names]);
|
|
749
|
+
return enabled_names.size;
|
|
750
|
+
}
|
|
395
751
|
async function list_client_locations() {
|
|
396
752
|
return await Promise.all(client_adapters.flatMap((adapter) => adapter.locations().map(async (location) => ({
|
|
397
753
|
client: adapter.id,
|
|
@@ -421,7 +777,7 @@ async function read_server_registry() {
|
|
|
421
777
|
async function write_server_registry(registry) {
|
|
422
778
|
const registry_path = get_server_registry_path();
|
|
423
779
|
await ensure_directory_exists(get_mcpick_dir());
|
|
424
|
-
await
|
|
780
|
+
await atomic_json_write(registry_path, () => registry);
|
|
425
781
|
}
|
|
426
782
|
async function add_server_to_registry(server) {
|
|
427
783
|
const registry = await read_server_registry();
|
|
@@ -431,7 +787,7 @@ async function add_server_to_registry(server) {
|
|
|
431
787
|
await write_server_registry(registry);
|
|
432
788
|
}
|
|
433
789
|
async function get_all_available_servers() {
|
|
434
|
-
const { get_enabled_servers, read_claude_config } = await import("./config-
|
|
790
|
+
const { get_enabled_servers, read_claude_config } = await import("./config-DzMmTJYL.js").then((n) => n.t);
|
|
435
791
|
const registry = await read_server_registry();
|
|
436
792
|
const config_servers = get_enabled_servers(await read_claude_config());
|
|
437
793
|
const config_by_name = new Map(config_servers.map((s) => [s.name, s]));
|
|
@@ -566,17 +922,26 @@ async function add_mcp_via_cli(server, scope) {
|
|
|
566
922
|
/**
|
|
567
923
|
* Remove an MCP server using Claude CLI
|
|
568
924
|
*/
|
|
569
|
-
|
|
925
|
+
function build_remove_args(name, scope) {
|
|
926
|
+
return scope ? [
|
|
927
|
+
"mcp",
|
|
928
|
+
"remove",
|
|
929
|
+
name,
|
|
930
|
+
"--scope",
|
|
931
|
+
scope
|
|
932
|
+
] : [
|
|
933
|
+
"mcp",
|
|
934
|
+
"remove",
|
|
935
|
+
name
|
|
936
|
+
];
|
|
937
|
+
}
|
|
938
|
+
async function remove_mcp_via_cli(name, scope) {
|
|
570
939
|
if (!await check_claude_cli()) return {
|
|
571
940
|
success: false,
|
|
572
941
|
error: "Claude CLI not found. Please install Claude Code CLI."
|
|
573
942
|
};
|
|
574
943
|
try {
|
|
575
|
-
await run_claude(
|
|
576
|
-
"mcp",
|
|
577
|
-
"remove",
|
|
578
|
-
name
|
|
579
|
-
]);
|
|
944
|
+
await run_claude(build_remove_args(name, scope));
|
|
580
945
|
return { success: true };
|
|
581
946
|
} catch (error) {
|
|
582
947
|
return {
|
|
@@ -657,77 +1022,176 @@ async function update_plugin_via_cli(key, scope = "user") {
|
|
|
657
1022
|
};
|
|
658
1023
|
}
|
|
659
1024
|
}
|
|
1025
|
+
const GITHUB_OWNER_PATTERN = "[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?";
|
|
1026
|
+
const GITHUB_REPO_PATTERN = "[A-Za-z0-9._-]+";
|
|
660
1027
|
/**
|
|
661
1028
|
* Extract GitHub owner/repo from various source formats.
|
|
662
1029
|
* Returns null if not a recognizable GitHub reference.
|
|
663
1030
|
*/
|
|
664
1031
|
function parse_github_repo(source) {
|
|
665
|
-
const
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
1032
|
+
const trimmed = source.trim();
|
|
1033
|
+
try {
|
|
1034
|
+
const url = new URL(trimmed);
|
|
1035
|
+
if (url.hostname !== "github.com") return null;
|
|
1036
|
+
const [owner, raw_repo, ...rest] = url.pathname.split("/").filter(Boolean);
|
|
1037
|
+
if (!owner || !raw_repo || rest.length > 0) return null;
|
|
1038
|
+
const repo = raw_repo.replace(/\.git$/, "");
|
|
1039
|
+
if (!is_valid_github_repo(owner, repo)) return null;
|
|
1040
|
+
return {
|
|
1041
|
+
owner,
|
|
1042
|
+
repo,
|
|
1043
|
+
kind: "https"
|
|
1044
|
+
};
|
|
1045
|
+
} catch {}
|
|
1046
|
+
const ssh_match = trimmed.match(new RegExp(`^git@github\\.com:(${GITHUB_OWNER_PATTERN})/(${GITHUB_REPO_PATTERN})(?:\\.git)?$`));
|
|
671
1047
|
if (ssh_match) return {
|
|
672
1048
|
owner: ssh_match[1],
|
|
673
|
-
repo: ssh_match[2]
|
|
1049
|
+
repo: ssh_match[2].replace(/\.git$/, ""),
|
|
1050
|
+
kind: "ssh"
|
|
674
1051
|
};
|
|
675
|
-
const shorthand_match =
|
|
1052
|
+
const shorthand_match = trimmed.match(new RegExp(`^(${GITHUB_OWNER_PATTERN})/(${GITHUB_REPO_PATTERN})$`));
|
|
676
1053
|
if (shorthand_match) return {
|
|
677
1054
|
owner: shorthand_match[1],
|
|
678
|
-
repo: shorthand_match[2]
|
|
1055
|
+
repo: shorthand_match[2],
|
|
1056
|
+
kind: "shorthand"
|
|
679
1057
|
};
|
|
680
1058
|
return null;
|
|
681
1059
|
}
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
1060
|
+
function is_valid_github_repo(owner, repo) {
|
|
1061
|
+
return new RegExp(`^${GITHUB_OWNER_PATTERN}$`).test(owner) && new RegExp(`^${GITHUB_REPO_PATTERN}$`).test(repo);
|
|
1062
|
+
}
|
|
1063
|
+
function build_marketplace_add_args(source) {
|
|
1064
|
+
return [
|
|
1065
|
+
"plugin",
|
|
1066
|
+
"marketplace",
|
|
1067
|
+
"add",
|
|
1068
|
+
source
|
|
1069
|
+
];
|
|
1070
|
+
}
|
|
1071
|
+
function get_github_repo_id(ref) {
|
|
1072
|
+
return `${ref.owner}/${ref.repo}`;
|
|
1073
|
+
}
|
|
1074
|
+
function get_github_clone_urls(ref) {
|
|
1075
|
+
const repo_id = get_github_repo_id(ref);
|
|
1076
|
+
const https_url = `https://github.com/${repo_id}.git`;
|
|
1077
|
+
const ssh_url = `git@github.com:${repo_id}.git`;
|
|
1078
|
+
if (ref.kind === "https") return [https_url];
|
|
1079
|
+
if (ref.kind === "ssh") return [ssh_url];
|
|
1080
|
+
return [https_url, ssh_url];
|
|
1081
|
+
}
|
|
1082
|
+
function get_process_error_output(error) {
|
|
1083
|
+
const process_error = error;
|
|
1084
|
+
return redact_text([
|
|
1085
|
+
process_error.message,
|
|
1086
|
+
process_error.stderr?.toString(),
|
|
1087
|
+
process_error.stdout?.toString()
|
|
1088
|
+
].filter(Boolean).join("\n"));
|
|
1089
|
+
}
|
|
1090
|
+
async function can_access_with_git(url) {
|
|
1091
|
+
try {
|
|
1092
|
+
await exec_file_async$1("git", [
|
|
1093
|
+
"ls-remote",
|
|
1094
|
+
"--exit-code",
|
|
1095
|
+
url,
|
|
1096
|
+
"HEAD"
|
|
1097
|
+
], {
|
|
1098
|
+
env: {
|
|
1099
|
+
...process.env,
|
|
1100
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
1101
|
+
GIT_SSH_COMMAND: "ssh -o BatchMode=yes"
|
|
1102
|
+
},
|
|
1103
|
+
timeout: 15e3
|
|
1104
|
+
});
|
|
1105
|
+
return null;
|
|
1106
|
+
} catch (error) {
|
|
1107
|
+
return get_process_error_output(error);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
async function can_view_with_gh(repo_id) {
|
|
687
1111
|
try {
|
|
688
|
-
|
|
1112
|
+
await exec_file_async$1("gh", [
|
|
1113
|
+
"repo",
|
|
1114
|
+
"view",
|
|
1115
|
+
repo_id,
|
|
1116
|
+
"--json",
|
|
1117
|
+
"nameWithOwner"
|
|
1118
|
+
], { timeout: 15e3 });
|
|
1119
|
+
return true;
|
|
1120
|
+
} catch (error) {
|
|
1121
|
+
const message = get_process_error_output(error).toLowerCase();
|
|
1122
|
+
if (message.includes("could not resolve") || message.includes("not found") || message.includes("repository not found")) return false;
|
|
1123
|
+
return null;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
async function get_github_api_status(repo_id) {
|
|
1127
|
+
try {
|
|
1128
|
+
return (await fetch(`https://api.github.com/repos/${repo_id}`, {
|
|
689
1129
|
method: "GET",
|
|
690
1130
|
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}'.`;
|
|
1131
|
+
})).status;
|
|
696
1132
|
} catch {
|
|
697
1133
|
return null;
|
|
698
1134
|
}
|
|
699
1135
|
}
|
|
1136
|
+
/**
|
|
1137
|
+
* Validate that a GitHub repository can be accessed by git without cloning it.
|
|
1138
|
+
* Returns an error message if validation fails, null if OK.
|
|
1139
|
+
*/
|
|
1140
|
+
async function validate_github_repo(ref) {
|
|
1141
|
+
const repo_id = get_github_repo_id(ref);
|
|
1142
|
+
const git_errors = [];
|
|
1143
|
+
for (const url of get_github_clone_urls(ref)) {
|
|
1144
|
+
const git_error = await can_access_with_git(url);
|
|
1145
|
+
if (!git_error) return null;
|
|
1146
|
+
git_errors.push(git_error);
|
|
1147
|
+
}
|
|
1148
|
+
const gh_visible = await can_view_with_gh(repo_id);
|
|
1149
|
+
if (gh_visible === false) return `Repository '${repo_id}' not found or not accessible with your GitHub account.`;
|
|
1150
|
+
const api_status = await get_github_api_status(repo_id);
|
|
1151
|
+
if (api_status === 404 && gh_visible !== true) return `Repository '${repo_id}' not found or private/inaccessible. Check the name, sign in with 'gh auth login', or use an SSH URL with access.`;
|
|
1152
|
+
if (api_status === 403 && gh_visible !== true) return `Unable to validate '${repo_id}' because GitHub API access was denied or rate-limited. Git access also failed; check your credentials.`;
|
|
1153
|
+
return format_git_access_error(ref, git_errors.join("\n"));
|
|
1154
|
+
}
|
|
1155
|
+
function format_git_access_error(ref, message) {
|
|
1156
|
+
const repo_id = get_github_repo_id(ref);
|
|
1157
|
+
const lower = message.toLowerCase();
|
|
1158
|
+
if (ref.kind === "shorthand" && (lower.includes("could not read username") || lower.includes("authentication failed")) && (lower.includes("permission denied (publickey)") || lower.includes("ssh authentication"))) return `Repository '${repo_id}' exists, but Git cannot access it over HTTPS or SSH. Run 'gh auth login' and 'gh auth setup-git', or configure an SSH key with GitHub.`;
|
|
1159
|
+
if (ref.kind === "https" || lower.includes("https authentication failed") || lower.includes("could not read username") || lower.includes("authentication failed")) return `Repository '${repo_id}' exists, but HTTPS Git authentication failed. Run 'gh auth login' and 'gh auth setup-git', configure a Git credential helper, or use git@github.com:${repo_id}.git.`;
|
|
1160
|
+
if (ref.kind === "ssh" || lower.includes("permission denied (publickey)") || lower.includes("ssh authentication")) return `SSH authentication failed for '${repo_id}'. Configure an SSH key with GitHub or use https://github.com/${repo_id}.git with a configured Git credential helper.`;
|
|
1161
|
+
return `Unable to access GitHub repository '${repo_id}' with git. Check that the repository exists and that your HTTPS or SSH credentials can clone it.`;
|
|
1162
|
+
}
|
|
700
1163
|
async function marketplace_add_via_cli(source) {
|
|
701
1164
|
if (!await check_claude_cli()) return {
|
|
702
1165
|
success: false,
|
|
703
1166
|
error: "Claude CLI not found. Please install Claude Code CLI."
|
|
704
1167
|
};
|
|
705
1168
|
const gh = parse_github_repo(source);
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
const validation_error = await validate_github_repo(gh.owner, gh.repo);
|
|
1169
|
+
if (gh) {
|
|
1170
|
+
const validation_error = await validate_github_repo(gh);
|
|
709
1171
|
if (validation_error) return {
|
|
710
1172
|
success: false,
|
|
711
1173
|
error: validation_error
|
|
712
1174
|
};
|
|
713
1175
|
}
|
|
714
1176
|
try {
|
|
715
|
-
await run_claude(
|
|
716
|
-
"plugin",
|
|
717
|
-
"marketplace",
|
|
718
|
-
"add",
|
|
719
|
-
source
|
|
720
|
-
]);
|
|
1177
|
+
await run_claude(build_marketplace_add_args(source));
|
|
721
1178
|
return { success: true };
|
|
722
1179
|
} catch (error) {
|
|
723
1180
|
const message = get_redacted_error_message(error);
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
1181
|
+
const lower_message = message.toLowerCase();
|
|
1182
|
+
if (gh) {
|
|
1183
|
+
if (lower_message.includes("https authentication failed") || lower_message.includes("could not read username") || lower_message.includes("authentication failed")) return {
|
|
1184
|
+
success: false,
|
|
1185
|
+
error: format_git_access_error(gh, message)
|
|
1186
|
+
};
|
|
1187
|
+
if (message.includes("SSH") || message.includes("Permission denied (publickey)")) return {
|
|
1188
|
+
success: false,
|
|
1189
|
+
error: format_git_access_error(gh, message)
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
if (lower_message.includes("not found") || lower_message.includes("does not exist")) return {
|
|
729
1193
|
success: false,
|
|
730
|
-
error: `Repository '${source}' not found. Check the name and your access permissions.`
|
|
1194
|
+
error: gh ? `Repository '${get_github_repo_id(gh)}' not found or not accessible with your GitHub account.` : `Repository '${source}' not found. Check the name and your access permissions.`
|
|
731
1195
|
};
|
|
732
1196
|
return {
|
|
733
1197
|
success: false,
|
|
@@ -1053,7 +1517,7 @@ async function edit_claude_config() {
|
|
|
1053
1517
|
}
|
|
1054
1518
|
}
|
|
1055
1519
|
for (const name of servers_to_remove) {
|
|
1056
|
-
const result = await remove_mcp_via_cli(name);
|
|
1520
|
+
const result = await remove_mcp_via_cli(name, scope);
|
|
1057
1521
|
if (!result.success) {
|
|
1058
1522
|
error_count++;
|
|
1059
1523
|
log.warn(`Failed to remove ${name}: ${result.error}`);
|
|
@@ -1358,7 +1822,7 @@ async function read_disabled_hooks() {
|
|
|
1358
1822
|
}
|
|
1359
1823
|
async function write_disabled_hooks(entries) {
|
|
1360
1824
|
await ensure_directory_exists(get_mcpick_dir());
|
|
1361
|
-
await
|
|
1825
|
+
await safe_json_write(get_disabled_hooks_path(), entries, " ");
|
|
1362
1826
|
}
|
|
1363
1827
|
/**
|
|
1364
1828
|
* Remove a specific hook handler from a hooks.json file by matching the handler.
|
|
@@ -1387,7 +1851,7 @@ async function remove_hook_from_file(hooks_path, event, handler) {
|
|
|
1387
1851
|
}
|
|
1388
1852
|
if (!removed) return false;
|
|
1389
1853
|
if (matchers.length === 0) delete hooks_obj[event];
|
|
1390
|
-
await
|
|
1854
|
+
await safe_json_write(hooks_path, hooks_data, " ");
|
|
1391
1855
|
return true;
|
|
1392
1856
|
}
|
|
1393
1857
|
/**
|
|
@@ -1446,7 +1910,7 @@ async function add_hook_to_file(hooks_path, event, matcher_pattern, handler) {
|
|
|
1446
1910
|
}
|
|
1447
1911
|
if (matcher.hooks.some((h) => h.type === handler.type && h.command === handler.command && h.url === handler.url && h.prompt === handler.prompt)) return;
|
|
1448
1912
|
matcher.hooks.push(handler);
|
|
1449
|
-
await
|
|
1913
|
+
await safe_json_write(hooks_path, hooks_data, " ");
|
|
1450
1914
|
}
|
|
1451
1915
|
/**
|
|
1452
1916
|
* Re-enable a previously disabled plugin hook.
|
|
@@ -2107,7 +2571,7 @@ async function save_profile(name) {
|
|
|
2107
2571
|
await ensure_directory_exists(get_profiles_dir());
|
|
2108
2572
|
const profile_data = { mcpServers: servers };
|
|
2109
2573
|
if (plugin_count > 0) profile_data.enabledPlugins = plugins;
|
|
2110
|
-
await
|
|
2574
|
+
await safe_json_write(get_profile_path(name), profile_data, 2);
|
|
2111
2575
|
return {
|
|
2112
2576
|
serverCount: server_count,
|
|
2113
2577
|
pluginCount: plugin_count
|
|
@@ -2402,17 +2866,18 @@ const SUBCOMMANDS = new Set([
|
|
|
2402
2866
|
"cache",
|
|
2403
2867
|
"dev",
|
|
2404
2868
|
"marketplace",
|
|
2405
|
-
"reload"
|
|
2869
|
+
"reload",
|
|
2870
|
+
"rollback"
|
|
2406
2871
|
]);
|
|
2407
2872
|
const arg = process.argv[2];
|
|
2408
2873
|
if (arg && SUBCOMMANDS.has(arg) || arg === "--help" || arg === "-h" || !process.stdout.isTTY) {
|
|
2409
2874
|
if (!arg && !process.stdout.isTTY) process.argv.push("--help");
|
|
2410
|
-
import("./cli-
|
|
2875
|
+
import("./cli-By-0nYNQ.js").then((m) => m.run());
|
|
2411
2876
|
} else main().catch((error) => {
|
|
2412
2877
|
console.error("Fatal error:", error);
|
|
2413
2878
|
process.exit(1);
|
|
2414
2879
|
});
|
|
2415
2880
|
//#endregion
|
|
2416
|
-
export {
|
|
2881
|
+
export { add_client_server as A, get_all_plugins as B, validate_plugin_via_cli as C, list_plugin_backups as D, list_backups as E, resolve_client_location as F, list_config_backups as G, remove_hook as H, set_client_server_enabled as I, restore_config_backup as K, add_hook as L, get_client_adapter as M, list_client_locations as N, read_server_registry as O, remove_client_server as P, build_enabled_plugins as R, update_plugin_via_cli as S, get_all_available_servers as T, write_claude_settings as U, read_claude_settings as V, atomic_json_write as W, 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, add_client_server_config 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, get_all_hooks as z };
|
|
2417
2882
|
|
|
2418
2883
|
//# sourceMappingURL=index.js.map
|