hiiro 0.1.370 → 0.1.371
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 +6 -0
- data/docs/t.md +2 -2
- data/lib/hiiro/herdr.rb +17 -0
- data/lib/hiiro/task_cli.rb +52 -9
- 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: 4910c0070a20706820a27abe45f728d8751da4d9d2297cacd05d26836c96dce1
|
|
4
|
+
data.tar.gz: 3173f0e134ef9353383405aff0dfd72ebe502a72ec3a2f149000dd6d221e6116
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e0fb10ffa72f026a25c0cba9ebe6f926f7d6c7504ea1d51f729519feed61918cba7fb2cf0da814c322f787c90ec532b667ea22c9e2c3bbfacb414758fb30cbc8
|
|
7
|
+
data.tar.gz: 5192581c3c17c73e5d7781f3cfd736e16678d89be171ca1676dfedd7e49c53e007bef1efe5b1f80bf61e827113fad82b07206564ce9779ccc97a1cbec28f4449
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.1.371] - 2026-09-14
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- `t ls` lists the panes under every open workspace with directory, agent state, and foreground command via `herdr pane process-info`; `t switch` accepts an exact pane ID and its picker includes panes
|
|
9
|
+
- `Hiiro::Herdr#process_info` and `Pane#foreground_command`
|
|
10
|
+
|
|
5
11
|
## [0.1.370] - 2026-09-14
|
|
6
12
|
|
|
7
13
|
### Added
|
data/docs/t.md
CHANGED
|
@@ -29,7 +29,7 @@ Task names may be `parent/child` for subtasks. `t new NAME` creates exactly `NAM
|
|
|
29
29
|
|
|
30
30
|
| Command | Behavior |
|
|
31
31
|
|---|---|
|
|
32
|
-
| `t`, `t ls`, `t list` | Lists all tasks alphabetically with open todo counts, e.g. `prez (3)`, plus `next:` and `waiting:` text. When Herdr is running, tasks whose workspace is open get an `@` marker, and live workspaces that belong to no task are listed afterwards with their IDs |
|
|
32
|
+
| `t`, `t ls`, `t list` | Lists all tasks alphabetically with open todo counts, e.g. `prez (3)`, plus `next:` and `waiting:` text. When Herdr is running, tasks whose workspace is open get an `@` marker, every open workspace lists its panes as `ID ~/dir agent (status) command` (from `herdr pane process-info`), and live workspaces that belong to no task are listed afterwards with their IDs |
|
|
33
33
|
| `t show [TASK]` | Status, home, next action, waiting text, code directory, workspace label, resources, documents, and todos |
|
|
34
34
|
| `t current [TASK]` | Prints the resolved task name; never saves |
|
|
35
35
|
| `t use TASK` (alias `pin`) | Saves TASK as the fallback for when no workspace or directory identifies a task |
|
|
@@ -112,7 +112,7 @@ t pane run [TASK] ID|LABEL [--] COMMAND...
|
|
|
112
112
|
t pane split [TASK] ID|LABEL [--direction right|down] [--directory PATH] [--command COMMAND]
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
-
These require a running Herdr server. The workspace label is the task session or name with `.` replaced by `_`. `switch` also accepts the name of a live Herdr workspace that belongs to no task, exactly or by unique prefix, and focuses it; a task with the same name wins. When the name is ambiguous, or no task or context is given, a terminal gets a fuzzy finder over tasks and loose workspaces, with duplicate workspace names numbered in the label only. `switch` focuses the existing task workspace or creates it in the start directory (`--directory`, else primary directory, worktree, or home), and saves the task as the fallback when it was named explicitly. `--show` only prints the workspace, tabs, and panes. Tab and pane references match a live ID or label, then a unique prefix; with no reference and several candidates a fuzzy finder opens.
|
|
115
|
+
These require a running Herdr server. The workspace label is the task session or name with `.` replaced by `_`. `switch` also accepts the name of a live Herdr workspace that belongs to no task, exactly or by unique prefix, and focuses it; a task with the same name wins. An exact pane ID such as `w6:p2` focuses that pane, and the picker lists every live pane with its directory and foreground command. When the name is ambiguous, or no task or context is given, a terminal gets a fuzzy finder over tasks and loose workspaces, with duplicate workspace names numbered in the label only. `switch` focuses the existing task workspace or creates it in the start directory (`--directory`, else primary directory, worktree, or home), and saves the task as the fallback when it was named explicitly. `--show` only prints the workspace, tabs, and panes. Tab and pane references match a live ID or label, then a unique prefix; with no reference and several candidates a fuzzy finder opens.
|
|
116
116
|
|
|
117
117
|
## Native AI sessions
|
|
118
118
|
|
data/lib/hiiro/herdr.rb
CHANGED
|
@@ -204,6 +204,19 @@ class Hiiro
|
|
|
204
204
|
def to_s
|
|
205
205
|
"#{id} #{name} [#{workspace_id} / #{tab_id}]#{focused? ? ' *' : ''}"
|
|
206
206
|
end
|
|
207
|
+
|
|
208
|
+
# `herdr pane process-info` for this pane, fetched once.
|
|
209
|
+
def process_info
|
|
210
|
+
@process_info ||= client.process_info(id)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Command line of the foreground process group leader, or nil at a shell prompt.
|
|
214
|
+
def foreground_command
|
|
215
|
+
procs = process_info['foreground_processes'] || []
|
|
216
|
+
leader = procs.find { |proc| proc['pid'] == process_info['foreground_process_group_id'] } || procs.last
|
|
217
|
+
return nil if leader.nil? || leader['pid'] == process_info['shell_pid']
|
|
218
|
+
leader['cmdline']
|
|
219
|
+
end
|
|
207
220
|
end
|
|
208
221
|
|
|
209
222
|
class Panes < Collection
|
|
@@ -319,6 +332,10 @@ class Hiiro
|
|
|
319
332
|
row && Tab.new(row, client: self)
|
|
320
333
|
end
|
|
321
334
|
|
|
335
|
+
def process_info(pane_id)
|
|
336
|
+
capture_result('pane', 'process-info', '--pane', pane_id).fetch('process_info', {})
|
|
337
|
+
end
|
|
338
|
+
|
|
322
339
|
def get_pane(id)
|
|
323
340
|
row = capture_result('pane', 'get', id)['pane']
|
|
324
341
|
row && Pane.new(row, client: self, rect: pane_rect(id))
|
data/lib/hiiro/task_cli.rb
CHANGED
|
@@ -186,8 +186,31 @@ class Hiiro
|
|
|
186
186
|
[]
|
|
187
187
|
end
|
|
188
188
|
|
|
189
|
+
# All live panes grouped by workspace id, or {} when Herdr is not running.
|
|
190
|
+
def live_panes
|
|
191
|
+
@live_panes ||= begin
|
|
192
|
+
herdr_client.server_running? ? client.panes(all: true).to_a.group_by(&:workspace_id) : {}
|
|
193
|
+
rescue Hiiro::Error
|
|
194
|
+
{}
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# "ID ~/dir agent (status) command line" for listings and pickers.
|
|
199
|
+
def pane_line(pane)
|
|
200
|
+
dir = (pane.foreground_cwd || pane.cwd).to_s.sub(/\A#{Regexp.escape(Dir.home)}(?=\/|\z)/, '~')
|
|
201
|
+
what = []
|
|
202
|
+
what << "#{pane.agent}#{pane.agent_status ? " (#{pane.agent_status})" : ''}" if pane.agent
|
|
203
|
+
what << (pane.foreground_command || (pane.agent ? nil : 'shell'))
|
|
204
|
+
[pane.id, dir, *what.compact].reject(&:empty?).join(' ')
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def print_panes(workspace_id, indent: ' ')
|
|
208
|
+
live_panes.fetch(workspace_id, []).each { |pane| puts "#{indent}#{pane_line(pane)}" }
|
|
209
|
+
end
|
|
210
|
+
|
|
189
211
|
# Tasks with open todo counts, an @ marker when the task workspace is open,
|
|
190
|
-
# then any live Herdr workspaces that belong to no task.
|
|
212
|
+
# then any live Herdr workspaces that belong to no task. Panes are listed
|
|
213
|
+
# under every open workspace with their directory and foreground command.
|
|
191
214
|
def list_tasks
|
|
192
215
|
tasks = Hiiro::TaskRecord.all_as_list
|
|
193
216
|
workspaces = live_workspaces
|
|
@@ -211,11 +234,18 @@ class Hiiro
|
|
|
211
234
|
end
|
|
212
235
|
name_col = rows.map { |row| row[0].length }.max || 0
|
|
213
236
|
status_col = rows.map { |row| row[1].length }.max || 0
|
|
214
|
-
rows.
|
|
237
|
+
rows.each_with_index do |(label, status, detail), index|
|
|
238
|
+
puts format("%-#{name_col}s %-#{status_col}s %s", label, status, detail).rstrip
|
|
239
|
+
workspace = workspaces.find { |candidate| candidate.name == workspace_label(tasks[index]) }
|
|
240
|
+
print_panes(workspace.id) if workspace
|
|
241
|
+
end
|
|
215
242
|
return if loose.empty?
|
|
216
243
|
puts unless rows.empty?
|
|
217
244
|
puts 'Workspaces without a task:'
|
|
218
|
-
loose.each
|
|
245
|
+
loose.each do |workspace|
|
|
246
|
+
puts " #{workspace.name} #{workspace.id}"
|
|
247
|
+
print_panes(workspace.id)
|
|
248
|
+
end
|
|
219
249
|
end
|
|
220
250
|
|
|
221
251
|
|
|
@@ -485,10 +515,11 @@ class Hiiro
|
|
|
485
515
|
client.workspaces.reject { |workspace| labels.include?(workspace.name) }
|
|
486
516
|
end
|
|
487
517
|
|
|
488
|
-
# Fuzzy map over tasks and
|
|
489
|
-
# in the label only, so the choice still maps to the exact
|
|
490
|
-
def pick_switch_target(tasks, workspaces)
|
|
491
|
-
|
|
518
|
+
# Fuzzy map over tasks, loose workspaces, and live panes; duplicate workspace
|
|
519
|
+
# names are numbered in the label only, so the choice still maps to the exact one.
|
|
520
|
+
def pick_switch_target(tasks, workspaces, panes: nil)
|
|
521
|
+
panes = live_panes.values.flatten if panes.nil?
|
|
522
|
+
raise Error, 'No tasks, workspaces, or panes to choose from' if tasks.empty? && workspaces.empty? && panes.empty?
|
|
492
523
|
counts = workspaces.group_by(&:name).transform_values(&:length)
|
|
493
524
|
seen = Hash.new(0)
|
|
494
525
|
map = tasks.to_h { |task| [task.name, task] }
|
|
@@ -497,9 +528,16 @@ class Hiiro
|
|
|
497
528
|
label = counts[workspace.name] > 1 ? "#{workspace.name} ##{seen[workspace.name]}" : workspace.name
|
|
498
529
|
map["#{label} (workspace)"] = workspace
|
|
499
530
|
end
|
|
531
|
+
panes.each { |pane| map["#{pane_line(pane)} (pane)"] = pane }
|
|
500
532
|
fuzzyfind_from_map(map) || raise(Error, 'Nothing selected')
|
|
501
533
|
end
|
|
502
534
|
|
|
535
|
+
def focus_pane_target(pane)
|
|
536
|
+
check_result(client.focus_workspace(pane.workspace_id))
|
|
537
|
+
check_result(client.focus_pane(pane.id), "Could not focus pane #{pane.id}")
|
|
538
|
+
puts pane_line(pane)
|
|
539
|
+
end
|
|
540
|
+
|
|
503
541
|
# [target, explicit]. A task wins over a workspace with the same name.
|
|
504
542
|
def switch_target(args)
|
|
505
543
|
args = args.dup
|
|
@@ -513,6 +551,8 @@ class Hiiro
|
|
|
513
551
|
workspaces = loose_workspaces
|
|
514
552
|
exact = tasks.find { |task| task.name == first }
|
|
515
553
|
return [exact, true] if exact
|
|
554
|
+
pane = live_panes.values.flatten.find { |candidate| candidate.id == first }
|
|
555
|
+
return [pane, false] if pane
|
|
516
556
|
exact_ws = workspaces.select { |workspace| workspace.name == first }
|
|
517
557
|
return [exact_ws.first, false] if exact_ws.one?
|
|
518
558
|
task_matches = tasks.select { |task| task.name.start_with?(first) }
|
|
@@ -522,7 +562,7 @@ class Hiiro
|
|
|
522
562
|
raise Error, "No task or workspace matches #{first}" if task_matches.empty? && ws_matches.empty?
|
|
523
563
|
names = (task_matches.map(&:name) + ws_matches.map { |w| "#{w.name} (workspace)" }).join(', ')
|
|
524
564
|
raise Error, "Ambiguous #{first}: #{names}" unless $stdin.tty?
|
|
525
|
-
target = pick_switch_target(task_matches, ws_matches)
|
|
565
|
+
target = pick_switch_target(task_matches, ws_matches, panes: [])
|
|
526
566
|
return [target, target.is_a?(Hiiro::TaskRecord)]
|
|
527
567
|
end
|
|
528
568
|
args.shift if first == '.'
|
|
@@ -756,7 +796,10 @@ class Hiiro
|
|
|
756
796
|
add_option :directory, desc: 'Existing start directory'
|
|
757
797
|
add_cmd(:switch, :workspace, args: ['task-or-workspace?'], opts: [*TASK_OPTIONS, :directory, :show]) do
|
|
758
798
|
target, explicit = switch_target(opts.args)
|
|
759
|
-
if target.is_a?(Hiiro::Herdr::
|
|
799
|
+
if target.is_a?(Hiiro::Herdr::Pane)
|
|
800
|
+
raise Error, '--show applies to task workspaces' if opts.show
|
|
801
|
+
focus_pane_target(target)
|
|
802
|
+
elsif target.is_a?(Hiiro::Herdr::Workspace)
|
|
760
803
|
raise Error, '--show applies to task workspaces' if opts.show
|
|
761
804
|
check_result(client.focus_workspace(target.id))
|
|
762
805
|
puts target
|
data/lib/hiiro/version.rb
CHANGED