hiiro 0.1.349 → 0.1.351

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: d6b10e8a634a24ac7953cb7a8639574cd7e8b7b33b4876d6930bbdc5edc2a938
4
- data.tar.gz: 2ea99568dc1b989af28b281268587f7c33c1f5174ed115935e938b0a2f21dc86
3
+ metadata.gz: '0586a9cc0e070785c9a0ef12282146ed71172875e706480d3dca6d663f5a46e8'
4
+ data.tar.gz: ffec55f5b2ce8882cb06189dedf5e29ccb75a3b8214a76fc72e7dc460851f2cd
5
5
  SHA512:
6
- metadata.gz: 7e8c40d072f6e425770b8b817c08e35aca290ce8af7468e3ddb8bde5f2f5c137055843210abb08b241f36562bad44b779790aad009998cb2172adf649a529ead
7
- data.tar.gz: 5e83c626d13d0b0497c95a3a159eb20b6bffecb6168f8b7d2290a3cd89c312a0038aaf5dc3339a28bc448d5655b87cccffabc848c17bd857b81a9cde28a408c3
6
+ metadata.gz: e40c167924bce7ac225b17e0fd7c82855c0d6034e66814c4b3e4897ee00dc80b9214b52dafe4d3a98c7916b0194c0a7d4b234e9f55a5a51fd0d73329315132ee
7
+ data.tar.gz: 994e44431c7a4dc5a15a2eaaa0ffa6c9b0382229b5d84dac1521c56129b599c08316e124511e6f5cf830e7189ab326672e599c228c11b7ddb7776be42c86bfdc
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [Unreleased]
4
+
5
+ ### Fixed
6
+ - `h capture path <num>` now prints the path of the Nth most recent capture (was always printing the captures dir regardless of args)
7
+ - `h capture new` / `h capture file` now record to the DB even when interrupted (Ctrl-C, exception) so partial captures show up in `h capture ls`. Interrupted captures display with the existing `?` glyph and `(exit interrupted)` message.
8
+
9
+ ## [0.1.350] - 2026-04-18
10
+
11
+ ### Added
12
+ - `h-capture` command and `Hiiro::Capture` module for clipboard/selection capture
13
+
14
+ ### Changed
15
+ - Optimize `Hiiro::Shell::Result#plain_text` and `#lines` with instance-level caching
16
+ - Refactor `Shell::Result#lines` to use `String#lines(chomp: true)` for improved line handling
17
+ - Reduce Claude API effort to `low` in publish script for faster changelog generation
18
+
3
19
  ## [0.1.349] - 2026-04-17
4
20
 
5
21
  ### Removed
@@ -300,93 +316,4 @@
300
316
  - `h db cleanup` subcommand to preview and prune duplicate rows from SQLite tables
301
317
 
302
318
  ### Fixed
