hiiro 0.1.230 → 0.1.231
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.
- checksums.yaml +4 -4
- data/bin/h-notify +152 -67
- data/lib/hiiro/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3f8ec87844086b0454fd8fbae741e0606702cba54c405a63f4084cd3e55d7fcf
|
|
4
|
+
data.tar.gz: 3a770a7ad8c6c9ffb6abc47c1f1a09869ee61c9d7f712300c1a0ba0e516fab20
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e6fa5471a21551ee74ac7764f040ce6d3874f022afd43831411e5d32236bce24636c6537b819cd0d06ee35b001bee02f5b4b13810a7d7bd99d589d3e0f6ed92b
|
|
7
|
+
data.tar.gz: e1f351cc92d23a2345c9e5f4823af94f9b6d59d83bd937f75e0657990a0ae57f2330cf2da3db56c2a0eca8719192fd0f3d848e38d6badc504693bdfa817ff3e7
|
data/bin/h-notify
CHANGED
|
@@ -13,6 +13,25 @@ TYPE_PRESETS = {
|
|
|
13
13
|
|
|
14
14
|
LOG_FILE = Hiiro::Config.data_path('notify_log.yml')
|
|
15
15
|
|
|
16
|
+
TMUX_CONF = File.join(Dir.home, '.tmux.conf')
|
|
17
|
+
NOTIFY_CONF = File.join(Dir.home, '.config', 'tmux', 'h-notify.tmux.conf')
|
|
18
|
+
CLAUDE_SETTINGS = File.join(Dir.home, '.claude', 'settings.json')
|
|
19
|
+
|
|
20
|
+
NOTIFY_TMUX_HOOKS = %w[after-kill-pane window-unlinked session-closed].freeze
|
|
21
|
+
|
|
22
|
+
NOTIFY_TMUX_CONF_CONTENT = <<~'CONF'
|
|
23
|
+
# --- Notify hooks (clean up notifications when panes/windows/sessions close) ---
|
|
24
|
+
set-hook -g after-kill-pane "run-shell -b 'h notify remove_pane \#{hook_pane_id}'"
|
|
25
|
+
set-hook -g window-unlinked "run-shell -b 'h notify remove_window \#{hook_window_id}'"
|
|
26
|
+
set-hook -g session-closed "run-shell -b 'h notify remove_session \#{hook_session_name}'"
|
|
27
|
+
|
|
28
|
+
# --- Notify keybinding (prefix + N to open notification menu) ---
|
|
29
|
+
bind-key N run-shell "h notify menu"
|
|
30
|
+
CONF
|
|
31
|
+
|
|
32
|
+
CLAUDE_NOTIFICATION_CMD = %q(MSG=$(cat | jq -r '.message // "Needs input"'); h alert -t 'Claude Code' -m "$MSG" -s k -c "echo switchc -t '$TMUX_PANE' | pbcopy"; h notify push -t info "$MSG")
|
|
33
|
+
CLAUDE_STOP_CMD = %q(h alert -t 'Claude Code' -m 'Work completed' -s b -c "echo switchc -t '$TMUX_PANE' | pbcopy"; h notify push -t success 'Work completed')
|
|
34
|
+
|
|
16
35
|
def read_log
|
|
17
36
|
return {} unless File.exist?(LOG_FILE)
|
|
18
37
|
YAML.safe_load(File.read(LOG_FILE)) || {}
|
|
@@ -26,6 +45,16 @@ def write_log(data)
|
|
|
26
45
|
File.write(LOG_FILE, data.to_yaml)
|
|
27
46
|
end
|
|
28
47
|
|
|
48
|
+
def current_pane
|
|
49
|
+
# Prefer $TMUX_PANE env var — it's reliably set even in hook subprocesses
|
|
50
|
+
env_id = ENV['TMUX_PANE'].to_s
|
|
51
|
+
if !env_id.empty?
|
|
52
|
+
Hiiro::Tmux::Panes.fetch(all: true).find_by_id(env_id)
|
|
53
|
+
else
|
|
54
|
+
Hiiro::Tmux::Pane.current
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
29
58
|
def pane_exists?(pane_id)
|
|
30
59
|
Hiiro::Tmux::Panes.fetch(all: true).find_by_id(pane_id)
|
|
31
60
|
end
|
|
@@ -44,7 +73,7 @@ Hiiro.run(*ARGV, tasks: true) do
|
|
|
44
73
|
next
|
|
45
74
|
end
|
|
46
75
|
|
|
47
|
-
pane =
|
|
76
|
+
pane = current_pane
|
|
48
77
|
session = pane&.session_name || Hiiro::Tmux::Session.current&.name
|
|
49
78
|
|
|
50
79
|
unless pane && session
|
|
@@ -64,7 +93,7 @@ Hiiro.run(*ARGV, tasks: true) do
|
|
|
64
93
|
|
|
65
94
|
data = read_log
|
|
66
95
|
data[session] ||= []
|
|
67
|
-
# One entry per pane — replace existing
|
|
96
|
+
# One entry per pane — replace existing, newest first
|
|
68
97
|
data[session].reject! { |e| e['pane_id'] == pane.id }
|
|
69
98
|
data[session].unshift(entry)
|
|
70
99
|
write_log(data)
|
|
@@ -167,7 +196,7 @@ Hiiro.run(*ARGV, tasks: true) do
|
|
|
167
196
|
tmux_client.display_message('Notifications cleared')
|
|
168
197
|
end
|
|
169
198
|
|
|
170
|
-
# Called by tmux hook: after-kill-pane
|
|
199
|
+
# Called by tmux hook: after-kill-pane
|
|
171
200
|
add_subcmd(:remove_pane) do |pane_id = nil|
|
|
172
201
|
next unless pane_id
|
|
173
202
|
data = read_log
|
|
@@ -175,7 +204,7 @@ Hiiro.run(*ARGV, tasks: true) do
|
|
|
175
204
|
write_log(data)
|
|
176
205
|
end
|
|
177
206
|
|
|
178
|
-
# Called by tmux hook: window-
|
|
207
|
+
# Called by tmux hook: window-unlinked
|
|
179
208
|
add_subcmd(:remove_window) do |window_id = nil|
|
|
180
209
|
next unless window_id
|
|
181
210
|
data = read_log
|
|
@@ -191,78 +220,134 @@ Hiiro.run(*ARGV, tasks: true) do
|
|
|
191
220
|
write_log(data)
|
|
192
221
|
end
|
|
193
222
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
notify_conf = <<~'CONF'
|
|
203
|
-
# --- Notify hooks (clean up notifications when panes/windows/sessions close) ---
|
|
204
|
-
set-hook -g after-kill-pane "run-shell -b 'h notify remove_pane \#{hook_pane_id}'"
|
|
205
|
-
set-hook -g window-unlinked "run-shell -b 'h notify remove_window \#{hook_window_id}'"
|
|
206
|
-
set-hook -g session-closed "run-shell -b 'h notify remove_session \#{hook_session_name}'"
|
|
207
|
-
|
|
208
|
-
# --- Notify keybinding (prefix + N to open notification menu) ---
|
|
209
|
-
bind-key N run-shell "h notify menu"
|
|
210
|
-
CONF
|
|
211
|
-
|
|
212
|
-
File.write(conf_file, notify_conf)
|
|
213
|
-
puts "Wrote #{conf_file}"
|
|
214
|
-
|
|
215
|
-
if File.exist?(tmux_conf)
|
|
216
|
-
existing = File.read(tmux_conf)
|
|
217
|
-
if existing.include?(source_line)
|
|
218
|
-
puts "#{tmux_conf} already sources #{conf_file}"
|
|
219
|
-
else
|
|
220
|
-
File.open(tmux_conf, 'a') { |f| f.puts; f.puts source_line }
|
|
221
|
-
puts "Added '#{source_line}' to #{tmux_conf}"
|
|
223
|
+
# --- h notify tmux ---
|
|
224
|
+
|
|
225
|
+
add_subcmd(:tmux) do
|
|
226
|
+
make_child {
|
|
227
|
+
add_subcmd(:setup) do
|
|
228
|
+
FileUtils.mkdir_p(File.dirname(NOTIFY_CONF))
|
|
229
|
+
File.write(NOTIFY_CONF, NOTIFY_TMUX_CONF_CONTENT)
|
|
230
|
+
puts "Wrote #{NOTIFY_CONF}"
|
|
222
231
|
end
|
|
223
|
-
else
|
|
224
|
-
File.write(tmux_conf, source_line + "\n")
|
|
225
|
-
puts "Created #{tmux_conf} with source line"
|
|
226
|
-
end
|
|
227
232
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
233
|
+
add_subcmd(:add_hooks) do
|
|
234
|
+
source_line = "source-file #{NOTIFY_CONF}"
|
|
235
|
+
|
|
236
|
+
if File.exist?(TMUX_CONF)
|
|
237
|
+
existing = File.read(TMUX_CONF)
|
|
238
|
+
if existing.include?(source_line)
|
|
239
|
+
puts "#{TMUX_CONF} already sources #{NOTIFY_CONF}"
|
|
240
|
+
else
|
|
241
|
+
File.open(TMUX_CONF, 'a') { |f| f.puts; f.puts source_line }
|
|
242
|
+
puts "Added '#{source_line}' to #{TMUX_CONF}"
|
|
243
|
+
end
|
|
244
|
+
else
|
|
245
|
+
File.write(TMUX_CONF, source_line + "\n")
|
|
246
|
+
puts "Created #{TMUX_CONF} with source line"
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
system('tmux', 'source-file', TMUX_CONF)
|
|
250
|
+
puts "Reloaded #{TMUX_CONF}"
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
add_subcmd(:reset_hooks) do
|
|
254
|
+
NOTIFY_TMUX_HOOKS.each do |hook|
|
|
255
|
+
system('tmux', 'set-hook', '-gu', hook)
|
|
256
|
+
puts "Unset tmux hook: #{hook}"
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
add_subcmd(:load_hooks) do
|
|
261
|
+
system('tmux', 'source-file', TMUX_CONF)
|
|
262
|
+
puts "Sourced #{TMUX_CONF}"
|
|
263
|
+
end
|
|
264
|
+
}.run
|
|
243
265
|
end
|
|
244
266
|
|
|
245
|
-
|
|
246
|
-
settings_file = File.join(Dir.home, '.claude', 'settings.json')
|
|
267
|
+
# --- h notify claude ---
|
|
247
268
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
269
|
+
add_subcmd(:claude) do
|
|
270
|
+
require 'json'
|
|
271
|
+
|
|
272
|
+
read_claude_settings = lambda do
|
|
273
|
+
return {} unless File.exist?(CLAUDE_SETTINGS)
|
|
274
|
+
JSON.parse(File.read(CLAUDE_SETTINGS))
|
|
275
|
+
rescue JSON::ParserError
|
|
276
|
+
{}
|
|
251
277
|
end
|
|
252
278
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
279
|
+
write_claude_settings = lambda do |settings|
|
|
280
|
+
File.write(CLAUDE_SETTINGS, JSON.pretty_generate(settings))
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
make_child {
|
|
284
|
+
add_subcmd(:setup) do
|
|
285
|
+
settings = read_claude_settings.call
|
|
286
|
+
settings['hooks'] ||= {}
|
|
287
|
+
settings['hooks']['Notification'] = [{ 'hooks' => [{ 'type' => 'command', 'command' => CLAUDE_NOTIFICATION_CMD }] }]
|
|
288
|
+
settings['hooks']['Stop'] = [{ 'hooks' => [{ 'type' => 'command', 'command' => CLAUDE_STOP_CMD }] }]
|
|
289
|
+
write_claude_settings.call(settings)
|
|
290
|
+
puts "Updated #{CLAUDE_SETTINGS}"
|
|
291
|
+
puts " Notification -> h alert + h notify push -t info"
|
|
292
|
+
puts " Stop -> h alert + h notify push -t success"
|
|
293
|
+
end
|
|
256
294
|
|
|
257
|
-
|
|
258
|
-
|
|
295
|
+
add_subcmd(:add_hooks) do
|
|
296
|
+
settings = read_claude_settings.call
|
|
297
|
+
unless File.exist?(CLAUDE_SETTINGS)
|
|
298
|
+
puts "#{CLAUDE_SETTINGS} not found"
|
|
299
|
+
next
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
settings['hooks'] ||= {}
|
|
303
|
+
|
|
304
|
+
# Inject h notify push into existing commands if not already present,
|
|
305
|
+
# otherwise write fresh hooks
|
|
306
|
+
%w[Notification Stop].each do |event|
|
|
307
|
+
existing_hooks = settings.dig('hooks', event, 0, 'hooks') || []
|
|
308
|
+
if existing_hooks.any? { |h| h['command']&.include?('h notify push') }
|
|
309
|
+
puts "#{event} hook already includes h notify push — skipping"
|
|
310
|
+
next
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
cmd = event == 'Notification' ? CLAUDE_NOTIFICATION_CMD : CLAUDE_STOP_CMD
|
|
314
|
+
settings['hooks'][event] = [{ 'hooks' => [{ 'type' => 'command', 'command' => cmd }] }]
|
|
315
|
+
puts "Set #{event} hook -> h alert + h notify push"
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
write_claude_settings.call(settings)
|
|
319
|
+
end
|
|
259
320
|
|
|
260
|
-
|
|
261
|
-
|
|
321
|
+
add_subcmd(:reset_hooks) do
|
|
322
|
+
unless File.exist?(CLAUDE_SETTINGS)
|
|
323
|
+
puts "#{CLAUDE_SETTINGS} not found"
|
|
324
|
+
next
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
settings = read_claude_settings.call
|
|
328
|
+
settings['hooks'] ||= {}
|
|
329
|
+
|
|
330
|
+
%w[Notification Stop].each do |event|
|
|
331
|
+
hooks = settings.dig('hooks', event, 0, 'hooks') || []
|
|
332
|
+
hooks.each do |h|
|
|
333
|
+
next unless h['command']&.include?('h notify push')
|
|
334
|
+
# Strip the h notify push portion from the command
|
|
335
|
+
h['command'] = h['command']
|
|
336
|
+
.split(';')
|
|
337
|
+
.reject { |part| part.strip.start_with?('h notify push') }
|
|
338
|
+
.join(';')
|
|
339
|
+
.strip
|
|
340
|
+
end
|
|
341
|
+
puts "Stripped h notify push from #{event} hook"
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
write_claude_settings.call(settings)
|
|
345
|
+
end
|
|
262
346
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
347
|
+
add_subcmd(:load_hooks) do
|
|
348
|
+
puts "Claude Code settings are loaded automatically on startup."
|
|
349
|
+
puts "Restart claude to pick up changes to #{CLAUDE_SETTINGS}"
|
|
350
|
+
end
|
|
351
|
+
}.run
|
|
267
352
|
end
|
|
268
353
|
end
|
data/lib/hiiro/version.rb
CHANGED