lux-hammer 0.3.28 → 0.3.29

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: 48fd3473ba922f93fa0ebcc897d0bf6a88ec78283ed4436281d938a1297e53ac
4
- data.tar.gz: 30eef033e89155cd82b362563e5f4dea0716551f56d686ae5c1fde16fc23170c
3
+ metadata.gz: 6dab0deb8eba80ec1aac72b16620370af2d80d2528641e9117be71df84e8d416
4
+ data.tar.gz: 6460bbda170ba18cedf234846e4898f1c6efc48732c81ed122fc6eaa11d237b8
5
5
  SHA512:
6
- metadata.gz: 7e3961d9aa6c6d89081ec6e1ee111346fab1944c8152644c7e22b300815bea3152606459c6d90e88592c8933021cc7c8ba75b262f8c5aebc4105b05816c0b9cb
7
- data.tar.gz: 50fd5ecc020ecb0d31ad7f79d7d7dc5ae313e6646c30cdc0758b07bfbad1bfe145f50301215853b168cd3d79ccd5d66f0372551f405984ac87b0d026f13d047f
6
+ metadata.gz: c24c74422db1ac9a72b5a03ae4f7d4e56e486c7622be40f5e136c16b9a15b2a04feeb7d2f61cdfdaaeafbc5c3913e71c2f8faf567cac8ea236eaeeadcb7fcb4f
7
+ data.tar.gz: 823288d67fd112a118c09b3b1dd3599a3b080c5c74fd6e5a35492be182a72fa3fbe82359498e1facb0396b61773b1a51e61dba786284d2bde19426a8e3eddc81
data/.version CHANGED
@@ -1 +1 @@
1
- 0.3.28
1
+ 0.3.29
@@ -64,7 +64,7 @@ module LlmUsage
64
64
  # and caching would freeze the staleness note. Only the API call caches.
65
65
  data, note = fetch_claude_usage(period: label, cache: cache, now: now)
66
66
  notes << note unless note.nil? || note.empty?
67
- rows.concat(parse_claude(data, now: now)) if data
67
+ rows.concat(parse_claude(data, now: now, period: label)) if data
68
68
  end
69
69
 
70
70
  if wanted.include?(:codex)
@@ -294,7 +294,7 @@ module LlmUsage
294
294
  "#{mins}m"
295
295
  end
296
296
 
297
- def parse_claude(data, now: Time.now)
297
+ def parse_claude(data, now: Time.now, period: nil)
298
298
  return [] unless data.is_a?(Hash)
299
299
 
300
300
  five = data['five_hour']
@@ -329,13 +329,13 @@ module LlmUsage
329
329
  month_reset: '-'
330
330
  )
331
331
  end
332
- return rows
332
+ return rows + claude_scoped_rows(data['limits'], rows, now: now, period: period)
333
333
  end
334
334
 
335
335
  week = data['seven_day']
336
336
  return [] unless five.is_a?(Hash) || week.is_a?(Hash)
337
337
 
