1package hooks
2
3import (
4 "encoding/base64"
5 "encoding/json"
6 "strings"
7)
8
9// parseShellEnv parses hook results from environment variables.
10func parseShellEnv(env []string) *HookResult {
11 result := &HookResult{Continue: true}
12
13 for _, line := range env {
14 if !strings.HasPrefix(line, "CRUSH_") {
15 continue
16 }
17
18 key, value, ok := strings.Cut(line, "=")
19 if !ok {
20 continue
21 }
22
23 switch key {
24 case "CRUSH_CONTINUE":
25 result.Continue = value == "true"
26
27 case "CRUSH_PERMISSION":
28 result.Permission = value
29
30 case "CRUSH_MESSAGE":
31 result.Message = value
32
33 case "CRUSH_MODIFIED_PROMPT":
34 result.ModifiedPrompt = &value
35
36 case "CRUSH_CONTEXT_CONTENT":
37 if decoded, err := base64.StdEncoding.DecodeString(value); err == nil {
38 result.ContextContent = string(decoded)
39 } else {
40 result.ContextContent = value
41 }
42
43 case "CRUSH_CONTEXT_FILES":
44 if value != "" {
45 result.ContextFiles = strings.Split(value, ":")
46 }
47
48 case "CRUSH_MODIFIED_INPUT":
49 if value != "" {
50 result.ModifiedInput = parseKeyValuePairs(value)
51 }
52
53 case "CRUSH_MODIFIED_OUTPUT":
54 if value != "" {
55 result.ModifiedOutput = parseKeyValuePairs(value)
56 }
57 }
58 }
59
60 return result
61}
62
63// parseJSONResult parses hook results from JSON output.
64func parseJSONResult(data []byte) (*HookResult, error) {
65 result := &HookResult{Continue: true}
66
67 var raw map[string]any
68 if err := json.Unmarshal(data, &raw); err != nil {
69 return nil, err
70 }
71
72 if v, ok := raw["continue"].(bool); ok {
73 result.Continue = v
74 }
75
76 if v, ok := raw["permission"].(string); ok {
77 result.Permission = v
78 }
79
80 if v, ok := raw["message"].(string); ok {
81 result.Message = v
82 }
83
84 if v, ok := raw["modified_prompt"].(string); ok {
85 result.ModifiedPrompt = &v
86 }
87
88 if v, ok := raw["modified_input"].(map[string]any); ok {
89 result.ModifiedInput = v
90 }
91
92 if v, ok := raw["modified_output"].(map[string]any); ok {
93 result.ModifiedOutput = v
94 }
95
96 if v, ok := raw["context_content"].(string); ok {
97 result.ContextContent = v
98 }
99
100 if v, ok := raw["context_files"].([]any); ok {
101 for _, file := range v {
102 if s, ok := file.(string); ok {
103 result.ContextFiles = append(result.ContextFiles, s)
104 }
105 }
106 }
107
108 return result, nil
109}
110
111// mergeJSONResult merges JSON-parsed result into env-parsed result.
112func mergeJSONResult(base *HookResult, jsonResult *HookResult) {
113 if !jsonResult.Continue {
114 base.Continue = false
115 }
116
117 if jsonResult.Permission != "" {
118 base.Permission = jsonResult.Permission
119 }
120
121 if jsonResult.Message != "" {
122 if base.Message == "" {
123 base.Message = jsonResult.Message
124 } else {
125 base.Message += "; " + jsonResult.Message
126 }
127 }
128
129 if jsonResult.ModifiedPrompt != nil {
130 base.ModifiedPrompt = jsonResult.ModifiedPrompt
131 }
132
133 if len(jsonResult.ModifiedInput) > 0 {
134 if base.ModifiedInput == nil {
135 base.ModifiedInput = make(map[string]any)
136 }
137 for k, v := range jsonResult.ModifiedInput {
138 base.ModifiedInput[k] = v
139 }
140 }
141
142 if len(jsonResult.ModifiedOutput) > 0 {
143 if base.ModifiedOutput == nil {
144 base.ModifiedOutput = make(map[string]any)
145 }
146 for k, v := range jsonResult.ModifiedOutput {
147 base.ModifiedOutput[k] = v
148 }
149 }
150
151 if jsonResult.ContextContent != "" {
152 if base.ContextContent == "" {
153 base.ContextContent = jsonResult.ContextContent
154 } else {
155 base.ContextContent += "\n\n" + jsonResult.ContextContent
156 }
157 }
158
159 base.ContextFiles = append(base.ContextFiles, jsonResult.ContextFiles...)
160}
161
162// parseKeyValuePairs parses "key=value:key2=value2" format into a map.
163// Values are parsed as JSON when possible, otherwise treated as strings.
164func parseKeyValuePairs(encoded string) map[string]any {
165 result := make(map[string]any)
166 pairs := strings.Split(encoded, ":")
167 for _, pair := range pairs {
168 key, value, ok := strings.Cut(pair, "=")
169 if !ok {
170 continue
171 }
172
173 // Try to parse value as JSON to support numbers, booleans, arrays, objects
174 var jsonValue any
175 if err := json.Unmarshal([]byte(value), &jsonValue); err == nil {
176 result[key] = jsonValue
177 } else {
178 // Fall back to string if not valid JSON
179 result[key] = value
180 }
181 }
182 return result
183}