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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 975c627734daa28e7efa5c45802cd3f296ec3f338c7e2ad73835e22d219223e6
4
- data.tar.gz: bcd42f9f3b2ca7bbaf33b67e1535aa4729d3701f353d313bfe3a7f64c27b3e19
3
+ metadata.gz: 032503df05064bda5cc037dd3cd37eccea64c836264d08f40f9a23455d08f8a6
4
+ data.tar.gz: e9b478550d879b3b205e0ca9f04d442782a94c777116eb06901e726c21e785c7
5
5
  SHA512:
6
- metadata.gz: 22b19c645b77356da44007ab866f00f769ef875444b9ad5b4d00ae080ff5797b3b4cc3c500b65118ba3801f025c061f8ef01bb885aff7a63692c83c3b1ee09cc
7
- data.tar.gz: aad14264c543596424e10c301401e618e13adbef8dd7e6088b9d1b6c3e9374a357040d2886843057c290f0dda65a6f6100992bfbf3bdd3d1049eb914984ea8c5
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. Ambiguous prefixes fail.
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 is an error rather than a guess.
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
 
@@ -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
- def task_names
44
- Hiiro::TaskRecord.all_as_list.map(&:name)
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
- raise Error, "Ambiguous task #{reference}: #{result.matches.map { |m| m.item.name }.join(', ')}" if !match && result.ambiguous?
52
- match&.item
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
- if tasks.empty?
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 = opts.task || opts.find || (opts.args.first && opts.args.first != '.')
673
- task = take_task_only(opts.args)
674
- opts.show ? show_workspace(task) : open_workspace(task, save: explicit)
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
@@ -1,4 +1,4 @@
1
1
  class Hiiro
2
- VERSION = "0.1.368"
2
+ VERSION = "0.1.370"
3
3
  SUPPORTED_RUBY_VERSION = ">= 3.2.0"
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hiiro
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.368
4
+ version: 0.1.370
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Toyota