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.
- checksums.yaml +4 -4
- data/bin/g-pr +327 -0
- data/bin/h-branch +18 -0
- data/bin/h-dot +35 -0
- data/bin/h-dotfiles +42 -0
- data/bin/h-home +62 -0
- data/bin/h-html +380 -0
- data/bin/h-link +243 -125
- data/bin/h-mic +93 -0
- data/bin/h-note +119 -0
- data/bin/h-plugin +1 -1
- data/bin/h-pr +72 -2
- data/bin/h-pr-monitor +3 -0
- data/bin/h-pr-watch +3 -0
- data/bin/h-project +173 -0
- data/bin/h-runtask +79 -0
- data/bin/h-serve +6 -0
- data/bin/h-session +21 -1
- data/bin/h-sha +10 -0
- data/bin/h-subtask +2 -1
- data/bin/h-task +81 -12
- data/bin/h-video +2 -1
- data/bin/h-vim +63 -0
- data/lib/hiiro/history.rb +1 -3
- data/lib/hiiro/version.rb +1 -1
- data/script/sync +107 -0
- metadata +17 -2
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
|
|
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
|
-
|
|
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("
|
|
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
|
-
|
|
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
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
|
|
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
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.
|
|
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-
|
|
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
|