mcpick 0.0.16 → 0.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/dist/cli/commands/hooks.js +314 -0
- package/dist/cli/commands/marketplace.js +8 -0
- package/dist/cli/index.js +1 -0
- package/dist/commands/manage-hooks.js +99 -0
- package/dist/commands/manage-marketplace.js +18 -0
- package/dist/core/hook-state.js +220 -0
- package/dist/core/settings.js +191 -0
- package/dist/index.js +10 -0
- package/dist/utils/paths.js +3 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { defineCommand } from 'citty';
|
|
2
|
+
import { check_restored_hooks, disable_plugin_hook, enable_plugin_hook, read_disabled_hooks, redisable_restored_hooks, } from '../../core/hook-state.js';
|
|
3
|
+
import { add_hook, get_all_hooks, remove_hook, } from '../../core/settings.js';
|
|
4
|
+
import { error, output } from '../output.js';
|
|
5
|
+
const list = defineCommand({
|
|
6
|
+
meta: {
|
|
7
|
+
name: 'list',
|
|
8
|
+
description: 'List all configured hooks (settings + plugins + disabled)',
|
|
9
|
+
},
|
|
10
|
+
args: {
|
|
11
|
+
scope: {
|
|
12
|
+
type: 'string',
|
|
13
|
+
description: 'Filter by source: user, project, project-local, or plugin',
|
|
14
|
+
},
|
|
15
|
+
json: {
|
|
16
|
+
type: 'boolean',
|
|
17
|
+
description: 'Output as JSON',
|
|
18
|
+
default: false,
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
async run({ args }) {
|
|
22
|
+
const hooks = await get_all_hooks();
|
|
23
|
+
const disabled = await read_disabled_hooks();
|
|
24
|
+
const filtered = args.scope
|
|
25
|
+
? hooks.filter((h) => h.source === args.scope || h.scope === args.scope)
|
|
26
|
+
: hooks;
|
|
27
|
+
if (args.json) {
|
|
28
|
+
output({ active: filtered, disabled }, true);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
if (filtered.length === 0 && disabled.length === 0) {
|
|
32
|
+
console.log('No hooks configured.');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (filtered.length > 0) {
|
|
36
|
+
for (let i = 0; i < filtered.length; i++) {
|
|
37
|
+
const h = filtered[i];
|
|
38
|
+
const detail = h.handler.command ||
|
|
39
|
+
h.handler.url ||
|
|
40
|
+
h.handler.prompt ||
|
|
41
|
+
'(unknown)';
|
|
42
|
+
const matcher_str = h.matcher ? ` [${h.matcher}]` : '';
|
|
43
|
+
const source = h.source === 'plugin'
|
|
44
|
+
? `plugin: ${h.plugin_key}`
|
|
45
|
+
: h.scope;
|
|
46
|
+
console.log(`${i}: [${source}] ${h.event}${matcher_str} → ${h.handler.type}: ${detail}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (disabled.length > 0) {
|
|
50
|
+
console.log('\nDisabled:');
|
|
51
|
+
for (let i = 0; i < disabled.length; i++) {
|
|
52
|
+
const d = disabled[i];
|
|
53
|
+
const detail = d.original_handler.command ||
|
|
54
|
+
d.original_handler.url ||
|
|
55
|
+
d.original_handler.prompt ||
|
|
56
|
+
'(unknown)';
|
|
57
|
+
const matcher_str = d.matcher ? ` [${d.matcher}]` : '';
|
|
58
|
+
console.log(`${i}: [${d.plugin_key}] ${d.event}${matcher_str} → ${d.original_handler.type}: ${detail}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
const disable = defineCommand({
|
|
65
|
+
meta: {
|
|
66
|
+
name: 'disable',
|
|
67
|
+
description: 'Disable a hook by index (use "hooks list" to see indices)',
|
|
68
|
+
},
|
|
69
|
+
args: {
|
|
70
|
+
index: {
|
|
71
|
+
type: 'positional',
|
|
72
|
+
description: 'Hook index from "hooks list" (0-based)',
|
|
73
|
+
required: true,
|
|
74
|
+
},
|
|
75
|
+
json: {
|
|
76
|
+
type: 'boolean',
|
|
77
|
+
description: 'Output as JSON',
|
|
78
|
+
default: false,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
async run({ args }) {
|
|
82
|
+
const hooks = await get_all_hooks();
|
|
83
|
+
const idx = parseInt(args.index, 10);
|
|
84
|
+
if (isNaN(idx) || idx < 0 || idx >= hooks.length) {
|
|
85
|
+
error(`Invalid index: ${args.index}. Run "mcpick hooks list" to see available hooks (0-${hooks.length - 1}).`);
|
|
86
|
+
}
|
|
87
|
+
const entry = hooks[idx];
|
|
88
|
+
if (entry.source === 'plugin') {
|
|
89
|
+
await disable_plugin_hook(entry);
|
|
90
|
+
if (args.json) {
|
|
91
|
+
output({
|
|
92
|
+
disabled: true,
|
|
93
|
+
event: entry.event,
|
|
94
|
+
plugin_key: entry.plugin_key,
|
|
95
|
+
}, true);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
console.log(`Disabled: ${entry.event} from ${entry.plugin_key}`);
|
|
99
|
+
console.log('Restart Claude Code for changes to take effect.');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
// Settings hook — remove it
|
|
104
|
+
await remove_hook(entry);
|
|
105
|
+
if (args.json) {
|
|
106
|
+
output({
|
|
107
|
+
removed: true,
|
|
108
|
+
event: entry.event,
|
|
109
|
+
scope: entry.scope,
|
|
110
|
+
}, true);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
console.log(`Removed: ${entry.event} (${entry.scope})`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
const enable = defineCommand({
|
|
119
|
+
meta: {
|
|
120
|
+
name: 'enable',
|
|
121
|
+
description: 'Re-enable a disabled hook by index (use "hooks list" to see disabled hooks)',
|
|
122
|
+
},
|
|
123
|
+
args: {
|
|
124
|
+
index: {
|
|
125
|
+
type: 'positional',
|
|
126
|
+
description: 'Disabled hook index from "hooks list" (0-based)',
|
|
127
|
+
required: true,
|
|
128
|
+
},
|
|
129
|
+
json: {
|
|
130
|
+
type: 'boolean',
|
|
131
|
+
description: 'Output as JSON',
|
|
132
|
+
default: false,
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
async run({ args }) {
|
|
136
|
+
const disabled = await read_disabled_hooks();
|
|
137
|
+
const idx = parseInt(args.index, 10);
|
|
138
|
+
if (isNaN(idx) || idx < 0 || idx >= disabled.length) {
|
|
139
|
+
error(`Invalid index: ${args.index}. Run "mcpick hooks list" to see disabled hooks (0-${disabled.length - 1}).`);
|
|
140
|
+
}
|
|
141
|
+
const entry = disabled[idx];
|
|
142
|
+
await enable_plugin_hook(entry);
|
|
143
|
+
if (args.json) {
|
|
144
|
+
output({
|
|
145
|
+
enabled: true,
|
|
146
|
+
event: entry.event,
|
|
147
|
+
plugin_key: entry.plugin_key,
|
|
148
|
+
}, true);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
console.log(`Re-enabled: ${entry.event} for ${entry.plugin_key}`);
|
|
152
|
+
console.log('Restart Claude Code for changes to take effect.');
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
const add = defineCommand({
|
|
157
|
+
meta: {
|
|
158
|
+
name: 'add',
|
|
159
|
+
description: 'Add a new settings-based hook',
|
|
160
|
+
},
|
|
161
|
+
args: {
|
|
162
|
+
event: {
|
|
163
|
+
type: 'positional',
|
|
164
|
+
description: 'Hook event type (e.g. UserPromptSubmit, PreToolUse)',
|
|
165
|
+
required: true,
|
|
166
|
+
},
|
|
167
|
+
handler_type: {
|
|
168
|
+
type: 'positional',
|
|
169
|
+
description: 'Handler type: command, prompt, http, or agent',
|
|
170
|
+
required: true,
|
|
171
|
+
},
|
|
172
|
+
value: {
|
|
173
|
+
type: 'positional',
|
|
174
|
+
description: 'Handler value (command string, prompt text, URL, or agent prompt)',
|
|
175
|
+
required: true,
|
|
176
|
+
},
|
|
177
|
+
matcher: {
|
|
178
|
+
type: 'string',
|
|
179
|
+
description: 'Matcher pattern (e.g. Bash, Edit|Write) — only for tool/session events',
|
|
180
|
+
},
|
|
181
|
+
scope: {
|
|
182
|
+
type: 'string',
|
|
183
|
+
description: 'Scope: user, project, or project-local (default: user)',
|
|
184
|
+
default: 'user',
|
|
185
|
+
},
|
|
186
|
+
json: {
|
|
187
|
+
type: 'boolean',
|
|
188
|
+
description: 'Output as JSON',
|
|
189
|
+
default: false,
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
async run({ args }) {
|
|
193
|
+
const scope = args.scope;
|
|
194
|
+
if (!['user', 'project', 'project-local'].includes(scope)) {
|
|
195
|
+
error(`Invalid scope: ${scope}. Use user, project, or project-local.`);
|
|
196
|
+
}
|
|
197
|
+
const handler_type = args.handler_type;
|
|
198
|
+
if (!['command', 'prompt', 'http', 'agent'].includes(handler_type)) {
|
|
199
|
+
error(`Invalid handler type: ${handler_type}. Use command, prompt, http, or agent.`);
|
|
200
|
+
}
|
|
201
|
+
const handler = { type: handler_type };
|
|
202
|
+
if (handler_type === 'command')
|
|
203
|
+
handler.command = args.value;
|
|
204
|
+
else if (handler_type === 'prompt')
|
|
205
|
+
handler.prompt = args.value;
|
|
206
|
+
else if (handler_type === 'http')
|
|
207
|
+
handler.url = args.value;
|
|
208
|
+
else if (handler_type === 'agent')
|
|
209
|
+
handler.prompt = args.value;
|
|
210
|
+
await add_hook(scope, args.event, args.matcher || undefined, handler);
|
|
211
|
+
if (args.json) {
|
|
212
|
+
output({
|
|
213
|
+
added: true,
|
|
214
|
+
event: args.event,
|
|
215
|
+
handler_type,
|
|
216
|
+
scope,
|
|
217
|
+
matcher: args.matcher || null,
|
|
218
|
+
}, true);
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
console.log(`Hook added: ${args.event} → ${handler_type} (${scope})`);
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
const remove = defineCommand({
|
|
226
|
+
meta: {
|
|
227
|
+
name: 'remove',
|
|
228
|
+
description: 'Remove a settings hook by index (use "hooks list" to see indices)',
|
|
229
|
+
},
|
|
230
|
+
args: {
|
|
231
|
+
index: {
|
|
232
|
+
type: 'positional',
|
|
233
|
+
description: 'Hook index from "hooks list" (0-based)',
|
|
234
|
+
required: true,
|
|
235
|
+
},
|
|
236
|
+
json: {
|
|
237
|
+
type: 'boolean',
|
|
238
|
+
description: 'Output as JSON',
|
|
239
|
+
default: false,
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
async run({ args }) {
|
|
243
|
+
const hooks = await get_all_hooks();
|
|
244
|
+
const settings_hooks = hooks.filter((h) => h.source !== 'plugin');
|
|
245
|
+
const idx = parseInt(args.index, 10);
|
|
246
|
+
if (isNaN(idx) || idx < 0 || idx >= settings_hooks.length) {
|
|
247
|
+
error(`Invalid index: ${args.index}. Use "mcpick hooks list" to see settings hooks.`);
|
|
248
|
+
}
|
|
249
|
+
const entry = settings_hooks[idx];
|
|
250
|
+
await remove_hook(entry);
|
|
251
|
+
if (args.json) {
|
|
252
|
+
output({
|
|
253
|
+
removed: true,
|
|
254
|
+
event: entry.event,
|
|
255
|
+
scope: entry.scope,
|
|
256
|
+
}, true);
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
const detail = entry.handler.command ||
|
|
260
|
+
entry.handler.url ||
|
|
261
|
+
entry.handler.prompt ||
|
|
262
|
+
'(unknown)';
|
|
263
|
+
console.log(`Removed: [${entry.scope}] ${entry.event} → ${entry.handler.type}: ${detail}`);
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
const check = defineCommand({
|
|
268
|
+
meta: {
|
|
269
|
+
name: 'check',
|
|
270
|
+
description: 'Check if marketplace updates restored any disabled hooks',
|
|
271
|
+
},
|
|
272
|
+
args: {
|
|
273
|
+
fix: {
|
|
274
|
+
type: 'boolean',
|
|
275
|
+
description: 'Automatically re-disable restored hooks',
|
|
276
|
+
default: false,
|
|
277
|
+
},
|
|
278
|
+
json: {
|
|
279
|
+
type: 'boolean',
|
|
280
|
+
description: 'Output as JSON',
|
|
281
|
+
default: false,
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
async run({ args }) {
|
|
285
|
+
const restored = await check_restored_hooks();
|
|
286
|
+
if (args.json) {
|
|
287
|
+
output({ restored: restored.length, hooks: restored }, true);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (restored.length === 0) {
|
|
291
|
+
console.log('No disabled hooks were restored. All good.');
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
console.log(`${restored.length} disabled hook(s) were restored:`);
|
|
295
|
+
for (const r of restored) {
|
|
296
|
+
console.log(` ${r.plugin_key}: ${r.event}`);
|
|
297
|
+
}
|
|
298
|
+
if (args.fix) {
|
|
299
|
+
const result = await redisable_restored_hooks(restored);
|
|
300
|
+
console.log(`Re-disabled ${result.success} hook(s).${result.failed > 0 ? ` Failed: ${result.failed}` : ''}`);
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
console.log('Run with --fix to re-disable, or use "mcpick hooks disable".');
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
});
|
|
307
|
+
export default defineCommand({
|
|
308
|
+
meta: {
|
|
309
|
+
name: 'hooks',
|
|
310
|
+
description: 'Manage hooks (settings + plugin). Disable individual plugin hooks, add/remove settings hooks.',
|
|
311
|
+
},
|
|
312
|
+
subCommands: { list, disable, enable, add, remove, check },
|
|
313
|
+
});
|
|
314
|
+
//# sourceMappingURL=hooks.js.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { defineCommand } from 'citty';
|
|
2
|
+
import { check_restored_hooks, redisable_restored_hooks, } from '../../core/hook-state.js';
|
|
2
3
|
import { read_marketplace_manifest } from '../../core/plugin-cache.js';
|
|
3
4
|
import { marketplace_add_via_cli, marketplace_list_via_cli, marketplace_remove_via_cli, marketplace_update_via_cli, } from '../../utils/claude-cli.js';
|
|
4
5
|
import { error, output } from '../output.js';
|
|
@@ -182,6 +183,13 @@ const update = defineCommand({
|
|
|
182
183
|
console.log(args.name
|
|
183
184
|
? `Marketplace updated: ${args.name}`
|
|
184
185
|
: 'All marketplaces updated.');
|
|
186
|
+
// Check if update restored any disabled hooks
|
|
187
|
+
const restored = await check_restored_hooks();
|
|
188
|
+
if (restored.length > 0) {
|
|
189
|
+
console.log(`\nWarning: ${restored.length} disabled hook(s) were restored by the update.`);
|
|
190
|
+
const redisable_result = await redisable_restored_hooks(restored);
|
|
191
|
+
console.log(`Re-disabled ${redisable_result.success} hook(s).${redisable_result.failed > 0 ? ` Failed: ${redisable_result.failed}` : ''}`);
|
|
192
|
+
}
|
|
185
193
|
}
|
|
186
194
|
else {
|
|
187
195
|
error(result.error || 'Unknown error');
|
package/dist/cli/index.js
CHANGED
|
@@ -17,6 +17,7 @@ const main = defineCommand({
|
|
|
17
17
|
restore: () => import('./commands/restore.js').then((m) => m.default),
|
|
18
18
|
profile: () => import('./commands/profile.js').then((m) => m.default),
|
|
19
19
|
plugins: () => import('./commands/plugins.js').then((m) => m.default),
|
|
20
|
+
hooks: () => import('./commands/hooks.js').then((m) => m.default),
|
|
20
21
|
cache: () => import('./commands/cache.js').then((m) => m.default),
|
|
21
22
|
dev: () => import('./commands/dev.js').then((m) => m.default),
|
|
22
23
|
marketplace: () => import('./commands/marketplace.js').then((m) => m.default),
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { isCancel, log, multiselect, note } from '@clack/prompts';
|
|
2
|
+
import { disable_plugin_hook, enable_plugin_hook, read_disabled_hooks, } from '../core/hook-state.js';
|
|
3
|
+
import { get_all_hooks } from '../core/settings.js';
|
|
4
|
+
function format_hook(entry) {
|
|
5
|
+
const detail = entry.handler.command ||
|
|
6
|
+
entry.handler.url ||
|
|
7
|
+
entry.handler.prompt ||
|
|
8
|
+
'(unknown)';
|
|
9
|
+
const truncated = detail.length > 50 ? detail.substring(0, 47) + '...' : detail;
|
|
10
|
+
return `${entry.event} → ${entry.handler.type}: ${truncated}`;
|
|
11
|
+
}
|
|
12
|
+
function format_source(entry) {
|
|
13
|
+
if (entry.source === 'plugin' && entry.plugin_key) {
|
|
14
|
+
return entry.plugin_key;
|
|
15
|
+
}
|
|
16
|
+
return entry.scope;
|
|
17
|
+
}
|
|
18
|
+
export async function manage_hooks() {
|
|
19
|
+
// Get all active hooks + disabled hooks
|
|
20
|
+
const active_hooks = await get_all_hooks();
|
|
21
|
+
const disabled = await read_disabled_hooks();
|
|
22
|
+
const items = [];
|
|
23
|
+
// Active hooks
|
|
24
|
+
for (let i = 0; i < active_hooks.length; i++) {
|
|
25
|
+
const h = active_hooks[i];
|
|
26
|
+
items.push({
|
|
27
|
+
id: `active:${i}`,
|
|
28
|
+
active_entry: h,
|
|
29
|
+
label: format_hook(h),
|
|
30
|
+
hint: format_source(h),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
// Disabled hooks
|
|
34
|
+
for (let i = 0; i < disabled.length; i++) {
|
|
35
|
+
const d = disabled[i];
|
|
36
|
+
const detail = d.original_handler.command ||
|
|
37
|
+
d.original_handler.url ||
|
|
38
|
+
d.original_handler.prompt ||
|
|
39
|
+
'(unknown)';
|
|
40
|
+
const truncated = detail.length > 50 ? detail.substring(0, 47) + '...' : detail;
|
|
41
|
+
items.push({
|
|
42
|
+
id: `disabled:${i}`,
|
|
43
|
+
disabled_index: i,
|
|
44
|
+
label: `${d.event} → ${d.original_handler.type}: ${truncated}`,
|
|
45
|
+
hint: `${d.plugin_key} (disabled)`,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
if (items.length === 0) {
|
|
49
|
+
note('No hooks found (settings or plugins).');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
// Currently enabled = active hooks
|
|
53
|
+
const currently_enabled = items
|
|
54
|
+
.filter((item) => item.active_entry)
|
|
55
|
+
.map((item) => item.id);
|
|
56
|
+
const selected = await multiselect({
|
|
57
|
+
message: 'Toggle hooks on/off:',
|
|
58
|
+
options: items.map((item) => ({
|
|
59
|
+
value: item.id,
|
|
60
|
+
label: item.label,
|
|
61
|
+
hint: item.hint,
|
|
62
|
+
})),
|
|
63
|
+
initialValues: currently_enabled,
|
|
64
|
+
required: false,
|
|
65
|
+
});
|
|
66
|
+
if (isCancel(selected))
|
|
67
|
+
return;
|
|
68
|
+
const selected_set = new Set(selected);
|
|
69
|
+
let changes = 0;
|
|
70
|
+
// Disable hooks that were deselected (active → disabled)
|
|
71
|
+
for (const item of items) {
|
|
72
|
+
if (!item.active_entry)
|
|
73
|
+
continue;
|
|
74
|
+
if (selected_set.has(item.id))
|
|
75
|
+
continue;
|
|
76
|
+
// Only plugin hooks can be disabled — settings hooks get removed
|
|
77
|
+
if (item.active_entry.source === 'plugin') {
|
|
78
|
+
await disable_plugin_hook(item.active_entry);
|
|
79
|
+
changes++;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Enable hooks that were selected (disabled → active)
|
|
83
|
+
for (const item of items) {
|
|
84
|
+
if (item.disabled_index === undefined)
|
|
85
|
+
continue;
|
|
86
|
+
if (!selected_set.has(item.id))
|
|
87
|
+
continue;
|
|
88
|
+
await enable_plugin_hook(disabled[item.disabled_index]);
|
|
89
|
+
changes++;
|
|
90
|
+
}
|
|
91
|
+
if (changes > 0) {
|
|
92
|
+
log.success(`${changes} hook(s) updated.`);
|
|
93
|
+
log.info('Restart Claude Code for changes to take effect.');
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
log.info('No changes.');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=manage-hooks.js.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { confirm, isCancel, log, multiselect, note, select, text, } from '@clack/prompts';
|
|
2
|
+
import { check_restored_hooks, redisable_restored_hooks, } from '../core/hook-state.js';
|
|
2
3
|
import { read_known_marketplaces, read_marketplace_manifest, } from '../core/plugin-cache.js';
|
|
3
4
|
import { get_all_plugins, read_claude_settings, } from '../core/settings.js';
|
|
4
5
|
import { install_plugin_via_cli, marketplace_add_via_cli, marketplace_remove_via_cli, marketplace_update_via_cli, uninstall_plugin_via_cli, } from '../utils/claude-cli.js';
|
|
@@ -216,6 +217,23 @@ async function handle_update() {
|
|
|
216
217
|
log.error(result.error || 'Unknown error');
|
|
217
218
|
}
|
|
218
219
|
}
|
|
220
|
+
// Check if update restored any disabled hooks
|
|
221
|
+
const restored = await check_restored_hooks();
|
|
222
|
+
if (restored.length > 0) {
|
|
223
|
+
log.warn(`${restored.length} disabled hook(s) were restored by the update.`);
|
|
224
|
+
const should_redisable = await confirm({
|
|
225
|
+
message: 'Re-disable these hooks?',
|
|
226
|
+
});
|
|
227
|
+
if (!isCancel(should_redisable) && should_redisable) {
|
|
228
|
+
const redisable_result = await redisable_restored_hooks(restored);
|
|
229
|
+
if (redisable_result.success > 0) {
|
|
230
|
+
log.success(`Re-disabled ${redisable_result.success} hook(s).`);
|
|
231
|
+
}
|
|
232
|
+
if (redisable_result.failed > 0) {
|
|
233
|
+
log.error(`Failed to re-disable ${redisable_result.failed} hook(s).`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
219
237
|
}
|
|
220
238
|
export async function manage_marketplace() {
|
|
221
239
|
while (true) {
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { ensure_directory_exists, get_disabled_hooks_path, get_marketplaces_dir, get_mcpick_dir, } from '../utils/paths.js';
|
|
4
|
+
export async function read_disabled_hooks() {
|
|
5
|
+
try {
|
|
6
|
+
const content = await readFile(get_disabled_hooks_path(), 'utf-8');
|
|
7
|
+
return JSON.parse(content);
|
|
8
|
+
}
|
|
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, '\t'), '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
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
const hooks_data = JSON.parse(content);
|
|
30
|
+
const hooks_obj = (hooks_data.hooks || hooks_data);
|
|
31
|
+
const matchers = hooks_obj[event];
|
|
32
|
+
if (!matchers)
|
|
33
|
+
return false;
|
|
34
|
+
let removed = false;
|
|
35
|
+
for (const m of matchers) {
|
|
36
|
+
const idx = m.hooks?.findIndex((h) => h.type === handler.type &&
|
|
37
|
+
h.command === handler.command &&
|
|
38
|
+
h.url === handler.url &&
|
|
39
|
+
h.prompt === handler.prompt);
|
|
40
|
+
if (idx !== undefined && idx >= 0) {
|
|
41
|
+
m.hooks.splice(idx, 1);
|
|
42
|
+
removed = true;
|
|
43
|
+
if (m.hooks.length === 0) {
|
|
44
|
+
matchers.splice(matchers.indexOf(m), 1);
|
|
45
|
+
}
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (!removed)
|
|
50
|
+
return false;
|
|
51
|
+
if (matchers.length === 0) {
|
|
52
|
+
delete hooks_obj[event];
|
|
53
|
+
}
|
|
54
|
+
await writeFile(hooks_path, JSON.stringify(hooks_data, null, '\t'), 'utf-8');
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get all hooks.json paths for a plugin (cache + marketplace source).
|
|
59
|
+
*/
|
|
60
|
+
function get_all_hooks_paths(plugin_key, primary_path) {
|
|
61
|
+
const paths = [primary_path];
|
|
62
|
+
const at_index = plugin_key.lastIndexOf('@');
|
|
63
|
+
if (at_index > 0) {
|
|
64
|
+
const plugin_name = plugin_key.substring(0, at_index);
|
|
65
|
+
const marketplace_name = plugin_key.substring(at_index + 1);
|
|
66
|
+
paths.push(join(get_marketplaces_dir(), marketplace_name, 'plugins', plugin_name, 'hooks', 'hooks.json'));
|
|
67
|
+
}
|
|
68
|
+
return [...new Set(paths)]; // deduplicate
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Disable a specific hook from a plugin.
|
|
72
|
+
* Removes from both cache and marketplace source hooks.json files.
|
|
73
|
+
*/
|
|
74
|
+
export async function disable_plugin_hook(entry) {
|
|
75
|
+
if (!entry.hooks_json_path || !entry.plugin_key) {
|
|
76
|
+
throw new Error('Not a plugin hook');
|
|
77
|
+
}
|
|
78
|
+
// Save to disabled state
|
|
79
|
+
const disabled = await read_disabled_hooks();
|
|
80
|
+
disabled.push({
|
|
81
|
+
plugin_key: entry.plugin_key,
|
|
82
|
+
hooks_json_path: entry.hooks_json_path,
|
|
83
|
+
event: entry.event,
|
|
84
|
+
matcher: entry.matcher,
|
|
85
|
+
matcher_index: entry.matcher_index,
|
|
86
|
+
hook_index: entry.hook_index,
|
|
87
|
+
original_handler: entry.handler,
|
|
88
|
+
disabled_at: new Date().toISOString(),
|
|
89
|
+
});
|
|
90
|
+
await write_disabled_hooks(disabled);
|
|
91
|
+
// Remove from all hooks.json files (cache + marketplace source)
|
|
92
|
+
const all_paths = get_all_hooks_paths(entry.plugin_key, entry.hooks_json_path);
|
|
93
|
+
for (const hooks_path of all_paths) {
|
|
94
|
+
await remove_hook_from_file(hooks_path, entry.event, entry.handler);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Add a hook handler back into a hooks.json file.
|
|
99
|
+
*/
|
|
100
|
+
async function add_hook_to_file(hooks_path, event, matcher_pattern, handler) {
|
|
101
|
+
let hooks_data;
|
|
102
|
+
try {
|
|
103
|
+
const content = await readFile(hooks_path, 'utf-8');
|
|
104
|
+
hooks_data = JSON.parse(content);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
hooks_data = { hooks: {} };
|
|
108
|
+
}
|
|
109
|
+
const hooks_obj = (hooks_data.hooks ||
|
|
110
|
+
(hooks_data.hooks = {}));
|
|
111
|
+
if (!hooks_obj[event])
|
|
112
|
+
hooks_obj[event] = [];
|
|
113
|
+
const matchers = hooks_obj[event];
|
|
114
|
+
let matcher = matchers.find((m) => (m.matcher || undefined) === matcher_pattern);
|
|
115
|
+
if (!matcher) {
|
|
116
|
+
matcher = { hooks: [] };
|
|
117
|
+
if (matcher_pattern)
|
|
118
|
+
matcher.matcher = matcher_pattern;
|
|
119
|
+
matchers.push(matcher);
|
|
120
|
+
}
|
|
121
|
+
// Only add if not already present (avoids duplicates)
|
|
122
|
+
const already_exists = matcher.hooks.some((h) => h.type === handler.type &&
|
|
123
|
+
h.command === handler.command &&
|
|
124
|
+
h.url === handler.url &&
|
|
125
|
+
h.prompt === handler.prompt);
|
|
126
|
+
if (already_exists)
|
|
127
|
+
return;
|
|
128
|
+
matcher.hooks.push(handler);
|
|
129
|
+
await writeFile(hooks_path, JSON.stringify(hooks_data, null, '\t'), 'utf-8');
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Re-enable a previously disabled plugin hook.
|
|
133
|
+
* Restores to both cache and marketplace source hooks.json files.
|
|
134
|
+
*/
|
|
135
|
+
export async function enable_plugin_hook(disabled_entry) {
|
|
136
|
+
const all_paths = get_all_hooks_paths(disabled_entry.plugin_key, disabled_entry.hooks_json_path);
|
|
137
|
+
for (const hooks_path of all_paths) {
|
|
138
|
+
await add_hook_to_file(hooks_path, disabled_entry.event, disabled_entry.matcher, disabled_entry.original_handler);
|
|
139
|
+
}
|
|
140
|
+
// Remove from disabled state
|
|
141
|
+
const disabled = await read_disabled_hooks();
|
|
142
|
+
const updated = disabled.filter((d) => !(d.plugin_key === disabled_entry.plugin_key &&
|
|
143
|
+
d.event === disabled_entry.event &&
|
|
144
|
+
d.disabled_at === disabled_entry.disabled_at));
|
|
145
|
+
await write_disabled_hooks(updated);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Check if any previously disabled hooks have been restored (e.g. by marketplace update).
|
|
149
|
+
* Returns entries that were re-added and need to be re-disabled.
|
|
150
|
+
*/
|
|
151
|
+
export async function check_restored_hooks() {
|
|
152
|
+
const disabled = await read_disabled_hooks();
|
|
153
|
+
if (disabled.length === 0)
|
|
154
|
+
return [];
|
|
155
|
+
const restored = [];
|
|
156
|
+
for (const entry of disabled) {
|
|
157
|
+
const all_paths = get_all_hooks_paths(entry.plugin_key, entry.hooks_json_path);
|
|
158
|
+
let found = false;
|
|
159
|
+
for (const hooks_path of all_paths) {
|
|
160
|
+
let hooks_data;
|
|
161
|
+
try {
|
|
162
|
+
const content = await readFile(hooks_path, 'utf-8');
|
|
163
|
+
hooks_data = JSON.parse(content);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
const hooks_obj = (hooks_data.hooks || hooks_data);
|
|
169
|
+
const matchers = hooks_obj[entry.event];
|
|
170
|
+
if (!matchers)
|
|
171
|
+
continue;
|
|
172
|
+
for (const m of matchers) {
|
|
173
|
+
if ((m.matcher || undefined) !== entry.matcher)
|
|
174
|
+
continue;
|
|
175
|
+
const has_match = m.hooks?.some((h) => h.type === entry.original_handler.type &&
|
|
176
|
+
(h.command === entry.original_handler.command ||
|
|
177
|
+
h.url === entry.original_handler.url ||
|
|
178
|
+
h.prompt === entry.original_handler.prompt));
|
|
179
|
+
if (has_match) {
|
|
180
|
+
found = true;
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (found)
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
if (found)
|
|
188
|
+
restored.push(entry);
|
|
189
|
+
}
|
|
190
|
+
return restored;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Re-disable hooks that were restored by a marketplace update.
|
|
194
|
+
*/
|
|
195
|
+
export async function redisable_restored_hooks(restored) {
|
|
196
|
+
let success = 0;
|
|
197
|
+
let failed = 0;
|
|
198
|
+
for (const entry of restored) {
|
|
199
|
+
try {
|
|
200
|
+
const all_paths = get_all_hooks_paths(entry.plugin_key, entry.hooks_json_path);
|
|
201
|
+
let any_removed = false;
|
|
202
|
+
for (const hooks_path of all_paths) {
|
|
203
|
+
const removed = await remove_hook_from_file(hooks_path, entry.event, entry.original_handler);
|
|
204
|
+
if (removed)
|
|
205
|
+
any_removed = true;
|
|
206
|
+
}
|
|
207
|
+
if (any_removed) {
|
|
208
|
+
success++;
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
failed++;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
failed++;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return { success, failed };
|
|
219
|
+
}
|
|
220
|
+
//# sourceMappingURL=hook-state.js.map
|
package/dist/core/settings.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { access, readFile } from 'node:fs/promises';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
2
3
|
import { atomic_json_write } from '../utils/atomic-write.js';
|
|
3
4
|
import { get_claude_settings_path } from '../utils/paths.js';
|
|
4
5
|
export async function read_claude_settings() {
|
|
@@ -49,4 +50,194 @@ export function build_enabled_plugins(plugins) {
|
|
|
49
50
|
}
|
|
50
51
|
return result;
|
|
51
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
|
+
}
|
|
52
243
|
//# sourceMappingURL=settings.js.map
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { backup_config } from './commands/backup.js';
|
|
|
5
5
|
import { edit_config } from './commands/edit-config.js';
|
|
6
6
|
import { edit_plugins } from './commands/edit-plugins.js';
|
|
7
7
|
import { manage_cache } from './commands/manage-cache.js';
|
|
8
|
+
import { manage_hooks } from './commands/manage-hooks.js';
|
|
8
9
|
import { manage_marketplace } from './commands/manage-marketplace.js';
|
|
9
10
|
import { restore_config } from './commands/restore.js';
|
|
10
11
|
import { write_claude_config } from './core/config.js';
|
|
@@ -184,6 +185,11 @@ async function main() {
|
|
|
184
185
|
label: 'Manage marketplaces',
|
|
185
186
|
hint: 'Add, remove, or update plugin marketplaces',
|
|
186
187
|
},
|
|
188
|
+
{
|
|
189
|
+
value: 'manage-hooks',
|
|
190
|
+
label: 'Manage hooks',
|
|
191
|
+
hint: 'List, add, or remove event hooks',
|
|
192
|
+
},
|
|
187
193
|
{
|
|
188
194
|
value: 'manage-cache',
|
|
189
195
|
label: 'Manage plugin cache',
|
|
@@ -235,6 +241,9 @@ async function main() {
|
|
|
235
241
|
case 'manage-marketplace':
|
|
236
242
|
await manage_marketplace();
|
|
237
243
|
break;
|
|
244
|
+
case 'manage-hooks':
|
|
245
|
+
await manage_hooks();
|
|
246
|
+
break;
|
|
238
247
|
case 'manage-cache':
|
|
239
248
|
await manage_cache();
|
|
240
249
|
break;
|
|
@@ -288,6 +297,7 @@ const SUBCOMMANDS = new Set([
|
|
|
288
297
|
'add-json',
|
|
289
298
|
'get',
|
|
290
299
|
'reset-project-choices',
|
|
300
|
+
'hooks',
|
|
291
301
|
'backup',
|
|
292
302
|
'restore',
|
|
293
303
|
'profile',
|
package/dist/utils/paths.js
CHANGED
|
@@ -105,6 +105,9 @@ export function get_plugin_cache_dir() {
|
|
|
105
105
|
export function get_marketplaces_dir() {
|
|
106
106
|
return join(get_plugins_dir(), 'marketplaces');
|
|
107
107
|
}
|
|
108
|
+
export function get_disabled_hooks_path() {
|
|
109
|
+
return join(get_mcpick_dir(), 'disabled-hooks.json');
|
|
110
|
+
}
|
|
108
111
|
export function get_marketplace_manifest_path(name) {
|
|
109
112
|
return join(get_marketplaces_dir(), name, '.claude-plugin', 'marketplace.json');
|
|
110
113
|
}
|