303
- - Prevent duplicate pinned_prs during import with `insert_conflict` and per-row rescue
304
-
305
- ## [0.1.308.pre.6] - 2026-03-31
306
-
307
- ### Fixed
308
- - Prevent duplicate pinned_prs during import with `insert_conflict` and per-row rescue
309
-
310
- ## [0.1.308.pre.5] - 2026-03-31
311
-
312
- ### Added
313
- - `h-claude vim` subcommand to open all matched files in `$EDITOR`
314
-
315
- ### Fixed
316
- - `h branch ls` now sorts oldest-first so most recent branches appear at bottom
317
-
318
- ## [0.1.308.pre.4] - 2026-03-31
319
-
320
- ### Fixed
321
- - Don't pass empty string arg to gem when `--pre` is false
322
-
323
- ## [0.1.308.pre.3] - 2026-03-31
324
-
325
- ### Changed
326
- - Poll RubyGems in `delayed_update` instead of blind sleep for more reliable version detection
327
-
328
- ### Fixed
329
- - Per-row rescue in `import_todos` so one bad row doesn't abort the entire batch
330
-
331
- ## [0.1.308.pre.2] - 2026-03-31
332
-
333
- ### Added
334
- - `--pre`/`-p` flag to `h install` and `h update` for installing/updating pre-release versions
335
-
336
- ### Changed
337
- - Merge `prs` and `pinned_prs` tables into single `prs` table in SQLite schema
338
- - Refactor PR storage to use unified table structure
339
-
340
- ### Fixed
341
- - YAML migration for todos, prs, pinned_prs, and tags to correctly handle merged schema
342
-
343
- ## [0.1.308.pre.1] - 2026-03-31
344
-
345
- ### Added
346
- - `Hiiro::Effects` injectable interface for testable file system and command execution
347
- - `null_fs` to `TestHarness` for testing without side effects
348
- - Effects helpers and accessors to `TestHarness` for controlled effect simulation
349
- - `Hiiro::Invocation` and `Hiiro::InvocationResolution` tracking in PaneHome SQLite migration
350
-
351
- ### Changed
352
- - Refactor effects layer: expose `executor` and `fs` as accessors on `Hiiro::Effects`
353
- - `h-db` command now includes h-pane in SQLite migration
354
- - Gem version handling: treat non-main branches as pre-release in publish script
355
-
356
- ### Fixed
357
- - `h-branch co` and `h-branch rm` restore extra argument pass-through
358
- - Test suite: add missing `TestHarness` stubs and fix pre-existing test failures
359
- - Test fixtures: anchor `load_bin` path to project root instead of `Dir.pwd`
360
-
361
- ### Deprecated
362
- - `SystemCallCapture` — use `Hiiro::Effects` helpers in `TestHarness` instead
363
-
364
- ## [0.1.307]
365
-
366
- ### Added
367
- - `Hiiro::DB` — SQLite foundation via Sequel; `DB.setup!` creates all tables, `DB.connection` establishes connection eagerly at load time; supports `HIIRO_TEST_DB=sqlite::memory:` for tests
368
- - `lib/hiiro/db.rb` — one-time YAML→SQLite migration (`migrate_yaml!`) guarded by `schema_migrations` table; dual-write mode (`dual_write?` / `disable_dual_write!`) for gradual cutover
369
- - `lib/hiiro/branch.rb` — `Hiiro::Branch` Sequel model for worktree branch records
370
- - `lib/hiiro/tracked_pr.rb` — `Hiiro::TrackedPr` Sequel model for tracked PR records (`:prs` table)
371
- - `lib/hiiro/link.rb` — `Hiiro::Link` Sequel model with `matches?`, `display_string`, `to_h` helpers
372
- - `lib/hiiro/project.rb` — `Hiiro::Project` Sequel model
373
- - `lib/hiiro/pane_home.rb` — `Hiiro::PaneHome` Sequel model with `data_json` JSON blob
374
- - `lib/hiiro/pin_record.rb` — `Hiiro::PinRecord` Sequel model for per-command key-value pin storage
375
- - `lib/hiiro/task_record.rb` — `Hiiro::TaskRecord` Sequel model for task metadata
376
- - `lib/hiiro/app_record.rb` — `Hiiro::AppRecord` Sequel model for app directory mappings
377
- - `lib/hiiro/assignment.rb` — `Hiiro::Assignment` Sequel model for worktree→branch assignments
378
- - `lib/hiiro/reminder.rb` — `Hiiro::Reminder` Sequel model
379
- - `lib/hiiro/invocation.rb` — `Hiiro::Invocation` and `Hiiro::InvocationResolution` Sequel models; every CLI invocation is recorded to SQLite for history/analytics
380
- - `bin/h-db` — new subcommand: `h db status`, `h db tables`, `h db q <sql>`, `h db migrate`, `h db restore`
381
-
382
- ### Changed
383
- - `lib/hiiro/todo.rb` — `TodoItem` is now a `Sequel::Model`; `TodoManager` reads/writes via SQLite with YAML dual-write fallback
384
- - `lib/hiiro/tags.rb` — `Tag` is now a `Sequel::Model`; tag operations persist to SQLite with YAML dual-write fallback
385
- - `lib/hiiro/pinned_pr_manager.rb` — `PinnedPR` is now a `Sequel::Model` (`lib/hiiro/pinned_pr.rb`); `PinnedPRManager` reads/writes via SQLite with YAML dual-write
386
- - `lib/hiiro/projects.rb` — `Projects#from_config` reads from `Hiiro::Project` SQLite model with YAML fallback
387
- - `lib/hiiro/tasks.rb` — `TaskManager::Config` reads/writes tasks and apps via `Hiiro::TaskRecord` and `Hiiro::AppRecord` SQLite models
388
- - `bin/h-branch` — `BranchManager` reads/writes via `Hiiro::Branch` and `Hiiro::TrackedPr` SQLite models with YAML dual-write fallback; adds `q`/`query` subcommands for raw SQL inspection
389
- - `bin/h-link` — reads/writes links via `Hiiro::Link` SQLite model with YAML dual-write fallback; adds `q`/`query` subcommands
390
- - `bin/h-pane` — load/save pane homes via `Hiiro::PaneHome` model with YAML dual-write
391
- - `bin/h-pr` — adds `q`/`query` subcommands for inspecting PR records via raw SQL
392
- - `plugins/pins.rb` — `Pin` class reads/writes via `Hiiro::PinRecord` SQLite model with YAML dual-write fallback
319
+ - Prevent duplicate pinned_prs during import with `insert_conflict` and per-row rescue
data/bin/h-capture ADDED
@@ -0,0 +1,200 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'fileutils'
4
+ require 'open3'
5
+ require 'time'
6
+ require 'hiiro'
7
+
8
+ CAPTURES_DIR = File.expand_path('~/.local/share/hiiro/captures')
9
+
10
+ # TODO(USER): implement resolve_path.
11
+ #
12
+ # Given a name-or-path string from `h capture file <fpath_or_name> ...`,
13
+ # return the absolute filesystem path where the capture should be written.
14
+ #
15
+ # Spec from the user:
16
+ # - If the input has NO path separators (just a bare filename like "build.log"),
17
+ # the file goes in CAPTURES_DIR.
18
+ # - Otherwise it's a path expression — write it relative to the current
19
+ # working directory (or honor it as-is if already absolute).
20
+ #
21
+ # Open design questions for you to decide:
22
+ # - Should "~/foo.log" be expanded? (File.expand_path handles this for free)
23
+ # - Should we ensure parent dirs exist via FileUtils.mkdir_p?
24
+ # - Should paths starting with "./" be normalized?
25
+ #
26
+ # Whichever rules you pick, this 5–8 line method is the contract for where
27
+ # `h capture file` writes — so it's worth getting right for your workflow.
28
+ def resolve_path(name_or_path)
29
+ if File.dirname(name_or_path) != ?.
30
+ FileUtils.mkdir_p(File.dirname(name_or_path))
31
+ return name_or_path
32
+ end
33
+
34
+ FileUtils.mkdir_p(CAPTURES_DIR)
35
+ File.join(CAPTURES_DIR, name_or_path)
36
+ end
37
+
38
+ def ensure_dir(path)
39
+ FileUtils.mkdir_p(File.dirname(path))
40
+ end
41
+
42
+ # Run cmd, tee combined stdout+stderr to terminal AND to output_path.
43
+ # If our own stdin is piped (not a tty), forward it into the subprocess.
44
+ def run_and_capture(cmd_args, output_path)
45
+ ensure_dir(output_path)
46
+ exit_status = nil
47
+
48
+ File.open(output_path, 'w') do |f|
49
+ Open3.popen2e(*cmd_args) do |stdin, stdout_err, wait_thr|
50
+ pipe_thread = if !STDIN.tty?
51
+ Thread.new do
52
+ IO.copy_stream(STDIN, stdin)
53
+ rescue Errno::EPIPE, IOError
54
+ # subprocess closed stdin early — fine
55
+ ensure
56
+ stdin.close rescue nil
57
+ end
58
+ else
59
+ stdin.close
60
+ nil
61
+ end
62
+
63
+ loop do
64
+ chunk = stdout_err.readpartial(4096)
65
+ $stdout.write(chunk)
66
+ $stdout.flush
67
+ f.write(chunk)
68
+ rescue EOFError
69
+ break
70
+ end
71
+
72
+ pipe_thread&.join
73
+ exit_status = wait_thr.value.exitstatus
74
+ end
75
+ end
76
+
77
+ exit_status
78
+ end
79
+
80
+ def fetch_capture(num)
81
+ Hiiro::Capture.at_offset(num.to_i)
82
+ end
83
+
84
+ def record_capture(name:, path:, command:, exit_status:)
85
+ Hiiro::Capture.create(
86
+ name: name,
87
+ path: path,
88
+ command: command,
89
+ exit_status: exit_status
90
+ )
91
+ end
92
+
93
+ # Run the command and record the capture even if interrupted (Ctrl-C, error).
94
+ # `exit_status` is nil when the process didn't reach a clean exit — the `?`
95
+ # glyph in Hiiro::Capture#status_glyph already handles that case.
96
+ def capture_and_record(args, path:, name:)
97
+ code = nil
98
+ begin
99
+ code = run_and_capture(args, path)
100
+ ensure
101
+ record_capture(name: name, path: path, command: args.join(' '), exit_status: code)
102
+ warn "\n[capture saved: #{path} (exit #{code || 'interrupted'})]"
103
+ end
104
+ end
105
+
106
+ Hiiro.run(*ARGV) do
107
+ add_subcmd(:new) do |*args|
108
+ if args.empty?
109
+ puts "Usage: h capture new <command> [args...]"
110
+ exit 1
111
+ end
112
+
113
+ name = "#{Time.now.strftime('%Y%m%d%H%M%S')}.output"
114
+ path = File.join(CAPTURES_DIR, name)
115
+ ensure_dir(path)
116
+
117
+ capture_and_record(args, path: path, name: name)
118
+ end
119
+
120
+ add_subcmd(:file) do |fpath = nil, *args|
121
+ if fpath.nil? || args.empty?
122
+ puts "Usage: h capture file <fpath_or_name> <command> [args...]"
123
+ exit 1
124
+ end
125
+
126
+ path = resolve_path(fpath)
127
+ name = File.basename(fpath)
128
+
129
+ capture_and_record(args, path: path, name: name)
130
+ end
131
+
132
+ add_subcmd(:ls) do |num = nil|
133
+ captures = if num.nil?
134
+ Hiiro::Capture.recent
135
+ elsif num.to_i >= 0
136
+ cap = Hiiro::Capture.at_offset(num.to_i)
137
+ cap ? [cap] : []
138
+ else
139
+ Hiiro::Capture.recent(num.to_i.abs)
140
+ end
141
+
142
+ if captures.empty?
143
+ puts "No captures."
144
+ next
145
+ end
146
+
147
+ captures.each_with_index { |cap, i| puts cap.display_string(i) }
148
+ end
149
+
150
+ add_subcmd(:cat) do |num = 0|
151
+ cap = fetch_capture(num)
152
+ if cap.nil?
153
+ warn "No capture at offset #{num}."
154
+ exit 1
155
+ end
156
+ unless File.exist?(cap.path)
157
+ warn "File missing: #{cap.path}"
158
+ exit 1
159
+ end
160
+ $stdout.write(File.read(cap.path))
161
+ end
162
+
163
+ add_subcmd(:cppath) do |num = 0|
164
+ cap = fetch_capture(num)
165
+ if cap.nil?
166
+ warn "No capture at offset #{num}."
167
+ exit 1
168
+ end
169
+ Hiiro::Shell.pipe(cap.path, 'pbcopy')
170
+ puts "Copied path: #{cap.path}"
171
+ end
172
+
173
+ add_subcmd(:cp) do |num = 0|
174
+ cap = fetch_capture(num)
175
+ if cap.nil?
176
+ warn "No capture at offset #{num}."
177
+ exit 1
178
+ end
179
+ unless File.exist?(cap.path)
180
+ warn "File missing: #{cap.path}"
181
+ exit 1
182
+ end
183
+ Hiiro::Shell.pipe(File.read(cap.path), 'pbcopy')
184
+ puts "Copied contents of: #{cap.path}"
185
+ end
186
+
187
+ add_subcmd(:path) do |num = nil|
188
+ if num.nil?
189
+ print CAPTURES_DIR
190
+ next
191
+ end
192
+
193
+ cap = fetch_capture(num)
194
+ if cap.nil?
195
+ warn "No capture at offset #{num}."
196
+ exit 1
197
+ end
198
+ print cap.path
199
+ end
200
+ end
@@ -0,0 +1,42 @@
1
+ require 'sequel'
2
+
3
+ class Hiiro
4
+ class Capture < Sequel::Model(:captures)
5
+ Hiiro::DB.register(self)
6
+
7
+ def self.create_table!(db)
8
+ db.create_table?(:captures) do
9
+ primary_key :id
10
+ String :name, null: false
11
+ String :path, null: false
12
+ String :command
13
+ Integer :exit_status
14
+ end
15
+ end
16
+
17
+ # Most-recent-first scope. id is autoincrement, so desc(:id) == chronological reverse.
18
+ def self.recent(limit = nil)
19
+ ds = order(Sequel.desc(:id))
20
+ limit ? ds.limit(limit).all : ds.all
21
+ end
22
+
23
+ # Fetch the capture at offset n from the most recent (0 = newest).
24
+ def self.at_offset(n)
25
+ order(Sequel.desc(:id)).offset(n.to_i).first
26
+ end
27
+
28
+ def status_glyph
29
+ case exit_status
30
+ when 0 then "\e[32m✓\e[0m"
31
+ when nil then "\e[33m?\e[0m"
32
+ else "\e[31m✗\e[0m"
33
+ end
34
+ end
35
+
36
+ def display_string(idx)
37
+ cmd = command.to_s
38
+ cmd = cmd[0, 57] + '…' if cmd.length > 60
39
+ format("%4d. %s %-22s %s", idx, status_glyph, name, cmd)
40
+ end
41
+ end
42
+ end
data/lib/hiiro/shell.rb CHANGED
@@ -77,6 +77,21 @@ class Hiiro
77
77
  end
