hiiro 0.1.228 → 0.1.229
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-jumplist +2 -0
- data/bin/h-notify +190 -71
- data/bin/h-pr +2 -2
- 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: aba86cbd556dd278f47a67b4ca0fbc31b24babdab3986d07e1fca7b48a1401bf
|
|
4
|
+
data.tar.gz: 4162f95dbf01a01c0013c21ddda78a20a40c0a1933d66b3fd808fc21aac64b3c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 91f6d01eec5b29c6cefc09535549fcf8faf2b392e8598746d6491782ea5a88cf346b35c1b0ca216ec6971217108294dba8b5d56ebed79f112811fb6eb6e09d77
|
|
7
|
+
data.tar.gz: 51d183885dc6000e77ea159a23b5698d4f5af74b74643976d6eaf82e9d42a6257adfdaffc3f7c2d73d8ad4ec22e85749d2687282bfcb9eddd126d50af78a367c
|
data/bin/h-jumplist
CHANGED
|
@@ -76,10 +76,12 @@ Hiiro.run(*ARGV) do
|
|
|
76
76
|
jumplist_conf = <<~CONF
|
|
77
77
|
# --- Jumplist hooks (record pane/window/session navigation) ---
|
|
78
78
|
set-hook -g after-select-pane "run-shell -b 'h jumplist record'"
|
|
79
|
+
set-hook -g pane-focus-in "run-shell -b 'h jumplist record'"
|
|
79
80
|
set-hook -g after-select-window "run-shell -b 'h jumplist record'"
|
|
80
81
|
set-hook -g after-new-window "run-shell -b 'h jumplist record'"
|
|
81
82
|
set-hook -g after-split-window "run-shell -b 'h jumplist record'"
|
|
82
83
|
set-hook -g client-session-changed "run-shell -b 'h jumplist record'"
|
|
84
|
+
set-hook -g session-window-changed "run-shell -b 'h jumplist record'"
|
|
83
85
|
|
|
84
86
|
# --- Jumplist keybindings ---
|
|
85
87
|
bind-key -r C-b run-shell "h jumplist back"
|
data/bin/h-notify
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "hiiro"
|
|
4
4
|
require "fileutils"
|
|
5
|
+
require "yaml"
|
|
5
6
|
|
|
6
7
|
TYPE_PRESETS = {
|
|
7
8
|
'success' => { prefix: '[OK]', sound: 'Glass', title: 'Success' },
|
|
@@ -10,100 +11,117 @@ TYPE_PRESETS = {
|
|
|
10
11
|
'warning' => { prefix: '[WARN]', sound: 'Purr', title: 'Warning' },
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
FileUtils.mkdir_p(NOTIFY_DIR)
|
|
14
|
+
LOG_FILE = Hiiro::Config.data_path('notify_log.yml')
|
|
15
15
|
|
|
16
|
-
def
|
|
17
|
-
|
|
16
|
+
def read_log
|
|
17
|
+
return {} unless File.exist?(LOG_FILE)
|
|
18
|
+
YAML.safe_load(File.read(LOG_FILE)) || {}
|
|
19
|
+
rescue
|
|
20
|
+
{}
|
|
18
21
|
end
|
|
19
22
|
|
|
20
|
-
def
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def read_notifications(session = nil)
|
|
26
|
-
file = notify_file(session)
|
|
27
|
-
return [] unless File.exist?(file)
|
|
28
|
-
File.readlines(file, chomp: true).reject(&:empty?)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def write_notifications(entries, session = nil)
|
|
32
|
-
file = notify_file(session)
|
|
33
|
-
if entries.empty?
|
|
34
|
-
File.delete(file) if File.exist?(file)
|
|
35
|
-
else
|
|
36
|
-
File.write(file, entries.join("\n") + "\n")
|
|
37
|
-
end
|
|
23
|
+
def write_log(data)
|
|
24
|
+
FileUtils.mkdir_p(File.dirname(LOG_FILE))
|
|
25
|
+
data.reject! { |_, entries| entries.nil? || entries.empty? }
|
|
26
|
+
File.write(LOG_FILE, data.to_yaml)
|
|
38
27
|
end
|
|
39
28
|
|
|
40
29
|
def pane_exists?(pane_id)
|
|
41
|
-
|
|
42
|
-
panes.include?(pane_id)
|
|
30
|
+
Hiiro::Tmux::Panes.fetch(all: true).find_by_id(pane_id)
|
|
43
31
|
end
|
|
44
32
|
|
|
45
|
-
Hiiro.run(*ARGV) do
|
|
33
|
+
Hiiro.run(*ARGV, tasks: true) do
|
|
46
34
|
@opts = Hiiro::Options.parse(args) do
|
|
47
35
|
option(:type, short: 't', desc: 'Notification type (success, error, info, warning)', default: 'info')
|
|
48
36
|
end
|
|
49
37
|
|
|
50
38
|
@preset = TYPE_PRESETS[@opts.type] || TYPE_PRESETS['info']
|
|
51
39
|
|
|
52
|
-
add_subcmd(:push) do
|
|
53
|
-
message =
|
|
40
|
+
add_subcmd(:push) do
|
|
41
|
+
message = @opts.args.join(' ')
|
|
54
42
|
if message.empty?
|
|
55
43
|
puts "Usage: h notify push [-t TYPE] \"message\""
|
|
56
44
|
next
|
|
57
45
|
end
|
|
58
46
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
session = session_name
|
|
62
|
-
pane_cmd = `tmux display-message -p '\#{pane_current_command}'`.strip
|
|
63
|
-
timestamp = Time.now.to_i.to_s
|
|
47
|
+
pane = Hiiro::Tmux::Pane.current
|
|
48
|
+
session = pane&.session_name || Hiiro::Tmux::Session.current&.name
|
|
64
49
|
|
|
65
|
-
|
|
50
|
+
unless pane && session
|
|
51
|
+
$stderr.puts "h notify push: not in a tmux session"
|
|
52
|
+
next
|
|
53
|
+
end
|
|
66
54
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
55
|
+
entry = {
|
|
56
|
+
'pane_id' => pane.id,
|
|
57
|
+
'window_id' => pane.window_id,
|
|
58
|
+
'session' => session,
|
|
59
|
+
'timestamp' => Time.now.to_i,
|
|
60
|
+
'pane_cmd' => pane.command,
|
|
61
|
+
'message' => message,
|
|
62
|
+
'type' => @opts.type,
|
|
63
|
+
}
|
|
70
64
|
|
|
71
|
-
|
|
72
|
-
|
|
65
|
+
data = read_log
|
|
66
|
+
data[session] ||= []
|
|
67
|
+
# One entry per pane — replace existing entry for this pane, newest first
|
|
68
|
+
data[session].reject! { |e| e['pane_id'] == pane.id }
|
|
69
|
+
data[session].unshift(entry)
|
|
70
|
+
write_log(data)
|
|
73
71
|
|
|
74
|
-
#
|
|
72
|
+
tmux_client.display_message("#{@preset[:prefix]} #{message}")
|
|
75
73
|
system('terminal-notifier',
|
|
76
74
|
'-title', @preset[:title],
|
|
77
75
|
'-message', message,
|
|
78
76
|
'-sound', @preset[:sound])
|
|
79
77
|
end
|
|
80
78
|
|
|
79
|
+
add_subcmd(:ls) do
|
|
80
|
+
ls_opts = Hiiro::Options.parse(@opts.args) do
|
|
81
|
+
flag(:all, short: 'a', desc: 'Show all sessions')
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
data = read_log
|
|
85
|
+
sessions = ls_opts.all ? data.keys : [Hiiro::Tmux::Session.current&.name].compact
|
|
86
|
+
|
|
87
|
+
any = false
|
|
88
|
+
sessions.each do |sess|
|
|
89
|
+
entries = data[sess] || []
|
|
90
|
+
next if entries.empty?
|
|
91
|
+
|
|
92
|
+
puts "=== #{sess} ===" if ls_opts.all
|
|
93
|
+
entries.each_with_index do |e, i|
|
|
94
|
+
time_str = Time.at(e['timestamp']).strftime('%H:%M:%S')
|
|
95
|
+
prefix = TYPE_PRESETS[e['type']]&.dig(:prefix) || '[INFO]'
|
|
96
|
+
puts "%3d) %s [%s] %s: %s (%s)" % [i, prefix, e['pane_id'], e['pane_cmd'], e['message'], time_str]
|
|
97
|
+
end
|
|
98
|
+
any = true
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
puts "No notifications." unless any
|
|
102
|
+
end
|
|
103
|
+
|
|
81
104
|
add_subcmd(:menu) do
|
|
82
|
-
session =
|
|
83
|
-
|
|
105
|
+
session = Hiiro::Tmux::Session.current&.name
|
|
106
|
+
data = read_log
|
|
107
|
+
entries = data[session] || []
|
|
84
108
|
|
|
85
109
|
if entries.empty?
|
|
86
|
-
|
|
110
|
+
tmux_client.display_message('No notifications')
|
|
87
111
|
next
|
|
88
112
|
end
|
|
89
113
|
|
|
90
|
-
# Build display-menu arguments
|
|
91
114
|
menu_args = ['-T', 'Notifications']
|
|
92
115
|
|
|
93
|
-
entries.first(10).each_with_index do |
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
msg = msg[0..46] + '...' if msg.length > 50
|
|
99
|
-
|
|
100
|
-
label = "#{idx}: [#{pane_id}] #{cmd}: #{msg}"
|
|
116
|
+
entries.first(10).each_with_index do |e, idx|
|
|
117
|
+
msg = e['message']
|
|
118
|
+
msg = msg[0..46] + '...' if msg.length > 50
|
|
119
|
+
prefix = TYPE_PRESETS[e['type']]&.dig(:prefix) || '[INFO]'
|
|
120
|
+
label = "#{idx}: #{prefix} [#{e['pane_id']}] #{e['pane_cmd']}: #{msg}"
|
|
101
121
|
menu_args.push(label, idx.to_s, "run-shell 'h notify jump #{idx}'")
|
|
102
122
|
end
|
|
103
123
|
|
|
104
|
-
# Separator and clear option
|
|
105
124
|
menu_args.push('', '', 'Clear all', 'c', "run-shell 'h notify clear'")
|
|
106
|
-
|
|
107
125
|
system('tmux', 'display-menu', *menu_args)
|
|
108
126
|
end
|
|
109
127
|
|
|
@@ -113,39 +131,140 @@ Hiiro.run(*ARGV) do
|
|
|
113
131
|
next
|
|
114
132
|
end
|
|
115
133
|
|
|
116
|
-
index
|
|
117
|
-
session =
|
|
118
|
-
|
|
134
|
+
index = index_str.to_i
|
|
135
|
+
session = Hiiro::Tmux::Session.current&.name
|
|
136
|
+
data = read_log
|
|
137
|
+
entries = data[session] || []
|
|
119
138
|
|
|
120
139
|
if index >= entries.length
|
|
121
|
-
|
|
140
|
+
tmux_client.display_message('Notification not found')
|
|
122
141
|
next
|
|
123
142
|
end
|
|
124
143
|
|
|
125
144
|
entry = entries[index]
|
|
126
|
-
parts = entry.split('|')
|
|
127
|
-
target_pane = parts[0]
|
|
128
|
-
target_window = parts[1]
|
|
129
145
|
|
|
130
|
-
unless pane_exists?(
|
|
131
|
-
|
|
146
|
+
unless pane_exists?(entry['pane_id'])
|
|
147
|
+
tmux_client.display_message("Pane #{entry['pane_id']} no longer exists")
|
|
132
148
|
entries.delete_at(index)
|
|
133
|
-
|
|
149
|
+
data[session] = entries
|
|
150
|
+
write_log(data)
|
|
134
151
|
next
|
|
135
152
|
end
|
|
136
153
|
|
|
137
|
-
|
|
138
|
-
|
|
154
|
+
tmux_client.select_window(entry['window_id'])
|
|
155
|
+
tmux_client.select_pane(entry['pane_id'])
|
|
139
156
|
|
|
140
|
-
# Remove the notification
|
|
141
157
|
entries.delete_at(index)
|
|
142
|
-
|
|
158
|
+
data[session] = entries
|
|
159
|
+
write_log(data)
|
|
143
160
|
end
|
|
144
161
|
|
|
145
162
|
add_subcmd(:clear) do
|
|
146
|
-
session =
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
163
|
+
session = Hiiro::Tmux::Session.current&.name
|
|
164
|
+
data = read_log
|
|
165
|
+
data.delete(session)
|
|
166
|
+
write_log(data)
|
|
167
|
+
tmux_client.display_message('Notifications cleared')
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Called by tmux hook: after-kill-pane / pane-died
|
|
171
|
+
add_subcmd(:remove_pane) do |pane_id = nil|
|
|
172
|
+
next unless pane_id
|
|
173
|
+
data = read_log
|
|
174
|
+
data.each_value { |entries| entries.reject! { |e| e['pane_id'] == pane_id } }
|
|
175
|
+
write_log(data)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Called by tmux hook: window-closed
|
|
179
|
+
add_subcmd(:remove_window) do |window_id = nil|
|
|
180
|
+
next unless window_id
|
|
181
|
+
data = read_log
|
|
182
|
+
data.each_value { |entries| entries.reject! { |e| e['window_id'] == window_id } }
|
|
183
|
+
write_log(data)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Called by tmux hook: session-closed
|
|
187
|
+
add_subcmd(:remove_session) do |sess = nil|
|
|
188
|
+
next unless sess
|
|
189
|
+
data = read_log
|
|
190
|
+
data.delete(sess)
|
|
191
|
+
write_log(data)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
add_subcmd(:setup) do
|
|
195
|
+
conf_dir = File.join(Dir.home, '.config', 'tmux')
|
|
196
|
+
conf_file = File.join(conf_dir, 'h-notify.tmux.conf')
|
|
197
|
+
tmux_conf = File.join(Dir.home, '.tmux.conf')
|
|
198
|
+
source_line = "source-file #{conf_file}"
|
|
199
|
+
|
|
200
|
+
FileUtils.mkdir_p(conf_dir)
|
|
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 pane-died "run-shell -b 'h notify remove_pane \#{pane_id}'"
|
|
206
|
+
set-hook -g pane-exited "run-shell -b 'h notify remove_pane \#{pane_id}'"
|
|
207
|
+
set-hook -g window-unlinked "run-shell -b 'h notify remove_window \#{hook_window_id}'"
|
|
208
|
+
set-hook -g session-closed "run-shell -b 'h notify remove_session \#{hook_session_name}'"
|
|
209
|
+
|
|
210
|
+
# --- Notify keybinding (prefix + N to open notification menu) ---
|
|
211
|
+
bind-key N run-shell "h notify menu"
|
|
212
|
+
CONF
|
|
213
|
+
|
|
214
|
+
File.write(conf_file, notify_conf)
|
|
215
|
+
puts "Wrote #{conf_file}"
|
|
216
|
+
|
|
217
|
+
if File.exist?(tmux_conf)
|
|
218
|
+
existing = File.read(tmux_conf)
|
|
219
|
+
if existing.include?(source_line)
|
|
220
|
+
puts "#{tmux_conf} already sources #{conf_file}"
|
|
221
|
+
else
|
|
222
|
+
File.open(tmux_conf, 'a') { |f| f.puts; f.puts source_line }
|
|
223
|
+
puts "Added '#{source_line}' to #{tmux_conf}"
|
|
224
|
+
end
|
|
225
|
+
else
|
|
226
|
+
File.write(tmux_conf, source_line + "\n")
|
|
227
|
+
puts "Created #{tmux_conf} with source line"
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
puts
|
|
231
|
+
puts "Reload tmux config:"
|
|
232
|
+
puts " tmux source-file ~/.tmux.conf"
|
|
233
|
+
puts
|
|
234
|
+
puts "─────────────────────────────────────────────────"
|
|
235
|
+
puts "Claude Code hook setup"
|
|
236
|
+
puts "─────────────────────────────────────────────────"
|
|
237
|
+
puts
|
|
238
|
+
puts "Run this to auto-update ~/.claude/settings.json:"
|
|
239
|
+
puts " h notify update_hooks"
|
|
240
|
+
puts
|
|
241
|
+
puts "Or manually update hooks to:"
|
|
242
|
+
puts
|
|
243
|
+
puts ' Notification: h alert ... ; h notify push -t info "$MSG"'
|
|
244
|
+
puts ' Stop: h alert ... ; h notify push -t success "Work completed"'
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
add_subcmd(:update_hooks) do
|
|
248
|
+
settings_file = File.join(Dir.home, '.claude', 'settings.json')
|
|
249
|
+
|
|
250
|
+
unless File.exist?(settings_file)
|
|
251
|
+
puts "#{settings_file} not found"
|
|
252
|
+
next
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
require 'json'
|
|
256
|
+
settings = JSON.parse(File.read(settings_file))
|
|
257
|
+
settings['hooks'] ||= {}
|
|
258
|
+
|
|
259
|
+
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")
|
|
260
|
+
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')
|
|
261
|
+
|
|
262
|
+
settings['hooks']['Notification'] = [{ 'hooks' => [{ 'type' => 'command', 'command' => notification_cmd }] }]
|
|
263
|
+
settings['hooks']['Stop'] = [{ 'hooks' => [{ 'type' => 'command', 'command' => stop_cmd }] }]
|
|
264
|
+
|
|
265
|
+
File.write(settings_file, JSON.pretty_generate(settings))
|
|
266
|
+
puts "Updated #{settings_file}"
|
|
267
|
+
puts " Notification -> h alert + h notify push -t info"
|
|
268
|
+
puts " Stop -> h alert + h notify push -t success"
|
|
150
269
|
end
|
|
151
270
|
end
|
data/bin/h-pr
CHANGED
|
@@ -366,8 +366,8 @@ class PinnedPRManager
|
|
|
366
366
|
as = r['approved'].to_i
|
|
367
367
|
crs = r['changes_requested'].to_i
|
|
368
368
|
|
|
369
|
-
as_str = as > 0 ? "\e[
|
|
370
|
-
crs_str = crs > 0 ? "\e[
|
|
369
|
+
as_str = as > 0 ? "\e[30;102m#{as}\e[0m" : '-'
|
|
370
|
+
crs_str = crs > 0 ? "\e[30;103m#{crs}\e[0m" : '-'
|
|
371
371
|
reviews_str = "#{as_str}a/#{crs_str}cr"
|
|
372
372
|
|
|
373
373
|
repo = pr_repo(pr)
|
data/lib/hiiro/version.rb
CHANGED