Module: Hammer::Builtins

Defined in:
lib/hammer/builtins.rb

Overview

Built-in tasks of the hammer binary. Everything but :default lives under the reserved h: namespace (e.g. hammer h:update, hammer h:recipes) so the built-ins never collide with a project's own root tasks. Only the bare hammer invocation and the -h/--help/help request are handled at root - bare fires :default, which also carries the --version convenience flag.

Defined Under Namespace

Modules: RecipesActions

Class Method Summary collapse

Class Method Details

.register(klass) ⇒ Object

Single registration entry point - identical in every context (project Hammerfile or bare hammer binary). Namespacing under h: removes the old collision worries, so there's no core vs no-project split and no hidden-desc dance: the same tasks register everywhere, always dispatchable, and surface in the listing only under the extended --help view (the @builtin_namespace flag).



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/hammer/builtins.rb', line 17

def register(klass)
  register_default(klass) unless klass.commands.key?('default')

  existed = klass.namespaces.key?('h')
  klass.namespace(:h) {}          # ensure/reopen the reserved subclass
  h = klass.namespaces['h']
  # Flag the tree so the compact listing prunes it - the built-ins
  # only show under the extended `--help` view (always dispatchable).
  # Skip when the user already owns an `h:` namespace, so their own
  # tasks stay visible in the bare listing.
  h.instance_variable_set(:@builtin_namespace, true) unless existed
  register_help(h)    unless h.commands.key?('help')
  register_update(h)  unless h.commands.key?('update')
  register_agents(h)  unless h.commands.key?('agents')
  register_version(h) unless h.commands.key?('version')
  register_recipes(h) unless h.commands.key?('recipes')
  register_init(h)    unless h.commands.key?('init')
  register_json(h)    unless h.commands.key?('json')
  register_cron(h)    unless h.commands.key?('cron')
end

.register_agents(klass) ⇒ Object



90
91
92
93
94
95
96
97
# File 'lib/hammer/builtins.rb', line 90

def register_agents(klass)
  klass.class_eval do
    task :agents do
      desc 'Print AGENTS.md (Hammerfile authoring docs for AI assistants)'
      proc { Hammer.print_ai_help }
    end
  end
end

.register_cron(klass) ⇒ Object

The job server: runs every task that declares cron '<expr>' on its schedule, in the foreground, with a localhost web UI for status, logs and manual triggers. Per-job logs land in log/hammer/ (rotated at 1 MB, max 2 files), state in tmp/hammer/cron.state.json.



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/hammer/builtins.rb', line 121

def register_cron(klass)
  klass.class_eval do
    task :cron do
      desc <<~TXT
        Job server: run tasks that declare a `cron '<expr>'` schedule.

        Runs in the foreground (Ctrl-C to stop) with a web UI on
        localhost for job status, per-job logs and manual runs. Use
        --service to print a launchd plist / systemd unit and let the
        OS supervise it.
      TXT
      example 'h:cron'
      example 'h:cron --port=7777'
      example 'h:cron --pass=secret'
      example 'h:cron --list'
      example 'h:cron --service > ~/.config/systemd/user/hammer-cron.service'
      opt :port,    type: :integer, default: 4267, desc: 'web UI port (binds 127.0.0.1 only)'
      opt :pass,    type: :string,  desc: 'HTTP basic-auth password for the web UI (or CRON_HTTP_PASS env)'
      opt :list,    type: :boolean, desc: 'print scheduled jobs + next runs, then exit'
      opt :service, type: :boolean, desc: 'print a launchd plist (macOS) / systemd unit (linux)'
      proc do |opts|
        if self.class.root.instance_variable_get(:@no_hammerfile)
          error "no Hammerfile found - h:cron needs a project with scheduled tasks"
        end
        require_relative 'cron_server'
        server = Hammer::CronServer.new(self.class.root, port: opts[:port],
                                        pass: opts[:pass] || ENV['CRON_HTTP_PASS'])
        if opts[:service]
          server.print_service_unit
        elsif opts[:list]
          server.print_startup_summary
        else
          server.run!
        end
      end
    end
  end
end

.register_default(klass) ⇒ Object

:default fires on bare hammer and on leading-flag invocations (other than -h/--help). Hidden from listings (no desc). All it does now is print help - the old flag opts (--update, --ai, ...) moved to dedicated tasks (:update, :agents, ...).



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/hammer/builtins.rb', line 63

def register_default(klass)
  klass.class_eval do
    task :default do
      opt :version, type: :boolean, alias: :v, desc: 'print lux-hammer version'
      # Inlined rather than dispatched to :version - going through
      # `hammer :version` would print the gray run banner. The
      # implementation is one line; not worth the indirection.
      proc do |opts|
        if opts[:version]
          puts Hammer::VERSION
        else
          self.class.root.print_help
        end
      end
    end
  end
end

.register_help(klass) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/hammer/builtins.rb', line 38

