hiiro 0.1.365 → 0.1.367

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.
@@ -0,0 +1,51 @@
1
+ class Hiiro
2
+ class TaskScope
3
+ class Error < Hiiro::Error; end
4
+
5
+ attr_reader :reference
6
+
7
+ def initialize(reference, herdr:)
8
+ @reference = reference.to_s
9
+ raise Error, 'Task reference cannot be empty' if @reference.empty?
10
+
11
+ @herdr = herdr
12
+ end
13
+
14
+ def explicit?
15
+ !%w[. -].include?(reference)
16
+ end
17
+
18
+ def orphan?
19
+ reference == '-'
20
+ end
21
+
22
+ def task
23
+ return @task if defined?(@task)
24
+
25
+ @task = if orphan?
26
+ nil
27
+ elsif explicit?
28
+ result = Matcher.new(TaskRecord.all_as_list, :name).by_prefix(reference)
29
+ match = result.resolved
30
+ if !match && result.ambiguous?
31
+ raise Error, "Ambiguous task #{reference}: #{result.matches.map { |item| item.item.name }.join(', ')}"
32
+ end
33
+ match&.item
34
+ else
35
+ current_task
36
+ end
37
+ end
38
+
39
+ def task!
40
+ raise Error, "Use t - todo for orphan todos; '-' does not select a task" if orphan?
41
+
42
+ task || raise(Error, "Task not found: #{reference}")
43
+ end
44
+
45
+ private
46
+
47
+ def current_task
48
+ CurrentTask.new(herdr: @herdr, pin: true).resolve!
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,51 @@
1
+ require 'shellwords'
2
+
3
+ class Hiiro
4
+ class TaskSessions
5
+ class Error < Hiiro::Error; end
6
+
7
+ def initialize(client, workspace:, directory:)
8
+ @client = client
9
+ @workspace = workspace
10
+ @directory = directory
11
+ end
12
+
13
+ def run(tool, argv)
14
+ raise Error, "Unknown AI tool: #{tool}" unless %w[omp codex claude].include?(tool)
15
+
16
+ first = argv.first
17
+ resume = first && !first.empty? && 'resume'.start_with?(first)
18
+ args = resume ? argv.drop(1) : argv
19
+ if resume && args.empty?
20
+ panes = @client.panes(workspace: @workspace).select { |pane| pane.agent == tool }
21
+ if panes.length > 1
22
+ raise Error, "Multiple running #{tool} panes: #{panes.map(&:id).join(', ')}; choose a pane or pass native resume arguments"
23
+ end
24
+ if (pane = panes.first)
25
+ raise Error, "Could not focus pane #{pane.id}" unless @client.focus_pane(pane.id)
26
+
27
+ return pane.id
28
+ end
29
+ end
30
+
31
+ command = [tool]
32
+ command << (tool == 'codex' ? 'resume' : '--resume') if resume
33
+ command.concat(args)
34
+ result = @client.new_tab(
35
+ name: tool,
36
+ workspace: @workspace,
37
+ start_directory: @directory,
38
+ focus: true,
39
+ )
40
+ tab_id = result.is_a?(Hash) && result.dig('tab', 'tab_id')
41
+ raise Error, "Could not create #{tool} tab" unless tab_id.is_a?(String) && !tab_id.empty?
42
+ pane_id = result.dig('root_pane', 'pane_id')
43
+ raise Error, "Created #{tool} tab has no root pane" unless pane_id.is_a?(String) && !pane_id.empty?
44
+ unless @client.run_in_pane(pane_id, Shellwords.join(command))
45
+ raise Error, "Could not start #{tool} in pane #{pane_id}"
46
+ end
47
+
48
+ tab_id
49
+ end
50
+ end
51
+ end
data/lib/hiiro/tasks.rb CHANGED
@@ -175,6 +175,28 @@ class Hiiro
175
175
  task_name = scope == :subtask ? "#{current_parent_task.name}/#{name}" : name
