lux-hammer 0.3.25 → 0.3.26
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/lib/hammer/parser.rb +11 -0
- data/recipes/lib/llm/launch.rb +76 -0
- data/recipes/llm.rb +53 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: acc942b0818d0cc60a8cbe9a55fbf092665d6efcb46df209b2a8e9721aa1e190
|
|
4
|
+
data.tar.gz: 94baed7a0025611360d0315d084fd7432a1eb68d84302bfeaf0960ee0326aee7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 486bb304e25a56faada26757651998d75e589fabca7402f499dc9cdd3228a90b30cc3fc7cda765d2cd34fc90eecc906208681c031e38628e0087f59eb5250af7
|
|
7
|
+
data.tar.gz: 87115686cf4bbdd6a47ed9a25e048cf811fe1c8071966e5ee3143b7fcafc286ff1480d00c5ba494e407fa9032b8e1b6dda43e8623c011ae9ffdbdf70e8a33612
|
data/.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.3.
|
|
1
|
+
0.3.26
|
data/lib/hammer/parser.rb
CHANGED
|
@@ -50,6 +50,17 @@ class Hammer
|
|
|
50
50
|
next
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
+
# Bundled boolean short flags: `-cf` is `-c -f`. Only when every letter
|
|
54
|
+
# is a boolean switch, so `-pVALUE` below keeps its meaning.
|
|
55
|
+
if token =~ /\A-[^-]{2,}\z/
|
|
56
|
+
bundle = token[1..-1].chars.map { |ch| @by_switch["-#{ch}"] }
|
|
57
|
+
if bundle.all? { |o| o&.boolean? }
|
|
58
|
+
bundle.each { |o| values[o.name] = true }
|
|
59
|
+
i += 1
|
|
60
|
+
next
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
53
64
|
# Glued short flag with value: `-pVALUE` (non-boolean short opts only).
|
|
54
65
|
if token.start_with?('-') && !token.start_with?('--') && token.length > 2
|
|
55
66
|
if (opt = @by_switch[token[0, 2]]) && !opt.boolean?
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'pathname'
|
|
5
|
+
|
|
6
|
+
# Builds the command `llm wrap:run` hands to LlmWrap: picks the agent CLI by
|
|
7
|
+
# prefix and maps the generic -c / -f / -a switches to what that CLI takes.
|
|
8
|
+
module LlmLaunch
|
|
9
|
+
Launch = Struct.new(:argv, :env, :files)
|
|
10
|
+
|
|
11
|
+
# :agents gets the combined AGENTS.md text and the file paths, and answers
|
|
12
|
+
# with the argv to add and/or env to set - opencode has no flag for
|
|
13
|
+
# instructions, only inline config, and that one takes paths.
|
|
14
|
+
TOOLS ||= {
|
|
15
|
+
'claude' => { continue: %w[-c], full: %w[--dangerously-skip-permissions],
|
|
16
|
+
agents: ->(text, _files) { { args: ['--append-system-prompt', text] } } },
|
|
17
|
+
'codex' => { continue: %w[resume --last], full: %w[--dangerously-bypass-approvals-and-sandbox],
|
|
18
|
+
agents: ->(text, _files) { { args: ['-c', "developer_instructions=#{text}"] } } },
|
|
19
|
+
'grok' => { continue: %w[-c], full: %w[--always-approve],
|
|
20
|
+
agents: ->(text, _files) { { args: ['--rules', text] } } },
|
|
21
|
+
'opencode' => { continue: %w[-c], full: %w[--auto],
|
|
22
|
+
agents: ->(_text, files) { { env: { 'OPENCODE_CONFIG_CONTENT' => { instructions: files }.to_json } } } },
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
25
|
+
# Read AGENTS.md from the git root down on their own; -a only adds the
|
|
26
|
+
# files above it for these. Claude reads CLAUDE.md, so it gets the chain.
|
|
27
|
+
NATIVE_AGENTS ||= %w[codex grok opencode].freeze
|
|
28
|
+
|
|
29
|
+
# First tool whose name starts with the prefix, in TOOLS order: `c` is
|
|
30
|
+
# claude, `co` codex. No prefix means claude.
|
|
31
|
+
def self.tool(prefix)
|
|
32
|
+
return 'claude' if prefix.nil? || prefix.empty?
|
|
33
|
+
TOOLS.keys.find { |name| name.start_with?(prefix) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.build(tool, extra = [], continue: false, full: false, agents: false, cwd: Dir.pwd, home: Dir.home)
|
|
37
|
+
spec = TOOLS.fetch(tool)
|
|
38
|
+
argv = [tool]
|
|
39
|
+
argv.concat(spec[:continue]) if continue
|
|
40
|
+
argv.concat(spec[:full]) if full
|
|
41
|
+
|
|
42
|
+
files = agents ? agents_files(tool, cwd: cwd, home: home) : []
|
|
43
|
+
env = {}
|
|
44
|
+
if files.any?
|
|
45
|
+
added = spec[:agents].call(agents_text(files), files.map(&:to_s))
|
|
46
|
+
argv.concat(added[:args] || [])
|
|
47
|
+
env.update(added[:env] || {})
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
argv.concat(extra)
|
|
51
|
+
Launch.new(argv, env, files)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# AGENTS.md from home down to cwd, outermost first. For NATIVE_AGENTS tools
|
|
55
|
+
# the walk stops above the git root - or above cwd outside git, since that is
|
|
56
|
+
# what they take as the project then.
|
|
57
|
+
def self.agents_files(tool, cwd: Dir.pwd, home: Dir.home)
|
|
58
|
+
stop = nil
|
|
59
|
+
if NATIVE_AGENTS.include?(tool)
|
|
60
|
+
stop = IO.popen(['git', '-C', cwd, 'rev-parse', '--show-toplevel'], err: File::NULL, &:read).to_s.strip
|
|
61
|
+
stop = cwd if stop.empty?
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
Pathname(cwd).ascend
|
|
65
|
+
.select { |dir| dir.to_s.start_with?(home) }
|
|
66
|
+
.reject { |dir| stop && dir.to_s.start_with?(stop) }
|
|
67
|
+
.reverse
|
|
68
|
+
.map { |dir| dir + 'AGENTS.md' }
|
|
69
|
+
.select(&:file?)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def self.agents_text(files)
|
|
73
|
+
body = files.map { |f| "## #{f}\n\n#{f.read.strip}\n" }.join("\n")
|
|
74
|
+
"Project instructions (AGENTS.md), broad to specific:\n\n#{body}"
|
|
75
|
+
end
|
|
76
|
+
end
|
data/recipes/llm.rb
CHANGED
|
@@ -13,6 +13,7 @@ desc <<~TXT
|
|
|
13
13
|
Commands:
|
|
14
14
|
usage subscription limits and per-model token usage
|
|
15
15
|
wrap run a command with your last two prompts pinned to the screen
|
|
16
|
+
wrap:run start claude / codex / grok / opencode in wrap, same -c -f -a switches for all
|
|
16
17
|
TXT
|
|
17
18
|
|
|
18
19
|
require 'fileutils'
|
|
@@ -185,6 +186,58 @@ task :wrap do
|
|
|
185
186
|
end
|
|
186
187
|
end
|
|
187
188
|
|
|
189
|
+
namespace :wrap do
|
|
190
|
+
task :run do
|
|
191
|
+
desc <<~D
|
|
192
|
+
Start an agent CLI inside `llm wrap`, with the same switches whichever one it is.
|
|
193
|
+
|
|
194
|
+
TOOL is a prefix of claude, codex, grok or opencode - first match wins, so `c` is
|
|
195
|
+
claude and `co` is codex. No tool means claude. The switches map to what each
|
|
196
|
+
CLI actually takes:
|
|
197
|
+
|
|
198
|
+
-c continue the last session here claude/grok/opencode -c, codex resume --last
|
|
199
|
+
-f full permissions, no prompts --dangerously-skip-permissions, --dangerously-bypass-
|
|
200
|
+
approvals-and-sandbox, --always-approve, --auto
|
|
201
|
+
-a AGENTS.md from ~ down to cwd claude --append-system-prompt, codex -c
|
|
202
|
+
developer_instructions, grok --rules, opencode
|
|
203
|
+
inline config
|
|
204
|
+
|
|
205
|
+
codex, grok and opencode read AGENTS.md from the git root down by themselves, so
|
|
206
|
+
-a only adds the files above the repo for them; claude gets the whole chain.
|
|
207
|
+
Anything after `--` is handed to the tool untouched. `~/bin/ai` is this command.
|
|
208
|
+
D
|
|
209
|
+
example 'wrap:run'
|
|
210
|
+
example 'wrap:run g -cf'
|
|
211
|
+
example 'wrap:run cod -ca'
|
|
212
|
+
example 'wrap:run c -- --model opus'
|
|
213
|
+
|
|
214
|
+
opt :continue, type: :boolean, desc: 'continue the last session in this folder'
|
|
215
|
+
opt :full, type: :boolean, desc: 'full permissions, no approval prompts'
|
|
216
|
+
opt :agents, type: :boolean, desc: 'inject AGENTS.md found from ~ down to cwd'
|
|
217
|
+
|
|
218
|
+
proc do |opts|
|
|
219
|
+
require File.join(_llm_root, 'lib/llm/wrap')
|
|
220
|
+
require File.join(_llm_root, 'lib/llm/launch')
|
|
221
|
+
|
|
222
|
+
args = Array(opts[:args])
|
|
223
|
+
prefix = args.first unless args.first.nil? || args.first.start_with?('-')
|
|
224
|
+
tool = LlmLaunch.tool(prefix) ||
|
|
225
|
+
error("unknown tool #{prefix.inspect} - one of #{LlmLaunch::TOOLS.keys.join(', ')}")
|
|
226
|
+
extra = prefix ? args.drop(1) : args
|
|
227
|
+
|
|
228
|
+
launch = LlmLaunch.build(tool, extra, continue: opts[:continue], full: opts[:full], agents: opts[:agents])
|
|
229
|
+
ENV.update(launch.env)
|
|
230
|
+
say.gray "agents: #{launch.files.map { |f| f.to_s.sub(Dir.home, '~') }.join(', ')}" if launch.files.any?
|
|
231
|
+
|
|
232
|
+
flags = %i[continue full agents].select { |k| opts[k] }.map { |k| "--#{k}" }
|
|
233
|
+
origin = ['llm', 'wrap:run', tool, *flags, '--', *extra]
|
|
234
|
+
# Piped stdout is block-buffered and exec drops the buffer with the banner in it.
|
|
235
|
+
$stdout.flush
|
|
236
|
+
exit LlmWrap.run(launch.argv, origin: origin)
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
188
241
|
namespace :memory do
|
|
189
242
|
# Helpers are defined inside the namespace block (class_eval'd on the
|
|
190
243
|
# namespace's anonymous Hammer subclass) so the task procs reach them.
|
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.26
|
|
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-27 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: minitest
|
|
@@ -55,6 +55,7 @@ files:
|
|
|
55
55
|
- "./lib/lux-hammer.rb"
|
|
56
56
|
- "./recipes/deploy.rb"
|
|
57
57
|
- "./recipes/git-helper.rb"
|
|
58
|
+
- "./recipes/lib/llm/launch.rb"
|
|
58
59
|
- "./recipes/lib/llm/plan.rb"
|
|
59
60
|
- "./recipes/lib/llm/usage.rb"
|
|
60
61
|
- "./recipes/lib/llm/wrap.rb"
|