mcpick 0.0.17 → 0.0.19

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 (75) hide show
  1. package/.vscode/settings.json +5 -0
  2. package/CHANGELOG.md +15 -0
  3. package/dist/add-B9nVyh8T.js +113 -0
  4. package/dist/add-json-CXNDl3al.js +58 -0
  5. package/dist/atomic-write-BqEykHp9.js +26 -0
  6. package/dist/backup-DSDhHI5f.js +64 -0
  7. package/dist/cache-D6kd7qE8.js +226 -0
  8. package/dist/claude-cli-BeA-bmoW.js +394 -0
  9. package/dist/cli-DNNZjJYL.js +84 -0
  10. package/dist/clone-DLFLewBY.js +88 -0
  11. package/dist/config-DijVdEFn.js +176 -0
  12. package/dist/dev-DRJRNp7y.js +265 -0
  13. package/dist/disable-BA8tXPJN.js +39 -0
  14. package/dist/enable-Bdnnn_Cq.js +40 -0
  15. package/dist/get-BPjMXTMc.js +41 -0
  16. package/dist/hook-state-Di8lUsPr.js +171 -0
  17. package/dist/hooks-Bmn7pUZa.js +280 -0
  18. package/dist/index.js +1230 -303
  19. package/dist/list-B8YeDWt6.js +64 -0
  20. package/dist/marketplace-Br89Tg-Z.js +168 -0
  21. package/dist/output-BchYq0mR.js +15 -0
  22. package/dist/paths-BPISiJi4.js +124 -0
  23. package/dist/plugin-cache-Bby9Dxm9.js +405 -0
  24. package/dist/plugins-DHYJF5CP.js +212 -0
  25. package/dist/profile-CX97sMGp.js +120 -0
  26. package/dist/profile-DkY_lBEm.js +70 -0
  27. package/dist/redact-O35tjnRD.js +26 -0
  28. package/dist/registry-CfUKT7_C.js +92 -0
  29. package/dist/reload-CYDhkCVZ.js +31 -0
  30. package/dist/remove-DIPWYMpk.js +31 -0
  31. package/dist/reset-project-choices-DRM5KByw.js +28 -0
  32. package/dist/restore-DdMfUljI.js +84 -0
  33. package/dist/rolldown-runtime-CiIaOW0V.js +13 -0
  34. package/dist/settings-DEcWtzLE.js +201 -0
  35. package/dist/validation-xMlbgGCF.js +44 -0
  36. package/package.json +20 -19
  37. package/dist/cli/commands/add-json.js +0 -60
  38. package/dist/cli/commands/add.js +0 -135
  39. package/dist/cli/commands/backup.js +0 -83
  40. package/dist/cli/commands/cache.js +0 -296
  41. package/dist/cli/commands/dev.js +0 -161
  42. package/dist/cli/commands/disable.js +0 -36
  43. package/dist/cli/commands/enable.js +0 -39
  44. package/dist/cli/commands/get.js +0 -45
  45. package/dist/cli/commands/hooks.js +0 -314
  46. package/dist/cli/commands/list.js +0 -63
  47. package/dist/cli/commands/marketplace.js +0 -211
  48. package/dist/cli/commands/plugins.js +0 -265
  49. package/dist/cli/commands/profile.js +0 -134
  50. package/dist/cli/commands/reload.js +0 -36
  51. package/dist/cli/commands/remove.js +0 -35
  52. package/dist/cli/commands/reset-project-choices.js +0 -32
  53. package/dist/cli/commands/restore.js +0 -105
  54. package/dist/cli/index.js +0 -28
  55. package/dist/cli/output.js +0 -21
  56. package/dist/commands/add-server.js +0 -310
  57. package/dist/commands/backup.js +0 -60
  58. package/dist/commands/edit-config.js +0 -109
  59. package/dist/commands/edit-plugins.js +0 -201
  60. package/dist/commands/manage-cache.js +0 -155
  61. package/dist/commands/manage-hooks.js +0 -99
  62. package/dist/commands/manage-marketplace.js +0 -293
  63. package/dist/commands/restore.js +0 -118
  64. package/dist/core/config.js +0 -146
  65. package/dist/core/dev-override.js +0 -210
  66. package/dist/core/hook-state.js +0 -220
  67. package/dist/core/plugin-cache.js +0 -506
  68. package/dist/core/profile.js +0 -94
  69. package/dist/core/registry.js +0 -121
  70. package/dist/core/settings.js +0 -243
  71. package/dist/core/validation.js +0 -49
  72. package/dist/types.js +0 -2
  73. package/dist/utils/atomic-write.js +0 -27
  74. package/dist/utils/claude-cli.js +0 -483
  75. package/dist/utils/paths.js +0 -114
