hiiro 0.1.24 → 0.1.25

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-note ADDED
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "hiiro"
4
+ require "open3"
5
+ require "pathname"
6
+ require "tmpdir"
7
+
8
+ hiiro = Hiiro.init(*ARGV)
9
+
10
+ # Helper class for directory operations
11
+ class Directory
12
+ # Temporarily change to a directory, run block, then change back
13
+ def self.temp_cd(dir, &block)
14
+ original_dir = Dir.pwd
15
+ Dir.chdir(dir)
16
+ yield
17
+ ensure
18
+ Dir.chdir(original_dir)
19
+ end
20
+
21
+ # Print directory tree with indentation
22
+ def self.dir_tree(dir, prefix = "", base_dir = nil)
23
+ base_dir ||= dir
24
+ entries = Dir.entries(dir).reject { |e| e.start_with?('.') }.sort
25
+
26
+ entries.each do |entry|
27
+ path = File.join(dir, entry)
28
+ relative_path = Pathname.new(path).relative_path_from(base_dir)
29
+
30
+ if File.directory?(path)
31
+ puts "#{prefix}#{entry}/"
32
+ dir_tree(path, prefix + " ", base_dir)
33
+ else
34
+ puts "#{prefix}#{relative_path}"
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ notes_dir = ENV['NOTES_DIR'] || File.join(Dir.home, 'notes')
41
+
42
+ hiiro.add_subcmd(:edit) { |*args|
43
+ system(ENV['EDITOR'] || 'nvim', __FILE__)
44
+ }
45
+
46
+ hiiro.add_subcmd(:new) { |*args|
47
+ unless Dir.exist?(notes_dir)
48
+ Dir.mkdir(notes_dir)
49
+ end
50
+
51
+ Directory.temp_cd(notes_dir) {
52
+ system(ENV['EDITOR'] || 'vim', '-O', *args)
53
+ }
54
+ }
55
+
56
+ hiiro.add_subcmd(:categories) { |*args|
57
+ unless Dir.exist?(notes_dir)
58
+ Dir.mkdir(notes_dir)
59
+ end
60
+
61
+ Directory.temp_cd(notes_dir) {
62
+ Directory.dir_tree(notes_dir)
63
+ }
64
+ }
65
+
66
+ hiiro.add_subcmd(:tagged_files) { |*args|
67
+ unless Dir.exist?(notes_dir)
68
+ Dir.mkdir(notes_dir)
69
+ end
70
+
71
+ Directory.temp_cd(notes_dir) {
72
+ args.each do |tag|
73
+ tag = tag.sub(/^#+/, '')
74
+ tag_pattern = format('#%s\\b', tag)
75
+ cmd = format('rg -S %s %s', tag_pattern.inspect, notes_dir)
76
+ out, err, status = Open3.capture3(cmd)
77
+
78
+ if status.success?
79
+ puts format('#%s', tag)
80
+ puts format('#%s', tag).gsub(/./, ?-)
81
+ out.lines.each do |line|
82
+ file = line.sub(/:.*/, '')
83
+ puts Pathname.new(file).relative_path_from(notes_dir)
84
+ end
85
+ end
86
+
87
+ puts
88
+ end
89
+ }
90
+ }
91
+
92
+ hiiro.add_subcmd(:ls) { |*args|
93
+ unless Dir.exist?(notes_dir)
94
+ Dir.mkdir(notes_dir)
95
+ end
96
+
97
+ Directory.temp_cd(notes_dir) {
98
+ Dir.glob('**/*').each(&method(:puts))
99
+ }
100
+ }
101
+
102
+ hiiro.add_subcmd(:tags) { |*args|
103
+ unless Dir.exist?(notes_dir)
104
+ Dir.mkdir(notes_dir)
105
+ end
106
+
107
+ Directory.temp_cd(notes_dir) {
108
+ tag_pattern = '#[^#[:space:]]+'
109
+ cmd = format('rg -SI %s %s', tag_pattern.inspect, notes_dir)
110
+ out, err, status = Open3.capture3(cmd)
111
+
112
+ if status.success?
113
+ puts out.scan(Regexp.new(tag_pattern)).sort.uniq
114
+ end
115
+ }
116
+ }
117
+
118
+ hiiro.run
119
+
data/bin/h-plugin CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'json'
4
4
 
5
- load File.join(Dir.home, 'bin', 'h')
5
+ require 'hiiro'
6
6
 
7
7
  BASE_DIR = File.join(Dir.home, '.config/hiiro/plugins')
8
8
  o = Hiiro.init(*ARGV, plugins: [Tmux, Pins], dir: BASE_DIR)
data/bin/h-pr CHANGED
@@ -1,12 +1,13 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require "hiiro"
4
+ require "time"
4
5
  require "fileutils"
5
6
  require "yaml"
6
7
  require "json"
7
8
 
8
9
  Hiiro.load_env
9
- hiiro = Hiiro.init(*ARGV, plugins: [Task])
10
+ hiiro = Hiiro.init(*ARGV, plugins: [Task, Tmux, Pins])
10
11
 
11
12
  class PRManager
12
13
  attr_reader :hiiro
@@ -243,8 +244,77 @@ class PRManager
243
244
  end
244
245
  end
245
246
 
247
+ WATCH_BLOCK = lambda { |original_pr_number=nil, *args|
248
+ watch = hiiro.get_value(:watch)
249
+ fail_fast = hiiro.get_value(:fail_fast)
250
+
251
+ pr_valid = false
252
+ pr_number = nil
253
+ if !original_pr_number.nil? && pin_value = hiiro.pins.get(original_pr_number)
254
+ pr_valid = true
255
+ pr_number = pin_value
256
+ end
257
+
258
+ pr_args = [pr_valid ? pr_number : original_pr_number].compact
259
+ args = [*pr_args, *args]
260
+
261
+ base_cmd = %w[gh pr checks]
262
+ base_cmd << '--watch' if watch
263
+ base_cmd << '--fail-fast' if fail_fast
264
+
265
+ command = [
266
+ *base_cmd,
267
+ *args,
268
+ ]
269
+
270
+ puts command: command, base_cmd: base_cmd, args: args;
271
+
272
+ result = system(*command)
273
+
274
+ pr_info = JSON.parse(`gh pr view #{pr_args.first} --json url,number,title`)
275
+ pr_url = pr_info['url']
276
+ pr_number = pr_info['number']
277
+ pr_title = pr_info['title']
278
+
279
+ if result
280
+ system('say', 'pr good')
281
+ system('terminal-notifier', '-title', 'PR Good', '-message', "##{pr_number}: #{pr_title}", '-open', pr_url)
282
+ else
283
+ system('say', 'pr bad')
284
+ system('terminal-notifier', '-title', 'PR Bad', '-message', "##{pr_number}: #{pr_title}", '-open', pr_url)
285
+ end
286
+ }
287
+
246
288
  manager = PRManager.new(hiiro)
247
289
 
290
+ hiiro.add_subcmd(:check, &WATCH_BLOCK)
291
+ hiiro.add_subcmd(:watch, watch: true, &WATCH_BLOCK)
292
+ hiiro.add_subcmd(:fwatch, watch: true, fail_fast: true, &WATCH_BLOCK)
293
+
294
+ hiiro.add_subcmd(:number) { |*args|
295
+ stdout = `gh pr view`
296
+
297
+ number = stdout[/number:\s*(\d+)/, 1]
298
+
299
+ if number
300
+ print number
301
+ else
302
+ puts stdout
303
+ end
304
+ }
305
+
306
+ hiiro.add_subcmd(:link) { |*args|
307
+ stdout = `gh pr view`
308
+
309
+ number = stdout[/number:\s*(\d+)/, 1]
310
+
311
+ if number
312
+ print ['https://github.com/instacart/carrot/pull/', number].join
313
+ else
314
+ puts stdout
315
+ end
316
+ }
317
+
248
318
  hiiro.add_subcmd(:edit) { system(ENV['EDITOR'] || 'nvim', __FILE__) }
249
319
  hiiro.add_subcmd(:save) { |pr_number=nil| manager.save(pr_number) }
250
320
  hiiro.add_subcmd(:history) { |*args| manager.history(args) }
@@ -252,6 +322,6 @@ hiiro.add_subcmd(:current) { manager.current }
252
322
  hiiro.add_subcmd(:open) { |pr_number=nil| manager.open(pr_number) }
253
323
  hiiro.add_subcmd(:view) { |pr_number=nil| manager.view(pr_number) }
254
324
 
255
- hiiro.add_default { manager.help }
325
+ # hiiro.add_default { manager.help }
256
326
 
257
327
  hiiro.run
data/bin/h-pr-monitor ADDED
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+
3
+ h-watchprs
data/bin/h-pr-watch ADDED
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+
3
+ h-watchprs
data/bin/h-project ADDED
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "hiiro"
4
+
5
+ o = Hiiro.init(*ARGV)
6
+
7
+ # Helper to start or attach to a tmux session
8
+ def start_tmux_session(session_name)
9
+ session_name = session_name.to_s
10
+
11
+ unless system('tmux', 'has-session', '-t', session_name)
12
+ system('tmux', 'new', '-d', '-A', '-s', session_name)
13
+ end
14
+
15
+ if ENV['TMUX']
16
+ system('tmux', 'switchc', '-t', session_name)
17
+ elsif ENV['NVIM']
18
+ puts "Can't attach to tmux inside a vim terminal"
19
+ else
20
+ system('tmux', 'new', '-A', '-s', session_name)
21
+ end
22
+ end
23
+
24
+ # Get project directories from ~/proj/
25
+ def project_dirs
26
+ Dir.glob(File.join(Dir.home, 'proj', '*/')).map { |path|
27
+ [File.basename(path), path]
28
+ }.to_h
29
+ end
30
+
31
+ # Get projects from config file
32
+ def projects_from_config
33
+ projects_file = File.join(Dir.home, '.config/hiiro', 'projects.yml')
34
+
35
+ return {} unless File.exist?(projects_file)
36
+
37
+ require 'yaml'
38
+ YAML.safe_load_file(projects_file)
39
+ end
40
+
41
+ # === OPEN PROJECT (default) ===
42
+
43
+ o.add_subcmd(:open) { |project_name|
44
+ re = /#{project_name}/i
45
+
46
+ conf_matches = (projects_from_config || {}).select { |k, v| k.match?(re) }
47
+ dir_matches = (project_dirs || {}).select { |proj, path| proj.match?(re) }
48
+
49
+ matches = dir_matches.merge(conf_matches)
50
+ if matches.count > 1
51
+ matches = matches.select { |name, path| name == project_name }
52
+ end
53
+
54
+ case matches.count
55
+ when 0
56
+ name = 'proj'
57
+ path = File.join(Dir.home, 'proj')
58
+
59
+ unless Dir.exist?(path)
60
+ puts "Error: #{path.inspect} does not exist"
61
+ exit 1
62
+ end
63
+
64
+ puts "changing dir: #{path}"
65
+ Dir.chdir(path)
66
+
67
+ start_tmux_session(name)
68
+ when 1
69
+ name, path = matches.first
70
+
71
+ puts "changing dir: #{path}"
72
+ Dir.chdir(path)
73
+
74
+ start_tmux_session(name)
75
+ when (2..)
76
+ puts "ERROR: Multiple matches found"
77
+ puts
78
+ puts "Matches:"
79
+ matches.each { |name, path|
80
+ puts format(" %s: %s", name, path)
81
+ }
82
+ end
83
+ }
84
+
85
+ # === LIST PROJECTS ===
86
+
87
+ o.add_subcmd(:list) { |*args|
88
+ dirs = project_dirs
89
+ conf = projects_from_config
90
+
91
+ all_projects = dirs.merge(conf)
92
+
93
+ puts "Projects:"
94
+ puts
95
+ all_projects.keys.sort.each do |name|
96
+ path = all_projects[name]
97
+ source = conf.key?(name) ? '[config]' : '[dir]'
98
+ puts format(" %-12s %-8s %s", name, source, path)
99
+ end
100
+ }
101
+
102
+ o.add_subcmd(:ls) { |*args|
103
+ o.run_subcmd(:list, *args)
104
+ }
105
+
106
+ # === SHOW CONFIG FILE ===
107
+
108
+ o.add_subcmd(:config) { |*args|
109
+ projects_file = File.join(Dir.home, '.config/hiiro', 'projects.yml')
110
+
111
+ if File.exist?(projects_file)
112
+ puts File.read(projects_file)
113
+ else
114
+ puts "No config file found at: #{projects_file}"
115
+ puts
116
+ puts "Create it with YAML format:"
117
+ puts " project_name: /path/to/project"
118
+ end
119
+ }
120
+
121
+ # === EDIT CONFIG FILE ===
122
+
123
+ o.add_subcmd(:edit) { |*args|
124
+ projects_file = File.join(Dir.home, '.config/hiiro', 'projects.yml')
125
+ editor = ENV['EDITOR'] || 'vim'
126
+
127
+ # Create config dir if needed
128
+ config_dir = File.dirname(projects_file)
129
+ Dir.mkdir(config_dir) unless Dir.exist?(config_dir)
130
+
131
+ # Create empty file if it doesn't exist
132
+ unless File.exist?(projects_file)
133
+ File.write(projects_file, "# Project aliases\n# project_name: /path/to/project\n")
134
+ end
135
+
136
+ exec(editor, projects_file)
137
+ }
138
+
139
+ # === HELP ===
140
+
141
+ o.add_subcmd(:help) { |*args|
142
+ puts <<~HELP
143
+ h-project - Project directory and tmux session manager
144
+
145
+ USAGE:
146
+ h-project <project_name> Open project (fuzzy match) and start tmux session
147
+ h-project open <project_name> Same as above
148
+ h-project list List all known projects
149
+ h-project ls Alias for list
150
+ h-project config Show config file contents
151
+ h-project edit Edit config file
152
+
153
+ SOURCES:
154
+ Projects are discovered from two sources:
155
+ 1. Directories in ~/proj/
156
+ 2. Entries in ~/.config/hiiro/projects.yml
157
+
158
+ CONFIG FORMAT (projects.yml):
159
+ project_name: /path/to/project
160
+ another: /some/other/path
161
+
162
+ MATCHING:
163
+ Project names are matched using regex (case insensitive).
164
+ If multiple matches are found, an exact match is preferred.
165
+ If still ambiguous, all matches are displayed.
166
+ HELP
167
+ }
168
+
169
+ if o.runnable?
170
+ o.run
171
+ else
172
+ o.run_subcmd(:help)
173
+ end
data/bin/h-runtask ADDED
@@ -0,0 +1,79 @@
1
+ #!/bin/bash
2
+
3
+ stamp="$(date +"%Y%m%d%H%M%S")"
4
+ logfile="tmp/run-task.$stamp"
5
+ task_logfile="tmp/run-task.status.$stamp"
6
+ # task_name="generate-sso-config.retailers-domain.customers-backend.customers"
7
+ task_name="generate-sso-config.partners-domain.customers-backend.customers"
8
+ full_sha="$(git log -1 | head -1 | sed 's/commit //;s/[[:space:]].*//')"
9
+ sha="$(echo -n "$full_sha" | sed 's/\(.......\).*/\1/')"
10
+ trusted=${2:-false}
11
+ method_choice=${1:-id}
12
+
13
+ if test $method_choice = "id"
14
+ then
15
+ trusted=false
16
+ SSO_CONFIG="{\"retailer_name\":\"Joshtestidtoken\",\"method_choice\":\"id_token\",\"client_id\":\"updated client id\",\"popup\":true,\"trusted\":${trusted},\"scope\":\"default\",\"activation_status\":\"active\",\"store_config_id\":1459,\"code_challenge_method\":\"S256\",\"user_info_mapping\":{\"provider_uid\":\"sub\",\"first_name\":\"user_first_name\",\"last_name\":\"user_last_name\",\"email\":\"email\",\"phone\":\"default\",\"membership_number\":\"rewards_number\"},\"token_endpoint\":\"http://example.com/token\",\"authorization_endpoint\":\"http://example.com/authorization\",\"logout_endpoint\":\"http://example.com/logout\"}"
17
+ else
18
+ SSO_CONFIG="{\"retailer_name\":\"Joshuitest\",\"method_choice\":\"userinfo endpoint\",\"client_id\":\"updated client id\",\"popup\":true,\"trusted\":${trusted},\"scope\":\"default\",\"activation_status\":\"active\",\"store_config_id\":1459,\"code_challenge_method\":\"S256\",\"user_info_mapping\":{\"provider_uid\":\"sub\",\"first_name\":\"user_first_name\",\"last_name\":\"user_last_name\",\"email\":\"email\",\"phone\":\"default\",\"membership_number\":\"rewards_number\"},\"token_endpoint\":\"http://example.com/token\",\"authorization_endpoint\":\"http://example.com/authorization\",\"logout_endpoint\":\"http://example.com/logout\",\"user_info_endpoint\":\"http://example.com/user_info\"}"
19
+ fi
20
+
21
+ echo "stamp: $stamp"
22
+ echo "logfile: $logfile"
23
+ echo "task_name: $task_name"
24
+ echo "full_sha: $full_sha"
25
+ echo "sha: $sha"
26
+ echo "trusted: $trusted"
27
+ echo "method_choice: $method_choice"
28
+ echo "SSO_CONFIG: $SSO_CONFIG"
29
+ echo
30
+ echo "$SSO_CONFIG" | jq
31
+
32
+ mkdir -pv tmp
33
+
34
+ if test -f tmp/run-task
35
+ then
36
+ mv -v tmp/run-task "$logfile"
37
+ fi
38
+
39
+ if ! test -f $logfile
40
+ then
41
+ isc task -e production run $task_name $full_sha --command 'bundle exec rake partners_domain:sso_config:generate' --conf "SSO_CONFIG=${SSO_CONFIG}" | tee $logfile
42
+ fi
43
+
44
+ uuid="$(egrep -o '\b[0-9a-f-]{36}\b' $logfile | head -1)"
45
+
46
+
47
+ echo
48
+ echo "UUID: $uuid"
49
+ echo
50
+
51
+ while true
52
+ do
53
+ sleep 5
54
+ clear
55
+ isc -e production task runs "$task_name" "$uuid" | tee $task_logfile
56
+ echo
57
+ egrep 'Status: (pending|in-progress)' $task_logfile || break
58
+ done
59
+
60
+ log_url="$(egrep -o 'Datadog Logs:.*' $task_logfile | sed 's/Datadog Logs: //')"
61
+
62
+ if [[ ! -z $log_url ]]
63
+ then
64
+ open "$log_url"
65
+ fi
66
+
67
+ if egrep 'Status: failed' $task_logfile
68
+ then
69
+ say "task failed"
70
+ else
71
+ if egrep 'Status: succe' $task_logfile
72
+ then
73
+ say "task successful"
74
+ fi
75
+ fi
76
+
77
+ echo
78
+ rm -v $logfile $task_logfile
79
+
data/bin/h-serve ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'hiiro'
4
+
5
+ system('miniserve', '-p', '1111')
6
+
data/bin/h-session CHANGED
@@ -1,6 +1,26 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- load File.join(Dir.home, 'bin', 'h')
3
+ require 'hiiro'
4
+ require 'pry'
5
+ require 'rspec'
6
+
7
+ # $> h session
8
+ #
9
+ # Subcommand required!
10
+ #
11
+ # Possible subcommands:
12
+ # pin (subcommand) /Users/unixsuperhero/.config/hiiro/plugins/pins.rb:12
13
+ # edit (subcommand) /Users/unixsuperhero/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/hiiro-0.1.8/lib/hiiro.rb:14
14
+ # ses (subcommand) /Users/unixsuperhero/bin/h-session:99
15
+ # ls (subcommand) /Users/unixsuperhero/bin/h-session:111
16
+ # new (subcommand) /Users/unixsuperhero/bin/h-session:115
17
+ # kill (subcommand) /Users/unixsuperhero/bin/h-session:119
18
+ # attach (subcommand) /Users/unixsuperhero/bin/h-session:123
19
+ # rename (subcommand) /Users/unixsuperhero/bin/h-session:127
20
+ # switch (subcommand) /Users/unixsuperhero/bin/h-session:131
21
+ # detach (subcommand) /Users/unixsuperhero/bin/h-session:135
22
+ # has (subcommand) /Users/unixsuperhero/bin/h-session:139
23
+ # info (subcommand) /Users/unixsuperhero/bin/h-session:143
4
24
 
5
25
  o = Hiiro.init(*ARGV, plugins: [Pins])
6
26
 
data/bin/h-sha ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'hiiro'
4
+
5
+ BASE_DIR = File.join(Dir.home, 'work/carrot')
6
+
7
+ Hiiro.init(*ARGV, plugins: [Tmux, Pins, Project]) { |hiiro|
8
+
9
+ }.run
10
+
data/bin/h-subtask CHANGED
@@ -5,6 +5,7 @@
5
5
  require "hiiro"
6
6
  require "fileutils"
7
7
  require "yaml"
8
+ require "time"
8
9
 
9
10
  Hiiro.load_env
10
11
  hiiro = Hiiro.init(*ARGV, plugins: [Tmux, Task])
@@ -16,6 +17,6 @@ hiiro.add_subcmd(:ls) { tasks.list_subtasks }
16
17
  hiiro.add_subcmd(:new) { |subtask_name| tasks.new_subtask(subtask_name) }
17
18
  hiiro.add_subcmd(:switch) { |subtask_name| tasks.switch_subtask(subtask_name) }
18
19
 
19
- hiiro.add_default { tasks.subtask_help }
20
+ # hiiro.add_default { tasks.subtask_help }
20
21
 
21
22
  hiiro.run
data/bin/h-task CHANGED
@@ -3,6 +3,7 @@
3
3
  require "hiiro"
4
4
  require "fileutils"
5
5
  require "yaml"
6
+ require "pathname"
6
7
 
7
8
  Hiiro.load_env
8
9
  hiiro = Hiiro.init(*ARGV, plugins: [Tmux])
@@ -20,7 +21,7 @@ class TaskManager
20
21
  puts "Subcommands:"
21
22
  puts " list, ls List all worktrees and their active tasks"
22
23
  puts " start TASK [APP] Start a task (reuses available worktree or creates new)"
23
- puts " switch TASK Switch to an existing task"
24
+ puts " switch [TASK] Switch to an existing task (interactive if no task given)"
24
25
  puts " app APP_NAME Open a tmux window for an app in current worktree"
25
26
  puts " apps List configured apps from apps.yml"
26
27
  puts " save Save current tmux session info for this task"
@@ -42,17 +43,37 @@ class TaskManager
42
43
 
43
44
  current = current_task
44
45
  active, available = trees.partition { |tree_name| task_for_tree(tree_name) }
46
+ sessions = tmux_sessions
45
47
 
46
48
  active.each do |tree_name|
47
49
  task = task_for_tree(tree_name)
48
50
  marker = (current && current[:tree] == tree_name) ? "*" : " "
49
- puts format("%s %-20s => %s", marker, tree_name, task)
51
+
52
+ # Check if there's a tmux session for this task
53
+ session_name = session_name_for(task)
54
+ has_session = sessions.include?(session_name)
55
+ session_marker = has_session ? "+" : " "
56
+ session_info = has_session ? " (#{session_name})" : ""
57
+
58
+ puts format("%s%s %-20s => %s%s", marker, session_marker, tree_name, task, session_info)
50
59
  end
51
60
 
52
61
  puts if active.any? && available.any?
53
62
 
54
63
  available.each do |tree_name|
55
- puts format(" %-20s (available)", tree_name)
64
+ puts format(" %-20s (available)", tree_name)
65
+ end
66
+
67
+ # List tmux sessions without associated tasks
68
+ associated_sessions = active.map { |tree_name| session_name_for(task_for_tree(tree_name)) }
69
+ unassociated_sessions = sessions - associated_sessions
70
+
71
+ if unassociated_sessions.any?
72
+ puts
73
+ puts "Tmux sessions without tasks:"
74
+ unassociated_sessions.each do |session|
75
+ puts format(" %s", session)
76
+ end
56
77
  end
57
78
  end
58
79
 
@@ -136,7 +157,13 @@ class TaskManager
136
157
  end
137
158
 
138
159
  # Start working on a task
139
- def switch_task(task_name)
160
+ def switch_task(task_name = nil)
161
+ # If no task name provided, use interactive selection
162
+ if task_name.nil? || task_name.empty?
163
+ task_name = select_task_interactive
164
+ return false unless task_name
165
+ end
166
+
140
167
  tree, task = assignments.find { |tree, task| task.start_with?(task_name) } || []
141
168
 
142
169
  unless task
@@ -150,6 +177,34 @@ class TaskManager
150
177
  true
151
178
  end
152
179
 
180
+ # Interactive task selection using sk
181
+ def select_task_interactive
182
+ active_tasks = trees.select { |tree_name| task_for_tree(tree_name) }
183
+
184
+ if active_tasks.empty?
185
+ puts "No active tasks found."
186
+ return nil
187
+ end
188
+
189
+ # Build selection lines
190
+ lines = active_tasks.map do |tree_name|
191
+ task = task_for_tree(tree_name)
192
+ "#{tree_name.ljust(20)} => #{task}"
193
+ end
194
+
195
+ require 'open3'
196
+ selected, status = Open3.capture2('sk', stdin_data: lines.join("\n"))
197
+
198
+ if status.success? && !selected.strip.empty?
199
+ # Parse the selected line to extract the task name
200
+ if selected =~ /=>\s*(\S+)/
201
+ return $1
202
+ end
203
+ end
204
+
205
+ nil
206
+ end
207
+
153
208
  # Open an app window within the current tree
154
209
  def open_app(app_name)
155
210
  current = current_task
@@ -337,7 +392,8 @@ class TaskManager
337
392
  puts printable_apps.keys.sort.map{|k| format("%#{longest_app_name}s => %s", k, printable_apps[k]) }
338
393
  exit 1
339
394
  when 1
340
- print File.join(tree_root, apps_config[matching_apps.first])
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])
341
397
  exit 0
