hiiro 0.1.368 → 0.1.370
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 +12 -1
- data/README.md +2 -1
- data/docs/t.md +4 -4
- data/lib/hiiro/task_cli.rb +110 -19
- 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: 032503df05064bda5cc037dd3cd37eccea64c836264d08f40f9a23455d08f8a6
|
|
4
|
+
data.tar.gz: e9b478550d879b3b205e0ca9f04d442782a94c777116eb06901e726c21e785c7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 31f274880fc13eb3448a7970030c78e5d86a1807aff1bdd0ad66f2ffa9c87076f59c174f9d9ac74400a1fc773ad866a5e798f4d3f19e2d977c949ec59ba3514e
|
|
7
|
+
data.tar.gz: 931992993a1505231ae81781a3d8707c40d94c762c3902484c1a1357a5fe02240c616374d64e51622db5be11bae0abd7fc40114f0c994ebd4b4cf18669dd304b
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.1.370] - 2026-09-14
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- `t ls` marks tasks with an open Herdr workspace with `@` and lists workspaces that belong to no task
|
|
9
|
+
|
|
10
|
+
## [0.1.369] - 2026-09-14
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `t switch NAME` matches live Herdr workspaces that belong to no task, and the fuzzy finder for `switch` lists tasks plus those workspaces, numbering duplicate workspace names
|
|
14
|
+
- `t` opens the fuzzy finder instead of failing when a task prefix is ambiguous or no current task can be resolved, when stdin is a terminal
|
|
15
|
+
|
|
5
16
|
## [0.1.368] - 2026-09-14
|
|
6
17
|
|
|
7
18
|
### Added
|
|
@@ -97,4 +108,4 @@
|
|
|
97
108
|
- Focus the exact Herdr pane through the socket API and read the CLI's plain-text pane output without JSON parsing.
|
|
98
109
|
- Restore the missing `Hiiro::Bins` helper so `require "hiiro"` boots and commands like `h jumplist record` dispatch correctly.
|
|
99
110
|
- Make Hiiro's Ruby requirement explicit as Ruby 3.2+ and have rbenv-wide gem installs skip incompatible Ruby versions.
|
|
100
|
-
- Update the publish script to preserve the Ruby support constant, run only on supported Ruby, and install releases only into compatible rbenv versions.
|
|
111
|
+
- Update the publish script to preserve the Ruby support constant, run only on supported Ruby, and install releases only into compatible rbenv versions.
|
data/README.md
CHANGED
|
@@ -73,7 +73,8 @@ Commands that act on a task take it from the first positional argument when
|
|
|
73
73
|
that word names a task (exact or unique case-sensitive prefix); otherwise the
|
|
74
74
|
current task is used and the word stays in the payload, exactly like the old
|
|
75
75
|
`h task` commands. `-t TASK` forces a task, `-f` picks one with a fuzzy finder,
|
|
76
|
-
`.` is the current task, and `-` selects orphan todos.
|
|
76
|
+
`.` is the current task, and `-` selects orphan todos. On a terminal, an ambiguous
|
|
77
|
+
prefix or a missing current task opens the fuzzy finder; non-interactive runs fail.
|
|
77
78
|
|
|
78
79
|
The current task is the calling Herdr workspace, then the current directory
|
|
79
80
|
inside a task home, code directory, worktree, or registered directory, then the
|
data/docs/t.md
CHANGED
|
@@ -17,11 +17,11 @@ The command comes first. Commands that act on a task take it from the first posi
|
|
|
17
17
|
|
|
18
18
|
1. If the first positional word names a task, exactly or by a unique case-sensitive prefix, it is the task and is removed from the arguments.
|
|
19
19
|
2. Otherwise the current task is used and the word stays in the payload. So `t todo add fix build` adds to the current task and `t todo add prez fix build` adds to `prez`.
|
|
20
|
-
3. An ambiguous prefix
|
|
20
|
+
3. An ambiguous prefix opens the fuzzy finder over the matching tasks when stdin is a terminal, and is an error otherwise.
|
|
21
21
|
|
|
22
22
|
`-t TASK` / `--task TASK` forces a task and fails if it does not exist. `-f` / `--find` picks one with `sk` or `fzf`. `.` is the current task, useful when a payload could be mistaken for a name: `t next . Finish slides`. `-` selects orphan todos in todo commands only. Words starting with `-` are never treated as task names. Passthrough commands (`todo add`, `sh`, `pane run`, and the AI launchers) accept `-t`, `--task=NAME`, and `-f` only as their leading arguments.
|
|
23
23
|
|
|
24
|
-
The current task is resolved by `Hiiro::CurrentTask`: the calling Herdr workspace when `HERDR_*` variables identify one, then the working directory inside a task home, primary directory, worktree, or registered directory, then the task saved by `t use`. Ambiguous matches and stale or conflicting Herdr IDs are errors. Commands that only list, create, or show help never resolve the current task.
|
|
24
|
+
The current task is resolved by `Hiiro::CurrentTask`: the calling Herdr workspace when `HERDR_*` variables identify one, then the working directory inside a task home, primary directory, worktree, or registered directory, then the task saved by `t use`. With no context at all, a terminal gets the fuzzy finder over every task; non-interactive runs fail. Ambiguous directory or workspace matches and stale or conflicting Herdr IDs are always errors. Commands that only list, create, or show help never resolve the current task.
|
|
25
25
|
|
|
26
26
|
Task names may be `parent/child` for subtasks. `t new NAME` creates exactly `NAME`; nothing else creates tasks. Only `help`, `ls`, and `list` are reserved words; a task literally named `show` or `new` is reachable with `-t show`.
|
|
27
27
|
|
|
@@ -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 |
|
|
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 |
|
|
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` focuses the existing 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. 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/task_cli.rb
CHANGED
|
@@ -40,24 +40,36 @@ class Hiiro
|
|
|
40
40
|
add_flag :find, short: :f, desc: 'Choose the task with a fuzzy finder'
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# Exact name, else unique prefix; ambiguous prefixes fail; nil when nothing matches.
|
|
43
|
+
# Exact name, else unique prefix. An ambiguous prefix opens the fuzzy finder
|
|
44
|
+
# over the candidates on a TTY and fails otherwise. nil when nothing matches.
|
|
48
45
|
def lookup_task(reference)
|
|
49
46
|
result = Hiiro::Matcher.new(Hiiro::TaskRecord.all_as_list, :name).by_prefix(reference.to_s)
|
|
50
47
|
match = result.resolved
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
return match.item if match
|
|
49
|
+
return nil unless result.ambiguous?
|
|
50
|
+
candidates = result.matches.map(&:item)
|
|
51
|
+
raise Error, "Ambiguous task #{reference}: #{candidates.map(&:name).join(', ')}" unless $stdin.tty?
|
|
52
|
+
pick_task(candidates)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def pick_task(candidates)
|
|
56
|
+
raise Error, 'No tasks; create one with t new NAME' if candidates.empty?
|
|
57
|
+
name = fuzzyfind(candidates.map(&:name))
|
|
58
|
+
raise Error, 'No task selected' if name.nil? || name.strip.empty?
|
|
59
|
+
candidates.find { |task| task.name == name.strip } || raise(Error, "No task named #{name.strip}")
|
|
53
60
|
end
|
|
54
61
|
|
|
55
62
|
def lookup_task!(reference)
|
|
56
63
|
lookup_task(reference) || raise(Error, "Task not found: #{reference}; run t new #{reference}")
|
|
57
64
|
end
|
|
58
65
|
|
|
66
|
+
# Current task from Herdr, directory, or the saved pin. With no context at all,
|
|
67
|
+
# a TTY gets the fuzzy finder over every task; ambiguous or stale context still fails.
|
|
59
68
|
def current_task
|
|
60
69
|
Hiiro::CurrentTask.new(herdr: -> { herdr_client }, pin: true).resolve!
|
|
70
|
+
rescue Hiiro::Error => e
|
|
71
|
+
raise unless $stdin.tty? && e.message.start_with?('No current task')
|
|
72
|
+
pick_task(Hiiro::TaskRecord.all_as_list)
|
|
61
73
|
end
|
|
62
74
|
|
|
63
75
|
# Old h task rule: [task, remaining_args]. The first positional is the task
|
|
@@ -80,11 +92,7 @@ class Hiiro
|
|
|
80
92
|
end
|
|
81
93
|
end
|
|
82
94
|
return [lookup_task!(forced), args] if forced
|
|
83
|
-
if find
|
|
84
|
-
name = fuzzyfind(task_names)
|
|
85
|
-
raise Error, 'No task selected' if name.nil? || name.strip.empty?
|
|
86
|
-
return [lookup_task!(name.strip), args]
|
|
87
|
-
end
|
|
95
|
+
return [pick_task(Hiiro::TaskRecord.all_as_list), args] if find
|
|
88
96
|
first = args.first
|
|
89
97
|
if first == '.'
|
|
90
98
|
args.shift
|
|
@@ -171,24 +179,43 @@ class Hiiro
|
|
|
171
179
|
end
|
|
172
180
|
end
|
|
173
181
|
|
|
182
|
+
# Live Herdr workspaces, or [] when Herdr is not running.
|
|
183
|
+
def live_workspaces
|
|
184
|
+
herdr_client.server_running? ? client.workspaces.to_a : []
|
|
185
|
+
rescue Hiiro::Error
|
|
186
|
+
[]
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Tasks with open todo counts, an @ marker when the task workspace is open,
|
|
190
|
+
# then any live Herdr workspaces that belong to no task.
|
|
174
191
|
def list_tasks
|
|
175
192
|
tasks = Hiiro::TaskRecord.all_as_list
|
|
176
|
-
|
|
193
|
+
workspaces = live_workspaces
|
|
194
|
+
labels = tasks.map { |task| workspace_label(task) }
|
|
195
|
+
open_labels = workspaces.map(&:name)
|
|
196
|
+
loose = workspaces.reject { |workspace| labels.include?(workspace.name) }
|
|
197
|
+
if tasks.empty? && loose.empty?
|
|
177
198
|
puts 'No tasks. Create one with t new NAME.'
|
|
178
199
|
return
|
|
179
200
|
end
|
|
180
201
|
counts = open_todo_counts
|
|
202
|
+
attached = tasks.any? { |task| open_labels.include?(workspace_label(task)) }
|
|
181
203
|
rows = tasks.map do |task|
|
|
182
204
|
count = counts[task.name]
|
|
183
205
|
label = count.zero? ? task.name : "#{task.name} (#{count})"
|
|
206
|
+
label = "#{open_labels.include?(workspace_label(task)) ? '@' : ' '} #{label}" if attached
|
|
184
207
|
detail = []
|
|
185
208
|
detail << "next: #{task.next_action}" if task.next_action
|
|
186
209
|
detail << "waiting: #{task.waiting_on}" if task.waiting_on
|
|
187
210
|
[label, task.task_status, detail.join(' ')]
|
|
188
211
|
end
|
|
189
|
-
name_col = rows.map { |row| row[0].length }.max
|
|
190
|
-
status_col = rows.map { |row| row[1].length }.max
|
|
212
|
+
name_col = rows.map { |row| row[0].length }.max || 0
|
|
213
|
+
status_col = rows.map { |row| row[1].length }.max || 0
|
|
191
214
|
rows.each { |label, status, detail| puts format("%-#{name_col}s %-#{status_col}s %s", label, status, detail).rstrip }
|
|
215
|
+
return if loose.empty?
|
|
216
|
+
puts unless rows.empty?
|
|
217
|
+
puts 'Workspaces without a task:'
|
|
218
|
+
loose.each { |workspace| puts " #{workspace.name} #{workspace.id}" }
|
|
192
219
|
end
|
|
193
220
|
|
|
194
221
|
|
|
@@ -450,6 +477,65 @@ class Hiiro
|
|
|
450
477
|
puts workspace
|
|
451
478
|
end
|
|
452
479
|
|
|
480
|
+
# --- switch targets: tasks plus live Herdr workspaces that are not task workspaces ---
|
|
481
|
+
|
|
482
|
+
def loose_workspaces
|
|
483
|
+
return [] unless herdr_client.server_running?
|
|
484
|
+
labels = Hiiro::TaskRecord.all_as_list.map { |task| workspace_label(task) }
|
|
485
|
+
client.workspaces.reject { |workspace| labels.include?(workspace.name) }
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
# Fuzzy map over tasks and loose workspaces; duplicate workspace names are numbered
|
|
489
|
+
# in the label only, so the choice still maps to the exact workspace.
|
|
490
|
+
def pick_switch_target(tasks, workspaces)
|
|
491
|
+
raise Error, 'No tasks or workspaces to choose from' if tasks.empty? && workspaces.empty?
|
|
492
|
+
counts = workspaces.group_by(&:name).transform_values(&:length)
|
|
493
|
+
seen = Hash.new(0)
|
|
494
|
+
map = tasks.to_h { |task| [task.name, task] }
|
|
495
|
+
workspaces.each do |workspace|
|
|
496
|
+
seen[workspace.name] += 1
|
|
497
|
+
label = counts[workspace.name] > 1 ? "#{workspace.name} ##{seen[workspace.name]}" : workspace.name
|
|
498
|
+
map["#{label} (workspace)"] = workspace
|
|
499
|
+
end
|
|
500
|
+
fuzzyfind_from_map(map) || raise(Error, 'Nothing selected')
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
# [target, explicit]. A task wins over a workspace with the same name.
|
|
504
|
+
def switch_target(args)
|
|
505
|
+
args = args.dup
|
|
506
|
+
return [lookup_task!(opts.task), true] if opts.task
|
|
507
|
+
return [pick_switch_target(Hiiro::TaskRecord.all_as_list, loose_workspaces), true] if opts.find
|
|
508
|
+
first = args.first
|
|
509
|
+
if first && first != '.' && !first.start_with?('-')
|
|
510
|
+
args.shift
|
|
511
|
+
no_args!(args)
|
|
512
|
+
tasks = Hiiro::TaskRecord.all_as_list
|
|
513
|
+
workspaces = loose_workspaces
|
|
514
|
+
exact = tasks.find { |task| task.name == first }
|
|
515
|
+
return [exact, true] if exact
|
|
516
|
+
exact_ws = workspaces.select { |workspace| workspace.name == first }
|
|
517
|
+
return [exact_ws.first, false] if exact_ws.one?
|
|
518
|
+
task_matches = tasks.select { |task| task.name.start_with?(first) }
|
|
519
|
+
ws_matches = exact_ws.any? ? exact_ws : workspaces.select { |workspace| workspace.name.start_with?(first) }
|
|
520
|
+
return [task_matches.first, true] if task_matches.one? && ws_matches.empty?
|
|
521
|
+
return [ws_matches.first, false] if ws_matches.one? && task_matches.empty?
|
|
522
|
+
raise Error, "No task or workspace matches #{first}" if task_matches.empty? && ws_matches.empty?
|
|
523
|
+
names = (task_matches.map(&:name) + ws_matches.map { |w| "#{w.name} (workspace)" }).join(', ')
|
|
524
|
+
raise Error, "Ambiguous #{first}: #{names}" unless $stdin.tty?
|
|
525
|
+
target = pick_switch_target(task_matches, ws_matches)
|
|
526
|
+
return [target, target.is_a?(Hiiro::TaskRecord)]
|
|
527
|
+
end
|
|
528
|
+
args.shift if first == '.'
|
|
529
|
+
no_args!(args)
|
|
530
|
+
begin
|
|
531
|
+
[Hiiro::CurrentTask.new(herdr: -> { herdr_client }, pin: true).resolve!, false]
|
|
532
|
+
rescue Hiiro::Error => e
|
|
533
|
+
raise unless $stdin.tty? && e.message.start_with?('No current task')
|
|
534
|
+
target = pick_switch_target(Hiiro::TaskRecord.all_as_list, loose_workspaces)
|
|
535
|
+
[target, target.is_a?(Hiiro::TaskRecord)]
|
|
536
|
+
end
|
|
537
|
+
end
|
|
538
|
+
|
|
453
539
|
# --- Worktrees (shared with h task via Hiiro::TaskManager) ---
|
|
454
540
|
|
|
455
541
|
def tree_manager
|
|
@@ -668,10 +754,15 @@ class Hiiro
|
|
|
668
754
|
end
|
|
669
755
|
|
|
670
756
|
add_option :directory, desc: 'Existing start directory'
|
|
671
|
-
add_cmd(:switch, :workspace, args: ['task?'], opts: [*TASK_OPTIONS, :directory, :show]) do
|
|
672
|
-
explicit =
|
|
673
|
-
|
|
674
|
-
|
|
757
|
+
add_cmd(:switch, :workspace, args: ['task-or-workspace?'], opts: [*TASK_OPTIONS, :directory, :show]) do
|
|
758
|
+
target, explicit = switch_target(opts.args)
|
|
759
|
+
if target.is_a?(Hiiro::Herdr::Workspace)
|
|
760
|
+
raise Error, '--show applies to task workspaces' if opts.show
|
|
761
|
+
check_result(client.focus_workspace(target.id))
|
|
762
|
+
puts target
|
|
763
|
+
else
|
|
764
|
+
opts.show ? show_workspace(target) : open_workspace(target, save: explicit)
|
|
765
|
+
end
|
|
675
766
|
end
|
|
676
767
|
|
|
677
768
|
[%w[omp], %w[codex cdx], %w[claude cld]].each do |names|
|
data/lib/hiiro/version.rb
CHANGED