aircana 4.0.0.rc2 → 4.0.0.rc3

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.
@@ -1,276 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "json"
4
- require "tty-prompt"
5
- require_relative "../../generators/hooks_generator"
6
- require_relative "init"
7
-
8
- module Aircana
9
- module CLI
10
- module Hooks
11
- class << self
12
- def list
13
- available_hooks = Aircana::Generators::HooksGenerator.all_available_hooks
14
- installed_hooks_list = installed_hooks
15
-
16
- if available_hooks.empty?
17
- Aircana.human_logger.info "No hooks available."
18
- return
19
- end
20
-
21
- Aircana.human_logger.info "Available Hooks:"
22
- available_hooks.each do |hook_name|
23
- status = installed_hooks_list.include?(hook_name) ? "[INSTALLED]" : "[AVAILABLE]"
24
- description = hook_description(hook_name)
25
- is_default = Aircana::Generators::HooksGenerator.available_default_hooks.include?(hook_name)
26
- default_marker = is_default ? " (default)" : ""
27
- Aircana.human_logger.info " #{status} #{hook_name} - #{description}#{default_marker}"
28
- end
29
- end
30
-
31
- def enable(hook_name)
32
- unless Aircana::Generators::HooksGenerator.all_available_hooks.include?(hook_name)
33
- Aircana.human_logger.error "Hook '#{hook_name}' is not available."
34
- available_hooks_list = Aircana::Generators::HooksGenerator.all_available_hooks.join(", ")
35
- Aircana.human_logger.info "Available hooks: #{available_hooks_list}"
36
- return
37
- end
38
-
39
- # Generate the hook if it doesn't exist
40
- Aircana::Generators::HooksGenerator.create_default_hook(hook_name)
41
-
42
- # Install hooks to Claude settings
43
- Init.run
44
-
45
- Aircana.human_logger.success "Hook '#{hook_name}' has been enabled."
46
- end
47
-
48
- def disable(hook_name)
49
- hook_file = File.join(Aircana.configuration.hooks_dir, "#{hook_name}.sh")
50
-
51
- unless File.exist?(hook_file)
52
- Aircana.human_logger.warn "Hook '#{hook_name}' is not currently enabled."
53
- return
54
- end
55
-
56
- File.delete(hook_file)
57
-
58
- # Reinstall remaining hooks to update Claude settings
59
- Init.run
60
-
61
- Aircana.human_logger.success "Hook '#{hook_name}' has been disabled."
62
- end
63
-
64
- def create
65
- prompt = TTY::Prompt.new
66
-
67
- hook_name = prompt.ask("Hook name (lowercase, no spaces):")
68
- hook_name = hook_name.strip.downcase.gsub(" ", "_")
69
-
70
- hook_event = prompt.select("Select hook event:", %w[
71
- pre_tool_use
72
- post_tool_use
73
- user_prompt_submit
74
- session_start
75
- ])
76
-
77
- description = prompt.ask("Brief description of what this hook does:")
78
-
79
- # Ensure custom hook names include the event type for proper mapping
80
- # unless the name already contains it
81
- hook_name = "#{hook_name}_#{hook_event}" unless hook_name.include?(hook_event.gsub("_", ""))
82
-
83
- create_custom_hook(hook_name, hook_event, description)
84
- end
85
-
86
- def status
87
- settings_file = File.join(Aircana.configuration.claude_code_project_config_path, "settings.local.json")
88
-
89
- unless File.exist?(settings_file)
90
- Aircana.human_logger.info "No Claude settings file found at #{settings_file}"
91
- return
92
- end
93
-
94
- begin
95
- settings = JSON.parse(File.read(settings_file))
96
- hooks_config = settings["hooks"]
97
-
98
- if hooks_config.nil? || hooks_config.empty?
99
- Aircana.human_logger.info "No hooks configured in Claude settings."
100
- else
101
- Aircana.human_logger.info "Configured hooks in Claude settings:"
102
- hooks_config.each do |event, configs|
103
- configs = [configs] unless configs.is_a?(Array)
104
- configs.each do |config|
105
- next unless config["hooks"] && config["hooks"][0] && config["hooks"][0]["command"]
106
-
107
- command_path = config["hooks"][0]["command"]
108
- script_name = File.basename(command_path, ".sh")
109
- matcher_info = config["matcher"] ? " (matcher: #{config["matcher"]})" : ""
110
- Aircana.human_logger.info " #{event}: #{script_name}#{matcher_info}"
111
- end
112
- end
113
- end
114
- rescue JSON::ParserError => e
115
- Aircana.human_logger.error "Invalid JSON in settings file: #{e.message}"
116
- end
117
- end
118
-
119
- private
120
-
121
- def installed_hooks
122
- return [] unless Dir.exist?(Aircana.configuration.hooks_dir)
123
-
124
- Dir.glob("#{Aircana.configuration.hooks_dir}/*.sh").map do |file|
125
- File.basename(file, ".sh")
126
- end
127
- end
128
-
129
- def hook_description(hook_name)
130
- descriptions = {
131
- "pre_tool_use" => "General pre-tool validation hook",
132
- "post_tool_use" => "General post-tool processing hook",
133
- "user_prompt_submit" => "Add context to user prompts",
134
- "session_start" => "Initialize session with project context",
135
- "rubocop_pre_commit" => "Run RuboCop before git commits",
136
- "rspec_test" => "Run RSpec tests when Ruby files are modified",
137
- "bundle_install" => "Run bundle install when Gemfile changes"
138
- }
139
- descriptions[hook_name] || "Custom hook"
140
- end
141
-
142
- def create_custom_hook(hook_name, hook_event, description)
143
- template_content = generate_custom_hook_template(hook_event, description)
144
-
145
- hook_file = File.join(Aircana.configuration.hooks_dir, "#{hook_name}.sh")
146
- Aircana.create_dir_if_needed(File.dirname(hook_file))
147
-
148
- File.write(hook_file, template_content)
149
- File.chmod(0o755, hook_file)
150
-
151
- Aircana.human_logger.success "Custom hook created at #{hook_file}"
152
- Aircana.human_logger.info "You may need to customize the hook script for your specific needs."
153
-
154
- # Install hooks to Claude settings
155
- Init.run
156
- Aircana.human_logger.success "Hook installed to Claude settings"
157
-
158
- # Optionally offer to open in editor
159
- prompt = TTY::Prompt.new
160
- return unless prompt.yes?("Would you like to edit the hook file now?")
161
-
162
- open_file_in_editor(hook_file)
163
- end
164
-
165
- def generate_custom_hook_template(hook_event, description)
166
- case hook_event
167
- when "pre_tool_use"
168
- generate_pre_tool_use_template(description)
169
- when "post_tool_use"
170
- generate_post_tool_use_template(description)
171
- when "user_prompt_submit"
172
- generate_user_prompt_submit_template(description)
173
- when "session_start"
174
- generate_session_start_template(description)
175
- else
176
- generate_basic_template(hook_event, description)
177
- end
178
- end
179
-
180
- def generate_pre_tool_use_template(description)
181
- <<~SCRIPT
182
- #!/bin/bash
183
- # Custom pre-tool-use hook: #{description}
184
-
185
- TOOL_NAME="$1"
186
- TOOL_PARAMS="$2"
187
-
188
- # Add your custom validation logic here
189
- # Return exit code 0 to allow, exit code 1 to deny
190
-
191
- echo "Pre-tool validation: $TOOL_NAME"
192
-
193
- # Allow by default
194
- exit 0
195
- SCRIPT
196
- end
197
-
198
- def generate_post_tool_use_template(description)
199
- <<~SCRIPT
200
- #!/bin/bash
201
- # Custom post-tool-use hook: #{description}
202
-
203
- TOOL_NAME="$1"
204
- TOOL_PARAMS="$2"
205
- TOOL_RESULT="$3"
206
- EXIT_CODE="$4"
207
-
208
- # Add your custom post-processing logic here
209
-
210
- echo "Post-tool processing: $TOOL_NAME (exit code: $EXIT_CODE)"
211
-
212
- # Always allow result to proceed
213
- exit 0
214
- SCRIPT
215
- end
216
-
217
- def generate_user_prompt_submit_template(description)
218
- <<~SCRIPT
219
- #!/bin/bash
220
- # Custom user prompt submit hook: #{description}
221
-
222
- USER_PROMPT="$1"
223
-
224
- # Add custom context or modify the prompt here
225
- # Output JSON for advanced control or simple exit code
226
-
227
- echo "Processing user prompt"
228
-
229
- # Simple allow - no modifications
230
- exit 0
231
- SCRIPT
232
- end
233
-
234
- def generate_session_start_template(description)
235
- <<~SCRIPT
236
- #!/bin/bash
237
- # Custom session start hook: #{description}
238
-
239
- # Add session initialization logic here
240
-
241
- echo "Session started in $(pwd)"
242
-
243
- # Simple allow
244
- exit 0
245
- SCRIPT
246
- end
247
-
248
- def generate_basic_template(hook_event, description)
249
- <<~SCRIPT
250
- #!/bin/bash
251
- # Custom #{hook_event} hook: #{description}
252
-
253
- # Add your custom hook logic here
254
-
255
- exit 0
256
- SCRIPT
257
- end
258
-
259
- def open_file_in_editor(file_path)
260
- editor = ENV["EDITOR"] || find_available_editor
261
-
262
- if editor
263
- Aircana.human_logger.info "Opening #{file_path} in #{editor}..."
264
- system("#{editor} '#{file_path}'")
265
- else
266
- Aircana.human_logger.warn "No editor found. Please edit #{file_path} manually."
267
- end
268
- end
269
-
270
- def find_available_editor
271
- %w[code subl atom nano vim vi].find { |cmd| system("which #{cmd} > /dev/null 2>&1") }
272
- end
273
- end
274
- end
275
- end
276
- end