hiiro 0.1.149 → 0.1.151

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: f67c9b0a2688514af184098d8c69497f0fe09adb4a91a4e41b17596a4069e352
4
- data.tar.gz: c6f3ef4544274739bdbdde419a25101948d5af2119d0da2b6015a580864d80ec
3
+ metadata.gz: 98bf6f648f72277fb219044bd6f5a5e0c1320c21d122e8478cc4e6d6f7000a4b
4
+ data.tar.gz: 6308aa3432b0cf8e9f2fc3ca4fc66fb9075902fb3c421fad4d2dbd03f2105418
5
5
  SHA512:
6
- metadata.gz: 00e92bb9a9186cea72209f320df3b43c3e3f1537596ddecd78d740d0c87ba42b7f9ccd1c250b36e6fa331371875fdf15b2f0d8baf2d7b115b76ec64183aeb9aa
7
- data.tar.gz: 16e4b2d2af8857cd4d1bd7739add2d919466d58b2c0c9806371079344d8c094a66c75c32f49766efc36eda4dde6a5113c3bfde1710719919912745709d210429
6
+ metadata.gz: 268febf76230f6cb10ac9b492bc7c37fb34d48764b43d912dc430ae66f9b29cae1190c58109fa93d2935b4145a1e75685f6a8a1f4345e0ff53a40e70326301bc
7
+ data.tar.gz: dbf74be661003f9b67e878c9284d2b4a6ef4d20712226e6021023b5e2e7d8352bb671c8a86e184367fb0bad38301a4c589a6254c8d2ba2660d972a178d715feb
data/CLAUDE.md CHANGED
@@ -277,13 +277,153 @@ Hiiro::Notification.show(hiiro) # Show notification based on hiiro.args
277
277
  # Supports: -m message, -t title, -l link, -c command, -s sound