338
- [
338
+ rows = [
339
339
  UsageRow.new(
340
340
  name: 'Claude',
341
341
  session_pct: format_pct(session_pct),
@@ -346,6 +346,36 @@ module LlmUsage
346
346
  month_reset: month[:reset]
347
347
  )
348
348
  ]
349
+ rows + claude_scoped_rows(data['limits'], rows, now: now, period: period)
350
+ end
351
+
352
+ # Per-model weekly buckets live in `limits` as kind weekly_scoped - Fable has
353
+ # one, and seven_day_opus/_sonnet read null on the accounts that do. Named off
354
+ # the API's own display_name, so a model added later needs nothing here; a
355
+ # scope already carrying its own seven_day_* row is skipped rather than
356
+ # doubled. A weekly bucket has nothing to say in the month view, where these
357
+ # would be a row of dashes.
358
+ def claude_scoped_rows(limits, rows, now: Time.now, period: nil)
359
+ return [] if period_label(period) == 'month'
360
+
361
+ taken = rows.map(&:name)
362
+
363
+ Array(limits).filter_map do |limit|
364
+ next unless limit.is_a?(Hash) && limit['kind'] == 'weekly_scoped'
365
+
366
+ name = limit.dig('scope', 'model', 'display_name')
367
+ next if name.nil? || name.empty? || taken.include?(name)
368
+
369
+ UsageRow.new(
370
+ name: name,
371
+ session_pct: '-',
372
+ session_reset: '-',
373
+ week_pct: format_pct(limit['percent']),
374
+ week_reset: format_reset_short(limit['resets_at'], now: now),
375
+ month_pct: '-',
376
+ month_reset: '-'
377
+ )
378
+ end
349
379
  end
350
380
 
351
381
  def parse_codex(data, now: Time.now)
@@ -403,7 +433,7 @@ module LlmUsage
403
433
  return fetch_cached(:claude, cache) { fetch_claude_oauth_usage } if period == 'month'
404
434
 
405
435
  data, note = fetch_claude_snapshot(now: now, path: path)
406
- return [data, note] if data
436
+ return [merge_scoped_limits(data, cache: cache), note] if data
407
437
 
408
438
  api_data, api_note = fetch_cached(:claude, cache) { fetch_claude_oauth_usage }
409
439
  return [api_data, api_note] if api_data
@@ -411,6 +441,18 @@ module LlmUsage
411
441
  [nil, [note, api_note].compact.join('; ')]
412
442
  end
413
443
 
444
+ # The statusline snapshot carries no per-model windows - Claude Code's
445
+ # `rate_limits` input is five_hour/seven_day and nothing else - so borrow
446
+ # `limits` from the API payload. Cached, so this is one call per CACHE_TTL,
447
+ # and silent when it fails: the snapshot's own numbers are still good.
448
+ def merge_scoped_limits(data, cache: true)
449
+ api, = fetch_cached(:claude, cache) { fetch_claude_oauth_usage }
450
+ limits = api['limits'] if api.is_a?(Hash)
451
+ return data unless limits
452
+
453
+ data.merge('limits' => limits)
454
+ end
455
+
414
456
  def fetch_claude_snapshot(now: Time.now, path: CLAUDE_LIMITS_PATH)
415
457
  snapshot = read_json(path)
416
458
  return [nil, 'claude: no local snapshot (statusline writer not installed)'] unless snapshot.is_a?(Hash)
@@ -685,11 +727,12 @@ module LlmUsage
685
727
  end
686
728
  end
687
729
 
730
+ # A failure is cached too, or an unreachable endpoint costs its timeout on
731
+ # every run - and the snapshot path that now consults the API is the one
732
+ # that used to be instant and offline.
688
733
  data, note = yield
689
- if data
690
- FileUtils.mkdir_p(CACHE_DIR)
691
- File.write(path, JSON.generate('fetched_at' => Time.now.iso8601, 'data' => data, 'note' => note))
692
- end
734
+ FileUtils.mkdir_p(CACHE_DIR)
735
+ File.write(path, JSON.generate('fetched_at' => Time.now.iso8601, 'data' => data, 'note' => note))
693
736
  [data, note]
694
737
  end
695
738
 
data/recipes/llm.rb CHANGED
@@ -45,8 +45,10 @@ task :usage do
45
45
  `~/.grok/logs/unified.jsonl` for the period end; tokens still sum
46
46
  inference_done events in that log. Claude 5h/week windows come from the
47
47
  statusline snapshot at `~/.cache/llm/claude-limits.json`, falling back to
48
- the OAuth usage API. `month` shows Claude extra-usage credits (API only,
49
- and only when you've enabled them).
48
+ the OAuth usage API. Per-model weekly windows (Fable) get their own row and
49
+ come from that API either way - the snapshot doesn't carry them. `month`
50
+ shows Claude extra-usage credits (API only, and only when you've enabled
51
+ them).
50
52
  D
51
53
  example 'usage'
52
54
  example 'usage month'
@@ -740,9 +742,10 @@ namespace :prompt do
740
742
  D
741
743
  opt :claude, type: :boolean, default: false, desc: 'Claude Code hook mode'
742
744
  opt :codex, type: :boolean, default: false, desc: 'Codex hook mode'
745
+ opt :grok, type: :boolean, default: false, desc: 'Grok CLI hook mode'
743
746
 
744
747
  proc do |opts|
745
- error '--claude or --codex required' unless opts[:claude] || opts[:codex]
748
+ error '--claude, --codex or --grok required' unless opts[:claude] || opts[:codex] || opts[:grok]
746
749
 
747
750
  raw = opts[:stdin].to_s
748
751
  prompt = begin
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.28
4
+ version: 0.3.29
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-09-09 00:00:00.000000000 Z
11
+ date: 2026-09-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest