hiiro 0.1.318 → 0.1.319
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/CHANGELOG.md +17 -1
- data/bin/h-link +88 -4
- data/bin/h-pr +4 -2
- data/bin/h-session +17 -0
- data/lib/hiiro/db.rb +6 -1
- data/lib/hiiro/link.rb +5 -3
- data/lib/hiiro/pinned_pr_manager.rb +1 -1
- data/lib/hiiro/queue.rb +10 -1
- data/lib/hiiro/tasks.rb +17 -3
- 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: a85db7d6d14d76d893352967e47f40fcf6b6c47a2b21ad9ec640bbeafda0da6d
|
|
4
|
+
data.tar.gz: 62410ab480fc9638df31aaf0a49c48068c81d322dde3fd498b9351e464581a92
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e858ca0c1b70057dce4a6738e21d73512eb42ad6d12adc155a9bbc061b78e7e1d6a78b63dd651c8fcbe79a7d7e6196d79d5efd8c65b61f805e3e5c410e9aa26d
|
|
7
|
+
data.tar.gz: 75c7733377ca2db3a5f91470c720fa1e7b391cbc007aa66da8a3b5df2f3f17403f63d2f8e9a2359898e3b507ec514399489583173a6d20ce24d869dcdc7e8da6
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.319] - 2026-04-01
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `h queue ls [STATUS]` — filter by status with prefix matching (e.g. `h queue ls run` → running tasks); composable with existing `-s` flag
|
|
7
|
+
- `h session sh <session> [cmd...]` — open a new window in another tmux session (runs shell or given command there, then switches)
|
|
8
|
+
- `h task sh -s SESSION [cmd...]` — run task shell/command in a new window in a specific tmux session
|
|
9
|
+
- `h link tags` — list all known link tags; `h link tags tag1 tag2...` filters links by tags (prefix matching)
|
|
10
|
+
- `h link ls` now shows tags inline with colored badges
|
|
11
|
+
- `h link rm` / `h link remove` subcommand — remove links by number, shorthand, or fuzzy select
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- `h pr update` and `h pr ls -u` skip closed/merged PRs — only active PRs are refreshed
|
|
15
|
+
- `h link add -h` now shows help instead of adding `-h` as a URL
|
|
16
|
+
- `h db remigrate` no longer imports duplicate links — skips rows with an already-existing URL
|
|
17
|
+
- Add unique constraint on `links.url` to prevent duplicates at database level
|
|
18
|
+
|
|
3
19
|
## [0.1.318] - 2026-04-01
|
|
4
20
|
|
|
5
21
|
### Added
|
|
@@ -241,4 +257,4 @@
|
|
|
241
257
|
## [0.1.295]
|
|
242
258
|
|
|
243
259
|
### Changed
|
|
244
|
-
- Filter logic changes for PR management
|
|
260
|
+
- Filter logic changes for PR management
|
data/bin/h-link
CHANGED
|
@@ -238,9 +238,16 @@ lm = LinkManager.new
|
|
|
238
238
|
|
|
239
239
|
Hiiro.run(*ARGV, plugins: [Pins], links_file: lm.links_file) do
|
|
240
240
|
add_subcmd(:add) do |*add_args|
|
|
241
|
-
|
|
241
|
+
vals = opts.parse!(add_args)
|
|
242
|
+
|
|
243
|
+
if vals.help?
|
|
244
|
+
puts "Usage: h link add [<url>] [description] [--shorthand <alias>] [--tag <tag> ...]"
|
|
245
|
+
puts
|
|
246
|
+
puts opts.help_text
|
|
247
|
+
exit 0
|
|
248
|
+
end
|
|
242
249
|
|
|
243
|
-
if
|
|
250
|
+
if vals.args.empty?
|
|
244
251
|
new_links = lm.edit_links
|
|
245
252
|
|
|
246
253
|
new_links.each do |link|
|
|
@@ -262,8 +269,7 @@ Hiiro.run(*ARGV, plugins: [Pins], links_file: lm.links_file) do
|
|
|
262
269
|
exit 0
|
|
263
270
|
end
|
|
264
271
|
|
|
265
|
-
url =
|
|
266
|
-
vals = opts.parse!(add_args)
|
|
272
|
+
url = vals.args.shift
|
|
267
273
|
description = vals.args.join(' ')
|
|
268
274
|
tags = (Array(vals.tag) + Array(vals.tags)).uniq.reject(&:empty?)
|
|
269
275
|
|
|
@@ -419,6 +425,51 @@ Hiiro.run(*ARGV, plugins: [Pins], links_file: lm.links_file) do
|
|
|
419
425
|
end
|
|
420
426
|
end
|
|
421
427
|
|
|
428
|
+
add_subcmd(:rm) do |*rm_args|
|
|
429
|
+
links = lm.load_links
|
|
430
|
+
|
|
431
|
+
if links.empty?
|
|
432
|
+
puts "No links saved."
|
|
433
|
+
exit 0
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
if rm_args.empty?
|
|
437
|
+
lines = lm.load_link_hash(links)
|
|
438
|
+
selected = fuzzyfind(lines.keys)
|
|
439
|
+
next if selected.nil? || selected.strip.empty?
|
|
440
|
+
link = lines[selected.strip]
|
|
441
|
+
next unless link
|
|
442
|
+
Hiiro::Link.where(url: link.url).delete
|
|
443
|
+
lm.save_yaml_backup
|
|
444
|
+
puts "Removed: #{link.url}"
|
|
445
|
+
else
|
|
446
|
+
rm_args.each do |ref|
|
|
447
|
+
idx, link = lm.find_link_by_ref(ref, links)
|
|
448
|
+
if link.nil?
|
|
449
|
+
# Try URL substring match
|
|
450
|
+
matches = lm.search_links(links, ref)
|
|
451
|
+
if matches.length == 1
|
|
452
|
+
link = matches.first
|
|
453
|
+
elsif matches.length > 1
|
|
454
|
+
puts "Ambiguous: #{matches.length} links match '#{ref}'. Use a number or shorthand."
|
|
455
|
+
matches.each_with_index { |l, i| puts " #{l.display_string(i)}" }
|
|
456
|
+
next
|
|
457
|
+
else
|
|
458
|
+
puts "Link not found: #{ref}"
|
|
459
|
+
next
|
|
460
|
+
end
|
|
461
|
+
end
|
|
462
|
+
Hiiro::Link.where(url: link.url).delete
|
|
463
|
+
lm.save_yaml_backup
|
|
464
|
+
puts "Removed: #{link.url}"
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
add_subcmd(:remove) do |*remove_args|
|
|
470
|
+
run_subcmd(:rm, *remove_args)
|
|
471
|
+
end
|
|
472
|
+
|
|
422
473
|
add_subcmd(:open) do |*open_args|
|
|
423
474
|
links = lm.load_links
|
|
424
475
|
|
|
@@ -476,6 +527,39 @@ Hiiro.run(*ARGV, plugins: [Pins], links_file: lm.links_file) do
|
|
|
476
527
|
system('open', url)
|
|
477
528
|
end
|
|
478
529
|
|
|
530
|
+
add_subcmd(:tags) do |*tag_args|
|
|
531
|
+
all_known = Hiiro::Tag.where(taggable_type: 'Link').distinct.pluck(:name).sort
|
|
532
|
+
|
|
533
|
+
if tag_args.empty?
|
|
534
|
+
if all_known.empty?
|
|
535
|
+
puts "No tags found."
|
|
536
|
+
else
|
|
537
|
+
all_known.each { |t| puts t }
|
|
538
|
+
end
|
|
539
|
+
next
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
# Prefix-match the given args against known tags
|
|
543
|
+
matched_tags = tag_args.flat_map { |arg|
|
|
544
|
+
all_known.select { |t| t.start_with?(arg) }
|
|
545
|
+
}.uniq
|
|
546
|
+
|
|
547
|
+
if matched_tags.empty?
|
|
548
|
+
puts "No tags match: #{tag_args.join(', ')}"
|
|
549
|
+
next
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
# Batch-load all link tags to avoid N+1
|
|
553
|
+
tagged_ids = Hiiro::Tag.where(taggable_type: 'Link', name: matched_tags).map { |t| t.taggable_id.to_i }.uniq
|
|
554
|
+
links = lm.load_links.select { |link| tagged_ids.include?(link.id) }
|
|
555
|
+
|
|
556
|
+
if links.empty?
|
|
557
|
+
puts "No links tagged with: #{matched_tags.join(', ')}"
|
|
558
|
+
else
|
|
559
|
+
links.each_with_index { |link, idx| puts link.display_string(idx) }
|
|
560
|
+
end
|
|
561
|
+
end
|
|
562
|
+
|
|
479
563
|
add_subcmd(:paste) do |*paste_args|
|
|
480
564
|
url = `pbpaste`.strip
|
|
481
565
|
|
data/bin/h-pr
CHANGED
|
@@ -243,7 +243,8 @@ Hiiro.run(*ARGV, plugins: [Pins]) do
|
|
|
243
243
|
next puts "No tracked PRs" if pinned.empty?
|
|
244
244
|
|
|
245
245
|
if opts.update
|
|
246
|
-
|
|
246
|
+
active = pinned.select(&:active?)
|
|
247
|
+
puts "Updating status for #{active.length} active PR(s)..."
|
|
247
248
|
pinned_manager.refresh_all_status(pinned, force: true)
|
|
248
249
|
pinned_manager.save_pinned(pinned)
|
|
249
250
|
puts
|
|
@@ -331,7 +332,8 @@ Hiiro.run(*ARGV, plugins: [Pins]) do
|
|
|
331
332
|
next
|
|
332
333
|
end
|
|
333
334
|
|
|
334
|
-
|
|
335
|
+
active = pinned.select(&:active?)
|
|
336
|
+
puts "Updating status for #{active.length} active PR(s) (#{pinned.length - active.length} closed/merged skipped)..."
|
|
335
337
|
pinned_manager.refresh_all_status(pinned, force: opts.force_update)
|
|
336
338
|
pinned_manager.save_pinned(pinned)
|
|
337
339
|
puts "Done."
|
data/bin/h-session
CHANGED
|
@@ -97,6 +97,23 @@ Hiiro.run(*ARGV, tasks: true, plugins: [Pins]) do
|
|
|
97
97
|
tmux.open_session(name)
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
add_subcmd(:sh) { |session_name=nil, *cmd|
|
|
101
|
+
require 'shellwords'
|
|
102
|
+
session_name = resolve(:session, session_name)
|
|
103
|
+
unless session_name
|
|
104
|
+
puts "Usage: h session sh <session> [cmd...]"
|
|
105
|
+
next
|
|
106
|
+
end
|
|
107
|
+
unless tmux.session_exists?(session_name)
|
|
108
|
+
puts "Session '#{session_name}' does not exist"
|
|
109
|
+
next
|
|
110
|
+
end
|
|
111
|
+
window_args = ['tmux', 'new-window', '-t', session_name]
|
|
112
|
+
window_args << cmd.shelljoin unless cmd.empty?
|
|
113
|
+
system(*window_args)
|
|
114
|
+
tmux.open_session(session_name)
|
|
115
|
+
}
|
|
116
|
+
|
|
100
117
|
add_subcmd(:select) do
|
|
101
118
|
selected = resolve(:session)
|
|
102
119
|
print selected if selected
|
data/lib/hiiro/db.rb
CHANGED
|
@@ -172,7 +172,12 @@ class Hiiro
|
|
|
172
172
|
links = data.is_a?(Array) ? data : []
|
|
173
173
|
links.each do |row|
|
|
174
174
|
next unless row.is_a?(Hash)
|
|
175
|
-
|
|
175
|
+
normalized = row.transform_keys(&:to_s)
|
|
176
|
+
url = normalized['url'].to_s
|
|
177
|
+
next if url.empty? || connection[:links].where(url: url).count > 0
|
|
178
|
+
connection[:links].insert(normalized)
|
|
179
|
+
rescue => e
|
|
180
|
+
warn "Hiiro::DB: skipping link row (#{e.class}: #{e.message.lines.first&.strip})"
|
|
176
181
|
end
|
|
177
182
|
bak(path)
|
|
178
183
|
rescue => e
|
data/lib/hiiro/link.rb
CHANGED
|
@@ -7,7 +7,7 @@ class Hiiro
|
|
|
7
7
|
def self.create_table!(db)
|
|
8
8
|
db.create_table?(:links) do
|
|
9
9
|
primary_key :id
|
|
10
|
-
String :url, null: false
|
|
10
|
+
String :url, null: false, unique: true
|
|
11
11
|
String :description
|
|
12
12
|
String :shorthand
|
|
13
13
|
String :tags_json # JSON array of tag strings
|
|
@@ -15,7 +15,7 @@ class Hiiro
|
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
def tags = Hiiro::
|
|
18
|
+
def tags = Hiiro::Tag.for(self).map(&:name)
|
|
19
19
|
def tags=(v) ; self.tags_json = Hiiro::DB::JSON.dump(v); end
|
|
20
20
|
|
|
21
21
|
def self.find_by_shorthand(s)
|
|
@@ -44,7 +44,9 @@ class Hiiro
|
|
|
44
44
|
num = index ? "#{(index + 1).to_s.rjust(3)}." : ""
|
|
45
45
|
shorthand_str = shorthand ? " [#{shorthand}]" : ""
|
|
46
46
|
desc_str = description.to_s.empty? ? "" : " - #{description}"
|
|
47
|
-
|
|
47
|
+
link_tags = tags
|
|
48
|
+
tags_str = link_tags.any? ? " \e[30;104m#{link_tags.join(' ')}\e[0m" : ""
|
|
49
|
+
"#{num}#{shorthand_str} #{url}#{desc_str}#{tags_str}".strip
|
|
48
50
|
end
|
|
49
51
|
|
|
50
52
|
def to_h
|
|
@@ -272,7 +272,7 @@ class Hiiro
|
|
|
272
272
|
end
|
|
273
273
|
|
|
274
274
|
def refresh_all_status(prs, force: false)
|
|
275
|
-
prs_to_refresh = prs.select { |pr| needs_refresh?(pr, force: force) }
|
|
275
|
+
prs_to_refresh = prs.select { |pr| pr.active? && needs_refresh?(pr, force: force) }
|
|
276
276
|
|
|
277
277
|
if prs_to_refresh.empty?
|
|
278
278
|
puts "All PRs recently checked (within last 2 minutes). Use -U to force update." unless force
|
data/lib/hiiro/queue.rb
CHANGED
|
@@ -471,7 +471,16 @@ class Hiiro
|
|
|
471
471
|
flag(:all, short: :a, desc: 'Show all tasks without limit; use pager if output exceeds terminal height')
|
|
472
472
|
option(:status, short: :s, desc: "Filter by status (#{Queue::STATUSES.join(', ')}); repeat for multiple", multi: true)
|
|
473
473
|
end
|
|
474
|
-
|
|
474
|
+
statuses = Array(opts.status)
|
|
475
|
+
opts.args.each do |arg|
|
|
476
|
+
matched = Queue::STATUSES.select { |s| s.start_with?(arg) }
|
|
477
|
+
if matched.empty?
|
|
478
|
+
puts "Unknown status: '#{arg}' (valid: #{Queue::STATUSES.join(', ')})"
|
|
479
|
+
next
|
|
480
|
+
end
|
|
481
|
+
statuses.concat(matched)
|
|
482
|
+
end
|
|
483
|
+
lines = q.list_lines(all: opts.all, statuses: statuses.uniq)
|
|
475
484
|
if lines.empty?
|
|
476
485
|
puts "No tasks"
|
|
477
486
|
next
|
data/lib/hiiro/tasks.rb
CHANGED
|
@@ -1072,7 +1072,12 @@ class Hiiro
|
|
|
1072
1072
|
end
|
|
1073
1073
|
|
|
1074
1074
|
h.add_subcmd(:sh) do |*raw_args|
|
|
1075
|
-
|
|
1075
|
+
require 'shellwords'
|
|
1076
|
+
sh_opts_block = proc {
|
|
1077
|
+
instance_exec(&task_opts_block)
|
|
1078
|
+
option(:session, short: :s, desc: 'Run in a new window in this tmux session')
|
|
1079
|
+
}
|
|
1080
|
+
opts = Hiiro::Options.parse(raw_args, &sh_opts_block)
|
|
1076
1081
|
task, positional = resolve_task.call(opts, opts.args)
|
|
1077
1082
|
unless task
|
|
1078
1083
|
puts "Not in a task session (use -t or -f to specify)"
|
|
@@ -1080,8 +1085,17 @@ class Hiiro
|
|
|
1080
1085
|
end
|
|
1081
1086
|
tree = tm.environment.find_tree(task.tree_name)
|
|
1082
1087
|
path = tree ? tree.path : File.join(Hiiro::WORK_DIR, task.tree_name)
|
|
1083
|
-
|
|
1084
|
-
|
|
1088
|
+
|
|
1089
|
+
if opts.session
|
|
1090
|
+
session_name = opts.session
|
|
1091
|
+
window_args = ['tmux', 'new-window', '-t', session_name, '-c', path]
|
|
1092
|
+
window_args << positional.shelljoin unless positional.empty?
|
|
1093
|
+
system(*window_args)
|
|
1094
|
+
tmux_client.open_session(session_name)
|
|
1095
|
+
else
|
|
1096
|
+
Dir.chdir(path)
|
|
1097
|
+
positional.empty? ? exec(ENV['SHELL'] || 'zsh') : exec(*positional)
|
|
1098
|
+
end
|
|
1085
1099
|
end
|
|
1086
1100
|
|
|
1087
1101
|
h.add_subcmd(:status) { tm.status }
|
data/lib/hiiro/version.rb
CHANGED