mcpick 0.0.21 → 0.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/.github/copilot-instructions.md +39 -21
  2. package/CHANGELOG.md +19 -5
  3. package/CONTEXT.md +49 -0
  4. package/README.md +146 -127
  5. package/dist/{add-BDyaBew0.js → add-Bok0qbXi.js} +4 -5
  6. package/dist/{add-json-BjgzdeG-.js → add-json-C44vy2A_.js} +3 -3
  7. package/dist/{atomic-write-BqEykHp9.js → atomic-write-4lANmzsO.js} +1 -1
  8. package/dist/{backup-DSDhHI5f.js → backup-bdg6dvsb.js} +5 -5
  9. package/dist/{cache-D6kd7qE8.js → cache-CSUcGdZP.js} +3 -3
  10. package/dist/cli-avr5R1LO.js +111 -0
  11. package/dist/clients-CSQgqHzb.js +30 -0
  12. package/dist/{clone-DYKPEsar.js → clone-CQ0skkT6.js} +6 -7
  13. package/dist/{config-DijVdEFn.js → config-BhX4eAgg.js} +4 -4
  14. package/dist/{dev-DRJRNp7y.js → dev-CTDg5g-c.js} +6 -6
  15. package/dist/{disable-xJXZfUR_.js → disable-DLlOj7sc.js} +3 -4
  16. package/dist/{enable-RrpcN6la.js → enable-CGFYYC2A.js} +3 -4
  17. package/dist/{get-Bb1eOOIZ.js → get-l-eAJhBy.js} +3 -3
  18. package/dist/{hooks-Bmn7pUZa.js → hooks-BWZ_Kgx3.js} +4 -4
  19. package/dist/index.js +1479 -312
  20. package/dist/list-By--kltj.js +100 -0
  21. package/dist/{marketplace-DcKk5dc1.js → marketplace-DdiKDDKK.js} +4 -5
  22. package/dist/output-BgN9Uuxf.js +17 -0
  23. package/dist/{paths-BPISiJi4.js → paths-6wrIM8yh.js} +1 -1
  24. package/dist/{plugin-cache-Bby9Dxm9.js → plugin-cache-DKcW8LGV.js} +3 -3
  25. package/dist/{plugins-Dc7DN6R_.js → plugins-CsXE8AH4.js} +5 -5
  26. package/dist/{profile-CX97sMGp.js → profile-DzGPsdsl.js} +5 -5
  27. package/dist/redact-Dltz2gde.js +88 -0
  28. package/dist/{reload-CYDhkCVZ.js → reload-C29-vuvy.js} +2 -2
  29. package/dist/{remove-D1owHLhG.js → remove-B5q4rQRU.js} +3 -4
  30. package/dist/{reset-project-choices-BfRSNN3m.js → reset-project-choices-Dhh4CxIC.js} +3 -3
  31. package/dist/{restore-DdMfUljI.js → restore-BI8aiszM.js} +6 -6
  32. package/dist/{settings-DEcWtzLE.js → settings-CZR8bVfh.js} +5 -5
  33. package/dist/skills-DPBDmION.js +216 -0
  34. package/dist/{validation-xMlbgGCF.js → validation-qWlF51fw.js} +1 -1
  35. package/package.json +20 -6
  36. package/dist/claude-cli-DnmBJrjg.js +0 -445
  37. package/dist/cli-CsFfnWBo.js +0 -84
  38. package/dist/hook-state-Di8lUsPr.js +0 -171
  39. package/dist/list-B8YeDWt6.js +0 -64
  40. package/dist/output-BchYq0mR.js +0 -15
  41. package/dist/profile-DkY_lBEm.js +0 -70
  42. package/dist/redact-O35tjnRD.js +0 -26
  43. package/dist/registry-CfUKT7_C.js +0 -92