78
78
 
79
79
  class Result
80
+ # Factory: reads chunks from an IO (popen handle), optionally tee-ing
81
+ # each chunk to `tee` as it arrives. Used by Shell.stream / stream_combined.
82
+ # Pass tee: nil to capture without printing.
83
+ def self.collect_chunks(io, wait_thr, tee: $stdout)
84
+ output = +""
85
+ loop do
86
+ chunk = io.readpartial(4096)
87
+ tee&.write(chunk)
88
+ output << chunk
89
+ rescue EOFError
90
+ break
91
+ end
92
+ new(output, wait_thr.value)
93
+ end
94
+
80
95
  # Matches the full ANSI/VT100 escape sequence spec:
81
96
  # cursor movement, erase, colors, SGR — everything a terminal interprets.
82
97
  # [^[] catches all single-char Fe sequences (e.g. \eM, \eD); \[...] catches CSI.
@@ -115,27 +130,12 @@ class Hiiro
115
130
  # Strip ANSI escape codes for text processing or filtering.
116
131
  # Also strips bare \r (carriage-return-only, used by progress bars).
117
132
  def plain_text
118
- stdout.gsub(ANSI_PATTERN, '').gsub(/\r(?!\n)/, '')
133
+ @plain_text ||= stdout.gsub(ANSI_PATTERN, '').gsub(/\r(?!\n)/, '')
119
134
  end