176
176
  subtree_name = scope == :subtask ? "#{current_parent_task.name}/#{name}" : "#{name}/main"
177
177
 
178
+ target_path = create_tree(subtree_name, sparse_groups: sparse_groups)
179
+ return unless target_path
180
+
181
+ session_name = task_name
182
+ task = Task.new(name: task_name, tree: subtree_name, session: session_name)
183
+ config.save_task(task)
184
+
185
+ base_dir = target_path
186
+ if app_name
187
+ app = environment.find_app(app_name)
188
+ base_dir = app.resolve(target_path) if app
189
+ end
190
+
191
+ Dir.chdir(base_dir)
192
+ hiiro.start_herdr_workspace(session_name, start_directory: Dir.pwd)
193
+
194
+ puts "Started task '#{task_name}' in worktree '#{subtree_name}'"
195
+ end
196
+
197
+ # Create (or reuse an unassigned) worktree under WORK_DIR and return its path,
198
+ # or nil on failure. Shared by `h task start` and `t TASK tree new`.
199
+ def create_tree(subtree_name, sparse_groups: [])
178
200
  target_path = File.join(Hiiro::WORK_DIR, subtree_name)
179
201
 
180
202
  git = Hiiro::Git.new(nil, Hiiro::REPO_PATH)
@@ -184,33 +206,19 @@ class Hiiro
184
206
  FileUtils.mkdir_p(File.dirname(target_path))
185
207
  unless git.move_worktree(available.path, target_path, repo_path: Hiiro::REPO_PATH)
186
208
  puts "ERROR: Failed to rename worktree"
187
- return
209
+ return nil
188
210
  end
189
211
  else
190
212
  puts "Creating new worktree '#{subtree_name}'..."
191
213
  FileUtils.mkdir_p(File.dirname(target_path))
192
214
  unless git.add_worktree_detached(target_path, repo_path: Hiiro::REPO_PATH)
193
215
  puts "ERROR: Failed to create worktree"
194
- return
216
+ return nil
195
217
  end
196
218
  end
197
219
 
198
220
  apply_sparse_checkout(target_path, sparse_groups) if sparse_groups.any?
199
-
200
- session_name = task_name
201
- task = Task.new(name: task_name, tree: subtree_name, session: session_name)
202
- config.save_task(task)
203
-
204
- base_dir = target_path
205
- if app_name
206
- app = environment.find_app(app_name)
207
- base_dir = app.resolve(target_path) if app
208
- end
209
-
210
- Dir.chdir(base_dir)
211
- hiiro.start_herdr_workspace(session_name, start_directory: Dir.pwd)
212
-
213
- puts "Started task '#{task_name}' in worktree '#{subtree_name}'"
221
+ target_path
214
222
  end
215
223
 
216
224
  def switch_to_task(task, app_name: nil, force: false)
@@ -1463,10 +1471,11 @@ class Hiiro
1463
1471
  end
1464
1472
 
1465
1473
  def name
1466
- @name ||= if path.start_with?(Hiiro::WORK_DIR + '/')
1467
- path.sub(Hiiro::WORK_DIR + '/', '')
1468
- else
1469
- File.basename(path)
1474
+ @name ||= begin
1475
+ roots = [Hiiro::WORK_DIR]
1476
+ roots << File.realpath(Hiiro::WORK_DIR) if Dir.exist?(Hiiro::WORK_DIR)
1477
+ root = roots.uniq.find { |dir| path.start_with?(dir + '/') }
1478
+ root ? path.delete_prefix(root + '/') : File.basename(path)
1470
1479
  end
1471
1480
  end
1472
1481
 
@@ -1683,13 +1692,17 @@ class Hiiro
1683
1692
  end
1684
1693
 
1685
1694
  def task
1695
+ return @task if defined?(@task)
1696
+
1697
+ s = session
1698
+ t = tree
1699
+ @task = all_tasks.find { |task|
1700
+ (s && task.session_name == s.name) ||
1701
+ (t && (task.tree_name == t.name || task.tree_name == t.path))
1702
+ }
1686
1703
  @task ||= begin
