lux-hammer 0.3.8 → 0.3.9

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: 64f8d2c7864047c17b42187e610ac14e93982a5ff1bd8dc6187a35670f67e432
4
- data.tar.gz: 07b7014dcdfe70c48c293e7e18ce05475dcd565ee2c66310afa0c3a16d371f02
3
+ metadata.gz: 60fa663d47e0d1d68790c40125c1594ae0152d99695cd57fe1d86294b1a5c348
4
+ data.tar.gz: 779a8291e200bff69a8c3b237662a88647a50071eb038f47fe695d547c805094
5
5
  SHA512:
6
- metadata.gz: f1f4515e1340b132865081e4ff4118789540fd2c7c880d2ec42330d174e6f41f88b5355f40727207fa0320811bdba6f134d70a81604760221e2189be6cd73b7e
7
- data.tar.gz: f43b40bd53e03f3c2a4ad04f5e8a886863c038b79f2cf18e39f96eb8e09bb3e8dfaa3c5a6610fc0acaee8f76c6a31860a0dc10f4a181c80fbc8ee64e5a45d32b
6
+ metadata.gz: 6b2114d72e01ee7aef8166a4881915d4c124e0cce67c6c9e5d680fb945750aa8b338fc7041332dcaf6d54fe15fac04cf3e85271e8cd75b5bc3126edacb30f827
7
+ data.tar.gz: 7f85fb830fae721e969e2d23e28a9c282c6655b8b9f29aa5c8dd15a9fa3e5f55eddcfa9a978d7605c37c1ca20c177b43c1475114f64f870e41e5993c56287854
data/.version CHANGED
@@ -1 +1 @@
1
- 0.3.8
1
+ 0.3.9
data/lib/hammer/parser.rb CHANGED
@@ -63,7 +63,11 @@ class Hammer
63
63
  @options.each do |opt|
64
64
  break if positional.empty?
65
65
  next if opt.boolean? || values.key?(opt.name)
66
- values[opt.name] = opt.cast(positional.shift)
66
+ if opt.type == :array
67
+ values[opt.name] = opt.cast(positional.shift(positional.size))
68
+ else
69
+ values[opt.name] = opt.cast(positional.shift)
70
+ end
67
71
  end
68
72
 
69
73
  @options.each do |opt|
data/lib/hammer/shell.rb CHANGED
@@ -31,8 +31,8 @@ class Hammer
31
31
 
32
32
  # `say` with no args returns a proxy so you can write `say.cyan 'hi'`.
33
33
  # `say('')` still prints a blank line; `say('x', :cyan)` is unchanged.
34
- def say(text = nil, color = nil)
35
- return SayProxy.new if text.nil?
34
+ def say(text = :_say_no_arg, color = nil)
35
+ return SayProxy.new if text == :_say_no_arg
36
36
  puts paint(text, color)
37
37
  end
38
38
 
@@ -116,9 +116,9 @@ class Hammer
116
116
  $stdout.print "\e[#{items.size}A\r\e[J"
117
117
  $stdout.print "#{paint("> #{items[selected]}", :green)}\r\n"
118
118
  return selected
119
- when "\x03", 'q' # Ctrl-C, q
119
+ when "\x03" # Ctrl-C
120
120
  $stdout.print "\e[#{items.size}A\r\e[J"
121
- return nil
121
+ raise Interrupt
122
122
  when "\e"
123
123
  # ESC may stand alone or start an arrow sequence \e[A / \e[B.
124
124
  if IO.select([io], nil, nil, 0.01) && io.getch == '['
data/lib/lux-hammer.rb CHANGED
@@ -414,6 +414,13 @@ class Hammer
414
414
  run_command(cmd, argv, full: name, quiet: true)
415
415
  end
416
416
 
417
+ # True when -h or --help appears in argv before a `--` stop-marker.
418
+ def help_requested?(argv)
419
+ stop = argv.index('--')
420
+ scan = stop ? argv[0...stop] : argv
421
+ scan.include?('-h') || scan.include?('--help')
422
+ end
423
+
417
424
  public
418
425
 
419
426
  # Find a command by canonical name or alt within this class. Falls
@@ -605,30 +612,26 @@ class Hammer
605
612
  end
606
613
  end
607
614
 
608
- def help_requested?(argv)
609
- stop = argv.index('--')
610
- scan = stop ? argv[0...stop] : argv
611
- scan.include?('-h') || scan.include?('--help')
612
- end
615
+ public
613
616
 
614
617
  # `extended: true` is the verbose `help` / `-h` / `--help` form -
615
618
  # appends global flags, the GitHub footer, and (for the hammer binary)
616
619
  # a Hammerfile example. Bare invocation passes `extended: false` so
617
620
  # the no-args output stays a clean command listing.
618
- def print_help(target = nil, full: false, extended: false)
621
+ def print_help(target = nil, expanded: false, extended: false)
619
622
  if target
620
623
  # `help ns:` is equivalent to `ns:` - namespace listing.
621
624
  if target.end_with?(':') && target != ':'
622
625
  bare = target.chomp(':')
623
626
  ns, canonical = resolve_namespace(bare)
624
- return print_namespace_help(canonical, ns, extended: extended) if ns
627
+ return print_namespace_help(canonical, ns) if ns
625
628
  Shell.print_error("unknown: #{target}")
626
629
  return
627
630
  end
628
631
  cmd, _, canonical = resolve(target)
629
632
  return print_command_help(cmd, canonical) if cmd
630
633
  ns, canonical = resolve_namespace(target)
631
- return print_namespace_help(canonical, ns, full: full, extended: extended) if ns
634
+ return print_namespace_help(canonical, ns, expanded: expanded) if ns
632
635
  Shell.print_error("unknown: #{target}")
