hiiro 0.1.82 → 0.1.83
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-branch +109 -76
- data/bin/h-pane +0 -11
- data/bin/h-pr +0 -64
- data/bin/h-session +0 -12
- data/bin/h-window +0 -12
- data/lib/hiiro/version.rb +1 -1
- data/lib/hiiro.rb +0 -9
- metadata +6 -5
- data/lib/hiiro/history/entry.rb +0 -136
- data/lib/hiiro/history.rb +0 -454
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 599e565871c2afc37ae659954e4ccf01568bed4375032fb9d8ba21007e7e2673
|
|
4
|
+
data.tar.gz: 4fdcbf43a79f2b63ec3f95ecd003973cb3175b4ef0b6cdbb0cbc36444439b259
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e8945bc066927371fa804f6b0deef00ee36f84be0fc9178033e52d5228ce5f6b895b114cb3a0a37129e491f2c8d4a1156cfce36e18740d28048fa7885ad1f792
|
|
7
|
+
data.tar.gz: 31acaf4e204e3be5f8c6fddbf6009f0e757bad86e3dcbe5c770637c05d848a6c4a65c42552617e015bd8b55171aecc9d2fde86b58e6b16ed06ef23f8bdef6179
|
data/bin/h-branch
CHANGED
|
@@ -19,10 +19,6 @@ class BranchManager
|
|
|
19
19
|
puts
|
|
20
20
|
puts "Subcommands:"
|
|
21
21
|
puts " save Record current branch for this task"
|
|
22
|
-
puts " history List branch history (oldest to newest)"
|
|
23
|
-
puts " history --worktree=X Filter by worktree"
|
|
24
|
-
puts " history --task=X Filter by task"
|
|
25
|
-
puts " history --session=X Filter by tmux session"
|
|
26
22
|
puts " current Show current branch info"
|
|
27
23
|
end
|
|
28
24
|
|
|
@@ -62,35 +58,6 @@ class BranchManager
|
|
|
62
58
|
true
|
|
63
59
|
end
|
|
64
60
|
|
|
65
|
-
def history(args = [])
|
|
66
|
-
filters = parse_filters(args)
|
|
67
|
-
data = load_data
|
|
68
|
-
branches = data['branches'] || []
|
|
69
|
-
|
|
70
|
-
if branches.empty?
|
|
71
|
-
puts "No branches recorded."
|
|
72
|
-
puts "Use 'h branch save' to record the current branch."
|
|
73
|
-
return
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
# Apply filters
|
|
77
|
-
branches = filter_entries(branches, filters)
|
|
78
|
-
|
|
79
|
-
if branches.empty?
|
|
80
|
-
puts "No branches match the given filters."
|
|
81
|
-
return
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# Sort by created_at (oldest first)
|
|
85
|
-
branches = branches.sort_by { |b| b['created_at'] || '' }
|
|
86
|
-
|
|
87
|
-
puts "Branch history (oldest to newest):"
|
|
88
|
-
puts
|
|
89
|
-
branches.each_with_index do |branch, idx|
|
|
90
|
-
puts format_entry(branch, idx + 1)
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
|
|
94
61
|
def current
|
|
95
62
|
branch_name = current_branch
|
|
96
63
|
unless branch_name
|
|
@@ -132,48 +99,6 @@ class BranchManager
|
|
|
132
99
|
}
|
|
133
100
|
end
|
|
134
101
|
|
|
135
|
-
def parse_filters(args)
|
|
136
|
-
filters = {}
|
|
137
|
-
args.each do |arg|
|
|
138
|
-
case arg
|
|
139
|
-
when /^--worktree=(.+)$/
|
|
140
|
-
filters[:worktree] = $1
|
|
141
|
-
when /^--task=(.+)$/
|
|
142
|
-
filters[:task] = $1
|
|
143
|
-
when /^--session=(.+)$/
|
|
144
|
-
filters[:session] = $1
|
|
145
|
-
when /^-w(.+)$/
|
|
146
|
-
filters[:worktree] = $1
|
|
147
|
-
when /^-t(.+)$/
|
|
148
|
-
filters[:task] = $1
|
|
149
|
-
when /^-s(.+)$/
|
|
150
|
-
filters[:session] = $1
|
|
151
|
-
end
|
|
152
|
-
end
|
|
153
|
-
filters
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
def filter_entries(entries, filters)
|
|
157
|
-
entries.select do |entry|
|
|
158
|
-
next false if filters[:worktree] && !entry['worktree']&.include?(filters[:worktree])
|
|
159
|
-
next false if filters[:task] && !entry['task']&.include?(filters[:task])
|
|
160
|
-
next false if filters[:session] && entry.dig('tmux', 'session') != filters[:session]
|
|
161
|
-
true
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
def format_entry(entry, num)
|
|
166
|
-
lines = []
|
|
167
|
-
lines << "#{num}. #{entry['name']}"
|
|
168
|
-
lines << " Task: #{entry['task'] || '(none)'}"
|
|
169
|
-
lines << " Worktree: #{entry['worktree'] || '(none)'}"
|
|
170
|
-
if entry['tmux']
|
|
171
|
-
lines << " Tmux: #{entry['tmux']['session']}/#{entry['tmux']['window']}"
|
|
172
|
-
end
|
|
173
|
-
lines << " Created: #{entry['created_at']}"
|
|
174
|
-
lines.join("\n")
|
|
175
|
-
end
|
|
176
|
-
|
|
177
102
|
def show_entry(entry)
|
|
178
103
|
puts " Branch: #{entry[:name]}"
|
|
179
104
|
puts " Task: #{entry[:task] || '(none)'}"
|
|
@@ -204,7 +129,6 @@ manager = BranchManager.new(hiiro)
|
|
|
204
129
|
|
|
205
130
|
hiiro.add_subcmd(:edit) { system(ENV['EDITOR'] || 'nvim', __FILE__) }
|
|
206
131
|
hiiro.add_subcmd(:save) { manager.save }
|
|
207
|
-
hiiro.add_subcmd(:history) { |*args| manager.history(args) }
|
|
208
132
|
hiiro.add_subcmd(:current) { manager.current }
|
|
209
133
|
hiiro.add_subcmd(:select) do |*args|
|
|
210
134
|
branches = hiiro.git.branches(sort_by: 'authordate', ignore_case: true)
|
|
@@ -375,6 +299,115 @@ hiiro.add_subcmd(:behind) { |*args|
|
|
|
375
299
|
puts "#{branch} is #{count} commit(s) behind #{behind_of}"
|
|
376
300
|
}
|
|
377
301
|
|
|
302
|
+
hiiro.add_subcmd(:log) { |upstream = nil|
|
|
303
|
+
upstream ||= %w[origin/master master origin/main main].find { |ref|
|
|
304
|
+
system("git rev-parse --verify #{ref} >/dev/null 2>&1")
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
unless upstream
|
|
308
|
+
puts "Cannot find master or origin/master"
|
|
309
|
+
next
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
forkpoint = `git merge-base --fork-point #{upstream} HEAD 2>/dev/null`.strip
|
|
313
|
+
if forkpoint.empty?
|
|
314
|
+
forkpoint = `git merge-base #{upstream} HEAD 2>/dev/null`.strip
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
if forkpoint.empty?
|
|
318
|
+
puts "Could not find fork point"
|
|
319
|
+
next
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
system('git', 'log', '--oneline', '--decorate', "#{forkpoint}..HEAD")
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
hiiro.add_subcmd(:forkpoint) { |upstream = nil, branch = nil|
|
|
326
|
+
branch ||= 'HEAD'
|
|
327
|
+
upstream ||= %w[origin/master master origin/main main].find { |ref|
|
|
328
|
+
system("git rev-parse --verify #{ref} >/dev/null 2>&1")
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
unless upstream
|
|
332
|
+
puts "Cannot find master or origin/master"
|
|
333
|
+
next
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
forkpoint = `git merge-base --fork-point #{upstream} #{branch} 2>/dev/null`.strip
|
|
337
|
+
|
|
338
|
+
if forkpoint.empty?
|
|
339
|
+
# Fallback to regular merge-base if fork-point fails
|
|
340
|
+
forkpoint = `git merge-base #{upstream} #{branch} 2>/dev/null`.strip
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
if forkpoint.empty?
|
|
344
|
+
puts "Could not find fork point between #{upstream} and #{branch}"
|
|
345
|
+
else
|
|
346
|
+
puts forkpoint
|
|
347
|
+
end
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
hiiro.add_subcmd(:changed) { |*args|
|
|
351
|
+
show_all = args.delete('-a') || args.delete('--all')
|
|
352
|
+
upstream = args.first
|
|
353
|
+
|
|
354
|
+
upstream ||= %w[origin/master master origin/main main].find { |ref|
|
|
355
|
+
system("git rev-parse --verify #{ref} >/dev/null 2>&1")
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
unless upstream
|
|
359
|
+
puts "Cannot find master or origin/master"
|
|
360
|
+
next
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
forkpoint = `git merge-base --fork-point #{upstream} HEAD 2>/dev/null`.strip
|
|
364
|
+
if forkpoint.empty?
|
|
365
|
+
forkpoint = `git merge-base #{upstream} HEAD 2>/dev/null`.strip
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
if forkpoint.empty?
|
|
369
|
+
puts "Could not find fork point"
|
|
370
|
+
next
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
if show_all
|
|
374
|
+
system('git', 'diff', '--name-only', "#{forkpoint}...HEAD")
|
|
375
|
+
else
|
|
376
|
+
system('git', 'diff', '--name-only', '--relative', "#{forkpoint}...HEAD")
|
|
377
|
+
end
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
hiiro.add_subcmd(:changed) { |*args|
|
|
382
|
+
show_all = args.delete('-a') || args.delete('--all')
|
|
383
|
+
upstream = args.first
|
|
384
|
+
|
|
385
|
+
upstream ||= %w[origin/master master origin/main main].find { |ref|
|
|
386
|
+
system("git rev-parse --verify #{ref} >/dev/null 2>&1")
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
unless upstream
|
|
390
|
+
puts "Cannot find master or origin/master"
|
|
391
|
+
next
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
forkpoint = `git merge-base --fork-point #{upstream} HEAD 2>/dev/null`.strip
|
|
395
|
+
if forkpoint.empty?
|
|
396
|
+
forkpoint = `git merge-base #{upstream} HEAD 2>/dev/null`.strip
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
if forkpoint.empty?
|
|
400
|
+
puts "Could not find fork point"
|
|
401
|
+
next
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
if show_all
|
|
405
|
+
system('git', 'diff', '--name-only', "#{forkpoint}...HEAD")
|
|
406
|
+
else
|
|
407
|
+
system('git', 'diff', '--name-only', '--relative', "#{forkpoint}...HEAD")
|
|
408
|
+
end
|
|
409
|
+
}
|
|
410
|
+
|
|
378
411
|
hiiro.add_subcmd(:ancestor) { |*args|
|
|
379
412
|
case args.length
|
|
380
413
|
when 0
|
data/bin/h-pane
CHANGED
|
@@ -60,17 +60,6 @@ o.add_subcmd(:resize) { |*args|
|
|
|
60
60
|
system('tmux', 'resize-pane', *args)
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
o.add_subcmd(:history) { |pane_id=nil|
|
|
64
|
-
entries = Hiiro::History.by_pane(pane_id)
|
|
65
|
-
if entries.empty?
|
|
66
|
-
puts "No history for this pane"
|
|
67
|
-
else
|
|
68
|
-
puts "History for pane: #{pane_id || ENV['TMUX_PANE'] || 'current'}"
|
|
69
|
-
puts
|
|
70
|
-
entries.last(20).each_with_index { |e, i| puts e.oneline(i + 1) }
|
|
71
|
-
end
|
|
72
|
-
}
|
|
73
|
-
|
|
74
63
|
o.add_subcmd(:select) do |*args|
|
|
75
64
|
# Get all panes with details
|
|
76
65
|
output = `tmux list-panes -a -F '\#{session_name}:\#{window_index}.\#{pane_index} [\#{pane_current_command}] \#{pane_current_path}'`.strip
|
data/bin/h-pr
CHANGED
|
@@ -21,10 +21,6 @@ class PRManager
|
|
|
21
21
|
puts
|
|
22
22
|
puts "Subcommands:"
|
|
23
23
|
puts " save [PR_NUMBER] Record PR for this task (auto-detects if omitted)"
|
|
24
|
-
puts " history List PR history (oldest to newest)"
|
|
25
|
-
puts " history --worktree=X Filter by worktree"
|
|
26
|
-
puts " history --task=X Filter by task"
|
|
27
|
-
puts " history --session=X Filter by tmux session"
|
|
28
24
|
puts " current Show current branch's PR info"
|
|
29
25
|
puts " open [PR_NUMBER] Open PR in browser"
|
|
30
26
|
puts " view [PR_NUMBER] View PR details in terminal"
|
|
@@ -67,35 +63,6 @@ class PRManager
|
|
|
67
63
|
true
|
|
68
64
|
end
|
|
69
65
|
|
|
70
|
-
def history(args = [])
|
|
71
|
-
filters = parse_filters(args)
|
|
72
|
-
data = load_data
|
|
73
|
-
prs = data['prs'] || []
|
|
74
|
-
|
|
75
|
-
if prs.empty?
|
|
76
|
-
puts "No PRs recorded."
|
|
77
|
-
puts "Use 'h pr save' to record the current PR."
|
|
78
|
-
return
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
# Apply filters
|
|
82
|
-
prs = filter_entries(prs, filters)
|
|
83
|
-
|
|
84
|
-
if prs.empty?
|
|
85
|
-
puts "No PRs match the given filters."
|
|
86
|
-
return
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# Sort by created_at (oldest first)
|
|
90
|
-
prs = prs.sort_by { |p| p['created_at'] || '' }
|
|
91
|
-
|
|
92
|
-
puts "PR history (oldest to newest):"
|
|
93
|
-
puts
|
|
94
|
-
prs.each_with_index do |pr, idx|
|
|
95
|
-
puts format_entry(pr, idx + 1)
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
|
|
99
66
|
def current
|
|
100
67
|
pr_info = fetch_pr_info
|
|
101
68
|
unless pr_info
|
|
@@ -169,36 +136,6 @@ class PRManager
|
|
|
169
136
|
}
|
|
170
137
|
end
|
|
171
138
|
|
|
172
|
-
def parse_filters(args)
|
|
173
|
-
filters = {}
|
|
174
|
-
args.each do |arg|
|
|
175
|
-
case arg
|
|
176
|
-
when /^--worktree=(.+)$/
|
|
177
|
-
filters[:worktree] = $1
|
|
178
|
-
when /^--task=(.+)$/
|
|
179
|
-
filters[:task] = $1
|
|
180
|
-
when /^--session=(.+)$/
|
|
181
|
-
filters[:session] = $1
|
|
182
|
-
when /^-w(.+)$/
|
|
183
|
-
filters[:worktree] = $1
|
|
184
|
-
when /^-t(.+)$/
|
|
185
|
-
filters[:task] = $1
|
|
186
|
-
when /^-s(.+)$/
|
|
187
|
-
filters[:session] = $1
|
|
188
|
-
end
|
|
189
|
-
end
|
|
190
|
-
filters
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
def filter_entries(entries, filters)
|
|
194
|
-
entries.select do |entry|
|
|
195
|
-
next false if filters[:worktree] && !entry['worktree']&.include?(filters[:worktree])
|
|
196
|
-
next false if filters[:task] && !entry['task']&.include?(filters[:task])
|
|
197
|
-
next false if filters[:session] && entry.dig('tmux', 'session') != filters[:session]
|
|
198
|
-
true
|
|
199
|
-
end
|
|
200
|
-
end
|
|
201
|
-
|
|
202
139
|
def format_entry(entry, num)
|
|
203
140
|
lines = []
|
|
204
141
|
lines << "#{num}. PR ##{entry['number']}: #{entry['title']}"
|
|
@@ -541,7 +478,6 @@ hiiro.add_subcmd(:link) { |*args|
|
|
|
541
478
|
|
|
542
479
|
hiiro.add_subcmd(:edit) { system(ENV['EDITOR'] || 'nvim', __FILE__) }
|
|
543
480
|
hiiro.add_subcmd(:save) { |pr_number=nil| manager.save(pr_number) }
|
|
544
|
-
hiiro.add_subcmd(:history) { |*args| manager.history(args) }
|
|
545
481
|
hiiro.add_subcmd(:current) { manager.current }
|
|
546
482
|
hiiro.add_subcmd(:open) { |pr_number=nil| manager.open(pr_number) }
|
|
547
483
|
hiiro.add_subcmd(:view) { |pr_number=nil| manager.view(pr_number) }
|
data/bin/h-session
CHANGED
|
@@ -60,16 +60,4 @@ o.add_subcmd(:info) { |*args|
|
|
|
60
60
|
system('tmux', 'display-message', '-p', '#{session_name}: #{session_windows} windows, #{session_attached} attached', *args)
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
o.add_subcmd(:history) { |session_name=nil|
|
|
64
|
-
session_name ||= `tmux display-message -p '#S'`.strip if ENV['TMUX']
|
|
65
|
-
entries = Hiiro::History.by_session(session_name)
|
|
66
|
-
if entries.empty?
|
|
67
|
-
puts "No history for this session"
|
|
68
|
-
else
|
|
69
|
-
puts "History for session: #{session_name || 'current'}"
|
|
70
|
-
puts
|
|
71
|
-
entries.last(20).each_with_index { |e, i| puts e.oneline(i + 1) }
|
|
72
|
-
end
|
|
73
|
-
}
|
|
74
|
-
|
|
75
63
|
o.run
|
data/bin/h-window
CHANGED
|
@@ -56,18 +56,6 @@ o.add_subcmd(:unlink) { |*args|
|
|
|
56
56
|
system('tmux', 'unlink-window', *args)
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
o.add_subcmd(:history) { |window_id=nil|
|
|
60
|
-
window_id ||= `tmux display-message -p '#I'`.strip if ENV['TMUX']
|
|
61
|
-
entries = Hiiro::History.by_window(window_id)
|
|
62
|
-
if entries.empty?
|
|
63
|
-
puts "No history for this window"
|
|
64
|
-
else
|
|
65
|
-
puts "History for window: #{window_id || 'current'}"
|
|
66
|
-
puts
|
|
67
|
-
entries.last(20).each_with_index { |e, i| puts e.oneline(i + 1) }
|
|
68
|
-
end
|
|
69
|
-
}
|
|
70
|
-
|
|
71
59
|
o.add_subcmd(:select) do |*args|
|
|
72
60
|
# Get all windows with details
|
|
73
61
|
output = `tmux list-windows -a -F '\#{session_name}:\#{window_index} \#{window_name} [\#{window_panes} panes]'`.strip
|
data/lib/hiiro/version.rb
CHANGED
data/lib/hiiro.rb
CHANGED
|
@@ -6,7 +6,6 @@ require "pry"
|
|
|
6
6
|
require_relative "hiiro/version"
|
|
7
7
|
require_relative "hiiro/prefix_matcher"
|
|
8
8
|
require_relative "hiiro/git"
|
|
9
|
-
require_relative "hiiro/history"
|
|
10
9
|
require_relative "hiiro/options"
|
|
11
10
|
require_relative "hiiro/notification"
|
|
12
11
|
require_relative "hiiro/sk"
|
|
@@ -39,7 +38,6 @@ class Hiiro
|
|
|
39
38
|
bin_name = values[:bin_name] || $0
|
|
40
39
|
|
|
41
40
|
new(bin_name, *args, logging: logging, **values).tap do |hiiro|
|
|
42
|
-
History.load(hiiro)
|
|
43
41
|
hiiro.load_plugins(*plugins)
|
|
44
42
|
|
|
45
43
|
hiiro.add_subcommand(:pry) { |*args|
|
|
@@ -104,16 +102,9 @@ class Hiiro
|
|
|
104
102
|
@todo_manager ||= TodoManager.new
|
|
105
103
|
end
|
|
106
104
|
|
|
107
|
-
def history
|
|
108
|
-
@history ||= History.new
|
|
109
|
-
end
|
|
110
|
-
|
|
111
105
|
def run
|
|
112
106
|
result = runner.run(*args)
|
|
113
107
|
|
|
114
|
-
# Track command after running (only saves if state changed and in task context)
|
|
115
|
-
History.track(cmd: full_command, hiiro: self)
|
|
116
|
-
|
|
117
108
|
handle_result(result)
|
|
118
109
|
|
|
119
110
|
exit 1
|
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hiiro
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.83
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Joshua Toyota
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: exe
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2026-02-14 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: pry
|
|
@@ -100,8 +101,6 @@ files:
|
|
|
100
101
|
- lib/hiiro/git/remote.rb
|
|
101
102
|
- lib/hiiro/git/worktree.rb
|
|
102
103
|
- lib/hiiro/git/worktrees.rb
|
|
103
|
-
- lib/hiiro/history.rb
|
|
104
|
-
- lib/hiiro/history/entry.rb
|
|
105
104
|
- lib/hiiro/notification.rb
|
|
106
105
|
- lib/hiiro/options.rb
|
|
107
106
|
- lib/hiiro/prefix_matcher.rb
|
|
@@ -127,6 +126,7 @@ metadata:
|
|
|
127
126
|
homepage_uri: https://github.com/unixsuperhero/hiiro
|
|
128
127
|
source_code_uri: https://github.com/unixsuperhero/hiiro
|
|
129
128
|
changelog_uri: https://github.com/unixsuperhero/hiiro/blob/main/CHANGELOG.md
|
|
129
|
+
post_install_message:
|
|
130
130
|
rdoc_options: []
|
|
131
131
|
require_paths:
|
|
132
132
|
- lib
|
|
@@ -141,7 +141,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
141
141
|
- !ruby/object:Gem::Version
|
|
142
142
|
version: '0'
|
|
143
143
|
requirements: []
|
|
144
|
-
rubygems_version: 3.
|
|
144
|
+
rubygems_version: 3.3.7
|
|
145
|
+
signing_key:
|
|
145
146
|
specification_version: 4
|
|
146
147
|
summary: A lightweight CLI framework for Ruby
|
|
147
148
|
test_files: []
|
data/lib/hiiro/history/entry.rb
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
class Hiiro
|
|
2
|
-
class History
|
|
3
|
-
class Entry
|
|
4
|
-
FIELDS = %i[
|
|
5
|
-
id timestamp source cmd description pwd
|
|
6
|
-
tmux_session tmux_window tmux_pane
|
|
7
|
-
git_branch git_sha git_origin_sha git_worktree
|
|
8
|
-
task subtask app
|
|
9
|
-
].freeze
|
|
10
|
-
|
|
11
|
-
attr_reader(*FIELDS)
|
|
12
|
-
|
|
13
|
-
def initialize(data)
|
|
14
|
-
data ||= {}
|
|
15
|
-
@id = data['id']
|
|
16
|
-
@timestamp = data['timestamp']
|
|
17
|
-
@source = data['source']
|
|
18
|
-
@cmd = data['cmd']
|
|
19
|
-
@description = data['description']
|
|
20
|
-
@pwd = data['pwd']
|
|
21
|
-
@tmux_session = data['tmux_session']
|
|
22
|
-
@tmux_window = data['tmux_window']
|
|
23
|
-
@tmux_pane = data['tmux_pane']
|
|
24
|
-
@git_branch = data['git_branch']
|
|
25
|
-
@git_sha = data['git_sha']
|
|
26
|
-
@git_origin_sha = data['git_origin_sha']
|
|
27
|
-
@git_worktree = data['git_worktree']
|
|
28
|
-
@task = data['task']
|
|
29
|
-
@subtask = data['subtask']
|
|
30
|
-
@app = data['app']
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def to_h
|
|
34
|
-
{
|
|
35
|
-
'id' => id,
|
|
36
|
-
'timestamp' => timestamp,
|
|
37
|
-
'source' => source,
|
|
38
|
-
'cmd' => cmd,
|
|
39
|
-
'description' => description,
|
|
40
|
-
'pwd' => pwd,
|
|
41
|
-
'tmux_session' => tmux_session,
|
|
42
|
-
'tmux_window' => tmux_window,
|
|
43
|
-
'tmux_pane' => tmux_pane,
|
|
44
|
-
'git_branch' => git_branch,
|
|
45
|
-
'git_sha' => git_sha,
|
|
46
|
-
'git_origin_sha' => git_origin_sha,
|
|
47
|
-
'git_worktree' => git_worktree,
|
|
48
|
-
'task' => task,
|
|
49
|
-
'subtask' => subtask,
|
|
50
|
-
'app' => app,
|
|
51
|
-
}.compact
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# Data used for change detection (excludes timestamp, id, cmd)
|
|
55
|
-
def state_key
|
|
56
|
-
{
|
|
57
|
-
'pwd' => pwd,
|
|
58
|
-
'tmux_session' => tmux_session,
|
|
59
|
-
'tmux_window' => tmux_window,
|
|
60
|
-
'tmux_pane' => tmux_pane,
|
|
61
|
-
'git_branch' => git_branch,
|
|
62
|
-
'git_sha' => git_sha,
|
|
63
|
-
'git_origin_sha' => git_origin_sha,
|
|
64
|
-
'git_worktree' => git_worktree,
|
|
65
|
-
'task' => task,
|
|
66
|
-
'subtask' => subtask,
|
|
67
|
-
'app' => app,
|
|
68
|
-
}.compact
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def state_fingerprint
|
|
72
|
-
Digest::SHA256.hexdigest(state_key.to_yaml)[0..15]
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def short_line(index)
|
|
76
|
-
time_str = timestamp ? Time.parse(timestamp).strftime('%Y-%m-%d %H:%M') : Time.now.strftime('%Y-%m-%d %H:%M')
|
|
77
|
-
desc = description || cmd || '(no description)'
|
|
78
|
-
desc = desc[0..50] + '...' if desc.length > 53
|
|
79
|
-
[
|
|
80
|
-
format('%3d %s %s', index, time_str, desc),
|
|
81
|
-
format(' %s @ %s', git_branch || '(no branch)', task || '(no task)'),
|
|
82
|
-
].join("\n")
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def oneline(index = nil)
|
|
86
|
-
time_str = timestamp ? Time.parse(timestamp).strftime('%m/%d %H:%M') : ''
|
|
87
|
-
prefix = index ? format('%3d ', index) : ''
|
|
88
|
-
sha_str = git_sha ? git_sha[0..6] : '-------'
|
|
89
|
-
branch_str = git_branch ? "[#{git_branch}]" : ''
|
|
90
|
-
task_str = task ? "(#{task})" : ''
|
|
91
|
-
cmd_str = cmd || description || ''
|
|
92
|
-
cmd_str = cmd_str[0..35] + '...' if cmd_str.length > 38
|
|
93
|
-
|
|
94
|
-
"#{prefix}#{time_str} #{sha_str} #{branch_str.ljust(20)} #{task_str.ljust(15)} #{cmd_str}"
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def full_display
|
|
98
|
-
lines = []
|
|
99
|
-
lines << "ID: #{id}"
|
|
100
|
-
lines << "Timestamp: #{timestamp}"
|
|
101
|
-
lines << "Description: #{description}" if description
|
|
102
|
-
lines << "Command: #{cmd}" if cmd
|
|
103
|
-
lines << "PWD: #{pwd}" if pwd
|
|
104
|
-
lines << "Source: #{source}" if source
|
|
105
|
-
lines << ""
|
|
106
|
-
lines << "Tmux:"
|
|
107
|
-
lines << " Session: #{tmux_session}" if tmux_session
|
|
108
|
-
lines << " Window: #{tmux_window}" if tmux_window
|
|
109
|
-
lines << " Pane: #{tmux_pane}" if tmux_pane
|
|
110
|
-
lines << ""
|
|
111
|
-
lines << "Git:"
|
|
112
|
-
lines << " Worktree: #{git_worktree}" if git_worktree
|
|
113
|
-
lines << " Branch: #{git_branch}" if git_branch
|
|
114
|
-
lines << " SHA: #{git_sha}" if git_sha
|
|
115
|
-
lines << " Origin: #{git_origin_sha}" if git_origin_sha
|
|
116
|
-
lines << ""
|
|
117
|
-
lines << "Task:"
|
|
118
|
-
lines << " Task: #{task}" if task
|
|
119
|
-
lines << " Subtask: #{subtask}" if subtask
|
|
120
|
-
lines << " App: #{app}" if app
|
|
121
|
-
lines.join("\n")
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
# Filter matching
|
|
125
|
-
def matches?(filters)
|
|
126
|
-
filters.all? do |key, value|
|
|
127
|
-
entry_value = send(key) rescue nil
|
|
128
|
-
next true if value.nil?
|
|
129
|
-
next entry_value == value if value.is_a?(String)
|
|
130
|
-
next value.include?(entry_value) if value.is_a?(Array)
|
|
131
|
-
true
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
end
|
data/lib/hiiro/history.rb
DELETED
|
@@ -1,454 +0,0 @@
|
|
|
1
|
-
require 'yaml'
|
|
2
|
-
require 'time'
|
|
3
|
-
require 'fileutils'
|
|
4
|
-
require 'digest'
|
|
5
|
-
require_relative 'history/entry'
|
|
6
|
-
|
|
7
|
-
class Hiiro
|
|
8
|
-
class History
|
|
9
|
-
HISTORY_DIR = File.join(Dir.home, '.config/hiiro/history')
|
|
10
|
-
HISTORY_FILE = File.join(HISTORY_DIR, 'entries.yml')
|
|
11
|
-
LAST_STATE_FILE = File.join(HISTORY_DIR, 'last_state.yml')
|
|
12
|
-
|
|
13
|
-
class << self
|
|
14
|
-
def load(hiiro)
|
|
15
|
-
history = new
|
|
16
|
-
|
|
17
|
-
hiiro.add_subcmd(:history) do |*args|
|
|
18
|
-
subcmd, *rest = args
|
|
19
|
-
case subcmd
|
|
20
|
-
when 'ls', 'list', nil
|
|
21
|
-
history.list(limit: 20)
|
|
22
|
-
when 'all'
|
|
23
|
-
history.list(limit: nil)
|
|
24
|
-
when 'show'
|
|
25
|
-
history.show(rest.first)
|
|
26
|
-
when 'goto'
|
|
27
|
-
history.goto(rest.first)
|
|
28
|
-
when 'add'
|
|
29
|
-
history.add_manual(rest.join(' '), hiiro: hiiro)
|
|
30
|
-
when 'by'
|
|
31
|
-
# h history by pane|window|session|task|branch [value]
|
|
32
|
-
dimension, value = rest
|
|
33
|
-
history.list_by(dimension, value)
|
|
34
|
-
when 'clear'
|
|
35
|
-
history.clear!
|
|
36
|
-
puts "History cleared."
|
|
37
|
-
else
|
|
38
|
-
puts "Unknown history subcommand: #{subcmd}"
|
|
39
|
-
puts "Available: ls, all, show <id>, goto <id>, add <description>, by <dimension> [value], clear"
|
|
40
|
-
false
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# Called automatically when a command runs (if in a task context)
|
|
46
|
-
def track(cmd:, hiiro: nil)
|
|
47
|
-
new.track_command(cmd: cmd, hiiro: hiiro)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def log(description: nil, pwd: Dir.pwd, cmd: nil, source: nil, task: nil, subtask: nil)
|
|
51
|
-
new.add(
|
|
52
|
-
description: description,
|
|
53
|
-
cmd: cmd,
|
|
54
|
-
pwd: pwd,
|
|
55
|
-
source: source,
|
|
56
|
-
task: task,
|
|
57
|
-
subtask: subtask
|
|
58
|
-
)
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# Query helpers for other commands (e.g., h pane history)
|
|
62
|
-
def by_pane(pane_id = nil)
|
|
63
|
-
pane_id ||= current_tmux_pane
|
|
64
|
-
new.filter(tmux_pane: pane_id)
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def by_window(window_id = nil)
|
|
68
|
-
window_id ||= current_tmux_window
|
|
69
|
-
new.filter(tmux_window: window_id)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def by_session(session_name = nil)
|
|
73
|
-
session_name ||= current_tmux_session
|
|
74
|
-
new.filter(tmux_session: session_name)
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def by_task(task_name)
|
|
78
|
-
new.filter(task: task_name)
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def by_branch(branch_name)
|
|
82
|
-
new.filter(git_branch: branch_name)
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def by_worktree(worktree_path)
|
|
86
|
-
new.filter(git_worktree: worktree_path)
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def by_app(app_name)
|
|
90
|
-
new.filter(app: app_name)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
private
|
|
94
|
-
|
|
95
|
-
def current_tmux_pane
|
|
96
|
-
return nil unless ENV['TMUX']
|
|
97
|
-
`tmux display-message -p '#D'`.strip rescue nil
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
def current_tmux_window
|
|
101
|
-
return nil unless ENV['TMUX']
|
|
102
|
-
`tmux display-message -p '#S:#I'`.strip rescue nil
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
def current_tmux_session
|
|
106
|
-
return nil unless ENV['TMUX']
|
|
107
|
-
`tmux display-message -p '#S'`.strip rescue nil
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
def initialize
|
|
112
|
-
ensure_history_dir
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
def entries
|
|
116
|
-
@entries ||= load_entries
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
def reload
|
|
120
|
-
@entries = nil
|
|
121
|
-
entries
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def filter(filters = {})
|
|
125
|
-
entries.select { |e| e.matches?(filters) }
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
def list(limit: 20)
|
|
129
|
-
items = entries.reverse
|
|
130
|
-
items = items.first(limit) if limit
|
|
131
|
-
|
|
132
|
-
if items.empty?
|
|
133
|
-
puts "No history entries."
|
|
134
|
-
return true
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
items.reverse.each_with_index do |entry, idx|
|
|
138
|
-
puts entry.oneline(idx + 1)
|
|
139
|
-
end
|
|
140
|
-
true
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
def list_by(dimension, value = nil)
|
|
144
|
-
dimension_map = {
|
|
145
|
-
'pane' => :tmux_pane,
|
|
146
|
-
'window' => :tmux_window,
|
|
147
|
-
'session' => :tmux_session,
|
|
148
|
-
'task' => :task,
|
|
149
|
-
'subtask' => :subtask,
|
|
150
|
-
'branch' => :git_branch,
|
|
151
|
-
'worktree' => :git_worktree,
|
|
152
|
-
'app' => :app,
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
field = dimension_map[dimension]
|
|
156
|
-
unless field
|
|
157
|
-
puts "Unknown dimension: #{dimension}"
|
|
158
|
-
puts "Available: #{dimension_map.keys.join(', ')}"
|
|
159
|
-
return false
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
# If no value specified, use current context
|
|
163
|
-
value ||= current_value_for(field)
|
|
164
|
-
|
|
165
|
-
if value.nil?
|
|
166
|
-
puts "No current #{dimension} detected. Please specify a value."
|
|
167
|
-
return false
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
filtered = filter(field => value)
|
|
171
|
-
|
|
172
|
-
if filtered.empty?
|
|
173
|
-
puts "No history for #{dimension}=#{value}"
|
|
174
|
-
return true
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
puts "History for #{dimension}: #{value}"
|
|
178
|
-
puts
|
|
179
|
-
filtered.last(20).each_with_index do |entry, idx|
|
|
180
|
-
puts entry.oneline(idx + 1)
|
|
181
|
-
end
|
|
182
|
-
true
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
def show(ref)
|
|
186
|
-
entry = find_entry(ref)
|
|
187
|
-
unless entry
|
|
188
|
-
puts "Entry not found: #{ref}"
|
|
189
|
-
return false
|
|
190
|
-
end
|
|
191
|
-
|
|
192
|
-
puts entry.full_display
|
|
193
|
-
true
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
def goto(ref)
|
|
197
|
-
entry = find_entry(ref)
|
|
198
|
-
unless entry
|
|
199
|
-
puts "Entry not found: #{ref}"
|
|
200
|
-
return false
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
unless entry.tmux_session
|
|
204
|
-
puts "No tmux session recorded for this entry"
|
|
205
|
-
return false
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
target = entry.tmux_session
|
|
209
|
-
target += ":#{entry.tmux_window}" if entry.tmux_window
|
|
210
|
-
target += ".#{entry.tmux_pane}" if entry.tmux_pane
|
|
211
|
-
|
|
212
|
-
system('tmux', 'switch-client', '-t', target)
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
def add_manual(description, hiiro: nil)
|
|
216
|
-
add(
|
|
217
|
-
description: description,
|
|
218
|
-
source: 'manual',
|
|
219
|
-
cmd: hiiro&.full_command,
|
|
220
|
-
)
|
|
221
|
-
true
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
# Main tracking method - gathers all context and saves if changed
|
|
225
|
-
def track_command(cmd:, hiiro: nil, force: false)
|
|
226
|
-
context = gather_context
|
|
227
|
-
return nil unless context[:task] || force # Only track if in a task context (unless forced)
|
|
228
|
-
|
|
229
|
-
context[:cmd] = cmd
|
|
230
|
-
context[:source] = 'auto'
|
|
231
|
-
|
|
232
|
-
# Check if state changed
|
|
233
|
-
unless force
|
|
234
|
-
new_state = build_state_key(context)
|
|
235
|
-
return nil if state_unchanged?(new_state)
|
|
236
|
-
save_last_state(new_state)
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
add(**context)
|
|
240
|
-
end
|
|
241
|
-
|
|
242
|
-
def add(
|
|
243
|
-
description: nil, cmd: nil, source: nil, pwd: nil,
|
|
244
|
-
tmux_session: nil, tmux_window: nil, tmux_pane: nil,
|
|
245
|
-
git_branch: nil, git_sha: nil, git_origin_sha: nil, git_worktree: nil,
|
|
246
|
-
task: nil, subtask: nil, app: nil
|
|
247
|
-
)
|
|
248
|
-
context = gather_context
|
|
249
|
-
|
|
250
|
-
entry_data = {
|
|
251
|
-
'id' => generate_id,
|
|
252
|
-
'timestamp' => Time.now.iso8601,
|
|
253
|
-
'description' => description,
|
|
254
|
-
'cmd' => cmd,
|
|
255
|
-
'pwd' => pwd || context[:pwd],
|
|
256
|
-
'source' => source,
|
|
257
|
-
'tmux_session' => tmux_session || context[:tmux_session],
|
|
258
|
-
'tmux_window' => tmux_window || context[:tmux_window],
|
|
259
|
-
'tmux_pane' => tmux_pane || context[:tmux_pane],
|
|
260
|
-
'git_branch' => git_branch || context[:git_branch],
|
|
261
|
-
'git_sha' => git_sha || context[:git_sha],
|
|
262
|
-
'git_origin_sha' => git_origin_sha || context[:git_origin_sha],
|
|
263
|
-
'git_worktree' => git_worktree || context[:git_worktree],
|
|
264
|
-
'task' => task || context[:task],
|
|
265
|
-
'subtask' => subtask || context[:subtask],
|
|
266
|
-
'app' => app || context[:app],
|
|
267
|
-
}.compact
|
|
268
|
-
|
|
269
|
-
all = load_raw_entries
|
|
270
|
-
all << entry_data
|
|
271
|
-
save_entries(all)
|
|
272
|
-
|
|
273
|
-
Entry.new(entry_data)
|
|
274
|
-
end
|
|
275
|
-
|
|
276
|
-
def clear!
|
|
277
|
-
save_entries([])
|
|
278
|
-
File.delete(LAST_STATE_FILE) if File.exist?(LAST_STATE_FILE)
|
|
279
|
-
@entries = nil
|
|
280
|
-
end
|
|
281
|
-
|
|
282
|
-
private
|
|
283
|
-
|
|
284
|
-
def gather_context
|
|
285
|
-
{
|
|
286
|
-
pwd: Dir.pwd,
|
|
287
|
-
tmux_session: current_tmux_session,
|
|
288
|
-
tmux_window: current_tmux_window,
|
|
289
|
-
tmux_pane: current_tmux_pane,
|
|
290
|
-
git_branch: current_git_branch,
|
|
291
|
-
git_sha: current_git_sha,
|
|
292
|
-
git_origin_sha: current_git_origin_sha,
|
|
293
|
-
git_worktree: current_git_worktree,
|
|
294
|
-
task: current_task_name,
|
|
295
|
-
subtask: current_subtask_name,
|
|
296
|
-
app: current_app_name,
|
|
297
|
-
}
|
|
298
|
-
end
|
|
299
|
-
|
|
300
|
-
def build_state_key(context)
|
|
301
|
-
context.slice(
|
|
302
|
-
:pwd, :tmux_session, :tmux_window, :tmux_pane,
|
|
303
|
-
:git_branch, :git_sha, :git_origin_sha, :git_worktree,
|
|
304
|
-
:task, :subtask, :app
|
|
305
|
-
).compact
|
|
306
|
-
end
|
|
307
|
-
|
|
308
|
-
def state_unchanged?(new_state)
|
|
309
|
-
return false unless File.exist?(LAST_STATE_FILE)
|
|
310
|
-
last_state = YAML.load_file(LAST_STATE_FILE) rescue {}
|
|
311
|
-
last_state == new_state.transform_keys(&:to_s)
|
|
312
|
-
end
|
|
313
|
-
|
|
314
|
-
def save_last_state(state)
|
|
315
|
-
File.write(LAST_STATE_FILE, state.transform_keys(&:to_s).to_yaml)
|
|
316
|
-
end
|
|
317
|
-
|
|
318
|
-
def current_value_for(field)
|
|
319
|
-
context = gather_context
|
|
320
|
-
context[field]
|
|
321
|
-
end
|
|
322
|
-
|
|
323
|
-
def find_entry(ref)
|
|
324
|
-
return nil unless ref
|
|
325
|
-
|
|
326
|
-
if ref.to_s.match?(/^\d+$/)
|
|
327
|
-
idx = ref.to_i - 1
|
|
328
|
-
reversed = entries.reverse
|
|
329
|
-
return reversed[idx] if idx >= 0 && idx < reversed.length
|
|
330
|
-
end
|
|
331
|
-
|
|
332
|
-
entries.find { |e| e.id == ref.to_s }
|
|
333
|
-
end
|
|
334
|
-
|
|
335
|
-
def generate_id
|
|
336
|
-
Time.now.strftime('%Y%m%d%H%M%S') + '-' + rand(10000).to_s.rjust(4, '0')
|
|
337
|
-
end
|
|
338
|
-
|
|
339
|
-
def ensure_history_dir
|
|
340
|
-
FileUtils.mkdir_p(HISTORY_DIR) unless Dir.exist?(HISTORY_DIR)
|
|
341
|
-
|
|
342
|
-
unless File.exist?(HISTORY_FILE)
|
|
343
|
-
File.write(HISTORY_FILE, [].to_yaml)
|
|
344
|
-
end
|
|
345
|
-
end
|
|
346
|
-
|
|
347
|
-
def load_raw_entries
|
|
348
|
-
YAML.load_file(HISTORY_FILE) || []
|
|
349
|
-
rescue => e
|
|
350
|
-
[]
|
|
351
|
-
end
|
|
352
|
-
|
|
353
|
-
def load_entries
|
|
354
|
-
load_raw_entries.map { |data| Entry.new(data) }
|
|
355
|
-
end
|
|
356
|
-
|
|
357
|
-
def save_entries(entries_data)
|
|
358
|
-
# Keep only last 1000 entries to prevent unbounded growth
|
|
359
|
-
entries_data = entries_data.last(1000) if entries_data.length > 1000
|
|
360
|
-
File.write(HISTORY_FILE, entries_data.to_yaml)
|
|
361
|
-
end
|
|
362
|
-
|
|
363
|
-
# Tmux context
|
|
364
|
-
def current_tmux_session
|
|
365
|
-
return nil unless ENV['TMUX']
|
|
366
|
-
`tmux display-message -p '#S'`.strip rescue nil
|
|
367
|
-
end
|
|
368
|
-
|
|
369
|
-
def current_tmux_window
|
|
370
|
-
return nil unless ENV['TMUX']
|
|
371
|
-
`tmux display-message -p '#I'`.strip rescue nil
|
|
372
|
-
end
|
|
373
|
-
|
|
374
|
-
def current_tmux_pane
|
|
375
|
-
return nil unless ENV['TMUX']
|
|
376
|
-
ENV['TMUX_PANE'] || `tmux display-message -p '#P'`.strip rescue nil
|
|
377
|
-
end
|
|
378
|
-
|
|
379
|
-
# Git context
|
|
380
|
-
def current_git_branch
|
|
381
|
-
git_helper.branch_current
|
|
382
|
-
end
|
|
383
|
-
|
|
384
|
-
def current_git_sha
|
|
385
|
-
git_helper.commit('HEAD')
|
|
386
|
-
end
|
|
387
|
-
|
|
388
|
-
def current_git_origin_sha
|
|
389
|
-
branch = current_git_branch
|
|
390
|
-
return nil unless branch
|
|
391
|
-
git_helper.commit("origin/#{branch}")
|
|
392
|
-
end
|
|
393
|
-
|
|
394
|
-
def current_git_worktree
|
|
395
|
-
git_helper.root
|
|
396
|
-
end
|
|
397
|
-
|
|
398
|
-
def git_helper
|
|
399
|
-
@git_helper ||= Git.new(nil, Dir.pwd)
|
|
400
|
-
end
|
|
401
|
-
|
|
402
|
-
# Task context
|
|
403
|
-
def current_task_name
|
|
404
|
-
env = environment
|
|
405
|
-
return nil unless env
|
|
406
|
-
|
|
407
|
-
task = env.task
|
|
408
|
-
return nil unless task
|
|
409
|
-
|
|
410
|
-
task.subtask? ? task.parent_name : task.name
|
|
411
|
-
end
|
|
412
|
-
|
|
413
|
-
def current_subtask_name
|
|
414
|
-
env = environment
|
|
415
|
-
return nil unless env
|
|
416
|
-
|
|
417
|
-
task = env.task
|
|
418
|
-
return nil unless task
|
|
419
|
-
return nil unless task.subtask?
|
|
420
|
-
|
|
421
|
-
task.short_name
|
|
422
|
-
end
|
|
423
|
-
|
|
424
|
-
def current_app_name
|
|
425
|
-
env = environment
|
|
426
|
-
return nil unless env
|
|
427
|
-
|
|
428
|
-
pwd = Dir.pwd
|
|
429
|
-
task = env.task
|
|
430
|
-
return nil unless task
|
|
431
|
-
|
|
432
|
-
tree = env.find_tree(task.tree_name)
|
|
433
|
-
return nil unless tree
|
|
434
|
-
|
|
435
|
-
# Check if pwd is in an app directory
|
|
436
|
-
env.all_apps.each do |app|
|
|
437
|
-
app_path = app.resolve(tree.path)
|
|
438
|
-
if pwd == app_path || pwd.start_with?(app_path + '/')
|
|
439
|
-
return app.name
|
|
440
|
-
end
|
|
441
|
-
end
|
|
442
|
-
|
|
443
|
-
nil
|
|
444
|
-
end
|
|
445
|
-
|
|
446
|
-
def environment
|
|
447
|
-
@environment ||= begin
|
|
448
|
-
Environment.current
|
|
449
|
-
rescue NameError
|
|
450
|
-
nil
|
|
451
|
-
end
|
|
452
|
-
end
|
|
453
|
-
end
|
|
454
|
-
end
|