@@ -340,31 +340,65 @@ export async function installCommand(options: InstallOptions): Promise<void> {
340340
341341 divider ( ) ;
342342
343- // Hooks installation selection (two-level: none / full)
344- const { installHooks } = await inquirer . prompt ( [ {
343+ // Hooks installation selection
344+ const RECOMMENDED_HOOKS = [ 'ccw-coordinator-tracker' , 'ccw-coordinator-skill-context' ] ;
345+
346+ const { hookMode } = await inquirer . prompt ( [ {
345347 type : 'list' ,
346- name : 'installHooks ' ,
348+ name : 'hookMode ' ,
347349 message : 'Install Claude Code hooks?' ,
348350 choices : [
349- {
350- name : [
351- `${ chalk . cyan ( 'Full' ) } — install CCW hooks to settings.json (recommended)` ,
352- chalk . gray ( ' ccw-coordinator-tracker Stop — check /ccw chain progress on response end, write bridge' ) ,
353- chalk . gray ( ' ccw-coordinator-skill-context UserPromptSubmit — show active coordinator progress on /ccw input' ) ,
354- ] . join ( '\n' ) ,
355- value : 'full'
356- } ,
357- {
358- name : `${ chalk . gray ( 'None' ) } — skip hooks installation` ,
359- value : 'none'
360- }
351+ { name : `${ chalk . cyan ( 'Recommended' ) } — CCW coordinator hooks (2 hooks)` , value : 'recommended' } ,
352+ { name : `${ chalk . cyan ( 'All' ) } — install all available hooks (22 hooks)` , value : 'all' } ,
353+ { name : `${ chalk . yellow ( 'Custom' ) } — select individual hooks` , value : 'custom' } ,
354+ { name : `${ chalk . gray ( 'None' ) } — skip hooks installation` , value : 'none' } ,
361355 ]
362356 } ] ) ;
363357
358+ let selectedHookIds : string [ ] = [ ] ;
359+ if ( hookMode === 'recommended' ) {
360+ selectedHookIds = [ ...RECOMMENDED_HOOKS ] ;
361+ } else if ( hookMode === 'all' ) {
362+ const { getAllTemplates } = await import ( '../core/hooks/hook-templates.js' ) ;
363+ selectedHookIds = getAllTemplates ( ) . map ( t => t . id ) ;
364+ } else if ( hookMode === 'custom' ) {
365+ const { listTemplatesByCategory } = await import ( '../core/hooks/hook-templates.js' ) ;
366+ const categoryNames : Record < string , string > = {
367+ notification : 'Notification' ,
368+ automation : 'Automation' ,
369+ protection : 'Protection' ,
370+ indexing : 'Indexing' ,
371+ utility : 'Utility' ,
372+ } ;
373+ const byCategory = listTemplatesByCategory ( ) ;
374+ const checklistChoices : Array < { name : string ; value : string ; checked ?: boolean } > = [ ] ;
375+
376+ for ( const [ category , templates ] of Object . entries ( byCategory ) ) {
377+ if ( templates . length === 0 ) continue ;
378+ checklistChoices . push ( { name : `── ${ categoryNames [ category ] || category } ──` , value : `__sep_${ category } ` , checked : false } ) ;
379+ for ( const t of templates ) {
380+ checklistChoices . push ( {
381+ name : ` ${ chalk . yellow ( t . id ) } — ${ t . description } (${ t . trigger } ${ t . matcher ? ` / ${ t . matcher } ` : '' } )` ,
382+ value : t . id ,
383+ checked : RECOMMENDED_HOOKS . includes ( t . id ) ,
384+ } ) ;
385+ }
386+ }
387+
388+ const { selectedHooks } = await inquirer . prompt ( [ {
389+ type : 'checkbox' ,
390+ name : 'selectedHooks' ,
391+ message : 'Select hooks to install:' ,
392+ choices : checklistChoices ,
393+ pageSize : 20 ,
394+ } ] ) ;
395+ selectedHookIds = selectedHooks . filter ( ( id : string ) => ! id . startsWith ( '__sep_' ) ) ;
396+ }
397+
364398 let hooksInstalled : string [ ] = [ ] ;
365- if ( installHooks === 'full' ) {
366- const hookSpinner = createSpinner ( ' Installing Claude Code hooks...' ) . start ( ) ;
367- hooksInstalled = installCcwHooks ( mode === 'Global' ? homedir ( ) : installPath ) ;
399+ if ( selectedHookIds . length > 0 ) {
400+ const hookSpinner = createSpinner ( ` Installing ${ selectedHookIds . length } Claude Code hooks...` ) . start ( ) ;
401+ hooksInstalled = installSelectedHooks ( selectedHookIds , mode === 'Global' ? homedir ( ) : installPath ) ;
368402 hookSpinner . succeed ( `Installed ${ hooksInstalled . length } hooks: ${ hooksInstalled . join ( ', ' ) } ` ) ;
369403 }
370404
@@ -1508,28 +1542,23 @@ export async function installSkillHubCommand(options: SkillHubInstallOptions): P
15081542}
15091543
15101544/**
1511- * Install CCW hooks to settings.json
1545+ * Install selected hooks to settings.json
15121546 *
1513- * Installs a curated set of hooks for CCW coordinator tracking and monitoring.
1514- * Reuses the template system from hook-templates.ts.
1547+ * Installs the specified hook templates to settings.json using the template system.
15151548 *
1549+ * @param templateIds - List of hook template IDs to install
15161550 * @param settingsDir - Directory containing settings.json (e.g. ~/.claude/ or project/.claude/)
15171551 * @returns List of installed hook IDs
15181552 */
1519- function installCcwHooks ( settingsDir : string ) : string [ ] {
1553+ function installSelectedHooks ( templateIds : string [ ] , settingsDir : string ) : string [ ] {
15201554 // Dynamic import to avoid top-level dependency on hook-templates
15211555 // eslint-disable-next-line @typescript-eslint/no-require-imports
15221556 const { installTemplateToSettings } = require ( '../core/hooks/hook-templates.js' ) as typeof import ( '../core/hooks/hook-templates.js' ) ;
15231557
1524- const templates = [
1525- 'ccw-coordinator-tracker' , // PostToolUse — track /ccw and /ccw-coordinator progress
1526- 'ccw-coordinator-skill-context' , // UserPromptSubmit — inject progress hints when invoking /ccw
1527- ] ;
1528-
15291558 const installed : string [ ] = [ ] ;
15301559 const scope = settingsDir === homedir ( ) ? 'global' : 'project' ;
15311560
1532- for ( const templateId of templates ) {
1561+ for ( const templateId of templateIds ) {
15331562 try {
15341563 const result = installTemplateToSettings ( templateId , scope ) ;
15351564 if ( result . success && ! result . message . includes ( 'already' ) ) {
0 commit comments