claude_usage 0.1.2

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.
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClaudeUsage
4
+ class UsageAggregator
5
+ def initialize(project_name: nil)
6
+ @project_name = project_name || ClaudeUsage.configuration.resolved_project_name
7
+ @file_reader = FileReader.new(project_name: @project_name)
8
+ @cost_calculator = CostCalculator.new
9
+ end
10
+
11
+ def daily_usage
12
+ entries = @file_reader.read_usage_data
13
+ grouped = entries.group_by { |e| e[:timestamp].to_date }
14
+
15
+ grouped.map do |date, day_entries|
16
+ aggregate_entries(date, day_entries)
17
+ end.sort_by { |d| d[:date] }.reverse
18
+ end
19
+
20
+ def total_usage
21
+ daily = daily_usage
22
+
23
+ {
24
+ total_input_tokens: daily.sum { |d| d[:input_tokens] },
25
+ total_output_tokens: daily.sum { |d| d[:output_tokens] },
26
+ total_cache_creation_tokens: daily.sum { |d| d[:cache_creation_tokens] },
27
+ total_cache_read_tokens: daily.sum { |d| d[:cache_read_tokens] },
28
+ total_tokens: daily.sum { |d| d[:total_tokens] },
29
+ total_cost: daily.sum { |d| d[:cost] },
30
+ models_used: daily.flat_map { |d| d[:models_used] }.uniq,
31
+ first_usage: daily.last&.dig(:date),
32
+ last_usage: daily.first&.dig(:date),
33
+ days_active: daily.size
34
+ }
35
+ end
36
+
37
+ private
38
+
39
+ def aggregate_entries(date, entries)
40
+ {
41
+ date: date,
42
+ input_tokens: entries.sum { |e| e[:input_tokens] },
43
+ output_tokens: entries.sum { |e| e[:output_tokens] },
44
+ cache_creation_tokens: entries.sum { |e| e[:cache_creation_tokens] },
45
+ cache_read_tokens: entries.sum { |e| e[:cache_read_tokens] },
46
+ total_tokens: entries.sum do |e|
47
+ e[:input_tokens] + e[:output_tokens] + e[:cache_creation_tokens] + e[:cache_read_tokens]
48
+ end,
49
+ cost: entries.sum { |e| @cost_calculator.calculate_cost(e) },
50
+ models_used: entries.map { |e| e[:model] }.compact.uniq,
51
+ request_count: entries.size
52
+ }
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClaudeUsage
4
+ VERSION = '0.1.2'
5
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'claude_usage/version'
4
+ require_relative 'claude_usage/project_name_resolver'
5
+
6
+ # Only require engine if Rails is defined
7
+ require_relative 'claude_usage/engine' if defined?(Rails)
8
+
9
+ module ClaudeUsage
10
+ class Error < StandardError; end
11
+
12
+ # Configuration
13
+ class Configuration
14
+ attr_accessor :project_name, :claude_paths, :cache_expiry, :offline_mode, :verify_ssl
15
+
16
+ def initialize
17
+ @project_name = nil # Will auto-detect from Rails.root if not set
18
+ @claude_paths = nil # Will use defaults if not set
19
+ @cache_expiry = 3600 # 1 hour in seconds
20
+ @offline_mode = false
21
+ @verify_ssl = false # Disabled by default for macOS compatibility
22
+ end
23
+
24
+ # Auto-detect project name from Rails.root
25
+ # Converts: /Users/name/projects/myapp → -Users-name-projects-myapp
26
+ def resolved_project_name
27
+ ProjectNameResolver.resolve(@project_name, fallback: nil)
28
+ end
29
+ end
30
+
31
+ class << self
32
+ attr_writer :configuration
33
+
34
+ def configuration
35
+ @configuration ||= Configuration.new
36
+ end
37
+
38
+ def configure
39
+ yield(configuration)
40
+ end
41
+
42
+ def reset_configuration!
43
+ @configuration = Configuration.new
44
+ end
45
+ end
46
+ end
47
+
48
+ # Load service objects
49
+ require_relative 'claude_usage/file_reader'
50
+ require_relative 'claude_usage/litellm_pricing_fetcher'
51
+ require_relative 'claude_usage/cost_calculator'
52
+ require_relative 'claude_usage/usage_aggregator'
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+
5
+ module ClaudeUsage
6
+ module Generators
7
+ class InstallGenerator < Rails::Generators::Base
8
+ source_root File.expand_path('templates', __dir__)
9
+
10
+ desc 'Creates ClaudeUsage initializer and shows setup instructions'
11
+
12
+ def copy_initializer
13
+ @detected_project_name = detect_project_name
14
+ template 'initializer.rb', 'config/initializers/claude_usage.rb'
15
+ end
16
+
17
+ def show_readme
18
+ readme 'README' if behavior == :invoke
19
+ end
20
+
21
+ private
22
+
23
+ def detect_project_name
24
+ # Use shared resolver to avoid duplication
25
+ require_relative '../../claude_usage/project_name_resolver'
26
+ ClaudeUsage::ProjectNameResolver.resolve(nil, fallback: 'your-project-name')
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ ClaudeUsage.configure do |config|
4
+ # Project name - AUTO-DETECTED from Rails.root by default
5
+ #
6
+ # Claude Code stores projects using the full absolute path with dashes, like:
7
+ # /Users/name/projects/myapp → -Users-name-projects-myapp
8
+ #
9
+ # The gem automatically detects this, so you usually don't need to set it manually.
10
+ #
11
+ # Only uncomment if auto-detection fails or you need a custom name:
12
+ # config.project_name = "<%= @detected_project_name %>"
13
+ #
14
+ # To see what's auto-detected, run: rake claude_usage:show_project
15
+
16
+ # Custom Claude paths (optional)
17
+ # Default paths are already configured:
18
+ # - ~/.config/claude/projects
19
+ # - ~/.claude/projects
20
+ # config.claude_paths = [
21
+ # File.expand_path("~/.config/claude/projects"),
22
+ # File.expand_path("~/.claude/projects")
23
+ # ]
24
+
25
+ # Pricing cache expiry in seconds (default: 3600 = 1 hour)
26
+ # config.cache_expiry = 3600
27
+
28
+ # Offline mode (use cached pricing only, recommended for production)
29
+ # config.offline_mode = false
30
+
31
+ # SSL verification (disabled by default for macOS compatibility)
32
+ # Enable in production if your certificates are up-to-date
33
+ # config.verify_ssl = false
34
+ end
@@ -0,0 +1,313 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../claude_usage/formatters'
4
+
5
+ namespace :claude_usage do
6
+ desc 'Show daily usage report'
7
+ task daily: :environment do
8
+ project_name = ClaudeUsage.configuration.resolved_project_name
9
+ aggregator = ClaudeUsage::UsageAggregator.new
10
+ daily_usage = aggregator.daily_usage
11
+
12
+ puts "\n🤖 Claude Code - Daily Usage Report\n"
13
+ puts '=' * 80
14
+ puts "📁 Project: #{project_name}"
15
+ puts '=' * 80
16
+
17
+ if daily_usage.empty?
18
+ puts "\n❌ No usage data found!"
19
+ puts "\nTroubleshooting:"
20
+ puts ' 1. Run: rake claude_usage:show_project'
21
+ puts ' 2. Check that Claude Code has been used with this project'
22
+ puts " 3. Verify project name matches Claude Code's folder name"
23
+ puts "\n"
24
+ else
25
+ daily_usage.each do |day|
26
+ puts "\n📅 #{day[:date].strftime('%Y-%m-%d')}"
27
+ puts " Input Tokens: #{day[:input_tokens].to_s.rjust(10)}"
28
+ puts " Output Tokens: #{day[:output_tokens].to_s.rjust(10)}"
29
+ puts " Cache Creation: #{day[:cache_creation_tokens].to_s.rjust(10)}"
30
+ puts " Cache Read: #{day[:cache_read_tokens].to_s.rjust(10)}"
31
+ puts " Total Tokens: #{day[:total_tokens].to_s.rjust(10)}"
32
+ puts " Cost: $#{format('%.4f', day[:cost]).rjust(9)}"
33
+ puts " Requests: #{day[:request_count].to_s.rjust(10)}"
34
+ puts " Models: #{day[:models_used].join(', ')}"
35
+ end
36
+
37
+ puts "\n#{'=' * 80}"
38
+ end
39
+ end
40
+
41
+ desc 'Show total usage'
42
+ task total: :environment do
43
+ project_name = ClaudeUsage.configuration.resolved_project_name
44
+ aggregator = ClaudeUsage::UsageAggregator.new
45
+ totals = aggregator.total_usage
46
+
47
+ puts "\n🤖 Claude Code - Total Usage Report\n"
48
+ puts '=' * 80
49
+ puts "📁 Project: #{project_name}"
50
+ puts '=' * 80
51
+
52
+ if totals[:days_active].zero?
53
+ puts "\n❌ No usage data found!"
54
+ puts "\nTroubleshooting:"
55
+ puts ' 1. Run: rake claude_usage:show_project'
56
+ puts ' 2. Check that Claude Code has been used with this project'
57
+ puts " 3. Verify project name matches Claude Code's folder name"
58
+ puts "\n"
59
+ else
60
+ puts "\n💰 Cost: $#{format('%.4f', totals[:total_cost])}"
61
+
62
+ # Show warning if cost is 0 but tokens exist
63
+ if totals[:total_cost].zero? && totals[:total_tokens].positive?
64
+ puts ' ⚠️ Cost is $0 - model pricing may not be available'
65
+ puts ' Run: rake claude_usage:debug_pricing'
66
+ end
67
+
68
+ puts "📊 Total Tokens: #{ClaudeUsage::Formatters.format_number(totals[:total_tokens])}"
69
+ puts " - Input: #{ClaudeUsage::Formatters.format_number(totals[:total_input_tokens])}"
70
+ puts " - Output: #{ClaudeUsage::Formatters.format_number(totals[:total_output_tokens])}"
71
+ puts " - Cache Creation: #{ClaudeUsage::Formatters.format_number(totals[:total_cache_creation_tokens])}"
72
+ puts " - Cache Read: #{ClaudeUsage::Formatters.format_number(totals[:total_cache_read_tokens])}"
73
+ puts "📅 Days Active: #{totals[:days_active]}"
74
+ puts "📅 First Usage: #{totals[:first_usage]}"
75
+ puts "📅 Last Usage: #{totals[:last_usage]}"
76
+ puts "🤖 Models Used: #{totals[:models_used].join(', ')}"
77
+ puts "\n#{'=' * 80}\n"
78
+ end
79
+ end
80
+
81
+ desc 'Clear pricing cache'
82
+ task clear_cache: :environment do
83
+ Rails.cache.delete('claude_usage/litellm_pricing_data')
84
+ puts '✅ Pricing cache cleared!'
85
+ end
86
+
87
+ desc 'Show project name being tracked'
88
+ task show_project: :environment do
89
+ manual_config = ClaudeUsage.configuration.project_name
90
+ auto_detected = ClaudeUsage.configuration.resolved_project_name
91
+
92
+ puts "\n📁 Project Detection:"
93
+ puts " Rails.root: #{Rails.root}"
94
+
95
+ if manual_config
96
+ puts " Manual config: #{manual_config} (overriding auto-detection)"
97
+ project_name = manual_config
98
+ else
99
+ puts " Auto-detected: #{auto_detected}"
100
+ puts ' Status: ✅ Automatic (no config needed)'
101
+ project_name = auto_detected
102
+ end
103
+
104
+ claude_paths = ClaudeUsage.configuration.claude_paths || ClaudeUsage::FileReader::CLAUDE_PATHS
105
+ puts "\n📂 Searching in directories:"
106
+ found_any = false
107
+
108
+ claude_paths.each do |path|
109
+ if Dir.exist?(path)
110
+ puts " ✅ #{path} (exists)"
111
+
112
+ # List all projects in this directory
113
+ projects = Dir.glob(File.join(path, '*')).select { |f| File.directory?(f) }
114
+ if projects.any?
115
+ puts ' Available projects:'
116
+ projects.each do |p|
117
+ project_folder = File.basename(p)
118
+ jsonl_count = Dir.glob(File.join(p, '**', '*.jsonl')).size
119
+ marker = project_folder == project_name ? '👉' : ' '
120
+ puts " #{marker} #{project_folder} (#{jsonl_count} JSONL files)"
121
+ found_any = true if project_folder == project_name && jsonl_count.positive?
122
+ end
123
+ else
124
+ puts ' (no projects found)'
125
+ end
126
+ else
127
+ puts " ❌ #{path} (not found)"
128
+ end
129
+ puts ''
130
+ end
131
+
132
+ if found_any
133
+ puts "\n✅ Found data for project '#{project_name}'!"
134
+ puts ' Run: rake claude_usage:total'
135
+ else
136
+ puts "\n⚠️ No data found for project '#{project_name}'"
137
+ puts "\n💡 Solutions:"
138
+ puts ' 1. If your project name differs, set it in config/initializers/claude_usage.rb:'
139
+ puts ' ClaudeUsage.configure do |config|'
140
+ puts " config.project_name = 'actual-project-name'"
141
+ puts ' end'
142
+ puts "\n 2. Check available project names above (marked with 👉 when matching)"
143
+ puts "\n 3. Ensure you've used Claude Code with this project\n"
144
+ end
145
+ end
146
+
147
+ desc 'Debug pricing for current project models'
148
+ task debug_pricing: :environment do
149
+ puts "\n🔬 Debugging Model Pricing\n"
150
+ puts '=' * 80
151
+
152
+ ClaudeUsage.configuration.project_name || Rails.root.basename.to_s
153
+ aggregator = ClaudeUsage::UsageAggregator.new
154
+ totals = aggregator.total_usage
155
+
156
+ if totals[:models_used].empty?
157
+ puts '❌ No models found in usage data'
158
+ puts "\n"
159
+ next
160
+ end
161
+
162
+ puts "\n📋 Models in your usage data:"
163
+ totals[:models_used].each { |model| puts " - #{model}" }
164
+ puts ''
165
+
166
+ fetcher = ClaudeUsage::LiteLLMPricingFetcher.new
167
+
168
+ # First, let's check if we can fetch pricing at all
169
+ puts '🔍 Testing pricing fetch...'
170
+ pricing_data = fetcher.fetch_pricing_data
171
+
172
+ if pricing_data.empty?
173
+ puts '❌ Failed to fetch pricing data from LiteLLM!'
174
+ puts ' This might be a network issue or the API is down.'
175
+ puts ' Check your internet connection and try again.'
176
+ puts "\n"
177
+ next
178
+ else
179
+ puts "✅ Successfully fetched pricing for #{pricing_data.keys.size} models"
180
+
181
+ # Check if our specific models are in there
182
+ test_models = ['claude-haiku-4-5-20251001', 'claude-sonnet-4-5-20250929']
183
+ test_models.each do |test_model|
184
+ if pricing_data.key?(test_model)
185
+ puts " ✅ #{test_model} found in database"
186
+ else
187
+ puts " ❌ #{test_model} NOT in database"
188
+ end
189
+ end
190
+ end
191
+ puts ''
192
+
193
+ totals[:models_used].each do |model|
194
+ puts "\n🤖 Model: #{model}"
195
+ puts " #{'-' * 76}"
196
+
197
+ # Skip synthetic models
198
+ if model == '<synthetic>'
199
+ puts ' ℹ️ System/internal messages (not billable)'
200
+ next
201
+ end
202
+
203
+ pricing, debug_info = fetcher.get_model_pricing(model, debug: true)
204
+
205
+ if pricing
206
+ puts ' ✅ Pricing found!'
207
+ puts " Input: $#{pricing['input_cost_per_token'] || pricing[:input_cost_per_token]} per token"
208
+ puts " Output: $#{pricing['output_cost_per_token'] || pricing[:output_cost_per_token]} per token"
209
+ cache_creation = pricing['cache_creation_input_token_cost'] ||
210
+ pricing[:cache_creation_input_token_cost] || 'N/A'
211
+ puts " Cache Creation: $#{cache_creation} per token"
212
+ cache_read = pricing['cache_read_input_token_cost'] ||
213
+ pricing[:cache_read_input_token_cost] || 'N/A'
214
+ puts " Cache Read: $#{cache_read} per token"
215
+
216
+ # Show how it was found
217
+ if debug_info[:fuzzy_match]
218
+ puts " (Found via fuzzy match: #{debug_info[:fuzzy_match]})"
219
+ elsif debug_info[:fallbacks]&.any? { |f| f[:found] }
220
+ found = debug_info[:fallbacks].find { |f| f[:found] }
221
+ puts " (Found via fallback: #{found[:candidate]})"
222
+ end
223
+ else
224
+ puts ' ❌ No pricing found!'
225
+ puts ' This model will contribute $0 to cost calculations'
226
+ puts ''
227
+ puts ' Debugging info:'
228
+ puts " - Original model name: #{model}"
229
+
230
+ if debug_info[:tried].any?
231
+ puts ' - Provider prefixes tried:'
232
+ debug_info[:tried].each do |attempt|
233
+ puts " * #{attempt[:candidate]}"
234
+ end
235
+ end
236
+
237
+ if debug_info[:fallbacks].any?
238
+ puts ' - Fallback models tried:'
239
+ debug_info[:fallbacks].each do |attempt|
240
+ puts " * #{attempt[:candidate]}"
241
+ end
242
+ end
243
+
244
+ puts ''
245
+ puts ' 💡 Possible solutions:'
246
+ puts ' 1. Model may be too new for LiteLLM pricing database'
247
+ puts ' 2. Model name format may differ (check LiteLLM docs)'
248
+ puts ' 3. Clear cache and retry: rake claude_usage:clear_cache'
249
+ puts ' 4. Check LiteLLM database:'
250
+ puts ' https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json'
251
+ end
252
+ end
253
+
254
+ puts "\n#{'=' * 80}\n"
255
+ end
256
+
257
+ desc 'Test LiteLLM pricing fetch'
258
+ task test_fetch: :environment do
259
+ puts "\n🧪 Testing LiteLLM Pricing Fetch\n"
260
+ puts '=' * 80
261
+ puts ''
262
+
263
+ fetcher = ClaudeUsage::LiteLLMPricingFetcher.new
264
+
265
+ puts 'Fetching pricing data...'
266
+ pricing_data = fetcher.fetch_pricing_data
267
+
268
+ if pricing_data.empty?
269
+ puts '❌ FAILED - No pricing data received'
270
+ puts ''
271
+ puts 'Possible causes:'
272
+ puts ' 1. Network connectivity issue'
273
+ puts ' 2. LiteLLM API is down'
274
+ puts ' 3. HTTP gem not working correctly'
275
+ puts ''
276
+ puts 'Try:'
277
+ puts " curl #{ClaudeUsage::LiteLLMPricingFetcher::LITELLM_PRICING_URL}"
278
+ puts ''
279
+ else
280
+ puts "✅ SUCCESS - Fetched #{pricing_data.keys.size} models"
281
+ puts ''
282
+
283
+ # Test specific models
284
+ test_models = {
285
+ 'claude-haiku-4-5-20251001' => 'Claude 4.5 Haiku',
286
+ 'claude-sonnet-4-5-20250929' => 'Claude 4.5 Sonnet',
287
+ 'claude-3-5-haiku-20241022' => 'Claude 3.5 Haiku',
288
+ 'claude-3-5-sonnet-20241022' => 'Claude 3.5 Sonnet'
289
+ }
290
+
291
+ puts 'Testing specific models:'
292
+ test_models.each do |model_key, model_name|
293
+ if pricing_data.key?(model_key)
294
+ pricing = pricing_data[model_key]
295
+ puts " ✅ #{model_name} (#{model_key})"
296
+ puts " Input: $#{pricing[:input_cost_per_token]}"
297
+ puts " Output: $#{pricing[:output_cost_per_token]}"
298
+ else
299
+ puts " ❌ #{model_name} (#{model_key}) - NOT FOUND"
300
+ end
301
+ end
302
+ puts ''
303
+
304
+ # Show sample of available models
305
+ puts 'Sample of available models:'
306
+ pricing_data.keys.select { |k| k.include?('claude') }.first(10).each do |key|
307
+ puts " - #{key}"
308
+ end
309
+ end
310
+
311
+ puts "\n#{'=' * 80}\n"
312
+ end
313
+ end
@@ -0,0 +1,4 @@
1
+ module ClaudeUsage
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: claude_usage
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Shoeb
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: httparty
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '0.21'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '0.21'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rails
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '6.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '6.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rspec
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '3.0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '3.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rspec-rails
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '6.0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '6.0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: webmock
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '3.0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '3.0'
82
+ description: A Rails engine for monitoring Claude Code usage, calculating costs using
83
+ LiteLLM pricing, and displaying usage analytics in your Rails app
84
+ email:
85
+ - shoebtamboli98@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".rubocop.yml"
91
+ - CHANGELOG.md
92
+ - CODE_OF_CONDUCT.md
93
+ - LICENSE.txt
94
+ - README.md
95
+ - Rakefile
96
+ - app/controllers/claude_usage/application_controller.rb
97
+ - app/controllers/claude_usage/usage_controller.rb
98
+ - app/helpers/claude_usage/usage_helper.rb
99
+ - app/views/claude_usage/usage/index.html.erb
100
+ - config/routes.rb
101
+ - lib/claude_usage.rb
102
+ - lib/claude_usage/cost_calculator.rb
103
+ - lib/claude_usage/engine.rb
104
+ - lib/claude_usage/file_reader.rb
105
+ - lib/claude_usage/formatters.rb
106
+ - lib/claude_usage/litellm_pricing_fetcher.rb
107
+ - lib/claude_usage/project_name_resolver.rb
108
+ - lib/claude_usage/usage_aggregator.rb
109
+ - lib/claude_usage/version.rb
110
+ - lib/generators/claude_usage/install_generator.rb
111
+ - lib/generators/claude_usage/templates/initializer.rb
112
+ - lib/tasks/claude_usage_tasks.rake
113
+ - sig/claude_usage.rbs
114
+ homepage: https://github.com/Shoebtamboli/claude_usage
115
+ licenses:
116
+ - MIT
117
+ metadata:
118
+ homepage_uri: https://github.com/Shoebtamboli/claude_usage
119
+ source_code_uri: https://github.com/Shoebtamboli/claude_usage
120
+ changelog_uri: https://github.com/Shoebtamboli/claude_usage/blob/main/CHANGELOG.md
121
+ rubygems_mfa_required: 'true'
122
+ rdoc_options: []
123
+ require_paths:
124
+ - lib
125
+ required_ruby_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: 3.2.0
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ requirements: []
136
+ rubygems_version: 3.6.9
137
+ specification_version: 4
138
+ summary: Track Claude Code token usage and costs in Rails applications
139
+ test_files: []