parser.go

  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}