@@ -1,445 +0,0 @@
1
- import { execFile } from "node:child_process";
2
- import { promisify } from "node:util";
3
- //#region src/utils/claude-cli.ts
4
- const exec_file_async = promisify(execFile);
5
- /**
6
- * Check if Claude CLI is available
7
- */
8
- async function check_claude_cli() {
9
- try {
10
- await exec_file_async("claude", ["--version"]);
11
- return true;
12
- } catch {
13
- return false;
14
- }
15
- }
16
- /**
17
- * Validate environment variable key.
18
- * Must start with letter or underscore, contain only alphanumeric and underscores.
19
- */
20
- function is_valid_env_key(key) {
21
- return /^[A-Za-z_][A-Za-z0-9_]*$/.test(key);
22
- }
23
- /**
24
- * Build args array for claude mcp add command.
25
- * Returns raw args — no shell escaping needed since we use execFile.
26
- */
27
- function build_add_args(server, scope) {
28
- const args = ["mcp", "add"];
29
- args.push(server.name);
30
- const transport = server.type || "stdio";
31
- if (transport !== "stdio") args.push("--transport", transport);
32
- args.push("--scope", scope);
33
- if (transport === "stdio") {
34
- if (server.env) {
35
- for (const [key, value] of Object.entries(server.env)) if (is_valid_env_key(key)) args.push("-e", `${key}=${value}`);
36
- }
37
- if ("command" in server && server.command) {
38
- args.push("--");
39
- args.push(server.command);
40
- if (server.args && server.args.length > 0) args.push(...server.args);
41
- }
42
- } else {
43
- if ("url" in server && server.url) args.push(server.url);
44
- if ("headers" in server && server.headers) for (const [key, value] of Object.entries(server.headers)) args.push("-H", `${key}: ${value}`);
45
- }
46
- return args;
47
- }
48
- /**
49
- * Run a claude CLI command using execFile (no shell).
50
- * This avoids all shell escaping issues on every platform.
51
- */
52
- async function run_claude(args) {
53
- return exec_file_async("claude", args);
54
- }
55
- /**
56
- * Add an MCP server using Claude CLI
57
- */
58
- async function add_mcp_via_cli(server, scope) {
59
- if (!await check_claude_cli()) return {
60
- success: false,
61
- error: "Claude CLI not found. Please install Claude Code CLI."
62
- };
63
- try {
64
- await run_claude(build_add_args(server, scope));
65
- return { success: true };
66
- } catch (error) {
67
- return {
68
- success: false,
69
- error: `Failed to add server via CLI: ${error instanceof Error ? error.message : "Unknown error"}`
70
- };
71
- }
72
- }
73
- /**
74
- * Remove an MCP server using Claude CLI
75
- */
76
- async function remove_mcp_via_cli(name) {
77
- if (!await check_claude_cli()) return {
78
- success: false,
79
- error: "Claude CLI not found. Please install Claude Code CLI."
80
- };
81
- try {
82
- await run_claude([
83
- "mcp",
84
- "remove",
85
- name
86
- ]);
87
- return { success: true };
88
- } catch (error) {
89
- return {
90
- success: false,
91
- error: `Failed to remove server via CLI: ${error instanceof Error ? error.message : "Unknown error"}`
92
- };
93
- }
94
- }
95
- /**
96
- * Install a plugin via Claude CLI
97
- */
98
- async function install_plugin_via_cli(key, scope = "user") {
99
- if (!await check_claude_cli()) return {
100
- success: false,
101
- error: "Claude CLI not found. Please install Claude Code CLI."
102
- };
103
- try {
104
- await run_claude([
105
- "plugin",
106
- "install",
107
- key,
108
- "--scope",
109
- scope
110
- ]);
111
- return { success: true };
112
- } catch (error) {
113
- return {
114
- success: false,
115
- error: `Failed to install plugin: ${error instanceof Error ? error.message : "Unknown error"}`
116
- };
117
- }
118
- }
119
- /**
120
- * Uninstall a plugin via Claude CLI
121
- */
122
- async function uninstall_plugin_via_cli(key, scope = "user") {
123
- if (!await check_claude_cli()) return {
124
- success: false,
125
- error: "Claude CLI not found. Please install Claude Code CLI."
126
- };
127
- try {
128
- await run_claude([
129
- "plugin",
130
- "uninstall",
131
- key,
132
- "--scope",
133
- scope
134
- ]);
135
- return { success: true };
136
- } catch (error) {
137
- return {
138
- success: false,
139
- error: `Failed to uninstall plugin: ${error instanceof Error ? error.message : "Unknown error"}`
140
- };
141
- }
142
- }
143
- /**
144
- * Update a plugin via Claude CLI
145
- */
146
- async function update_plugin_via_cli(key, scope = "user") {
147
- if (!await check_claude_cli()) return {
148
- success: false,
149
- error: "Claude CLI not found. Please install Claude Code CLI."
150
- };
151
- try {
152
- await run_claude([
153
- "plugin",
154
- "update",
155
- key,
156
- "--scope",
157
- scope
158
- ]);
159
- return { success: true };
160
- } catch (error) {
161
- return {
162
- success: false,
163
- error: `Failed to update plugin: ${error instanceof Error ? error.message : "Unknown error"}`
164
- };
165
- }
166
- }
167
- /**
168
- * Extract GitHub owner/repo from various source formats.
169
- * Returns null if not a recognizable GitHub reference.
170
- */
171
- function parse_github_repo(source) {
172
- const https_match = source.match(/^https?:\/\/github\.com\/([^/]+)\/([^/.]+)(?:\.git)?$/);
173
- if (https_match) return {
174
- owner: https_match[1],
175
- repo: https_match[2]
176
- };
177
- const ssh_match = source.match(/^git@github\.com:([^/]+)\/([^/.]+)(?:\.git)?$/);
178
- if (ssh_match) return {
179
- owner: ssh_match[1],
180
- repo: ssh_match[2]
181
- };
182
- const shorthand_match = source.match(/^([^/\s]+)\/([^/\s]+)$/);
183
- if (shorthand_match) return {
184
- owner: shorthand_match[1],
185
- repo: shorthand_match[2]
186
- };
187
- return null;
188
- }
189
- /**
190
- * Validate that a GitHub repository exists and is accessible.
191
- * Returns an error message if validation fails, null if OK.
192
- */
193
- async function validate_github_repo(owner, repo) {
194
- try {
195
- const response = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
196
- method: "GET",
197
- headers: { Accept: "application/vnd.github.v3+json" }
198
- });
199
- if (response.status === 200) return null;
200
- if (response.status === 404) return `Repository '${owner}/${repo}' not found on GitHub. Check the name or ensure it's not private.`;
201
- if (response.status === 403) return `Access denied for '${owner}/${repo}'. The repository may be private — configure a GitHub token or use SSH.`;
202
- return `GitHub API returned status ${response.status} for '${owner}/${repo}'.`;
203
- } catch {
204
- return null;
205
- }
206
- }
207
- async function marketplace_add_via_cli(source) {
208
- if (!await check_claude_cli()) return {
209
- success: false,
210
- error: "Claude CLI not found. Please install Claude Code CLI."
211
- };
212
- const gh = parse_github_repo(source);
213
- const is_shorthand = gh && !source.startsWith("http") && !source.startsWith("git@");
214
- if (gh && is_shorthand) {
215
- const validation_error = await validate_github_repo(gh.owner, gh.repo);
216
- if (validation_error) return {
217
- success: false,
218
- error: validation_error
219
- };
220
- }
221
- try {
222
- await run_claude([
223
- "plugin",
224
- "marketplace",
225
- "add",
226
- source
227
- ]);
228
- return { success: true };
229
- } catch (error) {
230
- const message = error instanceof Error ? error.message : "Unknown error";
231
- if (message.includes("SSH") || message.includes("Permission denied (publickey)")) return {
232
- success: false,
233
- error: `SSH authentication failed for '${source}'. Either:\n - Configure SSH keys: https://docs.github.com/en/authentication/connecting-to-github-with-ssh\n - Use HTTPS URL instead: https://github.com/${gh ? `${gh.owner}/${gh.repo}` : source}`
234
- };
235
- if (message.includes("not found") || message.includes("does not exist")) return {
236
- success: false,
237
- error: `Repository '${source}' not found. Check the name and your access permissions.`
238
- };
239
- return {
240
- success: false,
241
- error: `Failed to add marketplace: ${message}`
242
- };
243
- }
244
- }
245
- /**
246
- * Remove a marketplace via Claude CLI
247
- */
248
- async function marketplace_remove_via_cli(name) {
249
- if (!await check_claude_cli()) return {
250
- success: false,
251
- error: "Claude CLI not found. Please install Claude Code CLI."
252
- };
253
- try {
254
- await run_claude([
255
- "plugin",
256
- "marketplace",
257
- "remove",
258
- name
259
- ]);
260
- return { success: true };
261
- } catch (error) {
262
- return {
263
- success: false,
264
- error: `Failed to remove marketplace: ${error instanceof Error ? error.message : "Unknown error"}`
265
- };
266
- }
267
- }
268
- /**
269
- * Update marketplace(s) via Claude CLI
270
- */
271
- async function marketplace_update_via_cli(name) {
272
- if (!await check_claude_cli()) return {
273
- success: false,
274
- error: "Claude CLI not found. Please install Claude Code CLI."
275
- };
276
- try {
277
- const args = [
278
- "plugin",
279
- "marketplace",
280
- "update"
281
- ];
282
- if (name) args.push(name);
283
- await run_claude(args);
284
- return { success: true };
285
- } catch (error) {
286
- return {
287
- success: false,
288
- error: `Failed to update marketplace: ${error instanceof Error ? error.message : "Unknown error"}`
289
- };
290
- }
291
- }
292
- /**
293
- * List marketplaces via Claude CLI
294
- */
295
- async function marketplace_list_via_cli() {
296
- if (!await check_claude_cli()) return {
297
- success: false,
298
- error: "Claude CLI not found. Please install Claude Code CLI."
299
- };
300
- try {
301
- const { stdout } = await run_claude([
302
- "plugin",
303
- "marketplace",
304
- "list"
305
- ]);
306
- return {
307
- success: true,
308
- stdout: stdout.trim()
309
- };
310
- } catch (error) {
311
- return {
312
- success: false,
313
- error: `Failed to list marketplaces: ${error instanceof Error ? error.message : "Unknown error"}`
314
- };
315
- }
316
- }
317
- /**
318
- * Get the scope description for display
319
- */
320
- function get_scope_description(scope) {
321
- switch (scope) {
322
- case "local": return "This project only (default)";
323
- case "project": return "Shared via .mcp.json (version controlled)";
324
- case "user": return "Global - all projects";
325
- }
326
- }
327
- /**
328
- * Validate a plugin or marketplace manifest via Claude CLI
329
- */
330
- async function validate_plugin_via_cli(path) {
331
- if (!await check_claude_cli()) return {
332
- success: false,
333
- error: "Claude CLI not found. Please install Claude Code CLI."
334
- };
335
- try {
336
- const { stdout } = await run_claude([
337
- "plugin",
338
- "validate",
339
- path
340
- ]);
341
- return {
342
- success: true,
343
- stdout: stdout.trim()
344
- };
345
- } catch (error) {
346
- return {
347
- success: false,
348
- error: `Validation failed: ${error instanceof Error ? error.message : "Unknown error"}`
349
- };
350
- }
351
- }
352
- /**
353
- * Get details about an MCP server via Claude CLI
354
- */
355
- async function mcp_get_via_cli(name) {
356
- if (!await check_claude_cli()) return {
357
- success: false,
358
- error: "Claude CLI not found. Please install Claude Code CLI."
359
- };
360
- try {
361
- const { stdout } = await run_claude([
362
- "mcp",
363
- "get",
364
- name
365
- ]);
366
- return {
367
- success: true,
368
- stdout: stdout.trim()
369
- };
370
- } catch (error) {
371
- return {
372
- success: false,
373
- error: `Failed to get server details: ${error instanceof Error ? error.message : "Unknown error"}`
374
- };
375
- }
376
- }
377
- /**
378
- * Add an MCP server from raw JSON via Claude CLI
379
- */
380
- async function mcp_add_json_via_cli(name, json, scope = "local") {
381
- if (!await check_claude_cli()) return {
382
- success: false,
383
- error: "Claude CLI not found. Please install Claude Code CLI."
384
- };
385
- try {
386
- await run_claude([
387
- "mcp",
388
- "add-json",
389
- name,
390
- json,
391
- "--scope",
392
- scope
393
- ]);
394
- return { success: true };
395
- } catch (error) {
396
- return {
397
- success: false,
398
- error: `Failed to add server from JSON: ${error instanceof Error ? error.message : "Unknown error"}`
399
- };
400
- }
401
- }
402
- /**
403
- * Reset project-scoped MCP server choices via Claude CLI
404
- */
405
- async function mcp_reset_project_choices_via_cli() {
406
- if (!await check_claude_cli()) return {
407
- success: false,
408
- error: "Claude CLI not found. Please install Claude Code CLI."
409
- };
410
- try {
411
- await run_claude(["mcp", "reset-project-choices"]);
412
- return { success: true };
413
- } catch (error) {
414
- return {
415
- success: false,
416
- error: `Failed to reset project choices: ${error instanceof Error ? error.message : "Unknown error"}`
417
- };
418
- }
419
- }
420
- /**
421
- * Get scope options for select prompt
422
- */
423
- function get_scope_options() {
424
- return [
425
- {
426
- value: "local",
427
- label: "Local",
428
- hint: "This project only (default)"
429
- },
430
- {
431
- value: "project",
432
- label: "Project",
433
- hint: "Shared via .mcp.json (git)"
434
- },
435
- {
436
- value: "user",
437
- label: "User (Global)",
438
- hint: "Available in all projects"
439
- }
440
- ];
441
- }
442
- //#endregion
443
- export { install_plugin_via_cli as a, marketplace_remove_via_cli as c, mcp_get_via_cli as d, mcp_reset_project_choices_via_cli as f, validate_plugin_via_cli as g, update_plugin_via_cli as h, get_scope_options as i, marketplace_update_via_cli as l, uninstall_plugin_via_cli as m, check_claude_cli as n, marketplace_add_via_cli as o, remove_mcp_via_cli as p, get_scope_description as r, marketplace_list_via_cli as s, add_mcp_via_cli as t, mcp_add_json_via_cli as u };
444
-
445
- //# sourceMappingURL=claude-cli-DnmBJrjg.js.map
@@ -1,84 +0,0 @@
1
- import { defineCommand, renderUsage, runMain } from "citty";
2
- //#region src/cli/index.ts
3
- const main = defineCommand({
4
- meta: {
5
- name: "mcpick",
6
- description: "Claude Code extension manager — MCP servers, plugins (skills, hooks, agents), and marketplaces"
7
- },
8
- subCommands: {
9
- list: () => import("./list-B8YeDWt6.js").then((m) => m.default),
10
- enable: () => import("./enable-RrpcN6la.js").then((m) => m.default),
11
- disable: () => import("./disable-xJXZfUR_.js").then((m) => m.default),
12
- remove: () => import("./remove-D1owHLhG.js").then((m) => m.default),
13
- add: () => import("./add-BDyaBew0.js").then((m) => m.default),
14
- "add-json": () => import("./add-json-BjgzdeG-.js").then((m) => m.default),
15
- clone: () => import("./clone-DYKPEsar.js").then((m) => m.default),
16
- get: () => import("./get-Bb1eOOIZ.js").then((m) => m.default),
17
- "reset-project-choices": () => import("./reset-project-choices-BfRSNN3m.js").then((m) => m.default),
18
- backup: () => import("./backup-DSDhHI5f.js").then((m) => m.default),
19
- restore: () => import("./restore-DdMfUljI.js").then((m) => m.default),
20
- profile: () => import("./profile-CX97sMGp.js").then((m) => m.default),
21
- plugins: () => import("./plugins-Dc7DN6R_.js").then((m) => m.default),
22
- hooks: () => import("./hooks-Bmn7pUZa.js").then((m) => m.default),
23
- cache: () => import("./cache-D6kd7qE8.js").then((m) => m.default),
24
- dev: () => import("./dev-DRJRNp7y.js").then((m) => m.default),
25
- marketplace: () => import("./marketplace-DcKk5dc1.js").then((m) => m.default),
26
- reload: () => import("./reload-CYDhkCVZ.js").then((m) => m.default)
27
- }
28
- });
29
- /**
30
- * Custom help renderer that appends workflow guidance and examples
31
- * after citty's standard help output. This is critical for LLM agents
32
- * that rely on --help output to understand multi-step workflows.
33
- */
34
- async function show_usage_with_examples(cmd, parent) {
35
- const base = await renderUsage(cmd, parent);
36
- if ((await (typeof cmd.meta === "function" ? cmd.meta() : cmd.meta))?.name === "mcpick") console.log(base + "\n" + WORKFLOW_SECTION + "\n" + CONCEPTS_SECTION + "\n" + EXAMPLES_SECTION + "\n");
37
- else console.log(base + "\n");
38
- }
39
- const WORKFLOW_SECTION = `
40
- \x1b[4m\x1b[1mWORKFLOW\x1b[22m\x1b[24m Marketplaces contain plugins. Plugins contain skills (/slash-commands), hooks, agents, and MCP servers.
41
-
42
- To install skills from a marketplace, follow these steps:
43
-
44
- 1. Add a marketplace: mcpick marketplace add <source>
45
- 2. Install a plugin: mcpick plugins install <name>@<marketplace>
46
- 3. Skills are now available as /slash-commands in Claude Code`;
47
- const CONCEPTS_SECTION = `
48
- \x1b[4m\x1b[1mCONCEPTS\x1b[22m\x1b[24m
49
-
50
- Marketplace A catalog of plugins, hosted on GitHub, GitLab, or locally
51
- Plugin A bundle containing any mix of: skills, hooks, agents, MCP servers
52
- Skill A SKILL.md file that extends Claude's behaviour, invocable as /slash-command
53
- MCP server A tool server providing external capabilities to Claude Code
54
- Hook An event handler that runs on tool use, session start, etc.
55
- Profile A saved snapshot of your MCP server and plugin configuration`;
56
- const EXAMPLES_SECTION = `
57
- \x1b[4m\x1b[1mEXAMPLES\x1b[22m\x1b[24m
58
-
59
- Add a marketplace from GitHub (owner/repo):
60
- mcpick marketplace add spences10/claude-code-toolkit
61
-
62
- Add a marketplace from a full URL:
63
- mcpick marketplace add https://github.com/spences10/claude-code-toolkit
64
-
65
- Install a plugin from a marketplace:
66
- mcpick plugins install my-plugin@claude-code-toolkit
67
-
68
- List all installed plugins and their status:
69
- mcpick plugins list
70
-
71
- Toggle an MCP server on or off:
72
- mcpick enable my-server
73
- mcpick disable my-server
74
-
75
- List all MCP servers:
76
- mcpick list
77
-
78
- All commands support --json for machine-readable output.
79
- Run without arguments to launch the interactive TUI (not suitable for LLM agents).`;
80
- const run = () => runMain(main, { showUsage: show_usage_with_examples });
81
- //#endregion
82
- export { run };
83
-
84
- //# sourceMappingURL=cli-CsFfnWBo.js.map
@@ -1,171 +0,0 @@
1
- import { c as get_disabled_hooks_path, f as get_marketplaces_dir, p as get_mcpick_dir, t as ensure_directory_exists } from "./paths-BPISiJi4.js";
2
- import { readFile, writeFile } from "node:fs/promises";
3
- import { join } from "node:path";
4
- //#region src/core/hook-state.ts
5
- async function read_disabled_hooks() {
6
- try {
7
- const content = await readFile(get_disabled_hooks_path(), "utf-8");
8
- return JSON.parse(content);
9
- } catch {
10
- return [];
11
- }
12
- }
13
- async function write_disabled_hooks(entries) {
14
- await ensure_directory_exists(get_mcpick_dir());
15
- await writeFile(get_disabled_hooks_path(), JSON.stringify(entries, null, " "), "utf-8");
16
- }
17
- /**
18
- * Remove a specific hook handler from a hooks.json file by matching the handler.
19
- * Returns true if the hook was found and removed.
20
- */
21
- async function remove_hook_from_file(hooks_path, event, handler) {
22
- let content;
23
- try {
24
- content = await readFile(hooks_path, "utf-8");
25
- } catch {
26
- return false;
27
- }
28
- const hooks_data = JSON.parse(content);
29
- const hooks_obj = hooks_data.hooks || hooks_data;
30
- const matchers = hooks_obj[event];
31
- if (!matchers) return false;
32
- let removed = false;
33
- for (const m of matchers) {
34
- const idx = m.hooks?.findIndex((h) => h.type === handler.type && h.command === handler.command && h.url === handler.url && h.prompt === handler.prompt);
35
- if (idx !== void 0 && idx >= 0) {
36
- m.hooks.splice(idx, 1);
37
- removed = true;
38
- if (m.hooks.length === 0) matchers.splice(matchers.indexOf(m), 1);
39
- break;
40
- }
41
- }
42
- if (!removed) return false;
43
- if (matchers.length === 0) delete hooks_obj[event];
44
- await writeFile(hooks_path, JSON.stringify(hooks_data, null, " "), "utf-8");
45
- return true;
46
- }
47
- /**
48
- * Get all hooks.json paths for a plugin (cache + marketplace source).
49
- */
50
- function get_all_hooks_paths(plugin_key, primary_path) {
51
- const paths = [primary_path];
52
- const at_index = plugin_key.lastIndexOf("@");
53
- if (at_index > 0) {
54
- const plugin_name = plugin_key.substring(0, at_index);
55
- const marketplace_name = plugin_key.substring(at_index + 1);
56
- paths.push(join(get_marketplaces_dir(), marketplace_name, "plugins", plugin_name, "hooks", "hooks.json"));
57
- }
58
- return [...new Set(paths)];
59
- }
60
- /**
61
- * Disable a specific hook from a plugin.
62
- * Removes from both cache and marketplace source hooks.json files.
63
- */
64
- async function disable_plugin_hook(entry) {
65
- if (!entry.hooks_json_path || !entry.plugin_key) throw new Error("Not a plugin hook");
66
- const disabled = await read_disabled_hooks();
67
- disabled.push({
68
- plugin_key: entry.plugin_key,
69
- hooks_json_path: entry.hooks_json_path,
70
- event: entry.event,
71
- matcher: entry.matcher,
72
- matcher_index: entry.matcher_index,
73
- hook_index: entry.hook_index,
74
- original_handler: entry.handler,
75
- disabled_at: (/* @__PURE__ */ new Date()).toISOString()
76
- });
77
- await write_disabled_hooks(disabled);
78
- const all_paths = get_all_hooks_paths(entry.plugin_key, entry.hooks_json_path);
79
- for (const hooks_path of all_paths) await remove_hook_from_file(hooks_path, entry.event, entry.handler);
80
- }
81
- /**
82
- * Add a hook handler back into a hooks.json file.
83
- */
84
- async function add_hook_to_file(hooks_path, event, matcher_pattern, handler) {
85
- let hooks_data;
86
- try {
87
- const content = await readFile(hooks_path, "utf-8");
88
- hooks_data = JSON.parse(content);
89
- } catch {
90
- hooks_data = { hooks: {} };
91
- }
92
- const hooks_obj = hooks_data.hooks || (hooks_data.hooks = {});
93
- if (!hooks_obj[event]) hooks_obj[event] = [];
94
- const matchers = hooks_obj[event];
95
- let matcher = matchers.find((m) => (m.matcher || void 0) === matcher_pattern);
96
- if (!matcher) {
97
- matcher = { hooks: [] };
98
- if (matcher_pattern) matcher.matcher = matcher_pattern;
99
- matchers.push(matcher);
100
- }
101
- if (matcher.hooks.some((h) => h.type === handler.type && h.command === handler.command && h.url === handler.url && h.prompt === handler.prompt)) return;
102
- matcher.hooks.push(handler);
103
- await writeFile(hooks_path, JSON.stringify(hooks_data, null, " "), "utf-8");
104
- }
105
- /**
106
- * Re-enable a previously disabled plugin hook.
107
- * Restores to both cache and marketplace source hooks.json files.
108
- */
109
- async function enable_plugin_hook(disabled_entry) {
110
- const all_paths = get_all_hooks_paths(disabled_entry.plugin_key, disabled_entry.hooks_json_path);
111
- for (const hooks_path of all_paths) await add_hook_to_file(hooks_path, disabled_entry.event, disabled_entry.matcher, disabled_entry.original_handler);
112
- 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)));
113
- }
114
- /**
115
- * Check if any previously disabled hooks have been restored (e.g. by marketplace update).
116
- * Returns entries that were re-added and need to be re-disabled.
117
- */
118
- async function check_restored_hooks() {
119
- const disabled = await read_disabled_hooks();
120
- if (disabled.length === 0) return [];
121
- const restored = [];
122
- for (const entry of disabled) {
123
- const all_paths = get_all_hooks_paths(entry.plugin_key, entry.hooks_json_path);
124
- let found = false;
125
- for (const hooks_path of all_paths) {
126
- let hooks_data;
127
- try {
128
- const content = await readFile(hooks_path, "utf-8");
129
- hooks_data = JSON.parse(content);
130
- } catch {
131
- continue;
132
- }
133
- const matchers = (hooks_data.hooks || hooks_data)[entry.event];
134
- if (!matchers) continue;
135
- for (const m of matchers) {
136
- if ((m.matcher || void 0) !== entry.matcher) continue;
137
- 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))) {
138
- found = true;
139
- break;
140
- }
141
- }
142
- if (found) break;
143
- }
144
- if (found) restored.push(entry);
145
- }
146
- return restored;
147
- }
148
- /**
149
- * Re-disable hooks that were restored by a marketplace update.
150
- */
151
- async function redisable_restored_hooks(restored) {
152
- let success = 0;
153
- let failed = 0;
154
- for (const entry of restored) try {
155
- const all_paths = get_all_hooks_paths(entry.plugin_key, entry.hooks_json_path);
156
- let any_removed = false;
157
- for (const hooks_path of all_paths) if (await remove_hook_from_file(hooks_path, entry.event, entry.original_handler)) any_removed = true;
158
- if (any_removed) success++;
159
- else failed++;
160
- } catch {
161
- failed++;
162
- }
163
- return {
164
- success,
165
- failed
166
- };
167
- }
168
- //#endregion
169
- export { redisable_restored_hooks as a, read_disabled_hooks as i, disable_plugin_hook as n, enable_plugin_hook as r, check_restored_hooks as t };
170
-
171
- //# sourceMappingURL=hook-state-Di8lUsPr.js.map