mcpick 0.0.21 → 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.
Files changed (48) hide show
  1. package/CHANGELOG.md +30 -5
  2. package/README.md +150 -127
  3. package/dist/add-LJQa2my2.js +164 -0
  4. package/dist/add-json-TEdYweZ5.js +95 -0
  5. package/dist/{backup-DSDhHI5f.js → backup-kyS5IVIr.js} +4 -4
  6. package/dist/{cache-D6kd7qE8.js → cache-DTfzTsEE.js} +3 -3
  7. package/dist/cli-By-0nYNQ.js +112 -0
  8. package/dist/clients-qMozizys.js +30 -0
  9. package/dist/{clone-DYKPEsar.js → clone-BVhYjRGO.js} +5 -6
  10. package/dist/{config-DijVdEFn.js → config-DzMmTJYL.js} +2 -2
  11. package/dist/{dev-DRJRNp7y.js → dev-Cst8WkQ-.js} +5 -5
  12. package/dist/disable-BaOs9lrm.js +83 -0
  13. package/dist/enable--3mjSmTq.js +84 -0
  14. package/dist/{get-Bb1eOOIZ.js → get-CjhNWyRj.js} +3 -3
  15. package/dist/{hooks-Bmn7pUZa.js → hooks-DFmxgD0t.js} +3 -4
  16. package/dist/index.js +1929 -297
  17. package/dist/list-D5CkCXpP.js +100 -0
  18. package/dist/{marketplace-DcKk5dc1.js → marketplace-C3EGyIG0.js} +4 -5
  19. package/dist/output-HtT5HCof.js +17 -0
  20. package/dist/{plugin-cache-Bby9Dxm9.js → plugin-cache-BSgB42wa.js} +34 -15
  21. package/dist/{plugins-Dc7DN6R_.js → plugins-Dn2mPFKm.js} +4 -5
  22. package/dist/{profile-CX97sMGp.js → profile-Dq3ORPil.js} +4 -5
  23. package/dist/redact-wBMtzbno.js +88 -0
  24. package/dist/{reload-CYDhkCVZ.js → reload-257iU7Z7.js} +2 -2
  25. package/dist/remove-26XFzkPd.js +87 -0
  26. package/dist/{reset-project-choices-BfRSNN3m.js → reset-project-choices-D2F04LfC.js} +3 -3
  27. package/dist/{restore-DdMfUljI.js → restore-BYYsoNqF.js} +4 -5
  28. package/dist/rollback-CPdaME91.js +55 -0
  29. package/dist/skills-DfWk9mpk.js +216 -0
  30. package/package.json +22 -8
  31. package/.github/copilot-instructions.md +0 -32
  32. package/.github/workflows/ci.yml +0 -26
  33. package/.vscode/settings.json +0 -5
  34. package/dist/add-BDyaBew0.js +0 -113
  35. package/dist/add-json-BjgzdeG-.js +0 -58
  36. package/dist/atomic-write-BqEykHp9.js +0 -26
  37. package/dist/claude-cli-DnmBJrjg.js +0 -445
  38. package/dist/cli-CsFfnWBo.js +0 -84
  39. package/dist/disable-xJXZfUR_.js +0 -39
  40. package/dist/enable-RrpcN6la.js +0 -40
  41. package/dist/hook-state-Di8lUsPr.js +0 -171
  42. package/dist/list-B8YeDWt6.js +0 -64
  43. package/dist/output-BchYq0mR.js +0 -15
  44. package/dist/profile-DkY_lBEm.js +0 -70
  45. package/dist/redact-O35tjnRD.js +0 -26
  46. package/dist/registry-CfUKT7_C.js +0 -92
  47. package/dist/remove-D1owHLhG.js +0 -31
  48. package/dist/settings-DEcWtzLE.js +0 -201