120
135
 
121
136
  # Plain text split into non-empty lines.
122
137
  def lines
123
- plain_text.split("\n").reject(&:empty?)
124
- end
125
-
126
- # Factory: reads chunks from an IO (popen handle), optionally tee-ing
127
- # each chunk to `tee` as it arrives. Used by Shell.stream / stream_combined.
128
- # Pass tee: nil to capture without printing.
129
- def self.collect_chunks(io, wait_thr, tee: $stdout)
130
- output = +""
131
- loop do
132
- chunk = io.readpartial(4096)
133
- tee&.write(chunk)
134
- output << chunk
135
- rescue EOFError
136
- break
137
- end
138
- new(output, wait_thr.value)
138
+ @lines ||= plain_text.lines(chomp: true)
139
139
  end
140
140
  end
141
141
  end
data/lib/hiiro/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Hiiro
2
- VERSION = "0.1.349"
2
+ VERSION = "0.1.351"
3
3
  end
data/lib/hiiro.rb CHANGED
@@ -21,6 +21,7 @@ require_relative "hiiro/options"
21
21
  require_relative "hiiro/input_file"
22
22
  require_relative "hiiro/paths"
23
23
  require_relative "hiiro/link"
24
+ require_relative "hiiro/capture"
24
25
  require_relative "hiiro/branch"
25
26
  require_relative "hiiro/check_run"
26
27
  require_relative "hiiro/tags"
data/script/publish CHANGED
@@ -166,7 +166,7 @@ class Claude
166
166
  }
167
167
  PROMPT
168
168
 
169
- raw = IO.popen(["claude", "-p", "--model", MODEL], "r+") { |io|
169
+ raw = IO.popen(["claude", "-p", "--model", MODEL, '--effort', 'low'], "r+") { |io|
170
170
  io.write(prompt)
171
171
  io.close_write
172
172
  io.read
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.349
4
+ version: 0.1.351
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Toyota
@@ -131,6 +131,7 @@ files:
131
131
  - bin/h-bin
132
132
  - bin/h-branch
133
133
  - bin/h-buffer
134
+ - bin/h-capture
134
135
  - bin/h-claude
135
136
  - bin/h-commit
136
137
  - bin/h-config
@@ -326,6 +327,7 @@ files:
326
327
  - lib/hiiro/background.rb
327
328
  - lib/hiiro/bins.rb
328
329
  - lib/hiiro/branch.rb
330
+ - lib/hiiro/capture.rb
329
331
  - lib/hiiro/check_run.rb
330
332
  - lib/hiiro/config.rb
331
333
  - lib/hiiro/db.rb