633
636
  return
634
637
  end
@@ -639,7 +642,7 @@ class Hammer
639
642
  Shell.say ''
640
643
  @app_desc.each_line { |l| Shell.say " #{l.chomp}" }
641
644
  end
642
- if full
645
+ if expanded
643
646
  each_command { |path, c| print_full_block(path, c) unless c.desc.empty? }
644
647
  else
645
648
  Shell.say ''
@@ -670,10 +673,9 @@ class Hammer
670
673
 
671
674
  # `extended:` is accepted for parity with `print_help` but intentionally
672
675
  # not used here - the global-flags / Hammerfile-example / footer block
673
- # is root-help-only. A namespace listing is just the commands under
674
- # that prefix; tool-meta noise (Recipes: section) is reserved for
675
- # `hammer --help` at the top level.
676
- def print_namespace_help(prefix, ns, full: false, extended: false)
676
+ # is root-help-only. `expanded:` is also accepted for parity; a namespace
677
+ # listing is always the compact command list.
678
+ def print_namespace_help(prefix, ns, expanded: false, extended: false)
677
679
  Shell.say "Usage: #{program_name} #{prefix}:COMMAND [ARGS]", :cyan
678
680
  rows = []
679
681
  sibling = find_namespace_sibling(prefix)
@@ -989,6 +991,17 @@ class Hammer
989
991
  argv = argv.dup
990
992
  force_system = !!argv.delete('--system')
991
993
 
994
+ # Shebang invocation: `hammer /path/to/script ...args` (kernel passes
995
+ # the script path as argv[0] for `#!/usr/bin/env hammer` files).
996
+ # Treat the script as a self-contained CLI: no Hammerfile lookup, no
997
+ # chdir (commands run in the caller's cwd), no `hammer`-binary
998
+ # built-ins/banners. Detection requires a `#!`+`hammer` first line so
999
+ # task names that happen to be paths don't get hijacked.
1000
+ if (script = shebang_script(argv.first))
1001
+ argv.shift
1002
+ return run_shebang(script, argv)
1003
+ end
1004
+
992
1005
  path = force_system ? nil : find_hammerfile(Dir.pwd)
993
1006
  unless path
994
1007
  # No Hammerfile (or --system) - all built-ins are reachable. Bare
@@ -1058,6 +1071,33 @@ class Hammer
1058
1071
  klass.start(argv)
1059
1072
  end
1060
1073
 
1074
+ # Returns the script path if `arg` looks like a shebang script that
1075
+ # delegates to hammer (first line starts with `#!` and mentions
1076
+ # `hammer`). Returns nil otherwise. Used by `cli` to detect
1077
+ # `#!/usr/bin/env hammer` invocations where the kernel passes the
1078
+ # script path as argv[0].
1079
+ def self.shebang_script(arg)
1080
+ return nil unless arg
1081
+ return nil if arg.start_with?('-')
1082
+ return nil unless File.file?(arg) && File.readable?(arg)
1083
+ head = File.open(arg, &:gets).to_s
1084
+ return nil unless head.start_with?('#!') && head.include?('hammer')
1085
+ arg
1086
+ end
1087
+
1088
+ # Evaluate a shebang script as a self-contained CLI. Mirrors `recipe`
1089
+ # semantics: no chdir, no `@hammer_binary` flag, no `register_core`
1090
+ # built-ins (so the script's `--help` shows only what it defines).
1091
+ # `program_name` is the script's basename so help reads "myscript foo"
1092
+ # rather than "hammer foo" - works even when invoked via a symlink in
1093
+ # PATH, since argv[0] is the path the user typed.
1094
+ def self.run_shebang(path, argv)
1095
+ klass = Class.new(Hammer)
1096
+ klass.instance_variable_set(:@program_name, File.basename(path))
1097
+ Builder.new(klass).evaluate(File.read(path), path)
1098
+ klass.start(argv)
1099
+ end
1100
+
1061
1101
  # True if argv goes through a built-in dispatch path (`:default` or
1062
1102
  # `:help`) - meaning bare `hammer`, leading-flag invocations like
1063
1103
  # `hammer -h`, or explicit help requests. These don't need a project
@@ -1,4 +1,6 @@
1
+ #!/usr/bin/env hammer
1
2
  # desc: work with git (commit, push, pull, rebase, branch, redate, ...)
3
+ # executable: chmod +x this file and run directly, or symlink into PATH
2
4
 
3
5
  desc <<~TXT
4
6
  Git helper. Short aliases over common `git` workflows.
data/recipes/llm.rb CHANGED
@@ -1,4 +1,6 @@
1
+ #!/usr/bin/env hammer
1
2
  # desc: personal LLM utility CLI (memory store, prompt-token expander, ...)
3
+ # executable: chmod +x this file and run directly, or symlink into PATH
2
4
 
3
5
  desc <<~TXT
4
6
  llm - personal LLM utility CLI
@@ -54,6 +56,7 @@ namespace :memory do
54
56
  end
55
57
  [meta, body.to_s.sub(/\A\n+/, '')]
56
58
  end
59
+
57
60
  task :list do
58
61
  desc 'List stored memories with type and one-line description'
59
62
  example 'llm memory list'
data/recipes/srt.rb CHANGED
@@ -1,4 +1,6 @@
1
+ #!/usr/bin/env hammer
1
2
  # desc: Local SRT extraction via whisper.cpp
3
+ # executable: chmod +x this file and run directly, or symlink into PATH
2
4
 
3
5
  desc <<~TXT
4
6
  Local subtitle extraction with whisper.cpp.
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.8
4
+ version: 0.3.9
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-05-25 00:00:00.000000000 Z
11
+ date: 2026-05-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest