hiiro 0.1.24 → 0.1.26

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.
data/bin/h-task CHANGED
@@ -3,8 +3,8 @@
3
3
  require "hiiro"
4
4
  require "fileutils"
5
5
  require "yaml"
6
+ require "pathname"
6
7
 
7
- Hiiro.load_env
8
8
  hiiro = Hiiro.init(*ARGV, plugins: [Tmux])
9
9
 
10
10
  class TaskManager
@@ -20,7 +20,7 @@ class TaskManager
20
20
  puts "Subcommands:"
21
21
  puts " list, ls List all worktrees and their active tasks"
22
22
  puts " start TASK [APP] Start a task (reuses available worktree or creates new)"
23
- puts " switch TASK Switch to an existing task"
23
+ puts " switch [TASK] Switch to an existing task (interactive if no task given)"
24
24
  puts " app APP_NAME Open a tmux window for an app in current worktree"
25
25
  puts " apps List configured apps from apps.yml"
26
26
  puts " save Save current tmux session info for this task"
@@ -42,17 +42,37 @@ class TaskManager
42
42
 
43
43
  current = current_task
44
44
  active, available = trees.partition { |tree_name| task_for_tree(tree_name) }
45
+ sessions = tmux_sessions
45
46
 
46
47
  active.each do |tree_name|
47
48
  task = task_for_tree(tree_name)
48
49
  marker = (current && current[:tree] == tree_name) ? "*" : " "
49
- puts format("%s %-20s => %s", marker, tree_name, task)
50
+
51
+ # Check if there's a tmux session for this task
52
+ session_name = session_name_for(task)
53
+ has_session = sessions.include?(session_name)
54
+ session_marker = has_session ? "+" : " "
55
+ session_info = has_session ? " (#{session_name})" : ""
56
+
57
+ puts format("%s%s %-20s => %s%s", marker, session_marker, tree_name, task, session_info)
50
58
  end
51
59
 
52
60
  puts if active.any? && available.any?
53
61
 
54
62
  available.each do |tree_name|
55
- puts format(" %-20s (available)", tree_name)
63
+ puts format(" %-20s (available)", tree_name)
64
+ end
65
+
66
+ # List tmux sessions without associated tasks
67
+ associated_sessions = active.map { |tree_name| session_name_for(task_for_tree(tree_name)) }
68
+ unassociated_sessions = sessions - associated_sessions
69
+
70
+ if unassociated_sessions.any?
71
+ puts
72
+ puts "Tmux sessions without tasks:"
73
+ unassociated_sessions.each do |session|
74
+ puts format(" %s", session)
75
+ end
56
76
  end
57
77
  end
58
78
 
@@ -136,7 +156,13 @@ class TaskManager
136
156
  end
137
157
 
138
158
  # Start working on a task
139
- def switch_task(task_name)
159
+ def switch_task(task_name = nil)
160
+ # If no task name provided, use interactive selection
161
+ if task_name.nil? || task_name.empty?
162
+ task_name = select_task_interactive
163
+ return false unless task_name
164
+ end
165
+
140
166
  tree, task = assignments.find { |tree, task| task.start_with?(task_name) } || []
141
167
 
142
168
  unless task
@@ -150,6 +176,34 @@ class TaskManager
150
176
  true
151
177
  end
152
178
 
179
+ # Interactive task selection using sk
180
+ def select_task_interactive
181
+ active_tasks = trees.select { |tree_name| task_for_tree(tree_name) }
182
+
183
+ if active_tasks.empty?
184
+ puts "No active tasks found."
185
+ return nil
186
+ end
187
+
188
+ # Build selection lines
189
+ lines = active_tasks.map do |tree_name|
190
+ task = task_for_tree(tree_name)
191
+ "#{tree_name.ljust(20)} => #{task}"
192
+ end
193
+
194
+ require 'open3'
195
+ selected, status = Open3.capture2('sk', stdin_data: lines.join("\n"))
196
+
197
+ if status.success? && !selected.strip.empty?
198
+ # Parse the selected line to extract the task name
199
+ if selected =~ /=>\s*(\S+)/
200
+ return $1
201
+ end
202
+ end
203
+
204
+ nil
205
+ end
206
+
153
207
  # Open an app window within the current tree
154
208
  def open_app(app_name)
155
209
  current = current_task
@@ -337,7 +391,12 @@ class TaskManager
337
391
  puts printable_apps.keys.sort.map{|k| format("%#{longest_app_name}s => %s", k, printable_apps[k]) }
338
392
  exit 1
339
393
  when 1
340
- print File.join(tree_root, apps_config[matching_apps.first])
394
+ if Pathname.pwd.ascend.any? { |parent| parent.equal?(tree_root) }
395
+ pwd_to_root = Pathname.new(tree_root).relative_path_from(Pathname.pwd)
396
+ print File.join(pwd_to_root, apps_config[matching_apps.first])
397
+ else
398
+ print File.join(tree_root, apps_config[matching_apps.first])
399
+ end
341
400
  exit 0
342
401
  else
343
402
  puts "Multiple matches found:"
@@ -346,8 +405,6 @@ class TaskManager
346
405
  end
347
406
  end
348
407
 
349
- private
350
-
351
408
  def printable_apps
352
409
  apps_config.transform_keys(&:to_s)
353
410
  end
@@ -506,6 +563,12 @@ class TaskManager
506
563
  { task: task_name, tree: tree, session: session }
507
564
  end
508
565
 
566
+ def task_name
567
+ task = current_task
568
+
569
+ (task || {})[:task]
570
+ end
571
+
509
572
  def switch_to_task(task_name, tree, app: nil)
510
573
  session = session_name_for(task_name)
511
574
  base_path = tree_path(tree)
@@ -623,6 +686,13 @@ class TaskManager
623
686
  { 'index' => idx, 'name' => name, 'path' => path }
624
687
  }
625
688
  end
689
+
690
+ # Get all tmux sessions
691
+ def tmux_sessions
692
+ output = `tmux list-sessions -F '\#{session_name}' 2>/dev/null`
693
+ return [] unless $?.success?
694
+ output.lines.map(&:strip)
695
+ end
626
696
  end
627
697
 
628
698
  # Create task manager instance
@@ -633,16 +703,15 @@ hiiro.add_subcmd(:edit) { system(ENV['EDITOR'] || 'nvim', __FILE__) }
633
703
  hiiro.add_subcmd(:list) { tasks.list_trees }
634
704
  hiiro.add_subcmd(:ls) { tasks.list_trees }
635
705
  hiiro.add_subcmd(:start) { |task_name, app=nil| tasks.start_task(task_name, app: app) }
636
- hiiro.add_subcmd(:switch) { |task_name| tasks.switch_task(task_name) }
706
+ hiiro.add_subcmd(:switch) { |task_name=nil| tasks.switch_task(task_name) }
637
707
  hiiro.add_subcmd(:app) { |app_name| tasks.open_app(app_name) }
638
- hiiro.add_subcmd(:path) { |app_name=nil, task=nil| tasks.app_path(app_name, task: task) }
708
+ hiiro.add_subcmd(:path) { |app_name=nil, task=nil| tasks.app_path(app_name, task: task || tasks.task_name) }
639
709
  hiiro.add_subcmd(:cd) { |*args| tasks.cd_app(*args) }
640
710
  hiiro.add_subcmd(:apps) { tasks.list_configured_apps }
641
711
  hiiro.add_subcmd(:status) { tasks.status }
642
712
  hiiro.add_subcmd(:st) { tasks.status }
643
713
  hiiro.add_subcmd(:save) { tasks.save_current }
644
714
  hiiro.add_subcmd(:stop) { |task_name=nil| task_name ? tasks.stop_task(task_name) : tasks.stop_current }
645
-
646
- hiiro.add_default { tasks.help }
715
+ hiiro.add_subcmd(:current) { print tasks.task_name }
647
716
 
648
717
  hiiro.run
data/bin/h-video CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
- load '/Users/unixsuperhero/bin/h'
2
+
3
+ require 'hiiro'
3
4
 
4
5
  o = Hiiro.init(*ARGV)
5
6
 
