claude-cli-advanced-starter-pack 1.0.0
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/LICENSE +21 -0
- package/OVERVIEW.md +597 -0
- package/README.md +439 -0
- package/bin/gtask.js +282 -0
- package/bin/postinstall.js +53 -0
- package/package.json +69 -0
- package/src/agents/phase-dev-templates.js +1011 -0
- package/src/agents/templates.js +668 -0
- package/src/analysis/checklist-parser.js +414 -0
- package/src/analysis/codebase.js +481 -0
- package/src/cli/menu.js +958 -0
- package/src/commands/claude-audit.js +1482 -0
- package/src/commands/claude-settings.js +2243 -0
- package/src/commands/create-agent.js +681 -0
- package/src/commands/create-command.js +337 -0
- package/src/commands/create-hook.js +262 -0
- package/src/commands/create-phase-dev/codebase-analyzer.js +813 -0
- package/src/commands/create-phase-dev/documentation-generator.js +352 -0
- package/src/commands/create-phase-dev/post-completion.js +404 -0
- package/src/commands/create-phase-dev/scale-calculator.js +344 -0
- package/src/commands/create-phase-dev/wizard.js +492 -0
- package/src/commands/create-phase-dev.js +481 -0
- package/src/commands/create-skill.js +313 -0
- package/src/commands/create.js +446 -0
- package/src/commands/decompose.js +392 -0
- package/src/commands/detect-tech-stack.js +768 -0
- package/src/commands/explore-mcp/claude-md-updater.js +252 -0
- package/src/commands/explore-mcp/mcp-installer.js +346 -0
- package/src/commands/explore-mcp/mcp-registry.js +438 -0
- package/src/commands/explore-mcp.js +638 -0
- package/src/commands/gtask-init.js +641 -0
- package/src/commands/help.js +128 -0
- package/src/commands/init.js +1890 -0
- package/src/commands/install.js +250 -0
- package/src/commands/list.js +116 -0
- package/src/commands/roadmap.js +750 -0
- package/src/commands/setup-wizard.js +482 -0
- package/src/commands/setup.js +351 -0
- package/src/commands/sync.js +534 -0
- package/src/commands/test-run.js +456 -0
- package/src/commands/test-setup.js +456 -0
- package/src/commands/validate.js +67 -0
- package/src/config/tech-stack.defaults.json +182 -0
- package/src/config/tech-stack.schema.json +502 -0
- package/src/github/client.js +359 -0
- package/src/index.js +84 -0
- package/src/templates/claude-command.js +244 -0
- package/src/templates/issue-body.js +284 -0
- package/src/testing/config.js +411 -0
- package/src/utils/template-engine.js +398 -0
- package/src/utils/validate-templates.js +223 -0
- package/src/utils.js +396 -0
- package/templates/commands/ccasp-setup.template.md +113 -0
- package/templates/commands/context-audit.template.md +97 -0
- package/templates/commands/create-task-list.template.md +382 -0
- package/templates/commands/deploy-full.template.md +261 -0
- package/templates/commands/github-task-start.template.md +99 -0
- package/templates/commands/github-update.template.md +69 -0
- package/templates/commands/happy-start.template.md +117 -0
- package/templates/commands/phase-track.template.md +142 -0
- package/templates/commands/tunnel-start.template.md +127 -0
- package/templates/commands/tunnel-stop.template.md +106 -0
- package/templates/hooks/context-guardian.template.js +173 -0
- package/templates/hooks/deployment-orchestrator.template.js +219 -0
- package/templates/hooks/github-progress-hook.template.js +197 -0
- package/templates/hooks/happy-checkpoint-manager.template.js +222 -0
- package/templates/hooks/phase-dev-enforcer.template.js +183 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create Command
|
|
3
|
+
*
|
|
4
|
+
* Interactive wizard for creating Claude Code slash commands
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import inquirer from 'inquirer';
|
|
9
|
+
import ora from 'ora';
|
|
10
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|
11
|
+
import { join, dirname } from 'path';
|
|
12
|
+
import { showHeader, showSuccess, showError, showWarning, showInfo } from '../cli/menu.js';
|
|
13
|
+
import { generateCommandTemplate, COMPLEXITY_LEVELS } from '../agents/templates.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Run the create-command wizard
|
|
17
|
+
*/
|
|
18
|
+
export async function runCreateCommand(options) {
|
|
19
|
+
showHeader('Create Slash Command');
|
|
20
|
+
|
|
21
|
+
console.log(chalk.dim('Slash commands are invoked with / in Claude Code.'));
|
|
22
|
+
console.log(chalk.dim('They can delegate to skills, agents, or run standalone.\n'));
|
|
23
|
+
|
|
24
|
+
// Step 1: Command name
|
|
25
|
+
const { name } = await inquirer.prompt([
|
|
26
|
+
{
|
|
27
|
+
type: 'input',
|
|
28
|
+
name: 'name',
|
|
29
|
+
message: 'Command name (without /):',
|
|
30
|
+
default: options.name || 'my-command',
|
|
31
|
+
validate: (input) => {
|
|
32
|
+
if (!/^[a-z][a-z0-9-]*$/.test(input)) {
|
|
33
|
+
return 'Use kebab-case (lowercase letters, numbers, hyphens)';
|
|
34
|
+
}
|
|
35
|
+
return true;
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
// Step 2: Description
|
|
41
|
+
const { description } = await inquirer.prompt([
|
|
42
|
+
{
|
|
43
|
+
type: 'input',
|
|
44
|
+
name: 'description',
|
|
45
|
+
message: 'Brief description (shown in menus):',
|
|
46
|
+
default: `Run ${name} workflow`,
|
|
47
|
+
validate: (input) => input.length > 0 || 'Description is required',
|
|
48
|
+
},
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
// Step 3: Complexity
|
|
52
|
+
console.log('');
|
|
53
|
+
console.log(chalk.cyan.bold('Complexity Levels:'));
|
|
54
|
+
Object.entries(COMPLEXITY_LEVELS).forEach(([key, level]) => {
|
|
55
|
+
console.log(chalk.dim(` ${level.name}: ${level.description} (${level.duration})`));
|
|
56
|
+
});
|
|
57
|
+
console.log('');
|
|
58
|
+
|
|
59
|
+
const { complexity } = await inquirer.prompt([
|
|
60
|
+
{
|
|
61
|
+
type: 'list',
|
|
62
|
+
name: 'complexity',
|
|
63
|
+
message: 'Expected complexity:',
|
|
64
|
+
choices: Object.entries(COMPLEXITY_LEVELS).map(([key, level]) => ({
|
|
65
|
+
name: `${level.name} - ${level.description}`,
|
|
66
|
+
value: key,
|
|
67
|
+
short: level.name,
|
|
68
|
+
})),
|
|
69
|
+
default: 'low',
|
|
70
|
+
},
|
|
71
|
+
]);
|
|
72
|
+
|
|
73
|
+
// Step 4: Delegation
|
|
74
|
+
const { delegationType } = await inquirer.prompt([
|
|
75
|
+
{
|
|
76
|
+
type: 'list',
|
|
77
|
+
name: 'delegationType',
|
|
78
|
+
message: 'What does this command use?',
|
|
79
|
+
choices: [
|
|
80
|
+
{ name: 'Standalone - No delegation, instructions only', value: 'standalone' },
|
|
81
|
+
{ name: 'Skill - Delegates to a skill package', value: 'skill' },
|
|
82
|
+
{ name: 'Agent - Spawns a specific agent', value: 'agent' },
|
|
83
|
+
{ name: 'Both - Uses skill + specific agent workflow', value: 'both' },
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
let delegatesTo = '';
|
|
89
|
+
if (delegationType === 'skill' || delegationType === 'both') {
|
|
90
|
+
const { skillName } = await inquirer.prompt([
|
|
91
|
+
{
|
|
92
|
+
type: 'input',
|
|
93
|
+
name: 'skillName',
|
|
94
|
+
message: 'Skill name:',
|
|
95
|
+
default: 'my-skill',
|
|
96
|
+
},
|
|
97
|
+
]);
|
|
98
|
+
delegatesTo = `skill:${skillName}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (delegationType === 'agent' || delegationType === 'both') {
|
|
102
|
+
const { agentName } = await inquirer.prompt([
|
|
103
|
+
{
|
|
104
|
+
type: 'input',
|
|
105
|
+
name: 'agentName',
|
|
106
|
+
message: 'Agent name:',
|
|
107
|
+
default: 'my-agent',
|
|
108
|
+
},
|
|
109
|
+
]);
|
|
110
|
+
delegatesTo = delegationType === 'both' ? `${delegatesTo} + @${agentName}` : `@${agentName}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Step 5: Arguments
|
|
114
|
+
const { hasArgs } = await inquirer.prompt([
|
|
115
|
+
{
|
|
116
|
+
type: 'confirm',
|
|
117
|
+
name: 'hasArgs',
|
|
118
|
+
message: 'Does this command accept arguments?',
|
|
119
|
+
default: false,
|
|
120
|
+
},
|
|
121
|
+
]);
|
|
122
|
+
|
|
123
|
+
let args = '';
|
|
124
|
+
if (hasArgs) {
|
|
125
|
+
const { argsSpec } = await inquirer.prompt([
|
|
126
|
+
{
|
|
127
|
+
type: 'input',
|
|
128
|
+
name: 'argsSpec',
|
|
129
|
+
message: 'Arguments format (e.g., "[target] [--flag]"):',
|
|
130
|
+
default: '[target]',
|
|
131
|
+
},
|
|
132
|
+
]);
|
|
133
|
+
args = argsSpec;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Step 6: Workflow steps
|
|
137
|
+
const { defineSteps } = await inquirer.prompt([
|
|
138
|
+
{
|
|
139
|
+
type: 'confirm',
|
|
140
|
+
name: 'defineSteps',
|
|
141
|
+
message: 'Define workflow steps? (recommended)',
|
|
142
|
+
default: true,
|
|
143
|
+
},
|
|
144
|
+
]);
|
|
145
|
+
|
|
146
|
+
const steps = [];
|
|
147
|
+
if (defineSteps) {
|
|
148
|
+
let addingSteps = true;
|
|
149
|
+
let stepNum = 1;
|
|
150
|
+
|
|
151
|
+
while (addingSteps) {
|
|
152
|
+
const { stepTitle, stepInstructions } = await inquirer.prompt([
|
|
153
|
+
{
|
|
154
|
+
type: 'input',
|
|
155
|
+
name: 'stepTitle',
|
|
156
|
+
message: `Step ${stepNum} title:`,
|
|
157
|
+
default: stepNum === 1 ? 'Analyze' : stepNum === 2 ? 'Execute' : 'Report',
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
type: 'input',
|
|
161
|
+
name: 'stepInstructions',
|
|
162
|
+
message: `Step ${stepNum} instructions:`,
|
|
163
|
+
default: 'Perform this step of the workflow.',
|
|
164
|
+
},
|
|
165
|
+
]);
|
|
166
|
+
|
|
167
|
+
steps.push({ title: stepTitle, instructions: stepInstructions });
|
|
168
|
+
stepNum++;
|
|
169
|
+
|
|
170
|
+
const { continueAdding } = await inquirer.prompt([
|
|
171
|
+
{
|
|
172
|
+
type: 'confirm',
|
|
173
|
+
name: 'continueAdding',
|
|
174
|
+
message: 'Add another step?',
|
|
175
|
+
default: stepNum <= 3,
|
|
176
|
+
},
|
|
177
|
+
]);
|
|
178
|
+
addingSteps = continueAdding;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Step 7: Examples
|
|
183
|
+
const examples = [];
|
|
184
|
+
const { addExamples } = await inquirer.prompt([
|
|
185
|
+
{
|
|
186
|
+
type: 'confirm',
|
|
187
|
+
name: 'addExamples',
|
|
188
|
+
message: 'Add usage examples?',
|
|
189
|
+
default: true,
|
|
190
|
+
},
|
|
191
|
+
]);
|
|
192
|
+
|
|
193
|
+
if (addExamples) {
|
|
194
|
+
let addingExamples = true;
|
|
195
|
+
while (addingExamples) {
|
|
196
|
+
const { exampleDesc, exampleArgs } = await inquirer.prompt([
|
|
197
|
+
{
|
|
198
|
+
type: 'input',
|
|
199
|
+
name: 'exampleDesc',
|
|
200
|
+
message: 'Example description:',
|
|
201
|
+
default: 'Basic usage',
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
type: 'input',
|
|
205
|
+
name: 'exampleArgs',
|
|
206
|
+
message: `Example args (after /${name}):`,
|
|
207
|
+
default: '',
|
|
208
|
+
},
|
|
209
|
+
]);
|
|
210
|
+
|
|
211
|
+
examples.push({ description: exampleDesc, args: exampleArgs });
|
|
212
|
+
|
|
213
|
+
const { continueAdding } = await inquirer.prompt([
|
|
214
|
+
{
|
|
215
|
+
type: 'confirm',
|
|
216
|
+
name: 'continueAdding',
|
|
217
|
+
message: 'Add another example?',
|
|
218
|
+
default: examples.length < 2,
|
|
219
|
+
},
|
|
220
|
+
]);
|
|
221
|
+
addingExamples = continueAdding;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Step 8: Related commands
|
|
226
|
+
const { relatedCommands } = await inquirer.prompt([
|
|
227
|
+
{
|
|
228
|
+
type: 'input',
|
|
229
|
+
name: 'relatedCommands',
|
|
230
|
+
message: 'Related commands (comma-separated, without /):',
|
|
231
|
+
default: '',
|
|
232
|
+
filter: (input) =>
|
|
233
|
+
input
|
|
234
|
+
.split(',')
|
|
235
|
+
.map((c) => c.trim())
|
|
236
|
+
.filter(Boolean),
|
|
237
|
+
},
|
|
238
|
+
]);
|
|
239
|
+
|
|
240
|
+
// Step 9: Output location
|
|
241
|
+
const { outputPath } = await inquirer.prompt([
|
|
242
|
+
{
|
|
243
|
+
type: 'list',
|
|
244
|
+
name: 'outputPath',
|
|
245
|
+
message: 'Where should the command be created?',
|
|
246
|
+
choices: [
|
|
247
|
+
{ name: '.claude/commands/ (standard location)', value: '.claude/commands' },
|
|
248
|
+
{ name: 'Custom location', value: 'custom' },
|
|
249
|
+
],
|
|
250
|
+
},
|
|
251
|
+
]);
|
|
252
|
+
|
|
253
|
+
let finalPath = join(process.cwd(), outputPath, `${name}.md`);
|
|
254
|
+
if (outputPath === 'custom') {
|
|
255
|
+
const { customPath } = await inquirer.prompt([
|
|
256
|
+
{
|
|
257
|
+
type: 'input',
|
|
258
|
+
name: 'customPath',
|
|
259
|
+
message: 'Custom path:',
|
|
260
|
+
default: `.claude/commands/${name}.md`,
|
|
261
|
+
},
|
|
262
|
+
]);
|
|
263
|
+
finalPath = join(process.cwd(), customPath);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Generate the command
|
|
267
|
+
const spinner = ora('Generating command...').start();
|
|
268
|
+
|
|
269
|
+
const commandContent = generateCommandTemplate({
|
|
270
|
+
name,
|
|
271
|
+
description,
|
|
272
|
+
complexity,
|
|
273
|
+
delegatesTo,
|
|
274
|
+
arguments: args,
|
|
275
|
+
steps,
|
|
276
|
+
examples,
|
|
277
|
+
relatedCommands,
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// Ensure directory exists
|
|
281
|
+
const dir = dirname(finalPath);
|
|
282
|
+
if (!existsSync(dir)) {
|
|
283
|
+
mkdirSync(dir, { recursive: true });
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Write the command file
|
|
287
|
+
writeFileSync(finalPath, commandContent, 'utf8');
|
|
288
|
+
|
|
289
|
+
// Try to update INDEX.md if it exists
|
|
290
|
+
const indexPath = join(dir, 'INDEX.md');
|
|
291
|
+
let indexUpdated = false;
|
|
292
|
+
if (existsSync(indexPath)) {
|
|
293
|
+
try {
|
|
294
|
+
const indexContent = readFileSync(indexPath, 'utf8');
|
|
295
|
+
const newEntry = `| \`/${name}\` | ${description} | ${complexity} |`;
|
|
296
|
+
|
|
297
|
+
// Check if command already in index
|
|
298
|
+
if (!indexContent.includes(`/${name}`)) {
|
|
299
|
+
// Find a good place to insert (before the last table row or at end of a table)
|
|
300
|
+
const tableMatch = indexContent.match(/(\|[^|]+\|[^|]+\|[^|]+\|\n)+/);
|
|
301
|
+
if (tableMatch) {
|
|
302
|
+
const updatedIndex = indexContent.replace(
|
|
303
|
+
tableMatch[0],
|
|
304
|
+
tableMatch[0] + newEntry + '\n'
|
|
305
|
+
);
|
|
306
|
+
writeFileSync(indexPath, updatedIndex, 'utf8');
|
|
307
|
+
indexUpdated = true;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
} catch {
|
|
311
|
+
// Ignore index update errors
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
spinner.succeed('Command created');
|
|
316
|
+
|
|
317
|
+
// Summary
|
|
318
|
+
const details = [
|
|
319
|
+
`Name: /${name}`,
|
|
320
|
+
`Complexity: ${COMPLEXITY_LEVELS[complexity].name}`,
|
|
321
|
+
delegatesTo ? `Delegates to: ${delegatesTo}` : '',
|
|
322
|
+
args ? `Arguments: ${args}` : '',
|
|
323
|
+
`Steps: ${steps.length}`,
|
|
324
|
+
'',
|
|
325
|
+
`Location: ${finalPath}`,
|
|
326
|
+
indexUpdated ? 'INDEX.md: Updated' : '',
|
|
327
|
+
].filter(Boolean);
|
|
328
|
+
|
|
329
|
+
showSuccess('Command Created!', details);
|
|
330
|
+
|
|
331
|
+
// Instructions
|
|
332
|
+
console.log(chalk.dim('\nTo use this command in Claude Code:'));
|
|
333
|
+
console.log(chalk.cyan(` /${name}${args ? ` ${args}` : ''}`));
|
|
334
|
+
console.log('');
|
|
335
|
+
|
|
336
|
+
return { name, path: finalPath, complexity, delegatesTo };
|
|
337
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create Hook Command
|
|
3
|
+
*
|
|
4
|
+
* Interactive wizard for creating Claude Code enforcement hooks
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import inquirer from 'inquirer';
|
|
9
|
+
import ora from 'ora';
|
|
10
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
11
|
+
import { join, dirname } from 'path';
|
|
12
|
+
import { showHeader, showSuccess, showError, showWarning, showInfo } from '../cli/menu.js';
|
|
13
|
+
import {
|
|
14
|
+
generateHookTemplate,
|
|
15
|
+
HOOK_EVENT_TYPES,
|
|
16
|
+
HOOK_TOOLS,
|
|
17
|
+
} from '../agents/templates.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Run the create-hook wizard
|
|
21
|
+
*/
|
|
22
|
+
export async function runCreateHook(options) {
|
|
23
|
+
showHeader('Create Hook');
|
|
24
|
+
|
|
25
|
+
console.log(chalk.dim('Hooks enforce patterns and inject context in Claude Code.'));
|
|
26
|
+
console.log(chalk.dim('They run automatically on tool calls or user prompts.\n'));
|
|
27
|
+
|
|
28
|
+
// Step 1: Hook name
|
|
29
|
+
const { name } = await inquirer.prompt([
|
|
30
|
+
{
|
|
31
|
+
type: 'input',
|
|
32
|
+
name: 'name',
|
|
33
|
+
message: 'Hook name (kebab-case):',
|
|
34
|
+
default: options.name || 'my-hook',
|
|
35
|
+
validate: (input) => {
|
|
36
|
+
if (!/^[a-z][a-z0-9-]*$/.test(input)) {
|
|
37
|
+
return 'Use kebab-case (lowercase letters, numbers, hyphens)';
|
|
38
|
+
}
|
|
39
|
+
return true;
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
// Step 2: Description
|
|
45
|
+
const { description } = await inquirer.prompt([
|
|
46
|
+
{
|
|
47
|
+
type: 'input',
|
|
48
|
+
name: 'description',
|
|
49
|
+
message: 'What does this hook do?',
|
|
50
|
+
default: `Enforce patterns for ${name}`,
|
|
51
|
+
},
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
// Step 3: Event type
|
|
55
|
+
console.log('');
|
|
56
|
+
console.log(chalk.cyan.bold('Event Types:'));
|
|
57
|
+
Object.entries(HOOK_EVENT_TYPES).forEach(([key, event]) => {
|
|
58
|
+
const canBlock = event.canBlock ? chalk.green('(can block)') : chalk.dim('(cannot block)');
|
|
59
|
+
console.log(chalk.dim(` ${key}: ${event.description} ${canBlock}`));
|
|
60
|
+
});
|
|
61
|
+
console.log('');
|
|
62
|
+
|
|
63
|
+
const { eventType } = await inquirer.prompt([
|
|
64
|
+
{
|
|
65
|
+
type: 'list',
|
|
66
|
+
name: 'eventType',
|
|
67
|
+
message: 'When should this hook trigger?',
|
|
68
|
+
choices: Object.entries(HOOK_EVENT_TYPES).map(([key, event]) => ({
|
|
69
|
+
name: `${key} - ${event.useCase}`,
|
|
70
|
+
value: key,
|
|
71
|
+
short: key,
|
|
72
|
+
})),
|
|
73
|
+
default: 'PreToolUse',
|
|
74
|
+
},
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
// Step 4: Target tools (for PreToolUse/PostToolUse)
|
|
78
|
+
let tools = [];
|
|
79
|
+
if (eventType === 'PreToolUse' || eventType === 'PostToolUse') {
|
|
80
|
+
const { selectedTools } = await inquirer.prompt([
|
|
81
|
+
{
|
|
82
|
+
type: 'checkbox',
|
|
83
|
+
name: 'selectedTools',
|
|
84
|
+
message: 'Which tools should trigger this hook?',
|
|
85
|
+
choices: HOOK_TOOLS.map((tool) => ({
|
|
86
|
+
name: tool,
|
|
87
|
+
value: tool,
|
|
88
|
+
checked: tool === 'Edit' || tool === 'Write',
|
|
89
|
+
})),
|
|
90
|
+
validate: (input) => input.length > 0 || 'Select at least one tool',
|
|
91
|
+
},
|
|
92
|
+
]);
|
|
93
|
+
tools = selectedTools;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Step 5: Target file patterns
|
|
97
|
+
const { targetPatterns } = await inquirer.prompt([
|
|
98
|
+
{
|
|
99
|
+
type: 'input',
|
|
100
|
+
name: 'targetPatterns',
|
|
101
|
+
message: 'File patterns to target (comma-separated, empty = all files):',
|
|
102
|
+
default: 'src/,apps/',
|
|
103
|
+
filter: (input) =>
|
|
104
|
+
input
|
|
105
|
+
.split(',')
|
|
106
|
+
.map((p) => p.trim())
|
|
107
|
+
.filter(Boolean),
|
|
108
|
+
},
|
|
109
|
+
]);
|
|
110
|
+
|
|
111
|
+
// Step 6: Purpose - block or warn
|
|
112
|
+
const { purpose } = await inquirer.prompt([
|
|
113
|
+
{
|
|
114
|
+
type: 'list',
|
|
115
|
+
name: 'purpose',
|
|
116
|
+
message: 'What should this hook do?',
|
|
117
|
+
choices: [
|
|
118
|
+
{ name: 'Block - Prevent operations matching patterns', value: 'block' },
|
|
119
|
+
{ name: 'Warn - Allow but show warnings', value: 'warn' },
|
|
120
|
+
{ name: 'Inject - Add context to Claude\'s response', value: 'inject' },
|
|
121
|
+
{ name: 'Log - Just log operations (no interference)', value: 'log' },
|
|
122
|
+
],
|
|
123
|
+
},
|
|
124
|
+
]);
|
|
125
|
+
|
|
126
|
+
// Step 7: Patterns based on purpose
|
|
127
|
+
let blockedPatterns = [];
|
|
128
|
+
let warningPatterns = [];
|
|
129
|
+
let blockReason = '';
|
|
130
|
+
|
|
131
|
+
if (purpose === 'block') {
|
|
132
|
+
const { patterns, reason } = await inquirer.prompt([
|
|
133
|
+
{
|
|
134
|
+
type: 'input',
|
|
135
|
+
name: 'patterns',
|
|
136
|
+
message: 'Patterns to BLOCK (comma-separated):',
|
|
137
|
+
default: 'console.log,debugger',
|
|
138
|
+
filter: (input) =>
|
|
139
|
+
input
|
|
140
|
+
.split(',')
|
|
141
|
+
.map((p) => p.trim())
|
|
142
|
+
.filter(Boolean),
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
type: 'input',
|
|
146
|
+
name: 'reason',
|
|
147
|
+
message: 'Why should these be blocked?',
|
|
148
|
+
default: 'These patterns are not allowed in production code',
|
|
149
|
+
},
|
|
150
|
+
]);
|
|
151
|
+
blockedPatterns = patterns;
|
|
152
|
+
blockReason = reason;
|
|
153
|
+
} else if (purpose === 'warn') {
|
|
154
|
+
const { patterns } = await inquirer.prompt([
|
|
155
|
+
{
|
|
156
|
+
type: 'input',
|
|
157
|
+
name: 'patterns',
|
|
158
|
+
message: 'Patterns to WARN about (comma-separated):',
|
|
159
|
+
default: 'TODO,FIXME,HACK',
|
|
160
|
+
filter: (input) =>
|
|
161
|
+
input
|
|
162
|
+
.split(',')
|
|
163
|
+
.map((p) => p.trim())
|
|
164
|
+
.filter(Boolean),
|
|
165
|
+
},
|
|
166
|
+
]);
|
|
167
|
+
warningPatterns = patterns;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Step 8: Reference documentation
|
|
171
|
+
const { referenceDoc } = await inquirer.prompt([
|
|
172
|
+
{
|
|
173
|
+
type: 'input',
|
|
174
|
+
name: 'referenceDoc',
|
|
175
|
+
message: 'Reference documentation path (optional):',
|
|
176
|
+
default: '',
|
|
177
|
+
},
|
|
178
|
+
]);
|
|
179
|
+
|
|
180
|
+
// Step 9: Output location
|
|
181
|
+
const { outputPath } = await inquirer.prompt([
|
|
182
|
+
{
|
|
183
|
+
type: 'list',
|
|
184
|
+
name: 'outputPath',
|
|
185
|
+
message: 'Where should the hook be created?',
|
|
186
|
+
choices: [
|
|
187
|
+
{ name: '.claude/hooks/tools/ (standard location)', value: '.claude/hooks/tools' },
|
|
188
|
+
{ name: 'Custom location', value: 'custom' },
|
|
189
|
+
],
|
|
190
|
+
},
|
|
191
|
+
]);
|
|
192
|
+
|
|
193
|
+
let finalPath = join(process.cwd(), outputPath, `${name}.js`);
|
|
194
|
+
if (outputPath === 'custom') {
|
|
195
|
+
const { customPath } = await inquirer.prompt([
|
|
196
|
+
{
|
|
197
|
+
type: 'input',
|
|
198
|
+
name: 'customPath',
|
|
199
|
+
message: 'Custom path:',
|
|
200
|
+
default: `.claude/hooks/tools/${name}.js`,
|
|
201
|
+
},
|
|
202
|
+
]);
|
|
203
|
+
finalPath = join(process.cwd(), customPath);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Generate the hook
|
|
207
|
+
const spinner = ora('Generating hook...').start();
|
|
208
|
+
|
|
209
|
+
const hookContent = generateHookTemplate({
|
|
210
|
+
name,
|
|
211
|
+
description,
|
|
212
|
+
eventType,
|
|
213
|
+
tools,
|
|
214
|
+
targetPatterns,
|
|
215
|
+
blockedPatterns,
|
|
216
|
+
warningPatterns,
|
|
217
|
+
blockReason,
|
|
218
|
+
referenceDoc,
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// Ensure directory exists
|
|
222
|
+
const dir = dirname(finalPath);
|
|
223
|
+
if (!existsSync(dir)) {
|
|
224
|
+
mkdirSync(dir, { recursive: true });
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Write the hook file
|
|
228
|
+
writeFileSync(finalPath, hookContent, 'utf8');
|
|
229
|
+
|
|
230
|
+
spinner.succeed('Hook created');
|
|
231
|
+
|
|
232
|
+
// Summary
|
|
233
|
+
const details = [
|
|
234
|
+
`Name: ${name}`,
|
|
235
|
+
`Event: ${eventType}`,
|
|
236
|
+
tools.length > 0 ? `Tools: ${tools.join(', ')}` : '',
|
|
237
|
+
targetPatterns.length > 0 ? `File patterns: ${targetPatterns.join(', ')}` : '',
|
|
238
|
+
blockedPatterns.length > 0 ? `Blocked: ${blockedPatterns.join(', ')}` : '',
|
|
239
|
+
warningPatterns.length > 0 ? `Warnings: ${warningPatterns.join(', ')}` : '',
|
|
240
|
+
'',
|
|
241
|
+
`Location: ${finalPath}`,
|
|
242
|
+
].filter(Boolean);
|
|
243
|
+
|
|
244
|
+
showSuccess('Hook Created!', details);
|
|
245
|
+
|
|
246
|
+
// Instructions
|
|
247
|
+
console.log(chalk.dim('\nTo activate this hook, add to .claude/settings.local.json:'));
|
|
248
|
+
console.log(chalk.cyan(`
|
|
249
|
+
{
|
|
250
|
+
"hooks": {
|
|
251
|
+
"${eventType}": [
|
|
252
|
+
{
|
|
253
|
+
"command": "node ${finalPath.replace(process.cwd(), '.')}"${tools.length > 0 ? `,
|
|
254
|
+
"tools": [${tools.map((t) => `"${t}"`).join(', ')}]` : ''}
|
|
255
|
+
}
|
|
256
|
+
]
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
`));
|
|
260
|
+
|
|
261
|
+
return { name, path: finalPath, eventType, tools };
|
|
262
|
+
}
|