1687
- s = session
1688
- t = tree
1689
- all_tasks.find { |task|
1690
- (s && task.session_name == s.name) ||
1691
- (t && (task.tree_name == t.name || task.tree_name == t.path))
1692
- }
1704
+ record = CurrentTask.new(herdr: -> { Hiiro::Herdr.client }, cwd: path, pin: false).resolve
1705
+ record && all_tasks.find { |task| task.name == record.name }
1693
1706
  end
1694
1707
  end
1695
1708
 
data/lib/hiiro/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  class Hiiro
2
- VERSION = "0.1.365"
2
+ VERSION = "0.1.367"
3
3
  SUPPORTED_RUBY_VERSION = ">= 3.2.0"
4
4
  end
data/lib/hiiro.rb CHANGED
@@ -6,6 +6,7 @@ require "ostruct"
6
6
  require 'awesome_print'
7
7
 
8
8
  require_relative "hiiro/version"
9
+ require_relative "hiiro/error"
9
10
  require_relative "hiiro/config"
10
11
  require_relative "hiiro/effects"
11
12
  require_relative "hiiro/db"
@@ -46,6 +47,10 @@ require_relative "hiiro/pane_home"
46
47
  require_relative "hiiro/pin_record"
47
48
  require_relative "hiiro/reminder"
48
49
  require_relative 'hiiro/registry'
50
+ require_relative 'hiiro/current_task'
51
+ require_relative 'hiiro/task_scope'
52
+ require_relative 'hiiro/task_sessions'
53
+ require_relative 'hiiro/task_cli'
49
54
  require_relative 'hiiro/tui'
50
55
 
51
56
  class String
@@ -65,7 +70,7 @@ class Hiiro
65
70
  WORK_DIR = File.join(Dir.home, 'work')
66
71
  REPO_PATH = File.join(WORK_DIR, '.git')
67
72
 
68
- def self.init(*oargs, plugins: [], logging: false, tasks: false, task_scope: nil, **values, &block)
73
+ def self.init(*oargs, plugins: [], logging: false, tasks: false, task_scope: nil, builtin_commands: true, **values, &block)
69
74
  load_env
70
75
  Hiiro::DB.setup!
71
76
 
@@ -98,24 +103,14 @@ class Hiiro
98
103
  new(h_bin, *h_args, logging: logging, tasks: tasks, task_scope: task_scope, **values).tap do |hiiro|
99
104
  hiiro.load_plugins(plugins)
100
105
 
101
- hiiro.add_subcommand(:pry) { |*args|
102
- binding.pry
103
- }
104
-
105
- hiiro.add_subcmd(:edit, **values) { |*args|
106
- hiiro.edit_files(hiiro.bin)
107
- }
108
-
109
- if hiiro.tasks_enabled?
110
- hiiro.add_subcmd(:task) do |*args|
111
- tm = TaskManager.new(hiiro, scope: :task)
112
- Tasks.build_hiiro(hiiro, tm).run
113
- end
106
+ if builtin_commands
107
+ hiiro.add_subcommand(:pry) { |*args|
108
+ binding.pry
109
+ }
114
110
 
115
- hiiro.add_subcmd(:subtask) do |*args|
116
- tm = TaskManager.new(hiiro, scope: :subtask)
117
- Tasks.build_hiiro(hiiro, tm).run
118
- end
111
+ hiiro.add_subcmd(:edit, **values) { |*args|
112
+ hiiro.edit_files(hiiro.bin)
113
+ }
119
114
  end
120
115
 
121
116
  if block
@@ -132,8 +127,8 @@ class Hiiro
132
127
  Options.setup(&block)
133
128
  end
134
129
 
