hiiro 0.1.230 → 0.1.232
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/bin/h-pr +75 -0
- 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: a0d1ba68ed577241b03e05bbc748dbc880b0c411547a646bdc7bb9c77733f8c3
|
|
4
|
+
data.tar.gz: e25ca589b0609094596456dbef9f9afcb83963045dea1e2d809a50c227f4d7cd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5c3558fd44e96b1b68ead9f8526f05c7de18e56fd0cd7345c77a3d8c649f6a236a82133aecd94676efd988e90dd192804a33120aca15aca73d1394d3c4043f76
|
|
7
|
+
data.tar.gz: e1ca686b11cdf7f0d795eb0a03a4275e660af825cf30f01a36563586f5843a75e5a4148bba648cf8b7c9bac264bcbac1b2f2dbb79c7324fedc349621f8e17c39
|
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/bin/h-pr
CHANGED
|
@@ -1071,6 +1071,81 @@ Hiiro.run(*ARGV, plugins: [Pins]) do
|
|
|
1071
1071
|
system('gh', 'pr', 'merge', pr_number.to_s, *merge_args)
|
|
1072
1072
|
end
|
|
1073
1073
|
|
|
1074
|
+
add_subcmd(:fix) do |*fix_args|
|
|
1075
|
+
opts = Hiiro::Options.parse(fix_args) do
|
|
1076
|
+
flag(:red, short: :r, desc: 'Fix all PRs with failing checks')
|
|
1077
|
+
flag(:run, short: :R, desc: 'Launch queue tasks immediately after queuing')
|
|
1078
|
+
end
|
|
1079
|
+
|
|
1080
|
+
pinned = pinned_manager.load_pinned
|
|
1081
|
+
|
|
1082
|
+
prs_to_fix = if opts.red
|
|
1083
|
+
failing = pinned.select { |pr| (c = pr['checks']) && c['failed'].to_i > 0 }
|
|
1084
|
+
if failing.empty?
|
|
1085
|
+
puts "No PRs with failing checks"
|
|
1086
|
+
next
|
|
1087
|
+
end
|
|
1088
|
+
failing
|
|
1089
|
+
elsif opts.args.empty?
|
|
1090
|
+
# Default: fuzzy-select from failing PRs, fall back to all tracked
|
|
1091
|
+
failing = pinned.select { |pr| (c = pr['checks']) && c['failed'].to_i > 0 }
|
|
1092
|
+
candidates = failing.empty? ? pinned : failing
|
|
1093
|
+
if candidates.empty?
|
|
1094
|
+
puts "No tracked PRs"
|
|
1095
|
+
next
|
|
1096
|
+
end
|
|
1097
|
+
lines = candidates.each_with_index.each_with_object({}) do |(pr, idx), h|
|
|
1098
|
+
h[pinned_manager.display_pinned(pr, idx)] = pr
|
|
1099
|
+
end
|
|
1100
|
+
selected = fuzzyfind_from_map(lines)
|
|
1101
|
+
next unless selected
|
|
1102
|
+
[selected]
|
|
1103
|
+
else
|
|
1104
|
+
# Resolve each positional arg as a PR ref
|
|
1105
|
+
opts.args.map { |ref|
|
|
1106
|
+
pr_num = resolve_pr.call(ref)
|
|
1107
|
+
next nil unless pr_num
|
|
1108
|
+
pinned.find { |p| p['number'].to_s == pr_num.to_s }
|
|
1109
|
+
}.compact
|
|
1110
|
+
end
|
|
1111
|
+
|
|
1112
|
+
if prs_to_fix.empty?
|
|
1113
|
+
puts "No PRs to fix"
|
|
1114
|
+
next
|
|
1115
|
+
end
|
|
1116
|
+
|
|
1117
|
+
q = Hiiro::Queue.current(self)
|
|
1118
|
+
queued_names = []
|
|
1119
|
+
|
|
1120
|
+
prs_to_fix.each do |pr|
|
|
1121
|
+
task_info = {
|
|
1122
|
+
task_name: pr['task'],
|
|
1123
|
+
tree_name: pr['worktree'],
|
|
1124
|
+
session_name: pr['tmux_session'],
|
|
1125
|
+
}.compact
|
|
1126
|
+
task_info = nil if task_info.empty?
|
|
1127
|
+
|
|
1128
|
+
result = q.add_with_frontmatter("/pr:fix #{pr['number']}", task_info: task_info)
|
|
1129
|
+
if result
|
|
1130
|
+
puts "Queued fix for ##{pr['number']}: #{pr['title']}"
|
|
1131
|
+
queued_names << result[:name]
|
|
1132
|
+
else
|
|
1133
|
+
STDERR.puts "Failed to queue fix for ##{pr['number']}"
|
|
1134
|
+
end
|
|
1135
|
+
end
|
|
1136
|
+
|
|
1137
|
+
unless queued_names.empty?
|
|
1138
|
+
puts
|
|
1139
|
+
puts "Queued #{queued_names.length} fix task(s)."
|
|
1140
|
+
if opts.run
|
|
1141
|
+
puts "Launching..."
|
|
1142
|
+
queued_names.each { |name| q.launch_task(name) }
|
|
1143
|
+
else
|
|
1144
|
+
puts "Run 'h queue run' to start."
|
|
1145
|
+
end
|
|
1146
|
+
end
|
|
1147
|
+
end
|
|
1148
|
+
|
|
1074
1149
|
add_subcmd(:comment) do |ref = nil|
|
|
1075
1150
|
pr_number = resolve_pr.call(ref)
|
|
1076
1151
|
next unless pr_number
|
data/lib/hiiro/version.rb
CHANGED