data/bin/h-vim ADDED
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'hiiro'
4
+
5
+ BASE_DIR = CONFIG_DIR = File.join(Dir.home, '.config/nvim')
6
+ TMUX_SESSION_NAME = 'vim'
7
+
8
+ o = Hiiro.init(*ARGV, plugins: [Tmux, Pins, Project])
9
+
10
+ o.add_subcmd(:edit) { |*args|
11
+ nvim = ENV['EDITOR']
12
+ system(nvim, __FILE__)
13
+ }
14
+
15
+ o.add_subcmd(:path) { |*args|
16
+ print BASE_DIR
17
+ }
18
+
19
+ o.add_subcmd(:init, :config) { |*args|
20
+ nvim = ENV['EDITOR']
21
+
22
+ Dir.chdir(BASE_DIR)
23
+ system(nvim, 'init.lua')
24
+ }
25
+
26
+ o.add_subcmd(:skf) { |*args|
27
+ Dir.chdir(BASE_DIR)
28
+ system('skf', *args)
29
+ }
30
+
31
+ o.add_subcmd(:rg) { |*args|
32
+ Dir.chdir(BASE_DIR)
33
+ system('rg', '-S', *args)
34
+ }
35
+
36
+ o.add_subcmd(:rgall) { |*args|
37
+ Dir.chdir(BASE_DIR)
38
+ system('rg', '-S', '--no-ignore-vcs', *args)
39
+ }
40
+
41
+ o.add_subcmd(:session) { |*args|
42
+ Dir.chdir(BASE_DIR)
43
+
44
+ o.switch_to_tmux_session(TMUX_SESSION_NAME)
45
+ }
46
+
47
+ # basically call :session
48
+ o.add_subcmd(:tmux) { |*args|
49
+ Dir.chdir(BASE_DIR)
50
+
51
+ o.switch_to_tmux_session(TMUX_SESSION_NAME)
52
+ }
53
+
54
+ if o.runnable?
55
+ o.run
56
+ else
57
+ puts format('ERROR: %s', :no_runnable_found)
58
+
59
+ puts
60
+
61
+ o.help
62
+ end
63
+
data/lib/hiiro/history.rb CHANGED
@@ -7,7 +7,7 @@ class Hiiro
7
7
  HISTORY_FILE = File.join(Dir.home, '.config/hiiro/history.yml')
8
8
 
9
9
  class Entry
10
- attr_reader :id, :timestamp, :source, :cmd, :description, :pwd
10
+ attr_reader :id, :timestamp, :source, :cmd, :description
11
11
  attr_reader :tmux_session, :tmux_window, :tmux_pane
12
12
  attr_reader :git_branch, :git_worktree
13
13
  attr_reader :task, :subtask
@@ -18,7 +18,6 @@ class Hiiro
18
18
  @timestamp = data['timestamp']
19
19
  @source = data['source']
20
20
  @cmd = data['cmd']
21
- @pwd = data['pwd'] || Dir.pwd
22
21
  @description = data['description']
23
22
  @tmux_session = data['tmux_session']
24
23
  @tmux_window = data['tmux_window']
@@ -35,7 +34,6 @@ class Hiiro
35
34
  'timestamp' => timestamp,
36
35
  'source' => source,
37
36
  'cmd' => cmd,
38
- 'pwd' => pwd,
39
37
  'description' => description,
40
38
  'tmux_session' => tmux_session,
41
39
  'tmux_window' => tmux_window,
data/lib/hiiro/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Hiiro
2
- VERSION = "0.1.24"
2
+ VERSION = "0.1.26"
3
3
  end
