lux-hammer 0.3.23 → 0.3.24
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/.version +1 -1
- data/README.md +5 -3
- data/lib/hammer/input.rb +28 -8
- data/lib/lux-hammer.rb +3 -2
- data/recipes/llm.rb +13 -8
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 68c2cd8d90138ab3eaf6a68a23aeabc273b31d26122224ba3966d235a9841657
|
|
4
|
+
data.tar.gz: ae13bf1ca96c396b51a1db3801ce4aa4179ad67f5bb8e4b04467715b9744164e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 222754053a8ca203a8f0344fd0007174e1a333f92e69935fd908567e7e6a8a1d1c8675d5bd8a443a905fd9140ba54ed4afc2fdbd7abe817c8cc90d3643e97fa1
|
|
7
|
+
data.tar.gz: 345ebe94f0d5eb3383fb4a0df98a1cc95d611fa330d2174c2ed6b3b25de724ee0acc51100682cd93cce813c446298cd0e19d004998e3fe95e8f5b01434368faa
|
data/.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.3.
|
|
1
|
+
0.3.24
|
data/README.md
CHANGED
|
@@ -506,9 +506,11 @@ end
|
|
|
506
506
|
|
|
507
507
|
## Stdin, JSON input, and global options
|
|
508
508
|
|
|
509
|
-
Every command
|
|
510
|
-
|
|
511
|
-
never
|
|
509
|
+
Every command can read **`opts[:stdin]`**: piped stdin (non-TTY, non-empty)
|
|
510
|
+
as a string, otherwise `nil`. It is read on the first lookup and kept, so a
|
|
511
|
+
command that never looks never waits on a pipe. Interactive TTYs are never
|
|
512
|
+
consumed, and a socket with nothing pending (agent harnesses hand one to
|
|
513
|
+
every command) reads as `nil` rather than blocking.
|
|
512
514
|
|
|
513
515
|
```ruby
|
|
514
516
|
task :count do
|
data/lib/hammer/input.rb
CHANGED
|
@@ -3,18 +3,23 @@
|
|
|
3
3
|
require 'json'
|
|
4
4
|
|
|
5
5
|
class Hammer
|
|
6
|
-
# Stdin + JSON helpers for opts. Hammer
|
|
7
|
-
# `opts[:stdin]
|
|
8
|
-
# `Hammer::Input.prepare_json!` (typically from a `before`
|
|
9
|
-
# `opt :json, type: :json` / `global_opt :json, type: :json`.
|
|
6
|
+
# Stdin + JSON helpers for opts. Hammer exposes piped stdin as
|
|
7
|
+
# `opts[:stdin]`, read lazily on first access; recipes opt into JSON body
|
|
8
|
+
# handling via `Hammer::Input.prepare_json!` (typically from a `before`
|
|
9
|
+
# hook) and/or `opt :json, type: :json` / `global_opt :json, type: :json`.
|
|
10
10
|
module Input
|
|
11
11
|
module_function
|
|
12
12
|
|
|
13
13
|
# Read piped stdin once. Returns nil when stdin is a TTY or empty.
|
|
14
|
-
# Does not consume an interactive TTY
|
|
14
|
+
# Does not consume an interactive TTY, and does not block on a socket
|
|
15
|
+
# that has nothing pending: agent harnesses (Claude Code's Bash tool)
|
|
16
|
+
# hand every command a unix socket as stdin that never reaches EOF, so
|
|
17
|
+
# a plain read would hang there for good. A shell pipe or a redirected
|
|
18
|
+
# file is a FIFO or a regular file and is still read in full.
|
|
15
19
|
def read_stdin
|
|
16
20
|
return nil if $stdin.closed?
|
|
17
21
|
return nil if $stdin.tty?
|
|
22
|
+
return nil if idle_socket?($stdin)
|
|
18
23
|
|
|
19
24
|
data = $stdin.read
|
|
20
25
|
return nil if data.nil?
|
|
@@ -25,11 +30,26 @@ class Hammer
|
|
|
25
30
|
nil
|
|
26
31
|
end
|
|
27
32
|
|
|
28
|
-
|
|
33
|
+
def idle_socket?(io)
|
|
34
|
+
return false unless io.respond_to?(:stat) && io.stat.socket?
|
|
35
|
+
|
|
36
|
+
IO.select([io], nil, nil, 0).nil?
|
|
37
|
+
rescue StandardError
|
|
38
|
+
false
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Reads stdin into the hash on the first `opts[:stdin]` lookup and keeps
|
|
42
|
+
# the result, so a command that never asks for stdin never waits on it.
|
|
43
|
+
LAZY_STDIN = lambda do |hash, key|
|
|
44
|
+
key == :stdin ? (hash[:stdin] = Input.read_stdin) : nil
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Idempotent: leaves an explicit opts[:stdin] alone, otherwise installs
|
|
48
|
+
# the lazy reader. Nothing is read until a recipe looks at opts[:stdin].
|
|
29
49
|
def attach_stdin!(opts)
|
|
30
|
-
return
|
|
50
|
+
return if opts.key?(:stdin) || opts.default_proc.equal?(LAZY_STDIN)
|
|
31
51
|
|
|
32
|
-
opts
|
|
52
|
+
opts.default_proc = LAZY_STDIN
|
|
33
53
|
end
|
|
34
54
|
|
|
35
55
|
# Parse a JSON source string into a Hash/Array with symbol keys.
|
data/lib/lux-hammer.rb
CHANGED
|
@@ -690,8 +690,9 @@ class Hammer
|
|
|
690
690
|
options = effective_options(cmd)
|
|
691
691
|
positional, opts = Parser.new(options).parse(argv)
|
|
692
692
|
opts[:args] = positional
|
|
693
|
-
#
|
|
694
|
-
# JSON body handling call Hammer::Input.prepare_json!
|
|
693
|
+
# opts[:stdin] reads piped stdin on first access (nil when TTY / empty).
|
|
694
|
+
# Recipes that want JSON body handling call Hammer::Input.prepare_json!
|
|
695
|
+
# in a before hook.
|
|
695
696
|
Hammer::Input.attach_stdin!(opts)
|
|
696
697
|
print_run_banner(cmd, full || cmd.name, positional, opts, options: options) unless quiet || ENV['HAMMER_QUIET']
|
|
697
698
|
instance = new
|
data/recipes/llm.rb
CHANGED
|
@@ -337,7 +337,9 @@ TODO_GUIDE ||= <<~TXT
|
|
|
337
337
|
Per-project task queue in ./LLM_TODO.local.md - workable by humans and any agent.
|
|
338
338
|
|
|
339
339
|
Human usage:
|
|
340
|
-
llm todo:add "fix login redirect" queue a task
|
|
340
|
+
llm todo:add "fix login redirect" queue a task; pipe stdin for bulk:
|
|
341
|
+
`* ` bullets delimit tasks (multi-line ok),
|
|
342
|
+
no bullets at all = one task per line
|
|
341
343
|
llm todo:list tasks by id, counts, format warnings
|
|
342
344
|
llm todo:pop + llm todo:done take / finish a single task by hand
|
|
343
345
|
|
|
@@ -491,9 +493,10 @@ namespace :todo do
|
|
|
491
493
|
desc <<~DESC
|
|
492
494
|
Add task(s) to ./#{TODO_FILE} (created on first add).
|
|
493
495
|
|
|
494
|
-
Text comes from the argument (one task), or from stdin
|
|
495
|
-
|
|
496
|
-
so multi-line tasks and whole lists can be
|
|
496
|
+
Text comes from the argument (one task), or from stdin. Piped input
|
|
497
|
+
that has `*` / `-` bullets is a list: a bullet begins a task and the
|
|
498
|
+
lines under it belong to it, so multi-line tasks and whole lists can be
|
|
499
|
+
piped in at once. Input with no bullets at all is one task per line.
|
|
497
500
|
DESC
|
|
498
501
|
example 'todo add "migrate the user model to Sequel"'
|
|
499
502
|
example 'cat tasks.md | llm todo add'
|
|
@@ -503,17 +506,19 @@ namespace :todo do
|
|
|
503
506
|
# unquoted multi-word input lands in :text + :args - join it back
|
|
504
507
|
arg = [opts[:text], *opts[:args]].compact.join(' ').strip
|
|
505
508
|
if arg.empty?
|
|
509
|
+
lines = opts[:stdin].to_s.split("\n")
|
|
510
|
+
bulleted = lines.any? { |l| l =~ /^[-*]\s+/ }
|
|
506
511
|
new_tasks = []
|
|
507
|
-
|
|
512
|
+
lines.each do |line|
|
|
508
513
|
if line =~ /^[-*]\s+(.*)$/
|
|
509
514
|
new_tasks << [$1.strip]
|
|
510
|
-
elsif
|
|
511
|
-
new_tasks.last << line.rstrip
|
|
515
|
+
elsif bulleted
|
|
516
|
+
new_tasks.last << line.rstrip if new_tasks.any?
|
|
512
517
|
elsif line.strip != ''
|
|
513
518
|
new_tasks << [line.rstrip]
|
|
514
519
|
end
|
|
515
520
|
end
|
|
516
|
-
new_tasks = new_tasks.map { |
|
|
521
|
+
new_tasks = new_tasks.map { |ls| ls.join("\n").rstrip }.reject(&:empty?)
|
|
517
522
|
else
|
|
518
523
|
new_tasks = [arg]
|
|
519
524
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lux-hammer
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.24
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dino Reic
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-08-
|
|
11
|
+
date: 2026-08-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: minitest
|