278
278
  ```
279
279
 
280
+ ### Hiiro::Queue (lib/hiiro/queue.rb)
281
+
282
+ Task queue that pipes prompts to `claude` via tmux. Tasks are markdown files with optional YAML frontmatter (`task_name`, `tree_name`, `session_name`) that flow through statuses: `wip` -> `pending` -> `running` -> `done`/`failed`.
283
+
284
+ Subcommands (`h queue <subcmd>`):
285
+ - `ls`/`list` - List all tasks with status, elapsed time, and preview
286
+ - `status` - Detailed status with working directory info
287
+ - `add` - Create a new prompt (opens editor or accepts stdin/args, supports `-t <task>` flag)
288
+ - `wip` - Create/edit a work-in-progress prompt
289
+ - `ready` - Move wip task to pending
290
+ - `run [name]` - Launch pending task(s) in tmux windows
291
+ - `watch` - Continuously poll and launch pending tasks
292
+ - `attach [name]` - Switch to running task's tmux window (fuzzy select if no name)
293
+ - `kill [name]` - Kill running task's tmux window, move to failed
294
+ - `retry [name]` - Move failed/done task back to pending
295
+ - `clean` - Remove all done/failed task files
296
+ - `dir` - Print queue directory path
297
+
298
+ Config: `~/.config/hiiro/queue/{wip,pending,running,done,failed}/`
299
+
300
+ Key internals:
301
+ - `Queue::Prompt` - Parses frontmatter to resolve task/tree/session for working directory
302
+ - Tasks launch in tmux windows within the task's session (from frontmatter) or default `hq` session
303
+ - Launcher script runs `cat prompt | claude`, then moves files to done/failed based on exit code
304
+
305
+ ### Hiiro::ServiceManager (lib/hiiro/service_manager.rb)
306
+
307
+ Manages background development services with tmux integration, env file management, and service groups.
308
+
309
+ Subcommands (`h service <subcmd>`):
310
+ - `ls`/`list` - List all services with running status, port, and base_dir
311
+ - `start <name> [--use VAR=variation ...]` - Start a service or group; prepares env file first
312
+ - `stop <name>` - Stop a running service (sends C-c to tmux pane or runs stop command)
313
+ - `attach <name>` - Switch to service's tmux pane
314
+ - `open <name>` - Open service URL in browser
315
+ - `url <name>` / `port <name>` - Print service URL or port
316
+ - `status <name>` - Show detailed service info (pid, pane, task, started_at)
317
+ - `add` - Add new service via editor template
318
+ - `rm`/`remove <name>` - Remove a service
319
+ - `config` - Edit services.yml
320
+ - `groups` - List all service groups and their members
321
+ - `env <name>` - Show env_vars, their variation options, and base_env/env_file config
322
+
323
+ Service config (`~/.config/hiiro/services.yml`):
324
+ ```yaml
325
+ my-rails:
326
+ base_dir: apps/myapp
327
+ host: localhost
328
+ port: 3000
329
+ init: ["bundle install"]
330
+ start: bundle exec rails s -p 3000
331
+ stop: ""
332
+ cleanup: []
333
+ env_file: .env.development # destination in base_dir
334
+ base_env: my-rails.env # template in ~/.config/hiiro/env_templates/
335
+ env_vars:
336
+ GRAPHQL_URL:
337
+ variations:
338
+ local: http://localhost:4000/graphql
339
+ staging: https://graphql.staging.example.com/graphql
340
+ ```
341
+
342
+ Service group config (same file, distinguished by `services:` key):
343
+ ```yaml
344
+ my-stack:
345
+ services:
346
+ - name: my-rails
347
+ use:
348
+ GRAPHQL_URL: staging
349
+ - name: my-graphql
350
+ ```
351
+
352
+ Key internals:
353
+ - `prepare_env(svc_name, variation_overrides:)` - Copies base_env template from `~/.config/hiiro/env_templates/` to `base_dir/env_file`, then injects variation values
354
+ - `find_group(name)` / `start_group(name, ...)` - Detect and start service groups, applying per-member `use:` overrides
355
+ - Default variation is `local` when not specified
356
+ - State tracked in `~/.config/hiiro/services/running.yml`
357
+
358
+ ### Hiiro::RunnerTool (lib/hiiro/runner_tool.rb)
359
+
360
+ Run dev tools (linters, formatters, test suites) against changed files.
361
+
362
+ Subcommands (`h run [change_set] [tool_type] [file_group]`):
363
+ - Default (no subcmd) - Run matching tools with positional filters
364
+ - `ls` - List configured tools with type, group, extensions, and variations
365
+ - `add` - Add new tool via editor template
366
+ - `rm <name>` - Remove a tool
367
+ - `config` - Edit tools.yml
368
+
369
+ Arguments (positional, any order):
370
+ - **change_set**: `dirty` (default, git status), `branch` (diff from main), `all`
371
+ - **tool_type**: `lint`, `test`, `format`
372
+ - **file_type_group**: custom group identifier (e.g., `ruby`, `frontend`)
373
+ - `--variation`/`-v <name>` - Use a named tool variation
374
+
375
+ Config (`~/.config/hiiro/tools.yml`):
376
+ ```yaml
377
+ rubocop:
378
+ tool_type: lint
379
+ command: "rubocop [FILENAMES]"
380
+ file_type_group: ruby
381
+ file_extensions: "rb"
382
+ variations:
383
+ quick: "rubocop --only Style [FILENAMES]"
384
+ fix: "rubocop -A [FILENAMES]"
385
+ ```
386
+
387
+ `[FILENAMES]` is replaced with the space-joined list of matching files.
388
+
389
+ ### Hiiro::AppFiles (lib/hiiro/app_files.rb)
390
+
391
+ Track frequently-used files per application, open them together in your editor.
392
+
393
+ Subcommands (`h file <subcmd>`):
394
+ - `ls [app_name]` - List tracked files (all apps or specific app)
395
+ - `add <app> <file1> [file2 ...]` - Add files to an app's file list
396
+ - `rm <app> <file1> [file2 ...]` - Remove files
397
+ - `edit <app>` - Open all tracked files in editor (vim uses `-O` for vertical splits)
398
+
399
+ Config: `~/.config/hiiro/app_files.yml`
400
+
401
+ Files are resolved relative to the current task's tree root when an environment is available.
402
+
403
+ ### Jumplist (bin/h-jumplist)
404
+
405
+ Vim-style navigation history for tmux. Records pane/window/session changes and lets you jump backward/forward through your navigation history.
406
+
407
+ Subcommands (`h jumplist <subcmd>`):
408
+ - `setup` - Install tmux hooks and keybindings (`Ctrl-B` back, `Ctrl-F` forward)
409
+ - `record` - Record current position (called automatically by tmux hooks)
410
+ - `back` - Navigate to previous position
411
+ - `forward` - Navigate to next position
412
+ - `ls`/`list` - Show history with timestamps and current position marker
413
+ - `clear` - Clear history
414
+ - `path` - Print jumplist file path
415
+
416
+ Config: `~/.config/hiiro/jumplist/` (per-client entries and position files, max 50 entries)
417
+
418
+ Dead panes are automatically pruned. Duplicate consecutive entries are deduplicated. Forward history is truncated when navigating to a new location (like vim).
419
+
280
420
  ## Key Files
281
421
 
282
- - `bin/h` - Entry point that loads lib/hiiro.rb
283
- - `bin/h-*` - External subcommands (tmux wrappers, git helpers, todo, links, etc.)
422
+ - `exe/h` - Entry point that loads lib/hiiro.rb
423
+ - `bin/h-*` - External subcommands (tmux wrappers, git helpers, jumplist, etc.)
284
424
  - `plugins/*.rb` - Reusable plugin modules (Pins, Project, Tasks, Notify)
285
425
  - `lib/hiiro.rb` - Main Hiiro class and Runners
286
- - `lib/hiiro/*.rb` - Supporting classes (Git, Matcher, Fuzzyfind, Todo, Shell, Options, Notification, Tmux)
426
+ - `lib/hiiro/*.rb` - Supporting classes (Git, Matcher, Fuzzyfind, Todo, Shell, Options, Notification, Tmux, Queue, ServiceManager, RunnerTool, AppFiles)
287
427
 
288
428
  ## External Dependencies
289
429
 
@@ -292,6 +432,7 @@ Hiiro::Notification.show(hiiro) # Show notification based on hiiro.args
292
432
  - `sk` (skim) or `fzf` for fuzzy finding
293
433
  - `gh` CLI for GitHub operations (h-pr)
294
434
  - `terminal-notifier` for macOS notifications (notify plugin)
435
+ - `claude` CLI for queue task execution
295
436
 
296
437
  ## Configuration Locations
297
438
 
@@ -299,8 +440,15 @@ All config lives in `~/.config/hiiro/`:
299
440
  - `plugins/` - Auto-loaded plugin files
300
441
  - `pins/` - Per-command YAML key-value storage
301
442
  - `tasks/` - Task metadata for worktree management
443
+ - `queue/` - Prompt queue (wip, pending, running, done, failed)
444
+ - `services/` - Service runtime state
445
+ - `jumplist/` - Per-client tmux navigation history
446
+ - `env_templates/` - Base .env template files for services
302
447
  - `projects.yml` - Project directory aliases
303
448
  - `apps.yml` - App directory mappings for task plugin
449
+ - `services.yml` - Service and service group definitions
450
+ - `tools.yml` - Runner tool definitions
451
+ - `app_files.yml` - Per-app tracked file lists
304
452
 
305
453
 
306
454
  # Groups of files
data/README.md CHANGED
@@ -83,6 +83,10 @@ h ping
83
83
  | `h pr` | GitHub PR management via gh CLI |
84
84
  | `h project` | Project navigation with tmux session management |
85
85
  | `h queue` | Claude prompt queue with tmux-based task execution |
86
+ | `h jumplist` | Vim-style tmux navigation history (back/forward through panes) |
87
+ | `h service` | Manage background dev services with env variations and groups |
88
+ | `h file` | Track and open frequently-used files per app |
89
+ | `h run` | Run dev tools (lint/test/format) against changed files |
86
90
  | `h session` | Tmux session management |
87
91
  | `h sha` | Extract short SHA from git log |
88
92
  | `h todo` | Todo list management with tags and task association |
@@ -214,9 +218,15 @@ All configuration lives in `~/.config/hiiro/`:
214
218
  plugins/ # Plugin files (auto-loaded)
215
219
  pins/ # Pin storage (per command)
216
220
  queue/ # Prompt queue (wip, pending, running, done, failed)
221
+ services/ # Service runtime state
222
+ jumplist/ # Per-client tmux navigation history
223
+ env_templates/ # Base .env templates for services
217
224
  tasks/ # Task metadata
218
225
  projects.yml # Project aliases
219
226
  apps.yml # App directory mappings
227
+ services.yml # Service and service group definitions
228
+ tools.yml # Runner tool definitions
229
+ app_files.yml # Per-app tracked file lists
220
230
  todo.yml # Todo items
221
231
  ```
222
232
 
@@ -1,6 +1,7 @@
1
1
  require 'yaml'
2
2
  require 'fileutils'
3
3
  require 'time'
4
+ require 'ostruct'
4
5
 
5
6
  class Hiiro
6
7
  class ServiceManager
@@ -108,13 +109,50 @@ class Hiiro
108
109
  return false
109
110
  end
110
111
 
112
+ session = tmux_info[:session] || current_tmux_session
113
+ unless session
114
+ puts "tmux is required to start a service group"
115
+ return false
116
+ end
117
+
111
118
  puts "Starting group '#{group[:name]}'..."
112
- members.each do |member|
119
+
120
+ # Create one window for the group, split panes for each service
121
+ first_member = members.first
122
+ first_name = first_member['name'] || first_member[:name]
123
+ first_svc = find_service(first_name)
124
+ first_base_dir = first_svc&.[](:base_dir) || Dir.pwd
125
+
126
+ # Create a new window for the group
127
+ window_target = create_tmux_window(session, group[:name], first_base_dir)
128
+ first_pane_id = capture_pane_id(window_target)
129
+
130
+ members.each_with_index do |member, idx|
113
131
  member_name = member['name'] || member[:name]
114
132
  use_overrides = member['use'] || member[:use] || {}
115
133
 
116
134
  prepare_env(member_name, variation_overrides: use_overrides)
117
- start(member_name, tmux_info: tmux_info, task_info: task_info, skip_env: true)
135
+
136
+ svc = find_service(member_name)
137
+ next unless svc
138
+
139
+ base_dir = svc[:base_dir] || Dir.pwd
140
+
141
+ if idx == 0
142
+ # First service uses the initial pane
143
+ pane_id = first_pane_id
144
+ else
145
+ # Subsequent services get split panes
146
+ pane_id = split_tmux_pane(window_target, base_dir)
147
+ end
148
+
149
+ member_tmux_info = tmux_info.merge(
150
+ session: session,
151
+ window: window_target,
152
+ pane: pane_id,
153
+ )
154
+
155
+ start(member_name, tmux_info: member_tmux_info, task_info: task_info, skip_env: true, skip_window_creation: true)
118
156
  end
119
157
  true
120
158
  end
@@ -128,7 +166,7 @@ class Hiiro
128
166
  load_state
129
167
  end
130
168
 
131
- def start(name, tmux_info: {}, task_info: {}, variation_overrides: {}, skip_env: false)
169
+ def start(name, tmux_info: {}, task_info: {}, variation_overrides: {}, skip_env: false, skip_window_creation: false)
132
170
  svc = find_service(name)
133
171
  unless svc
134
172
  puts "Service '#{name}' not found"
@@ -167,20 +205,35 @@ class Hiiro
167
205
  return false
168
206
  end
169
207
 
208
+ # Coerce start command to array
209
+ start_cmds = Array(start_cmd)
210
+
170
211
  base_dir = svc[:base_dir]
171
- pane_id = tmux_info[:pane] || ENV['TMUX_PANE']
212
+ session = tmux_info[:session] || current_tmux_session
213
+
214
+ if session && !skip_window_creation
215
+ # Create a new tmux window for this service
216
+ window_target = create_tmux_window(session, svc_name, base_dir || Dir.pwd)
217
+ pane_id = capture_pane_id(window_target)
218
+ elsif session && skip_window_creation
219
+ # Pane already created by start_group
220
+ pane_id = tmux_info[:pane]
221
+ window_target = tmux_info[:window]
222
+ else
223
+ pane_id = nil
224
+ window_target = nil
225
+ end
172
226
 
173
227
  if pane_id
174
- if base_dir
175
- system('tmux', 'send-keys', '-t', pane_id, "cd #{base_dir} && #{start_cmd}", 'Enter')
176
- else
177
- system('tmux', 'send-keys', '-t', pane_id, start_cmd, 'Enter')
178
- end
228
+ # Send each start command to the pane
229
+ combined_cmd = start_cmds.join(' && ')
230
+ system('tmux', 'send-keys', '-t', pane_id, combined_cmd, 'Enter')
179
231
  else
232
+ combined_cmd = start_cmds.join(' && ')
180
233
  if base_dir
181
- system("cd #{base_dir} && #{start_cmd} &")
234
+ system("cd #{base_dir} && #{combined_cmd} &")
182
235
  else
183
- system("#{start_cmd} &")
236
+ system("#{combined_cmd} &")
184
237
  end
185
238
  end
186
239
 
@@ -188,8 +241,8 @@ class Hiiro
188
241
  state = load_state
189
242
  state[svc_name] = {
190
243
  'pid' => nil,
191
- 'tmux_session' => tmux_info[:session],
192
- 'tmux_window' => tmux_info[:window],
244
+ 'tmux_session' => session || tmux_info[:session],
245
+ 'tmux_window' => window_target || tmux_info[:window],
193
246
  'tmux_pane' => pane_id,
194
247
  'task' => task_info[:task_name],
195
248
  'tree' => task_info[:tree],
@@ -219,7 +272,7 @@ class Hiiro
219
272
  info = running_services[svc_name]
220
273
  pane_id = info['tmux_pane']
221
274
 
222
- if svc[:stop]
275
+ if svc[:stop] && !svc[:stop].to_s.strip.empty?
223
276
  stop_cmd = svc[:stop]
224
277
  if info['pid']
225
278
  stop_cmd = stop_cmd.gsub('$PID', info['pid'].to_s)
@@ -259,11 +312,16 @@ class Hiiro
259
312
  info = running_services[svc_name]
260
313
  pane_id = info['tmux_pane']
261
314
  session = info['tmux_session']
315
+ window = info['tmux_window']
262
316
 
263
317
  if session
264
318
  system('tmux', 'switch-client', '-t', session)
265
319
  end
266
320
 
321
+ if window
322
+ system('tmux', 'select-window', '-t', window)
323
+ end
324
+
267
325
  if pane_id
268
326
  system('tmux', 'select-pane', '-t', pane_id)
269
327
  end
@@ -397,8 +455,6 @@ class Hiiro
397
455
 
398
456
  tmux_info = {
399
457
  session: ENV['TMUX'] ? `tmux display-message -p '#S'`.chomp : nil,
400
- window: ENV['TMUX'] ? `tmux display-message -p '#I'`.chomp : nil,
401
- pane: ENV['TMUX_PANE'],
402
458
  }
403
459
 
404
460
  task_info = {}
@@ -498,8 +554,7 @@ class Hiiro
498
554
  'host' => 'localhost',
499
555
  'port' => '',
500
556
  'init' => [],
501
- 'start' => '',
502
- 'stop' => '',
557
+ 'start' => [''],
503
558
  'cleanup' => [],
504
559
  }
505
560
 
@@ -605,6 +660,26 @@ class Hiiro
605
660
 
606
661
  private
607
662
 
663
+ def current_tmux_session
664
+ return nil unless ENV['TMUX']
665
+ `tmux display-message -p '#S'`.chomp
666
+ end
667
+
668
+ def create_tmux_window(session, name, start_dir)
669
+ system('tmux', 'new-window', '-d', '-t', session, '-n', name, '-c', start_dir)
670
+ "#{session}:#{name}"
671
+ end
672
+
673
+ def capture_pane_id(window_target)
674
+ `tmux list-panes -t #{window_target} -F '\#{pane_id}'`.chomp.split("\n").last
675
+ end
676
+
677
+ def split_tmux_pane(window_target, start_dir)
678
+ system('tmux', 'split-window', '-d', '-t', window_target, '-c', start_dir)
679
+ system('tmux', 'select-layout', '-t', window_target, 'even-vertical')
680
+ `tmux list-panes -t #{window_target} -F '\#{pane_id}'`.chomp.split("\n").last
681
+ end
682
+
608
683
  def load_config
609
684
  return {} unless File.exist?(config_file)
610
685
  YAML.safe_load_file(config_file, permitted_classes: [Symbol]) || {}
data/lib/hiiro/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Hiiro
2
- VERSION = "0.1.149"
2
+ VERSION = "0.1.151"
3
3
  end
data/lib/hiiro.rb CHANGED
@@ -2,6 +2,7 @@ require "fileutils"
2
2
  require "yaml"
3
3
  require "shellwords"
4
4
  require "pry"
5
+ require "ostruct"
5
6
 
6
7
  require_relative "hiiro/version"
7
8
  require_relative "hiiro/bins"
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.149
4
+ version: 0.1.151
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Toyota