135
- def self.run(*args, plugins: [], logging: false, tasks: false, task_scope: nil, **values, &block)
136
- hiiro = init(*args, plugins:, logging:, tasks:, task_scope:, **values, &block)
130
+ def self.run(*args, plugins: [], logging: false, tasks: false, task_scope: nil, builtin_commands: true, **values, &block)
131
+ hiiro = init(*args, plugins:, logging:, tasks:, task_scope:, builtin_commands:, **values, &block)
137
132
 
138
133
  hiiro.run
139
134
  end
@@ -227,6 +222,12 @@ class Hiiro
227
222
  Hiiro::Config.open(file, dir: dir)
228
223
  end
229
224
 
225
+ # Open a file, directory, or URL with the OS default handler.
226
+ def open_default(target)
227
+ executable = RUBY_PLATFORM.include?('darwin') ? 'open' : 'xdg-open'
228
+ system(executable, target)
229
+ end
230
+
230
231
  def herdr_client
231
232
  @herdr_client ||= Herdr.client!(self)
232
233
  end
@@ -276,6 +277,8 @@ class Hiiro
276
277
  handle_result(result)
277
278
 
278
279
  exit 1
280
+ rescue Hiiro::Error => e
281
+ abort "ERROR: #{e.message}"
279
282
  rescue => e
280
283
  puts "ERROR: #{e.message}"
281
284
  puts e.backtrace
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.365
4
+ version: 0.1.367
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-09-13 00:00:00.000000000 Z
11
+ date: 2026-09-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pry
@@ -129,6 +129,8 @@ email:
129
129
  executables:
130
130
  - h
131
131
  - hiiro
132
+ - t
133
+ - tt
132
134
  extensions: []
133
135
  extra_rdoc_files: []
134
136
  files:
@@ -170,11 +172,14 @@ files:
170
172
  - bin/h-sha
171
173
  - bin/h-sparse
172
174
  - bin/h-tags
175
+ - bin/h-task
173
176
  - bin/h-time
174
177
  - bin/h-title
175
178
  - bin/h-todo
176
179
  - bin/h-window
177
180
  - bin/h-wtree
181
+ - bin/t
182
+ - bin/tt
178
183
  - bkup..ruby-version
179
184
  - demo.sh
180
185
  - demo.typescript
@@ -325,6 +330,8 @@ files:
325
330
  - docs/h-window.md
326
331
  - docs/h-wtree.md
327
332
  - docs/h.md
333
+ - docs/t.md
334
+ - docs/why-t.md
328
335
  - editboth
329
336
  - examples/bin-with-proposed-options
330
337
  - examples/services.sleepers.yml
@@ -332,6 +339,8 @@ files:
332
339
  - examples/sparse_groups.yml
333
340
  - exe/h
334
341
  - exe/hiiro
342
+ - exe/t
343
+ - exe/tt
335
344
  - h-tmux-plugins.tmux
336
345
  - hiiro.gemspec
337
346
  - lib/hiiro.rb
@@ -345,8 +354,10 @@ files:
345
354
  - lib/hiiro/capture.rb
346
355
  - lib/hiiro/check_run.rb
347
356
  - lib/hiiro/config.rb
357
+ - lib/hiiro/current_task.rb
348
358
  - lib/hiiro/db.rb
349
359
  - lib/hiiro/effects.rb
360
+ - lib/hiiro/error.rb
350
361
  - lib/hiiro/fuzzyfind.rb
351
362
  - lib/hiiro/git.rb
352
363
  - lib/hiiro/git/branch.rb
@@ -379,7 +390,10 @@ files:
379
390
  - lib/hiiro/service_manager.rb
380
391
  - lib/hiiro/shell.rb
381
392
  - lib/hiiro/tags.rb
393
+ - lib/hiiro/task_cli.rb
382
394
  - lib/hiiro/task_record.rb
395
+ - lib/hiiro/task_scope.rb
396
+ - lib/hiiro/task_sessions.rb
383
397
  - lib/hiiro/tasks.rb
384
398
  - lib/hiiro/todo.rb
385
399
  - lib/hiiro/tui.rb