@@ -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
@@ -1,64 +0,0 @@
1
- import { n as get_all_available_servers } from "./registry-CfUKT7_C.js";
2
- import { o as get_enabled_servers_for_scope } from "./config-DijVdEFn.js";
3
- import { n as output, t as error } from "./output-BchYq0mR.js";
4
- import { t as redact_server } from "./redact-O35tjnRD.js";
5
- import { defineCommand } from "citty";
6
- //#region src/cli/commands/list.ts
7
- var list_default = defineCommand({
8
- meta: {
9
- name: "list",
10
- description: "List all MCP servers and their status"
11
- },
12
- args: {
13
- scope: {
14
- type: "string",
15
- description: "Scope to check: local, project, or user"
16
- },
17
- json: {
18
- type: "boolean",
19
- description: "Output as JSON",
20
- default: false
21
- }
22
- },
23
- async run({ args }) {
24
- const scopes = args.scope ? [args.scope] : [
25
- "local",
26
- "project",
27
- "user"
28
- ];
29
- if (args.scope && ![
30
- "local",
31
- "project",
32
- "user"
33
- ].includes(args.scope)) error(`Invalid scope: ${args.scope}. Use local, project, or user.`);
34
- const all_servers = await get_all_available_servers();
35
- const enabled_by_scope = {};
36
- for (const scope of scopes) enabled_by_scope[scope] = await get_enabled_servers_for_scope(scope);
37
- if (args.json) output(all_servers.map((server) => {
38
- const status = {};
39
- for (const scope of scopes) status[scope] = enabled_by_scope[scope].includes(server.name);
40
- const { name, ...rest } = redact_server(server);
41
- return {
42
- name,
43
- ...status,
44
- ...rest
45
- };
46
- }), true);
47
- else {
48
- if (all_servers.length === 0) {
49
- console.log("No servers in registry.");
50
- return;
51
- }
52
- for (const server of all_servers) {
53
- const statuses = scopes.map((scope) => {
54
- return `${scope}:${enabled_by_scope[scope].includes(server.name) ? "on" : "off"}`;
55
- }).join(" ");
56
- console.log(`${server.name} ${statuses}`);
57
- }
58
- }
59
- }
60
- });
61
- //#endregion
62
- export { list_default as default };
63
-
64
- //# sourceMappingURL=list-B8YeDWt6.js.map
@@ -1,15 +0,0 @@
1
- //#region src/cli/output.ts
2
- function output(data, json) {
3
- if (json) console.log(JSON.stringify(data, null, 2));
4
- else if (typeof data === "string") console.log(data);
5
- else if (Array.isArray(data)) for (const item of data) console.log(item);
6
- else console.log(data);
7
- }
8
- function error(message) {
9
- console.error(`error: ${message}`);
10
- process.exit(1);
11
- }
12
- //#endregion
13
- export { output as n, error as t };
14
-
15
- //# sourceMappingURL=output-BchYq0mR.js.map
@@ -1,70 +0,0 @@
1
- import { _ as get_profiles_dir, g as get_profile_path, t as ensure_directory_exists } from "./paths-BPISiJi4.js";
2
- import { t as validate_claude_config } from "./validation-xMlbgGCF.js";
3
- import { s as read_claude_config } from "./config-DijVdEFn.js";
4
- import { a as read_claude_settings } from "./settings-DEcWtzLE.js";
5
- import { access, readFile, readdir, writeFile } from "node:fs/promises";
6
- //#region src/core/profile.ts
7
- async function load_profile(name) {
8
- const profile_path = get_profile_path(name);
9
- try {
10
- await access(profile_path);
11
- const content = await readFile(profile_path, "utf-8");
12
- const parsed = JSON.parse(content);
13
- let config;
14
- if (parsed.mcpServers) config = validate_claude_config(parsed);
15
- else if (!parsed.enabledPlugins) config = validate_claude_config({ mcpServers: parsed });
16
- else config = validate_claude_config({ mcpServers: parsed.mcpServers || {} });
17
- return {
18
- config,
19
- enabledPlugins: parsed.enabledPlugins
20
- };
21
- } catch (error) {
22
- if (error instanceof Error && "code" in error && error.code === "ENOENT") throw new Error(`Profile '${name}' not found at ${profile_path}`);
23
- throw error;
24
- }
25
- }
26
- async function list_profiles() {
27
- const profiles_dir = get_profiles_dir();
28
- try {
29
- await access(profiles_dir);
30
- const json_files = (await readdir(profiles_dir)).filter((f) => f.endsWith(".json"));
31
- const profiles = [];
32
- for (const file of json_files) try {
33
- const path = get_profile_path(file);
34
- const content = await readFile(path, "utf-8");
35
- const parsed = JSON.parse(content);
36
- const servers = parsed.mcpServers || parsed;
37
- const plugins = parsed.enabledPlugins || {};
38
- profiles.push({
39
- name: file.replace(".json", ""),
40
- path,
41
- serverCount: Object.keys(servers).length,
42
- pluginCount: Object.keys(plugins).length
43
- });
44
- } catch {}
45
- return profiles;
46
- } catch {
47
- return [];
48
- }
49
- }
50
- async function save_profile(name) {
51
- const config = await read_claude_config();
52
- const settings = await read_claude_settings();
53
- const servers = config.mcpServers || {};
54
- const plugins = settings.enabledPlugins || {};
55
- const server_count = Object.keys(servers).length;
56
- const plugin_count = Object.keys(plugins).length;
57
- if (server_count === 0 && plugin_count === 0) throw new Error("No MCP servers or plugins configured to save");
58
- await ensure_directory_exists(get_profiles_dir());
59
- const profile_data = { mcpServers: servers };
60
- if (plugin_count > 0) profile_data.enabledPlugins = plugins;
61
- await writeFile(get_profile_path(name), JSON.stringify(profile_data, null, 2), "utf-8");
62
- return {
63
- serverCount: server_count,
64
- pluginCount: plugin_count
65
- };
66
- }
67
- //#endregion
68
- export { load_profile as n, save_profile as r, list_profiles as t };
69
-
70
- //# sourceMappingURL=profile-DkY_lBEm.js.map
@@ -1,26 +0,0 @@
1
- //#region src/utils/redact.ts
2
- /**
3
- * Redact sensitive values (env, headers) from a server config.
4
- * Shows key names but replaces values with "***".
5
- */
6
- function redact_server_base(server) {
7
- const redacted = { ...server };
8
- if ("env" in redacted && redacted.env) redacted.env = redact_record(redacted.env);
9
- if ("headers" in redacted && redacted.headers) redacted.headers = redact_record(redacted.headers);
10
- return redacted;
11
- }
12
- function redact_server(server) {
13
- return {
14
- ...redact_server_base(server),
15
- name: server.name
16
- };
17
- }
18
- function redact_record(record) {
19
- const redacted = {};
20
- for (const key of Object.keys(record)) redacted[key] = "***";
21
- return redacted;
22
- }
23
- //#endregion
24
- export { redact_server_base as n, redact_server as t };
25
-
26
- //# sourceMappingURL=redact-O35tjnRD.js.map
@@ -1,92 +0,0 @@
1
- import { 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";
2
- import { r as validate_server_registry } from "./validation-xMlbgGCF.js";
3
- import { access, readFile, readdir, writeFile } from "node:fs/promises";
4
- import { join } from "node:path";
5
- //#region src/core/registry.ts
6
- async function read_server_registry() {
7
- const registry_path = get_server_registry_path();
8
- try {
9
- await access(registry_path);
10
- const registry_content = await readFile(registry_path, "utf-8");
11
- return validate_server_registry(JSON.parse(registry_content));
12
- } catch (error) {
13
- if (error instanceof Error && "code" in error && error.code === "ENOENT") {
14
- await ensure_directory_exists(get_mcpick_dir());
15
- const default_registry = { servers: [] };
16
- await write_server_registry(default_registry);
17
- return default_registry;
18
- }
19
- throw error;
20
- }
21
- }
22
- async function write_server_registry(registry) {
23
- const registry_path = get_server_registry_path();
24
- await ensure_directory_exists(get_mcpick_dir());
25
- await writeFile(registry_path, JSON.stringify(registry, null, 2), "utf-8");
26
- }
27
- async function add_server_to_registry(server) {
28
- const registry = await read_server_registry();
29
- const existing_index = registry.servers.findIndex((s) => s.name === server.name);
30
- if (existing_index >= 0) registry.servers[existing_index] = server;
31
- else registry.servers.push(server);
32
- await write_server_registry(registry);
33
- }
34
- async function get_all_available_servers() {
35
- const { get_enabled_servers, read_claude_config } = await import("./config-DijVdEFn.js").then((n) => n.t);
36
- const registry = await read_server_registry();
37
- const config_servers = get_enabled_servers(await read_claude_config());
38
- const config_by_name = new Map(config_servers.map((s) => [s.name, s]));
39
- const known_names = /* @__PURE__ */ new Set();
40
- let registry_updated = false;
41
- for (let i = 0; i < registry.servers.length; i++) {
42
- const name = registry.servers[i].name;
43
- known_names.add(name);
44
- const config_server = config_by_name.get(name);
45
- if (config_server) {
46
- registry.servers[i] = config_server;
47
- registry_updated = true;
48
- }
49
- }
50
- for (const server of config_servers) if (!known_names.has(server.name)) {
51
- registry.servers.push(server);
52
- registry_updated = true;
53
- }
54
- if (registry_updated) await write_server_registry(registry);
55
- return registry.servers;
56
- }
57
- async function sync_servers_to_registry(servers) {
58
- const registry = await read_server_registry();
59
- servers.forEach((server) => {
60
- const existing_index = registry.servers.findIndex((s) => s.name === server.name);
61
- if (existing_index >= 0) registry.servers[existing_index] = server;
62
- else registry.servers.push(server);
63
- });
64
- await write_server_registry(registry);
65
- }
66
- function parse_backups(prefix, pattern) {
67
- return async () => {
68
- const backups_dir = get_backups_dir();
69
- try {
70
- await access(backups_dir);
71
- return (await readdir(backups_dir)).filter((file) => file.startsWith(prefix) && file.endsWith(".json")).map((file) => {
72
- const timestamp_match = file.match(pattern);
73
- if (!timestamp_match) return null;
74
- const [, year, month, day, hour, minute, second] = timestamp_match;
75
- return {
76
- filename: file,
77
- timestamp: new Date(parseInt(year), parseInt(month) - 1, parseInt(day), parseInt(hour), parseInt(minute), parseInt(second)),
78
- path: join(backups_dir, file)
79
- };
80
- }).filter((backup) => backup !== null).sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
81
- } catch (error) {
82
- if (error instanceof Error && "code" in error && error.code === "ENOENT") return [];
83
- throw error;
84
- }
85
- };
86
- }
87
- const list_backups = parse_backups("mcp-servers-", /mcp-servers-(\d{4})-(\d{2})-(\d{2})-(\d{2})(\d{2})(\d{2})\.json/);
88
- const list_plugin_backups = parse_backups("plugins-", /plugins-(\d{4})-(\d{2})-(\d{2})-(\d{2})(\d{2})(\d{2})\.json/);
89
- //#endregion
90
- export { read_server_registry as a, list_plugin_backups as i, get_all_available_servers as n, sync_servers_to_registry as o, list_backups as r, write_server_registry as s, add_server_to_registry as t };
91
-
92
- //# sourceMappingURL=registry-CfUKT7_C.js.map
@@ -1,31 +0,0 @@
1
- import { a as read_server_registry, n as get_all_available_servers, s as write_server_registry } from "./registry-CfUKT7_C.js";
2
- import { p as remove_mcp_via_cli } from "./claude-cli-DnmBJrjg.js";
3
- import { t as error } from "./output-BchYq0mR.js";
4
- import { defineCommand } from "citty";
5
- //#region src/cli/commands/remove.ts
6
- var remove_default = defineCommand({
7
- meta: {
8
- name: "remove",
9
- description: "Remove an MCP server from the registry and disable it"
10
- },
11
- args: { server: {
12
- type: "positional",
13
- description: "Server name to remove",
14
- required: true
15
- } },
16
- async run({ args }) {
17
- if (!(await get_all_available_servers()).find((s) => s.name === args.server)) error(`Server '${args.server}' not found. Run 'mcpick list' to see available servers.`);
18
- const registry = await read_server_registry();
19
- const index = registry.servers.findIndex((s) => s.name === args.server);
20
- if (index >= 0) {
21
- registry.servers.splice(index, 1);
22
- await write_server_registry(registry);
23
- }
24
- await remove_mcp_via_cli(args.server);
25
- console.log(`Removed '${args.server}'`);
26
- }
27
- });
28
- //#endregion
29
- export { remove_default as default };
30
-
31
- //# sourceMappingURL=remove-D1owHLhG.js.map
@@ -1,201 +0,0 @@
1
- import { a as get_claude_settings_path } from "./paths-BPISiJi4.js";
2
- import { t as atomic_json_write } from "./atomic-write-BqEykHp9.js";
3
- import { access, readFile } from "node:fs/promises";
4
- import { join, resolve } from "node:path";
5
- //#region src/core/settings.ts
6
- async function read_claude_settings() {
7
- const settings_path = get_claude_settings_path();
8
- try {
9
- await access(settings_path);
10
- const content = await readFile(settings_path, "utf-8");
11
- return JSON.parse(content);
12
- } catch (error) {
13
- if (error instanceof Error && "code" in error && error.code === "ENOENT") return {};
14
- throw error;
15
- }
16
- }
17
- async function write_claude_settings(updates) {
18
- await atomic_json_write(get_claude_settings_path(), (existing) => {
19
- for (const [key, value] of Object.entries(updates)) existing[key] = value;
20
- return existing;
21
- });
22
- }
23
- /**
24
- * Parse enabledPlugins into structured list.
25
- * Keys are in format "plugin-name@marketplace-name"
26
- */
27
- function get_all_plugins(settings) {
28
- const enabled_plugins = settings.enabledPlugins || {};
29
- return Object.entries(enabled_plugins).map(([key, enabled]) => {
30
- const at_index = key.lastIndexOf("@");
31
- return {
32
- name: at_index > 0 ? key.substring(0, at_index) : key,
33
- marketplace: at_index > 0 ? key.substring(at_index + 1) : "unknown",
34
- enabled
35
- };
36
- });
37
- }
38
- /**
39
- * Build the enabledPlugins record from a list of PluginInfo
40
- */
41
- function build_enabled_plugins(plugins) {
42
- const result = {};
43
- for (const plugin of plugins) {
44
- const key = `${plugin.name}@${plugin.marketplace}`;
45
- result[key] = plugin.enabled;
46
- }
47
- return result;
48
- }
49
- async function read_settings_file(path) {
50
- try {
51
- await access(path);
52
- const content = await readFile(path, "utf-8");
53
- return JSON.parse(content);
54
- } catch {
55
- return {};
56
- }
57
- }
58
- function get_settings_paths() {
59
- return [
60
- {
61
- scope: "user",
62
- path: resolve(process.env.HOME || process.env.USERPROFILE || "", ".claude", "settings.json")
63
- },
64
- {
65
- scope: "project",
66
- path: resolve(process.cwd(), ".claude", "settings.json")
67
- },
68
- {
69
- scope: "project-local",
70
- path: resolve(process.cwd(), ".claude", "settings.local.json")
71
- }
72
- ];
73
- }
74
- /**
75
- * Read all hooks across all scopes (settings + plugins), flattened for display.
76
- */
77
- async function get_all_hooks() {
78
- const entries = [];
79
- for (const { scope, path } of get_settings_paths()) {
80
- const hooks = (await read_settings_file(path)).hooks;
81
- if (!hooks) continue;
82
- for (const [event, matchers] of Object.entries(hooks)) {
83
- if (!Array.isArray(matchers)) continue;
84
- for (let mi = 0; mi < matchers.length; mi++) {
85
- const m = matchers[mi];
86
- if (!m.hooks?.length) continue;
87
- for (let hi = 0; hi < m.hooks.length; hi++) entries.push({
88
- event,
89
- matcher: m.matcher,
90
- handler: m.hooks[hi],
91
- scope,
92
- source: scope,
93
- matcher_index: mi,
94
- hook_index: hi
95
- });
96
- }
97
- }
98
- }
99
- const plugin_hooks = await get_all_plugin_hooks();
100
- entries.push(...plugin_hooks);
101
- return entries;
102
- }
103
- /**
104
- * Scan all installed plugins for hooks.json and return flattened hook entries.
105
- * Checks both cache and marketplace source paths since Claude Code reads from both.
106
- */
107
- async function get_all_plugin_hooks() {
108
- const { read_installed_plugins } = await import("./plugin-cache-Bby9Dxm9.js").then((n) => n.s);
109
- const { get_marketplaces_dir } = await import("./paths-BPISiJi4.js").then((n) => n.b);
110
- const installed = await read_installed_plugins();
111
- const entries = [];
112
- const seen_hooks = /* @__PURE__ */ new Set();
113
- for (const [plugin_key, installs] of Object.entries(installed.plugins)) {
114
- if (!installs?.length) continue;
115
- const install = installs[0];
116
- const at_index = plugin_key.lastIndexOf("@");
117
- const plugin_name = at_index > 0 ? plugin_key.substring(0, at_index) : plugin_key;
118
- const marketplace_name = at_index > 0 ? plugin_key.substring(at_index + 1) : "";
119
- const hooks_paths = [join(install.installPath, "hooks", "hooks.json")];
120
- if (marketplace_name) hooks_paths.push(join(get_marketplaces_dir(), marketplace_name, "plugins", plugin_name, "hooks", "hooks.json"));
121
- for (const hooks_path of hooks_paths) {
122
- let hooks_data;
123
- try {
124
- const content = await readFile(hooks_path, "utf-8");
125
- hooks_data = JSON.parse(content);
126
- } catch {
127
- continue;
128
- }
129
- const hooks = hooks_data.hooks || hooks_data;
130
- for (const [event, matchers] of Object.entries(hooks)) {
131
- if (!Array.isArray(matchers)) continue;
132
- for (let mi = 0; mi < matchers.length; mi++) {
133
- const m = matchers[mi];
134
- if (!m.hooks?.length) continue;
135
- for (let hi = 0; hi < m.hooks.length; hi++) {
136
- const h = m.hooks[hi];
137
- const dedup_key = `${plugin_key}:${event}:${h.type}:${h.command || h.url || h.prompt}`;
138
- if (seen_hooks.has(dedup_key)) continue;
139
- seen_hooks.add(dedup_key);
140
- entries.push({
141
- event,
142
- matcher: m.matcher,
143
- handler: h,
144
- scope: "user",
145
- source: "plugin",
146
- matcher_index: mi,
147
- hook_index: hi,
148
- plugin_key,
149
- hooks_json_path: hooks_path
150
- });
151
- }
152
- }
153
- }
154
- }
155
- }
156
- return entries;
157
- }
158
- /**
159
- * Remove a specific hook entry by scope/event/indices.
160
- */
161
- async function remove_hook(entry) {
162
- const scope_path = get_settings_paths().find((s) => s.scope === entry.scope);
163
- if (!scope_path) throw new Error(`Unknown scope: ${entry.scope}`);
164
- await atomic_json_write(scope_path.path, (existing) => {
165
- const hooks = existing.hooks;
166
- if (!hooks) return existing;
167
- const matchers = hooks[entry.event];
168
- if (!matchers?.[entry.matcher_index]) return existing;
169
- const matcher = matchers[entry.matcher_index];
170
- matcher.hooks.splice(entry.hook_index, 1);
171
- if (matcher.hooks.length === 0) matchers.splice(entry.matcher_index, 1);
172
- if (matchers.length === 0) delete hooks[entry.event];
173
- if (Object.keys(hooks).length === 0) delete existing.hooks;
174
- return existing;
175
- });
176
- }
177
- /**
178
- * Add a hook to a specific scope.
179
- */
180
- async function add_hook(scope, event, matcher, handler) {
181
- const scope_path = get_settings_paths().find((s) => s.scope === scope);
182
- if (!scope_path) throw new Error(`Unknown scope: ${scope}`);
183
- await atomic_json_write(scope_path.path, (existing) => {
184
- if (!existing.hooks) existing.hooks = {};
185
- const hooks = existing.hooks;
186
- if (!hooks[event]) hooks[event] = [];
187
- const matchers = hooks[event];
188
- const existing_matcher = matchers.find((m) => (m.matcher || void 0) === matcher);
189
- if (existing_matcher) existing_matcher.hooks.push(handler);
190
- else {
191
- const new_matcher = { hooks: [handler] };
192
- if (matcher) new_matcher.matcher = matcher;
193
- matchers.push(new_matcher);
194
- }
195
- return existing;
196
- });
197
- }
198
- //#endregion
199
- export { read_claude_settings as a, get_all_plugins as i, build_enabled_plugins as n, remove_hook as o, get_all_hooks as r, write_claude_settings as s, add_hook as t };
200
-
201
- //# sourceMappingURL=settings-DEcWtzLE.js.map