data/script/sync ADDED
@@ -0,0 +1,107 @@
1
+ #!/bin/bash
2
+
3
+ # Compare directories interactively
4
+ # This script compares bin/ and .config/hiiro/ with their counterparts in $OTHER_DIR
5
+
6
+ set -e
7
+
8
+ REPO_DIR="$(cd "$(dirname "$0")/.." && pwd)"
9
+ cd "$REPO_DIR"
10
+
11
+ OTHER_DIR="$REPO_DIR/../home/"
12
+
13
+ echo "Comparing directories with $OTHER_DIR"
14
+ echo "Repository: $REPO_DIR ($0)"
15
+ echo "Other Dir: $OTHER_DIR"
16
+ echo
17
+
18
+ # Function to prompt for y/n
19
+ ask_yn() {
20
+ local prompt="$1"
21
+ local response
22
+ read -p "$prompt (y/N): " response </dev/tty
23
+ [[ "$response" =~ ^[yY]$ ]]
24
+ }
25
+
26
+ # Function to copy file with confirmation
27
+ handle_copy() {
28
+ local src="$1"
29
+ local dst="$2"
30
+ local direction="$3"
31
+
32
+ echo
33
+ echo "File only in $direction: $src"
34
+ if ask_yn "Copy to $dst?"; then
35
+ mkdir -p "$(dirname "$dst")"
36
+ cp -v "$src" "$dst"
37
+ echo "Copied!"
38
+ else
39
+ echo "Skipped."
40
+ fi
41
+ }
42
+
43
+ # Function to diff files with confirmation
44
+ handle_diff() {
45
+ local file1="$1"
46
+ local file2="$2"
47
+
48
+ echo
49
+ echo "DIFFERENT: $file1 <=> $file2"
50
+ if ask_yn "Open in vim diff?"; then
51
+ nvim -d "$file1" "$file2" </dev/tty >/dev/tty
52
+ else
53
+ echo "Skipped."
54
+ fi
55
+ }
56
+
57
+ # Process diff output for a directory pair
58
+ process_diff() {
59
+ local repo_dir="$1"
60
+ local home_dir="$2"
61
+
62
+ echo "========================================="
63
+ echo "Comparing: $repo_dir <=> $home_dir"
64
+ echo "========================================="
65
+
66
+ # Use diff -qrs and process line by line
67
+ while IFS= read -r line; do
68
+ if [[ "$line" =~ ^"Only in "(.*)": "(.*) ]]; then
69
+ local dir="${BASH_REMATCH[1]}"
70
+ local file="${BASH_REMATCH[2]}"
71
+ local full_path="$dir/$file"
72
+
73
+ # Determine if it's only in repo or only in home
74
+ if [[ "$full_path" == "$repo_dir"* ]]; then
75
+ # Only in repo, offer to copy to home
76
+ local rel_path="${full_path#$repo_dir}"
77
+ handle_copy "$full_path" "$home_dir$rel_path" "repository"
78
+ else
79
+ # Only in home, offer to copy to repo
80
+ local rel_path="${full_path#$home_dir}"
81
+ handle_copy "$full_path" "$repo_dir$rel_path" "home directory"
82
+ fi
83
+
84
+ elif [[ "$line" =~ ^"Files "(.+)" and "(.+)" differ"$ ]]; then
85
+ local file1="${BASH_REMATCH[1]}"
86
+ local file2="${BASH_REMATCH[2]}"
87
+ handle_diff "$file1" "$file2"
88
+ fi
89
+ done < <(diff -qrs "$repo_dir" "$home_dir" 2>/dev/null)
90
+
91
+ echo
92
+ }
93
+
94
+ # Compare bin directories
95
+ if [[ -d "bin" && -d "$OTHER_DIR/bin" ]]; then
96
+ process_diff "$REPO_DIR/bin/" "$OTHER_DIR/bin/"
97
+ fi
98
+
99
+ # Compare .config directories
100
+ if [[ -d ".config" && -d "$OTHER_DIR/.config" ]]; then
101
+ process_diff "$REPO_DIR/.config/hiiro/" "$OTHER_DIR/.config/hiiro/"
102
+ fi
103
+
104
+ echo "========================================="
105
+ echo "Sync complete!"
106
+ echo "========================================="
107
+
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hiiro
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.24
4
+ version: 0.1.26
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Toyota
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-01-28 00:00:00.000000000 Z
11
+ date: 2026-01-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pry
@@ -38,17 +38,31 @@ files:
38
38
  - LICENSE
39
39
  - README.md
40
40
  - Rakefile
41
+ - bin/g-pr
41
42
  - bin/h
42
43
  - bin/h-branch
43
44
  - bin/h-buffer
45
+ - bin/h-dot
46
+ - bin/h-dotfiles
47
+ - bin/h-home
48
+ - bin/h-html
44
49
  - bin/h-link
50
+ - bin/h-mic
51
+ - bin/h-note
45
52
  - bin/h-pane
46
53
  - bin/h-plugin
47
54
  - bin/h-pr
55
+ - bin/h-pr-monitor
56
+ - bin/h-pr-watch
57
+ - bin/h-project
58
+ - bin/h-runtask
59
+ - bin/h-serve
48
60
  - bin/h-session
61
+ - bin/h-sha
49
62
  - bin/h-subtask
50
63
  - bin/h-task
51
64
  - bin/h-video
65
+ - bin/h-vim
52
66
  - bin/h-window
53
67
  - bin/h-wtree
54
68
  - docs/README.md
@@ -72,6 +86,7 @@ files:
72
86
  - script/compare
73
87
  - script/install
74
88
  - script/publish
89
+ - script/sync
75
90
  homepage: https://github.com/unixsuperhero/hiiro
76
91
  licenses:
77
92
  - MIT