mcpick 0.0.18 → 0.0.20

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 (79) hide show
  1. package/.github/workflows/ci.yml +26 -0
  2. package/.vscode/settings.json +5 -0
  3. package/CHANGELOG.md +15 -0
  4. package/LICENSE +21 -0
  5. package/dist/add-BDyaBew0.js +113 -0
  6. package/dist/add-json-BjgzdeG-.js +58 -0
  7. package/dist/atomic-write-BqEykHp9.js +26 -0
  8. package/dist/backup-DSDhHI5f.js +64 -0
  9. package/dist/cache-D6kd7qE8.js +226 -0
  10. package/dist/claude-cli-DnmBJrjg.js +445 -0
  11. package/dist/cli-CsFfnWBo.js +84 -0
  12. package/dist/clone-DYKPEsar.js +88 -0
  13. package/dist/config-DijVdEFn.js +176 -0
  14. package/dist/dev-DRJRNp7y.js +265 -0
  15. package/dist/disable-xJXZfUR_.js +39 -0
  16. package/dist/enable-RrpcN6la.js +40 -0
  17. package/dist/get-Bb1eOOIZ.js +41 -0
  18. package/dist/hook-state-Di8lUsPr.js +171 -0
  19. package/dist/hooks-Bmn7pUZa.js +280 -0
  20. package/dist/index.js +1232 -305
  21. package/dist/list-B8YeDWt6.js +64 -0
  22. package/dist/marketplace-DcKk5dc1.js +168 -0
  23. package/dist/output-BchYq0mR.js +15 -0
  24. package/dist/paths-BPISiJi4.js +124 -0
  25. package/dist/plugin-cache-Bby9Dxm9.js +405 -0
  26. package/dist/plugins-Dc7DN6R_.js +212 -0
  27. package/dist/profile-CX97sMGp.js +120 -0
  28. package/dist/profile-DkY_lBEm.js +70 -0
  29. package/dist/redact-O35tjnRD.js +26 -0
  30. package/dist/registry-CfUKT7_C.js +92 -0
  31. package/dist/reload-CYDhkCVZ.js +31 -0
  32. package/dist/remove-D1owHLhG.js +31 -0
  33. package/dist/reset-project-choices-BfRSNN3m.js +28 -0
  34. package/dist/restore-DdMfUljI.js +84 -0
  35. package/dist/rolldown-runtime-CiIaOW0V.js +13 -0
  36. package/dist/settings-DEcWtzLE.js +201 -0
  37. package/dist/validation-xMlbgGCF.js +44 -0
  38. package/package.json +23 -22
  39. package/dist/cli/commands/add-json.js +0 -60
  40. package/dist/cli/commands/add.js +0 -135
  41. package/dist/cli/commands/backup.js +0 -83
  42. package/dist/cli/commands/cache.js +0 -296
  43. package/dist/cli/commands/clone.js +0 -108
  44. package/dist/cli/commands/dev.js +0 -167
  45. package/dist/cli/commands/disable.js +0 -36
  46. package/dist/cli/commands/enable.js +0 -39
  47. package/dist/cli/commands/get.js +0 -45
  48. package/dist/cli/commands/hooks.js +0 -314
  49. package/dist/cli/commands/list.js +0 -64
  50. package/dist/cli/commands/marketplace.js +0 -211
  51. package/dist/cli/commands/plugins.js +0 -265
  52. package/dist/cli/commands/profile.js +0 -134
  53. package/dist/cli/commands/reload.js +0 -36
  54. package/dist/cli/commands/remove.js +0 -35
  55. package/dist/cli/commands/reset-project-choices.js +0 -32
  56. package/dist/cli/commands/restore.js +0 -105
  57. package/dist/cli/index.js +0 -29
  58. package/dist/cli/output.js +0 -21
  59. package/dist/commands/add-server.js +0 -310
  60. package/dist/commands/backup.js +0 -60
  61. package/dist/commands/edit-config.js +0 -109
  62. package/dist/commands/edit-plugins.js +0 -201
  63. package/dist/commands/manage-cache.js +0 -155
  64. package/dist/commands/manage-hooks.js +0 -99
  65. package/dist/commands/manage-marketplace.js +0 -293
  66. package/dist/commands/restore.js +0 -118
  67. package/dist/core/config.js +0 -200
  68. package/dist/core/dev-override.js +0 -155
  69. package/dist/core/hook-state.js +0 -220
  70. package/dist/core/plugin-cache.js +0 -506
  71. package/dist/core/profile.js +0 -94
  72. package/dist/core/registry.js +0 -121
  73. package/dist/core/settings.js +0 -243
  74. package/dist/core/validation.js +0 -49
  75. package/dist/types.js +0 -2
  76. package/dist/utils/atomic-write.js +0 -27
  77. package/dist/utils/claude-cli.js +0 -485
  78. package/dist/utils/paths.js +0 -114
  79. package/dist/utils/redact.js +0 -28
