77 "os"
88 "path/filepath"
99 "slices"
10- "strings"
1110
1211 "github.com/entireio/cli/cmd/entire/cli/agent"
1312 "github.com/entireio/cli/cmd/entire/cli/jsonutil"
@@ -123,13 +122,13 @@ func (c *ClaudeCodeAgent) InstallHooks(ctx context.Context, localDev bool, force
123122 postTaskCmd = "go run ${CLAUDE_PROJECT_DIR}/cmd/entire/main.go hooks claude-code post-task"
124123 postTodoCmd = "go run ${CLAUDE_PROJECT_DIR}/cmd/entire/main.go hooks claude-code post-todo"
125124 } else {
126- sessionStartCmd = "entire hooks claude-code session-start"
127- sessionEndCmd = "entire hooks claude-code session-end"
128- stopCmd = "entire hooks claude-code stop"
129- userPromptSubmitCmd = "entire hooks claude-code user-prompt-submit"
130- preTaskCmd = "entire hooks claude-code pre-task"
131- postTaskCmd = "entire hooks claude-code post-task"
132- postTodoCmd = "entire hooks claude-code post-todo"
125+ sessionStartCmd = agent . WrapProductionJSONWarningHookCommand ( "entire hooks claude-code session-start" , agent . WarningFormatMultiLine )
126+ sessionEndCmd = agent . WrapProductionSilentHookCommand ( "entire hooks claude-code session-end" )
127+ stopCmd = agent . WrapProductionSilentHookCommand ( "entire hooks claude-code stop" )
128+ userPromptSubmitCmd = agent . WrapProductionSilentHookCommand ( "entire hooks claude-code user-prompt-submit" )
129+ preTaskCmd = agent . WrapProductionSilentHookCommand ( "entire hooks claude-code pre-task" )
130+ postTaskCmd = agent . WrapProductionSilentHookCommand ( "entire hooks claude-code post-task" )
131+ postTodoCmd = agent . WrapProductionSilentHookCommand ( "entire hooks claude-code post-todo" )
133132 }
134133
135134 count := 0
@@ -195,14 +194,14 @@ func (c *ClaudeCodeAgent) InstallHooks(ctx context.Context, localDev bool, force
195194 marshalHookType (rawHooks , "PostToolUse" , postToolUse )
196195
197196 // Marshal hooks and update raw settings
198- hooksJSON , err := json . Marshal (rawHooks )
197+ hooksJSON , err := jsonutil . MarshalWithNoHTMLEscape (rawHooks )
199198 if err != nil {
200199 return 0 , fmt .Errorf ("failed to marshal hooks: %w" , err )
201200 }
202201 rawSettings ["hooks" ] = hooksJSON
203202
204203 // Marshal permissions and update raw settings
205- permJSON , err := json . Marshal (rawPermissions )
204+ permJSON , err := jsonutil . MarshalWithNoHTMLEscape (rawPermissions )
206205 if err != nil {
207206 return 0 , fmt .Errorf ("failed to marshal permissions: %w" , err )
208207 }
@@ -241,7 +240,7 @@ func marshalHookType(rawHooks map[string]json.RawMessage, hookType string, match
241240 delete (rawHooks , hookType )
242241 return
243242 }
244- data , err := json . Marshal (matchers )
243+ data , err := jsonutil . MarshalWithNoHTMLEscape (matchers )
245244 if err != nil {
246245 return // Silently ignore marshal errors (shouldn't happen)
247246 }
@@ -336,7 +335,7 @@ func (c *ClaudeCodeAgent) UninstallHooks(ctx context.Context) error {
336335
337336 // If permissions is empty, remove it entirely
338337 if len (rawPermissions ) > 0 {
339- permJSON , err := json . Marshal (rawPermissions )
338+ permJSON , err := jsonutil . MarshalWithNoHTMLEscape (rawPermissions )
340339 if err == nil {
341340 rawSettings ["permissions" ] = permJSON
342341 }
@@ -347,7 +346,7 @@ func (c *ClaudeCodeAgent) UninstallHooks(ctx context.Context) error {
347346
348347 // Marshal hooks back (preserving unknown hook types)
349348 if len (rawHooks ) > 0 {
350- hooksJSON , err := json . Marshal (rawHooks )
349+ hooksJSON , err := jsonutil . MarshalWithNoHTMLEscape (rawHooks )
351350 if err != nil {
352351 return fmt .Errorf ("failed to marshal hooks: %w" , err )
353352 }
@@ -385,14 +384,8 @@ func (c *ClaudeCodeAgent) AreHooksInstalled(ctx context.Context) bool {
385384 return false
386385 }
387386
388- // Check for at least one of our hooks (new or old format)
389- return hookCommandExists (settings .Hooks .Stop , "entire hooks claude-code stop" ) ||
390- hookCommandExists (settings .Hooks .Stop , "go run ${CLAUDE_PROJECT_DIR}/cmd/entire/main.go hooks claude-code stop" ) ||
391- // Backwards compatibility: check for old hook formats
392- hookCommandExists (settings .Hooks .Stop , "entire hooks claudecode stop" ) ||
393- hookCommandExists (settings .Hooks .Stop , "go run ${CLAUDE_PROJECT_DIR}/cmd/entire/main.go hooks claudecode stop" ) ||
394- hookCommandExists (settings .Hooks .Stop , "entire rewind claude-hook --stop" ) ||
395- hookCommandExists (settings .Hooks .Stop , "go run ${CLAUDE_PROJECT_DIR}/cmd/entire/main.go rewind claude-hook --stop" )
387+ // Check for at least one of our hooks (new, wrapped, or legacy format)
388+ return hasEntireHook (settings .Hooks .Stop )
396389}
397390
398391// Helper functions for hook management
@@ -408,6 +401,17 @@ func hookCommandExists(matchers []ClaudeHookMatcher, command string) bool {
408401 return false
409402}
410403
404+ func hasEntireHook (matchers []ClaudeHookMatcher ) bool {
405+ for _ , matcher := range matchers {
406+ for _ , hook := range matcher .Hooks {
407+ if isEntireHook (hook .Command ) {
408+ return true
409+ }
410+ }
411+ }
412+ return false
413+ }
414+
411415func hookCommandExistsWithMatcher (matchers []ClaudeHookMatcher , matcherName , command string ) bool {
412416 for _ , matcher := range matchers {
413417 if matcher .Matcher == matcherName {
@@ -457,12 +461,7 @@ func addHookToMatcher(matchers []ClaudeHookMatcher, matcherName, command string)
457461
458462// isEntireHook checks if a command is an Entire hook (old or new format)
459463func isEntireHook (command string ) bool {
460- for _ , prefix := range entireHookPrefixes {
461- if strings .HasPrefix (command , prefix ) {
462- return true
463- }
464- }
465- return false
464+ return agent .IsManagedHookCommand (command , entireHookPrefixes )
466465}
467466
468467// removeEntireHooks removes all Entire hooks from a list of matchers (for simple hooks like Stop)
0 commit comments