342
398
  else
343
399
  puts "Multiple matches found:"
@@ -623,6 +679,13 @@ class TaskManager
623
679
  { 'index' => idx, 'name' => name, 'path' => path }
624
680
  }
625
681
  end
682
+
683
+ # Get all tmux sessions
684
+ def tmux_sessions
685
+ output = `tmux list-sessions -F '\#{session_name}' 2>/dev/null`
686
+ return [] unless $?.success?
687
+ output.lines.map(&:strip)
688
+ end
626
689
  end
627
690
 
628
691
  # Create task manager instance
@@ -633,7 +696,7 @@ hiiro.add_subcmd(:edit) { system(ENV['EDITOR'] || 'nvim', __FILE__) }
633
696
  hiiro.add_subcmd(:list) { tasks.list_trees }
634
697
  hiiro.add_subcmd(:ls) { tasks.list_trees }
635
698
  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) }
699
+ hiiro.add_subcmd(:switch) { |task_name=nil| tasks.switch_task(task_name) }
637
700
  hiiro.add_subcmd(:app) { |app_name| tasks.open_app(app_name) }
638
701
  hiiro.add_subcmd(:path) { |app_name=nil, task=nil| tasks.app_path(app_name, task: task) }
639
702
  hiiro.add_subcmd(:cd) { |*args| tasks.cd_app(*args) }