@@ -0,0 +1,201 @@
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
@@ -0,0 +1,44 @@
1
+ import * as v from "valibot";
2
+ //#region src/core/validation.ts
3
+ const mcp_server_schema_stdio = v.object({
4
+ type: v.optional(v.literal("stdio")),
5
+ command: v.pipe(v.string(), v.minLength(1)),
6
+ args: v.optional(v.array(v.string())),
7
+ env: v.optional(v.record(v.string(), v.string())),
8
+ description: v.optional(v.string())
9
+ });
10
+ const mcp_server_schema_sse = v.object({
11
+ type: v.literal("sse"),
12
+ env: v.optional(v.record(v.string(), v.string())),
13
+ url: v.pipe(v.string(), v.minLength(1)),
14
+ headers: v.optional(v.record(v.string(), v.string())),
15
+ description: v.optional(v.string())
16
+ });
17
+ const mcp_server_schema_http = v.object({
18
+ type: v.literal("http"),
19
+ env: v.optional(v.record(v.string(), v.string())),
20
+ url: v.pipe(v.string(), v.minLength(1)),
21
+ headers: v.optional(v.record(v.string(), v.string())),
22
+ description: v.optional(v.string())
23
+ });
24
+ const mcp_server_schema_base = v.union([
25
+ mcp_server_schema_stdio,
26
+ mcp_server_schema_sse,
27
+ mcp_server_schema_http
28
+ ]);
29
+ const mcp_server_schema = v.intersect([v.object({ name: v.pipe(v.string(), v.minLength(1)) }), mcp_server_schema_base]);
30
+ const claude_config_schema = v.object({ mcpServers: v.optional(v.record(v.string(), mcp_server_schema_base)) });
31
+ const server_registry_schema = v.object({ servers: v.array(mcp_server_schema) });
32
+ function validate_mcp_server(data) {
33
+ return v.parse(mcp_server_schema, data);
34
+ }
35
+ function validate_claude_config(data) {
36
+ return v.parse(claude_config_schema, data);
37
+ }
38
+ function validate_server_registry(data) {
39
+ return v.parse(server_registry_schema, data);
40
+ }
41
+ //#endregion
42
+ export { validate_mcp_server as n, validate_server_registry as r, validate_claude_config as t };
43
+
44
+ //# sourceMappingURL=validation-xMlbgGCF.js.map
package/package.json CHANGED
@@ -1,24 +1,21 @@
1
1
  {
2
2
  "name": "mcpick",
3
- "version": "0.0.17",
4
- "description": "Dynamic MCP server and plugin configuration manager for Claude Code",
5
- "type": "module",
6
- "main": "./dist/index.js",
7
- "bin": {
8
- "mcpick": "./dist/index.js"
9
- },
10
- "engines": {
11
- "node": ">=22.0.0"
12
- },
3
+ "version": "0.0.19",
4
+ "description": "Claude Code extension manager — MCP servers, plugins (skills, hooks, agents), and marketplaces",
13
5
  "keywords": [
14
6
  "claude",
15
- "mcp",
7
+ "claude-code",
16
8
  "cli",
17
9
  "configuration",
18
- "claude-code"
10
+ "mcp"
19
11
  ],
20
- "author": "",
21
12
  "license": "MIT",
13
+ "author": "",
14
+ "bin": {
15
+ "mcpick": "./dist/index.js"
16
+ },
17
+ "type": "module",
18
+ "main": "./dist/index.js",
22
19
  "dependencies": {
23
20
  "@clack/prompts": "^1.1.0",
24
21
  "citty": "^0.2.1",
@@ -27,15 +24,19 @@
27
24
  "devDependencies": {
28
25
  "@changesets/cli": "^2.30.0",
29
26
  "@types/node": "^25.5.0",
30
- "prettier": "^3.8.1",
31
- "typescript": "^5.9.3"
27
+ "vite-plus": "^0.1.15",
28
+ "vitest": "^4.1.2"
29
+ },
30
+ "engines": {
31
+ "node": ">=22.0.0"
32
32
  },
33
33
  "scripts": {
34
- "build": "tsc",
35
- "dev": "tsc --watch",
34
+ "build": "vp pack",
35
+ "dev": "vp pack --watch",
36
36
  "start": "node ./dist/index.js",
37
- "format": "prettier --write .",
38
- "format:check": "prettier --check .",
37
+ "check": "vp check",
38
+ "format": "vp check --fix",
39
+ "test": "vp test",
39
40
  "changeset": "changeset",
40
41
  "version": "changeset version",
41
42
  "release": "pnpm run build && changeset publish"
@@ -1,60 +0,0 @@
1
- import { defineCommand } from 'citty';
2
- import { mcp_add_json_via_cli } from '../../utils/claude-cli.js';
3
- import { error, output } from '../output.js';
4
- export default defineCommand({
5
- meta: {
6
- name: 'add-json',
7
- description: 'Add an MCP server from a JSON configuration string',
8
- },
9
- args: {
10
- name: {
11
- type: 'positional',
12
- description: 'Server name',
13
- required: true,
14
- },
15
- config: {
16
- type: 'positional',
17
- description: 'JSON configuration string',
18
- required: true,
19
- },
20
- scope: {
21
- type: 'string',
22
- description: 'Scope: local, project, or user (default: local)',
23
- default: 'local',
24
- },
25
- json: {
26
- type: 'boolean',
27
- description: 'Output as JSON',
28
- default: false,
29
- },
30
- },
31
- async run({ args }) {
32
- const scope = args.scope;
33
- if (!['local', 'project', 'user'].includes(scope)) {
34
- error(`Invalid scope: ${scope}. Use local, project, or user.`);
35
- }
36
- // Validate the JSON is parseable
37
- try {
38
- JSON.parse(args.config);
39
- }
40
- catch {
41
- error('Invalid JSON configuration. Provide a valid JSON string.');
42
- }
43
- const result = await mcp_add_json_via_cli(args.name, args.config, scope);
44
- if (args.json) {
45
- output({
46
- added: args.name,
47
- scope,
48
- success: result.success,
49
- error: result.error,
50
- }, true);
51
- }
52
- else if (result.success) {
53
- console.log(`Added '${args.name}' from JSON (scope: ${scope})`);
54
- }
55
- else {
56
- error(result.error || 'Unknown error');
57
- }
58
- },
59
- });
60
- //# sourceMappingURL=add-json.js.map
@@ -1,135 +0,0 @@
1
- import { defineCommand } from 'citty';
2
- import { add_server_to_registry } from '../../core/registry.js';
3
- import { validate_mcp_server } from '../../core/validation.js';
4
- import { add_mcp_via_cli } from '../../utils/claude-cli.js';
5
- import { error, output } from '../output.js';
6
- export default defineCommand({
7
- meta: {
8
- name: 'add',
9
- description: 'Add a new MCP server to the registry and enable it',
10
- },
11
- args: {
12
- name: {
13
- type: 'string',
14
- description: 'Server name',
15
- required: true,
16
- },
17
- command: {
18
- type: 'string',
19
- description: 'Command to run (for stdio transport)',
20
- },
21
- args: {
22
- type: 'string',
23
- description: 'Comma-separated arguments (e.g. "npx,-y,mcp-sqlite")',
24
- },
25
- url: {
26
- type: 'string',
27
- description: 'URL (for sse or http transport)',
28
- },
29
- type: {
30
- type: 'string',
31
- description: 'Transport type: stdio, sse, or http (default: stdio)',
32
- default: 'stdio',
33
- },
34
- env: {
35
- type: 'string',
36
- description: 'Environment variables as KEY=val,KEY=val',
37
- },
38
- headers: {
39
- type: 'string',
40
- description: 'HTTP headers as KEY=val,KEY=val',
41
- },
42
- description: {
43
- type: 'string',
44
- description: 'Server description',
45
- },
46
- scope: {
47
- type: 'string',
48
- description: 'Scope: local, project, or user (default: local)',
49
- default: 'local',
50
- },
51
- json: {
52
- type: 'boolean',
53
- description: 'Output as JSON',
54
- default: false,
55
- },
56
- },
57
- async run({ args }) {
58
- const scope = args.scope;
59
- if (!['local', 'project', 'user'].includes(scope)) {
60
- error(`Invalid scope: ${scope}. Use local, project, or user.`);
61
- }
62
- const transport = args.type;
63
- if (!['stdio', 'sse', 'http'].includes(transport)) {
64
- error(`Invalid type: ${transport}. Use stdio, sse, or http.`);
65
- }
66
- // Build server object
67
- const server_data = {
68
- name: args.name,
69
- };
70
- if (transport === 'stdio') {
71
- if (!args.command) {
72
- error('--command is required for stdio transport');
73
- }
74
- server_data.command = args.command;
75
- if (args.args) {
76
- server_data.args = args.args.split(',');
77
- }
78
- }
79
- else {
80
- if (!args.url) {
81
- error(`--url is required for ${transport} transport`);
82
- }
83
- server_data.type = transport;
84
- server_data.url = args.url;
85
- if (args.headers) {
86
- server_data.headers = parse_key_value_pairs(args.headers);
87
- }
88
- }
89
- if (args.env) {
90
- server_data.env = parse_key_value_pairs(args.env);
91
- }
92
- if (args.description) {
93
- server_data.description = args.description;
94
- }
95
- // Validate
96
- let server;
97
- try {
98
- server = validate_mcp_server(server_data);
99
- }
100
- catch (err) {
101
- error(`Invalid server config: ${err instanceof Error ? err.message : 'validation failed'}`);
102
- }
103
- // Add to registry
104
- await add_server_to_registry(server);
105
- // Enable via CLI
106
- const result = await add_mcp_via_cli(server, scope);
107
- if (args.json) {
108
- output({
109
- added: server.name,
110
- scope,
111
- cli: result.success,
112
- error: result.error,
113
- }, true);
114
- }
115
- else {
116
- if (result.success) {
117
- console.log(`Added '${server.name}' and enabled (scope: ${scope})`);
118
- }
119
- else {
120
- console.log(`Added '${server.name}' to registry but CLI failed: ${result.error}`);
121
- }
122
- }
123
- },
124
- });
125
- function parse_key_value_pairs(input) {
126
- const result = {};
127
- for (const pair of input.split(',')) {
128
- const eq = pair.indexOf('=');
129
- if (eq > 0) {
130
- result[pair.substring(0, eq)] = pair.substring(eq + 1);
131
- }
132
- }
133
- return result;
134
- }
135
- //# sourceMappingURL=add.js.map
@@ -1,83 +0,0 @@
1
- import { defineCommand } from 'citty';
2
- import { readdir, unlink, writeFile } from 'node:fs/promises';
3
- import { join } from 'node:path';
4
- import { read_claude_config } from '../../core/config.js';
5
- import { read_claude_settings } from '../../core/settings.js';
6
- import { ensure_directory_exists, get_backup_filename, get_backups_dir, get_plugin_backup_filename, } from '../../utils/paths.js';
7
- import { output } from '../output.js';
8
- const MAX_BACKUPS = 10;
9
- async function cleanup_old_backups(prefix) {
10
- try {
11
- const backups_dir = get_backups_dir();
12
- const files = await readdir(backups_dir);
13
- const backup_files = files
14
- .filter((f) => f.startsWith(prefix) && f.endsWith('.json'))
15
- .sort()
16
- .reverse();
17
- if (backup_files.length > MAX_BACKUPS) {
18
- for (const file of backup_files.slice(MAX_BACKUPS)) {
19
- await unlink(join(backups_dir, file));
20
- }
21
- }
22
- }
23
- catch {
24
- // Cleanup is best-effort
25
- }
26
- }
27
- export default defineCommand({
28
- meta: {
29
- name: 'backup',
30
- description: 'Create a timestamped backup of MCP servers and plugins',
31
- },
32
- args: {
33
- json: {
34
- type: 'boolean',
35
- description: 'Output as JSON',
36
- default: false,
37
- },
38
- },
39
- async run({ args }) {
40
- const current_config = await read_claude_config();
41
- const current_settings = await read_claude_settings();
42
- const backups_dir = get_backups_dir();
43
- await ensure_directory_exists(backups_dir);
44
- // Backup MCP servers
45
- const mcp_filename = get_backup_filename();
46
- const mcp_path = join(backups_dir, mcp_filename);
47
- const mcp_backup = {
48
- mcpServers: current_config.mcpServers || {},
49
- };
50
- await writeFile(mcp_path, JSON.stringify(mcp_backup, null, 2), 'utf-8');
51
- // Backup plugins
52
- const plugins = current_settings.enabledPlugins || {};
53
- const plugin_count = Object.keys(plugins).length;
54
- let plugin_path = null;
55
- if (plugin_count > 0) {
56
- const plugin_filename = get_plugin_backup_filename();
57
- plugin_path = join(backups_dir, plugin_filename);
58
- const plugin_backup = { enabledPlugins: plugins };
59
- await writeFile(plugin_path, JSON.stringify(plugin_backup, null, 2), 'utf-8');
60
- }
61
- await cleanup_old_backups('mcp-servers-');
62
- await cleanup_old_backups('plugins-');
63
- const server_count = Object.keys(current_config.mcpServers || {}).length;
64
- if (args.json) {
65
- output({
66
- mcp: { path: mcp_path, servers: server_count },
67
- plugins: plugin_path
68
- ? {
69
- path: plugin_path,
70
- plugins: plugin_count,
71
- }
72
- : null,
73
- }, true);
74
- }
75
- else {
76
- console.log(`Backup created: ${mcp_path} (${server_count} servers)`);
77
- if (plugin_path) {
78
- console.log(`Plugin backup: ${plugin_path} (${plugin_count} plugins)`);
79
- }
80
- }
81
- },
82
- });
83
- //# sourceMappingURL=backup.js.map