@@ -1,121 +0,0 @@
1
- import { access, readdir, readFile, writeFile, } from 'node:fs/promises';
2
- import { join } from 'node:path';
3
- import { ensure_directory_exists, get_backups_dir, get_mcpick_dir, get_server_registry_path, } from '../utils/paths.js';
4
- import { validate_server_registry } from './validation.js';
5
- export async function read_server_registry() {
6
- const registry_path = get_server_registry_path();
7
- try {
8
- await access(registry_path);
9
- const registry_content = await readFile(registry_path, 'utf-8');
10
- const parsed_registry = JSON.parse(registry_content);
11
- return validate_server_registry(parsed_registry);
12
- }
13
- catch (error) {
14
- if (error instanceof Error &&
15
- 'code' in error &&
16
- error.code === 'ENOENT') {
17
- await ensure_directory_exists(get_mcpick_dir());
18
- const default_registry = { servers: [] };
19
- await write_server_registry(default_registry);
20
- return default_registry;
21
- }
22
- throw error;
23
- }
24
- }
25
- export async function write_server_registry(registry) {
26
- const registry_path = get_server_registry_path();
27
- await ensure_directory_exists(get_mcpick_dir());
28
- const registry_content = JSON.stringify(registry, null, 2);
29
- await writeFile(registry_path, registry_content, 'utf-8');
30
- }
31
- export async function add_server_to_registry(server) {
32
- const registry = await read_server_registry();
33
- const existing_index = registry.servers.findIndex((s) => s.name === server.name);
34
- if (existing_index >= 0) {
35
- registry.servers[existing_index] = server;
36
- }
37
- else {
38
- registry.servers.push(server);
39
- }
40
- await write_server_registry(registry);
41
- }
42
- export async function get_all_available_servers() {
43
- const { get_enabled_servers, read_claude_config } = await import('./config.js');
44
- const registry = await read_server_registry();
45
- const config = await read_claude_config();
46
- const config_servers = get_enabled_servers(config);
47
- // Merge: config is the live truth, so update registry entries with config data
48
- const config_by_name = new Map(config_servers.map((s) => [s.name, s]));
49
- const known_names = new Set();
50
- let registry_updated = false;
51
- for (let i = 0; i < registry.servers.length; i++) {
52
- const name = registry.servers[i].name;
53
- known_names.add(name);
54
- const config_server = config_by_name.get(name);
55
- if (config_server) {
56
- registry.servers[i] = config_server;
57
- registry_updated = true;
58
- }
59
- }
60
- for (const server of config_servers) {
61
- if (!known_names.has(server.name)) {
62
- registry.servers.push(server);
63
- registry_updated = true;
64
- }
65
- }
66
- // Persist updated data back to registry so it survives disable/enable cycles
67
- if (registry_updated) {
68
- await write_server_registry(registry);
69
- }
70
- return registry.servers;
71
- }
72
- export async function sync_servers_to_registry(servers) {
73
- const registry = await read_server_registry();
74
- servers.forEach((server) => {
75
- const existing_index = registry.servers.findIndex((s) => s.name === server.name);
76
- if (existing_index >= 0) {
77
- registry.servers[existing_index] = server;
78
- }
79
- else {
80
- registry.servers.push(server);
81
- }
82
- });
83
- await write_server_registry(registry);
84
- }
85
- function parse_backups(prefix, pattern) {
86
- return async () => {
87
- const backups_dir = get_backups_dir();
88
- try {
89
- await access(backups_dir);
90
- const files = await readdir(backups_dir);
91
- const backup_files = files
92
- .filter((file) => file.startsWith(prefix) && file.endsWith('.json'))
93
- .map((file) => {
94
- const timestamp_match = file.match(pattern);
95
- if (!timestamp_match)
96
- return null;
97
- const [, year, month, day, hour, minute, second] = timestamp_match;
98
- const timestamp = new Date(parseInt(year), parseInt(month) - 1, parseInt(day), parseInt(hour), parseInt(minute), parseInt(second));
99
- return {
100
- filename: file,
101
- timestamp,
102
- path: join(backups_dir, file),
103
- };
104
- })
105
- .filter((backup) => backup !== null)
106
- .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
107
- return backup_files;
108
- }
109
- catch (error) {
110
- if (error instanceof Error &&
111
- 'code' in error &&
112
- error.code === 'ENOENT') {
113
- return [];
114
- }
115
- throw error;
116
- }
117
- };
118
- }
119
- export const list_backups = parse_backups('mcp-servers-', /mcp-servers-(\d{4})-(\d{2})-(\d{2})-(\d{2})(\d{2})(\d{2})\.json/);
120
- export const list_plugin_backups = parse_backups('plugins-', /plugins-(\d{4})-(\d{2})-(\d{2})-(\d{2})(\d{2})(\d{2})\.json/);
121
- //# sourceMappingURL=registry.js.map
@@ -1,243 +0,0 @@
1
- import { access, readFile } from 'node:fs/promises';
2
- import { join, resolve } from 'node:path';
3
- import { atomic_json_write } from '../utils/atomic-write.js';
4
- import { get_claude_settings_path } from '../utils/paths.js';
5
- export async function read_claude_settings() {
6
- const settings_path = get_claude_settings_path();
7
- try {
8
- await access(settings_path);
9
- const content = await readFile(settings_path, 'utf-8');
10
- return JSON.parse(content);
11
- }
12
- catch (error) {
13
- if (error instanceof Error &&
14
- 'code' in error &&
15
- error.code === 'ENOENT') {
16
- return {};
17
- }
18
- throw error;
19
- }
20
- }
21
- export async function write_claude_settings(updates) {
22
- await atomic_json_write(get_claude_settings_path(), (existing) => {
23
- for (const [key, value] of Object.entries(updates)) {
24
- existing[key] = value;
25
- }
26
- return existing;
27
- });
28
- }
29
- /**
30
- * Parse enabledPlugins into structured list.
31
- * Keys are in format "plugin-name@marketplace-name"
32
- */
33
- export function get_all_plugins(settings) {
34
- const enabled_plugins = settings.enabledPlugins || {};
35
- return Object.entries(enabled_plugins).map(([key, enabled]) => {
36
- const at_index = key.lastIndexOf('@');
37
- const name = at_index > 0 ? key.substring(0, at_index) : key;
38
- const marketplace = at_index > 0 ? key.substring(at_index + 1) : 'unknown';
39
- return { name, marketplace, enabled };
40
- });
41
- }
42
- /**
43
- * Build the enabledPlugins record from a list of PluginInfo
44
- */
45
- export function build_enabled_plugins(plugins) {
46
- const result = {};
47
- for (const plugin of plugins) {
48
- const key = `${plugin.name}@${plugin.marketplace}`;
49
- result[key] = plugin.enabled;
50
- }
51
- return result;
52
- }
53
- async function read_settings_file(path) {
54
- try {
55
- await access(path);
56
- const content = await readFile(path, 'utf-8');
57
- return JSON.parse(content);
58
- }
59
- catch {
60
- return {};
61
- }
62
- }
63
- function get_settings_paths() {
64
- const home = process.env.HOME || process.env.USERPROFILE || '';
65
- return [
66
- {
67
- scope: 'user',
68
- path: resolve(home, '.claude', 'settings.json'),
69
- },
70
- {
71
- scope: 'project',
72
- path: resolve(process.cwd(), '.claude', 'settings.json'),
73
- },
74
- {
75
- scope: 'project-local',
76
- path: resolve(process.cwd(), '.claude', 'settings.local.json'),
77
- },
78
- ];
79
- }
80
- /**
81
- * Read all hooks across all scopes (settings + plugins), flattened for display.
82
- */
83
- export async function get_all_hooks() {
84
- const entries = [];
85
- // Settings-based hooks
86
- for (const { scope, path } of get_settings_paths()) {
87
- const data = await read_settings_file(path);
88
- const hooks = data.hooks;
89
- if (!hooks)
90
- continue;
91
- for (const [event, matchers] of Object.entries(hooks)) {
92
- if (!Array.isArray(matchers))
93
- continue;
94
- for (let mi = 0; mi < matchers.length; mi++) {
95
- const m = matchers[mi];
96
- if (!m.hooks?.length)
97
- continue;
98
- for (let hi = 0; hi < m.hooks.length; hi++) {
99
- entries.push({
100
- event: event,
101
- matcher: m.matcher,
102
- handler: m.hooks[hi],
103
- scope,
104
- source: scope,
105
- matcher_index: mi,
106
- hook_index: hi,
107
- });
108
- }
109
- }
110
- }
111
- }
112
- // Plugin-based hooks
113
- const plugin_hooks = await get_all_plugin_hooks();
114
- entries.push(...plugin_hooks);
115
- return entries;
116
- }
117
- /**
118
- * Scan all installed plugins for hooks.json and return flattened hook entries.
119
- * Checks both cache and marketplace source paths since Claude Code reads from both.
120
- */
121
- export async function get_all_plugin_hooks() {
122
- const { read_installed_plugins } = await import('./plugin-cache.js');
123
- const { get_marketplaces_dir } = await import('../utils/paths.js');
124
- const installed = await read_installed_plugins();
125
- const entries = [];
126
- const seen_hooks = new Set();
127
- for (const [plugin_key, installs] of Object.entries(installed.plugins)) {
128
- if (!installs?.length)
129
- continue;
130
- const install = installs[0];
131
- const at_index = plugin_key.lastIndexOf('@');
132
- const plugin_name = at_index > 0 ? plugin_key.substring(0, at_index) : plugin_key;
133
- const marketplace_name = at_index > 0 ? plugin_key.substring(at_index + 1) : '';
134
- // Collect all hooks.json paths for this plugin (cache + marketplace source)
135
- const hooks_paths = [
136
- join(install.installPath, 'hooks', 'hooks.json'),
137
- ];
138
- // Also check marketplace source path
139
- if (marketplace_name) {
140
- hooks_paths.push(join(get_marketplaces_dir(), marketplace_name, 'plugins', plugin_name, 'hooks', 'hooks.json'));
141
- }
142
- for (const hooks_path of hooks_paths) {
143
- let hooks_data;
144
- try {
145
- const content = await readFile(hooks_path, 'utf-8');
146
- hooks_data = JSON.parse(content);
147
- }
148
- catch {
149
- continue;
150
- }
151
- const hooks = (hooks_data.hooks || hooks_data);
152
- for (const [event, matchers] of Object.entries(hooks)) {
153
- if (!Array.isArray(matchers))
154
- continue;
155
- for (let mi = 0; mi < matchers.length; mi++) {
156
- const m = matchers[mi];
157
- if (!m.hooks?.length)
158
- continue;
159
- for (let hi = 0; hi < m.hooks.length; hi++) {
160
- // Deduplicate: same plugin + event + handler type + command
161
- const h = m.hooks[hi];
162
- const dedup_key = `${plugin_key}:${event}:${h.type}:${h.command || h.url || h.prompt}`;
163
- if (seen_hooks.has(dedup_key))
164
- continue;
165
- seen_hooks.add(dedup_key);
166
- entries.push({
167
- event: event,
168
- matcher: m.matcher,
169
- handler: h,
170
- scope: 'user',
171
- source: 'plugin',
172
- matcher_index: mi,
173
- hook_index: hi,
174
- plugin_key,
175
- hooks_json_path: hooks_path,
176
- });
177
- }
178
- }
179
- }
180
- }
181
- }
182
- return entries;
183
- }
184
- /**
185
- * Remove a specific hook entry by scope/event/indices.
186
- */
187
- export async function remove_hook(entry) {
188
- const scope_path = get_settings_paths().find((s) => s.scope === entry.scope);
189
- if (!scope_path)
190
- throw new Error(`Unknown scope: ${entry.scope}`);
191
- await atomic_json_write(scope_path.path, (existing) => {
192
- const hooks = existing.hooks;
193
- if (!hooks)
194
- return existing;
195
- const matchers = hooks[entry.event];
196
- if (!matchers?.[entry.matcher_index])
197
- return existing;
198
- const matcher = matchers[entry.matcher_index];
199
- matcher.hooks.splice(entry.hook_index, 1);
200
- // Clean up empty matchers
201
- if (matcher.hooks.length === 0) {
202
- matchers.splice(entry.matcher_index, 1);
203
- }
204
- // Clean up empty events
205
- if (matchers.length === 0) {
206
- delete hooks[entry.event];
207
- }
208
- // Clean up empty hooks object
209
- if (Object.keys(hooks).length === 0) {
210
- delete existing.hooks;
211
- }
212
- return existing;
213
- });
214
- }
215
- /**
216
- * Add a hook to a specific scope.
217
- */
218
- export async function add_hook(scope, event, matcher, handler) {
219
- const scope_path = get_settings_paths().find((s) => s.scope === scope);
220
- if (!scope_path)
221
- throw new Error(`Unknown scope: ${scope}`);
222
- await atomic_json_write(scope_path.path, (existing) => {
223
- if (!existing.hooks)
224
- existing.hooks = {};
225
- const hooks = existing.hooks;
226
- if (!hooks[event])
227
- hooks[event] = [];
228
- const matchers = hooks[event];
229
- // Find existing matcher group or create new
230
- const existing_matcher = matchers.find((m) => (m.matcher || undefined) === matcher);
231
- if (existing_matcher) {
232
- existing_matcher.hooks.push(handler);
233
- }
234
- else {
235
- const new_matcher = { hooks: [handler] };
236
- if (matcher)
237
- new_matcher.matcher = matcher;
238
- matchers.push(new_matcher);
239
- }
240
- return existing;
241
- });
242
- }
243
- //# sourceMappingURL=settings.js.map
@@ -1,49 +0,0 @@
1
- import * as v from 'valibot';
2
- export const mcp_server_schema_stdio = v.object({
3
- type: v.optional(v.literal('stdio')),
4
- command: v.pipe(v.string(), v.minLength(1)),
5
- args: v.optional(v.array(v.string())),
6
- env: v.optional(v.record(v.string(), v.string())),
7
- description: v.optional(v.string()),
8
- });
9
- export const mcp_server_schema_sse = v.object({
10
- type: v.literal('sse'),
11
- env: v.optional(v.record(v.string(), v.string())),
12
- url: v.pipe(v.string(), v.minLength(1)),
13
- headers: v.optional(v.record(v.string(), v.string())),
14
- description: v.optional(v.string()),
15
- });
16
- export const mcp_server_schema_http = v.object({
17
- type: v.literal('http'),
18
- env: v.optional(v.record(v.string(), v.string())),
19
- url: v.pipe(v.string(), v.minLength(1)),
20
- headers: v.optional(v.record(v.string(), v.string())),
21
- description: v.optional(v.string()),
22
- });
23
- export const mcp_server_schema_base = v.union([
24
- mcp_server_schema_stdio,
25
- mcp_server_schema_sse,
26
- mcp_server_schema_http,
27
- ]);
28
- export const mcp_server_schema = v.intersect([
29
- v.object({
30
- name: v.pipe(v.string(), v.minLength(1)),
31
- }),
32
- mcp_server_schema_base,
33
- ]);
34
- export const claude_config_schema = v.object({
35
- mcpServers: v.optional(v.record(v.string(), mcp_server_schema_base)),
36
- });
37
- export const server_registry_schema = v.object({
38
- servers: v.array(mcp_server_schema),
39
- });
40
- export function validate_mcp_server(data) {
41
- return v.parse(mcp_server_schema, data);
42
- }
43
- export function validate_claude_config(data) {
44
- return v.parse(claude_config_schema, data);
45
- }
46
- export function validate_server_registry(data) {
47
- return v.parse(server_registry_schema, data);
48
- }
49
- //# sourceMappingURL=validation.js.map
package/dist/types.js DELETED
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=types.js.map
@@ -1,27 +0,0 @@
1
- import { readFile, rename, writeFile } from 'node:fs/promises';
2
- import { dirname, join } from 'node:path';
3
- /**
4
- * Atomically write a JSON file with fresh-read merging.
5
- *
6
- * 1. Re-reads the file right before writing to pick up concurrent changes
7
- * 2. Applies the merge function to the freshest data
8
- * 3. Writes to a temp file, then renames (atomic on same filesystem)
9
- */
10
- export async function atomic_json_write(file_path, merge) {
11
- // Read the freshest version right before writing
12
- let existing = {};
13
- try {
14
- const content = await readFile(file_path, 'utf-8');
15
- existing = JSON.parse(content);
16
- }
17
- catch {
18
- // File doesn't exist or invalid — start fresh
19
- }
20
- const merged = merge(existing);
21
- const content = JSON.stringify(merged, null, 2);
22
- // Write to temp file then rename for atomicity
23
- const tmp_path = join(dirname(file_path), `.${Date.now()}.tmp`);
24
- await writeFile(tmp_path, content, 'utf-8');
25
- await rename(tmp_path, file_path);
26
- }
27
- //# sourceMappingURL=atomic-write.js.map