def register_help(klass)
  klass.class_eval do
    task :help do
      desc <<~TXT
        Show help. Optional TARGET = command name or namespace (`ns:`).

        Without TARGET prints the extended top-level help (commands,
        recipes, global flags, examples). With a command path prints
        per-command help; with a namespace prefix prints that
        namespace's command listing.
      TXT
      example 'h:help'
      example 'h:help build'
      example 'h:help db:'
      proc do |opts|
        self.class.root.print_help(opts[:args].first, extended: true)
      end
    end
  end
end

.register_init(klass) ⇒ Object



108
109
110
111
112
113
114
115
# File 'lib/hammer/builtins.rb', line 108

def register_init(klass)
  klass.class_eval do
    task :init do
      desc 'Write a starter Hammerfile in the current directory'
      proc { Hammer::Builtins.write_starter_hammerfile }
    end
  end
end

.register_json(klass) ⇒ Object



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/hammer/builtins.rb', line 160

def register_json(klass)
  klass.class_eval do
    task :json do
      desc <<~TXT
        Dump the CLI definition as JSON: tasks grouped exactly like
        the bare-`hammer` listing, each with desc, options, examples,
        aliases, needs. Consumed by the macOS GUI and any tooling
        that wants the full Hammerfile spec.
      TXT
      example 'h:json'
      example 'h:json --all       # include the reserved h: tasks'
      example 'h:json --compact   # minified, single line'
      opt :all,     type: :boolean, desc: 'include reserved built-in h: tasks'
      opt :compact, type: :boolean, desc: 'minified JSON (default: pretty)'
      proc do |opts|
        require 'json'
        spec = self.class.root.export_spec(include_builtins: opts[:all])
        puts opts[:compact] ? JSON.generate(spec) : JSON.pretty_generate(spec)
      end
    end
  end
end

.register_recipes(klass) ⇒ Object

:recipes rolls all recipe-management actions into one task. Bare invocation lists; opts pick the action and positional args carry the recipe name (and optional target path for --install). Run via --run NAME [ARGS] - use -- to forward flags to the recipe itself (e.g. hammer h:recipes --run srt -- --help).



188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/hammer/builtins.rb', line 188

def register_recipes(klass)
  klass.class_eval do
    task :recipes do
      desc <<~TXT
        Manage recipes - the standalone Hammerfile-style scripts
        bundled with the gem (and under ~/.config/hammer/recipes).
        Bare invocation lists; flags pick the action.
      TXT
      opt :install, type: :boolean, desc: 'install recipe stub (picker if no NAME). With TARGET path: write + chmod.'
      opt :show,    type: :boolean, desc: 'cat recipe source'
      opt :path,    type: :boolean, desc: 'print recipe abs path'
      opt :edit,    type: :boolean, desc: 'open recipe in $EDITOR (copies gem -> user dir first)'
      opt :run,     type: :boolean, desc: 'run a recipe without installing its bin (forwards remaining args)'
      example 'h:recipes'
      example 'h:recipes --install srt ~/bin/srt    # write + chmod in one shot'
      example 'h:recipes --install srt > ~/bin/srt && chmod +x $_'
      example 'h:recipes --show srt'
      example 'h:recipes --run srt extract movie.mp4'
      example 'h:recipes --run srt -- --help        # -- forwards flags to the recipe'
      proc do |opts|
        args = opts[:args]
        if opts[:install]
          Hammer::Builtins::RecipesActions.install(args[0], args[1])
        elsif opts[:show]
          Hammer::Builtins::RecipesActions.show(Hammer::Builtins::RecipesActions.require_name!(args[0], 'show'))
        elsif opts[:path]
          Hammer::Builtins::RecipesActions.path(Hammer::Builtins::RecipesActions.require_name!(args[0], 'path'))
        elsif opts[:edit]
          Hammer::Builtins::RecipesActions.edit(Hammer::Builtins::RecipesActions.require_name!(args[0], 'edit'))
        elsif opts[:run]
          name = Hammer::Builtins::RecipesActions.require_name!(args[0], 'run')
          Hammer.recipe(name, args[1..])
        else
          Hammer::Builtins::RecipesActions.list
        end
      end
    end
  end
end

.register_update(klass) ⇒ Object



81
82
83
84
85
86
87
88
# File 'lib/hammer/builtins.rb', line 81

def register_update(klass)
  klass.class_eval do
    task :update do
      desc 'Rebuild + reinstall lux-hammer from main'
      proc { Hammer.self_update }
    end
  end
end

.register_version(klass) ⇒ Object



99
100
101
102
103
104
105
106
# File 'lib/hammer/builtins.rb', line 99

def register_version(klass)
  klass.class_eval do
    task :version do
      desc 'Print lux-hammer version'
      proc { puts Hammer::VERSION }
    end
  end
end

.write_starter_hammerfileObject

Writes ./Hammerfile with the canonical starter template. Refuses if one already exists - init must not clobber.



230
231
232
233
234
235
236
237
238
# File 'lib/hammer/builtins.rb', line 230

def write_starter_hammerfile
  target = File.join(Dir.pwd, 'Hammerfile')
  if File.exist?(target)
    Shell.print_error "Hammerfile already exists at #{target}"
    exit 1
  end
  File.write(target, Hammer::STARTER_HAMMERFILE)
  Shell.say "created #{target}", :green
end