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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 18a7b5de061af02dfb8d60a1dd50eaf91e790937976ab598120439f99170bad3
4
- data.tar.gz: ca79b1405eaec4fb5ba0b8d43bd48ce6ff1b52136a806e08ddb7b4aa09bfb055
3
+ metadata.gz: a85db7d6d14d76d893352967e47f40fcf6b6c47a2b21ad9ec640bbeafda0da6d
4
+ data.tar.gz: 62410ab480fc9638df31aaf0a49c48068c81d322dde3fd498b9351e464581a92
5
5
  SHA512:
6
- metadata.gz: e32866b673ce6ec24d27c453653853c6a594aa331d714712b19a817a2138ea56dac74eecc546289c9405819c5631cc1f8f5970b98f7d2d10828ccfce707a6487
7
- data.tar.gz: 27f8163a315a7aa03e95cea498b57aadca0f22f2b2e09fdfe38f3cc760706be2ebf4cf54732f500c8b0a4174589ce56d44231fb8937f8a6820cd9e6f3c9250e8
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
- links = lm.load_links
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 add_args.empty?
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 = add_args.shift
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
- puts "Updating status for #{pinned.length} PR(s)..."
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
- puts "Updating status for #{pinned.length} PR(s)..."
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
- connection[:links].insert(row.transform_keys(&:to_s))
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::DB::JSON.load(tags_json) || []
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
- "#{num}#{shorthand_str} #{url}#{desc_str}".strip
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
- lines = q.list_lines(all: opts.all, statuses: opts.status)
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
- opts = Hiiro::Options.parse(raw_args, &task_opts_block)
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
- Dir.chdir(path)
1084
- positional.empty? ? exec(ENV['SHELL'] || 'zsh') : exec(*positional)
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
@@ -1,3 +1,3 @@
1
1
  class Hiiro
2
- VERSION = "0.1.318"
2
+ VERSION = "0.1.319"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hiiro
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.318
4
+ version: 0.1.319
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Toyota