gsc-cli 2.2.0 → 2.2.3

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.
@@ -384,7 +384,7 @@ module GSC
384
384
  key = input unless input.nil? || input.empty?
385
385
  key = existing if key.empty?
386
386
  else
387
- puts "Get your Keywords Everywhere API key at: #{Color.c('https://keywordseverywhere.com/', Color::UNDERLINE)}"
387
+ puts "Get your Keywords Everywhere API key at: #{Color.c('https://keywordseverywhere.com/?fpr=us25sg', Color::UNDERLINE)}"
388
388
  print "Paste your Keywords Everywhere API Key: "
389
389
  key = $stdin.gets&.strip
390
390
  end
@@ -415,7 +415,7 @@ module GSC
415
415
  puts " 2. #{Color.c('100% Free Live Google Trends (Breakout & Related terms):', Color::BLUE, Color::BOLD)}"
416
416
  puts " • Run: #{Color.c('gsc trends "seo audit"', Color::BLUE, Color::BOLD)}"
417
417
  puts " 3. #{Color.c('Top Up Credits for Automated Background CLI Lookups:', Color::GRAY, Color::BOLD)}"
418
- puts " • Purchase credits at: https://keywordseverywhere.com/credit-packages.html"
418
+ puts " • Purchase credits at: https://keywordseverywhere.com/credit-packages.html?fpr=us25sg"
419
419
  puts " • Your key is already saved; it will work immediately upon top-up.\n"
420
420
  else
421
421
  puts "\nTry searching keywords:"
@@ -440,7 +440,7 @@ module GSC
440
440
  puts "\n" + Color.c("ℹ️ You have 0 paid REST API credits remaining.", Color::YELLOW)
441
441
  puts "• Free Web Alternative: Check volume on keywordseverywhere.com, click 'Copy', then run: #{Color.c('gsc import clip', Color::CYAN)}"
442
442
  puts "• Free Live Trends: #{Color.c('gsc trends "seo audit"', Color::BLUE)}"
443
- puts "• Top up credits at: https://keywordseverywhere.com/credit-packages.html"
443
+ puts "• Top up credits at: https://keywordseverywhere.com/credit-packages.html?fpr=us25sg"
444
444
  end
445
445
  else
446
446
  if acct[:error].to_s =~ /401|Unauthorized/i
@@ -477,7 +477,7 @@ module GSC
477
477
  else
478
478
  puts Color.c("\n❌ Keywords Everywhere API key not configured!\n", Color::RED, Color::BOLD)
479
479
  puts "#{Color::BOLD}RECOMMENDATIONS:#{Color::RESET}"
480
- puts " 1. Connect a key: #{Color.c('gsc connect ke', Color::CYAN, Color::BOLD)} (Get key at https://keywordseverywhere.com/)"
480
+ puts " 1. Connect a key: #{Color.c('gsc connect ke', Color::CYAN, Color::BOLD)} (Get key at https://keywordseverywhere.com/?fpr=us25sg)"
481
481
  puts " 2. Use 100% free Google Trends (No key needed!): #{Color.c("gsc trends \"#{target}\"", Color::BLUE, Color::BOLD)}"
482
482
  puts " 3. Use 500 free daily web lookups: Copy web table -> #{Color.c('gsc import clip', Color::GREEN, Color::BOLD)}\n"
483
483
  end
@@ -524,7 +524,7 @@ module GSC
524
524
  { action: 'import_clipboard', cmd: 'gsc import clip', desc: 'Use 500 free daily web lookups on keywordseverywhere.com and import via clipboard' },
525
525
  { action: 'google_trends', cmd: "gsc trends \"#{target}\"", desc: '100% Free real-time search interest & rising breakout queries' },
526
526
  { action: 'gsc_analytics', cmd: 'gsc top-queries', desc: 'Real impressions & clicks from your active Google Search Console domain' },
527
- { action: 'topup', url: 'https://keywordseverywhere.com/credit-packages.html', desc: 'Purchase API credits to enable automated background CLI lookups' }
527
+ { action: 'topup', url: 'https://keywordseverywhere.com/credit-packages.html?fpr=us25sg', desc: 'Purchase API credits to enable automated background CLI lookups' }
528
528
  ]
529
529
  })
530
530
  return
@@ -547,13 +547,13 @@ module GSC
547
547
  puts " • High-impression queries and Page 2 striking-distance keywords:"
548
548
  puts " #{Color.c('gsc top-queries', Color::MAGENTA, Color::BOLD)} or #{Color.c('gsc opportunities', Color::MAGENTA, Color::BOLD)}\n"
549
549
  puts " 4. #{Color.c('Top Up API Credits for Automated Background CLI Lookups', Color::GRAY, Color::BOLD)}"
550
- puts " • Purchase credits at: #{Color.c('https://keywordseverywhere.com/credit-packages.html', Color::UNDERLINE)}"
550
+ puts " • Purchase credits at: #{Color.c('https://keywordseverywhere.com/credit-packages.html?fpr=us25sg', Color::UNDERLINE)}"
551
551
  puts " • Your key is already saved; it will work immediately once credited.\n"
552
552
  elsif err_str =~ /401|Unauthorized/i
553
553
  puts Color.c("\n🔑 Invalid or Expired Keywords Everywhere API Key", Color::RED, Color::BOLD)
554
554
  puts "Your API key was rejected by api.keywordseverywhere.com (HTTP 401).\n\n"
555
555
  puts "#{Color::BOLD}RECOMMENDATIONS:#{Color::RESET}"
556
- puts " • Check or generate your API key at: #{Color.c('https://keywordseverywhere.com/first-install-addon.html', Color::UNDERLINE)}"
556
+ puts " • Check or generate your API key at: #{Color.c('https://keywordseverywhere.com/first-install-addon.html?fpr=us25sg', Color::UNDERLINE)}"
557
557
  puts " • Reconnect key: #{Color.c('gsc connect ke <KEY>', Color::CYAN, Color::BOLD)}"
558
558
  puts " • Or use free Google Trends without any key: #{Color.c("gsc trends \"#{target}\"", Color::BLUE)}"
559
559
  elsif err_str =~ /429|Rate Limit/i
@@ -170,13 +170,61 @@ module GSC
170
170
  puts "╚══════════════════════════════════════════════════════════════╝"
171
171
 
172
172
  m = data[:metrics] || {}
173
- puts "\n#{Color::BOLD}📊 CORE WEB VITALS (Lab Metrics):#{Color::RESET}"
173
+ puts "\n#{Color::BOLD}🔬 LIGHTHOUSE LAB METRICS (Simulated #{strategy.upcase}):#{Color::RESET}"
174
174
  puts " • LCP (Largest Contentful Paint) : #{Color.c(m[:lcp] || 'N/A', Color::BOLD)}"
175
175
  puts " • FCP (First Contentful Paint) : #{Color.c(m[:fcp] || 'N/A', Color::BOLD)}"
176
176
  puts " • CLS (Cumulative Layout Shift) : #{Color.c(m[:cls] || 'N/A', Color::BOLD)}"
177
177
  puts " • TBT (Total Blocking Time) : #{Color.c(m[:tbt] || 'N/A', Color::BOLD)}"
178
178
  puts " • Speed Index : #{Color.c(m[:speed_index] || 'N/A', Color::BOLD)}"
179
179
 
180
+ fd = data[:field_data] || {}
181
+ if fd.any?
182
+ source_label = data[:field_source] == :origin ? "Domain-Wide Origin RUM" : "URL-Level RUM"
183
+ overall = data[:overall_category] || "UNKNOWN"
184
+ overall_col = overall == 'FAST' ? Color::GREEN : (overall == 'AVERAGE' ? Color::YELLOW : Color::RED)
185
+ puts "\n#{Color::BOLD}🌐 CrUX FIELD DATA (28-Day Real User Monitoring - #{source_label}):#{Color::RESET}"
186
+ puts " • Core Web Vitals Status : #{Color.c(overall, overall_col, Color::BOLD)}"
187
+
188
+ if fd['LARGEST_CONTENTFUL_PAINT_MS']
189
+ val = "#{(fd['LARGEST_CONTENTFUL_PAINT_MS'][:percentile].to_f / 1000).round(2)} s"
190
+ cat = fd['LARGEST_CONTENTFUL_PAINT_MS'][:category]
191
+ col = cat == 'FAST' ? Color::GREEN : (cat == 'AVERAGE' ? Color::YELLOW : Color::RED)
192
+ puts " • LCP (75th Percentile) : #{Color.c(val, Color::BOLD)} [#{Color.c(cat, col)}]"
193
+ end
194
+
195
+ if fd['INTERACTION_TO_NEXT_PAINT']
196
+ val = "#{fd['INTERACTION_TO_NEXT_PAINT'][:percentile]} ms"
197
+ cat = fd['INTERACTION_TO_NEXT_PAINT'][:category]
198
+ col = cat == 'FAST' ? Color::GREEN : (cat == 'AVERAGE' ? Color::YELLOW : Color::RED)
199
+ puts " • INP (75th Percentile) : #{Color.c(val, Color::BOLD)} [#{Color.c(cat, col)}]"
200
+ end
201
+
202
+ if fd['CUMULATIVE_LAYOUT_SHIFT_SCORE']
203
+ val = (fd['CUMULATIVE_LAYOUT_SHIFT_SCORE'][:percentile].to_f / 100).round(3).to_s
204
+ cat = fd['CUMULATIVE_LAYOUT_SHIFT_SCORE'][:category]
205
+ col = cat == 'FAST' ? Color::GREEN : (cat == 'AVERAGE' ? Color::YELLOW : Color::RED)
206
+ puts " • CLS (75th Percentile) : #{Color.c(val, Color::BOLD)} [#{Color.c(cat, col)}]"
207
+ end
208
+
209
+ if fd['FIRST_CONTENTFUL_PAINT_MS']
210
+ val = "#{(fd['FIRST_CONTENTFUL_PAINT_MS'][:percentile].to_f / 1000).round(2)} s"
211
+ cat = fd['FIRST_CONTENTFUL_PAINT_MS'][:category]
212
+ col = cat == 'FAST' ? Color::GREEN : (cat == 'AVERAGE' ? Color::YELLOW : Color::RED)
213
+ puts " • FCP (75th Percentile) : #{Color.c(val, Color::BOLD)} [#{Color.c(cat, col)}]"
214
+ end
215
+
216
+ if fd['EXPERIMENTAL_TIME_TO_FIRST_BYTE']
217
+ val = "#{fd['EXPERIMENTAL_TIME_TO_FIRST_BYTE'][:percentile]} ms"
218
+ cat = fd['EXPERIMENTAL_TIME_TO_FIRST_BYTE'][:category]
219
+ col = cat == 'FAST' ? Color::GREEN : (cat == 'AVERAGE' ? Color::YELLOW : Color::RED)
220
+ puts " • TTFB (75th Percentile) : #{Color.c(val, Color::BOLD)} [#{Color.c(cat, col)}]"
221
+ end
222
+ else
223
+ puts "\n#{Color::BOLD}🌐 CrUX FIELD DATA (28-Day Real User Monitoring):#{Color::RESET}"
224
+ puts " #{Color.c('ℹ️ Insufficient real-user Chrome visits over the rolling 28-day window for Google CrUX telemetry.', Color::GRAY)}"
225
+ puts " #{Color.c(' (Google requires a minimum traffic threshold before publishing real-user CrUX field data).', Color::GRAY)}"
226
+ end
227
+
180
228
  opps = data[:opportunities] || []
181
229
  unless opps.empty?
182
230
  puts "\n#{Color::BOLD}💡 TOP SPEED OPPORTUNITIES:#{Color::RESET}"
@@ -28,9 +28,9 @@ module GSC
28
28
 
29
29
  uri = URI("#{API_URL}?#{query_params.join('&')}")
30
30
  req = Net::HTTP::Get.new(uri)
31
- req['User-Agent'] = 'gsc-cli/2.1'
31
+ req['User-Agent'] = "gsc-cli/#{GSC::VERSION}"
32
32
 
33
- res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true, open_timeout: 10, read_timeout: 30) do |http|
33
+ res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true, open_timeout: 15, read_timeout: 60) do |http|
34
34
  http.request(req)
35
35
  end
36
36
 
@@ -64,16 +64,28 @@ module GSC
64
64
  tbt = audits.dig('total-blocking-time', 'displayValue')
65
65
  si = audits.dig('speed-index', 'displayValue')
66
66
 
67
- # CrUX Field Data (if available)
67
+ # CrUX Field Data (URL or Origin fallback if available)
68
68
  crux_metrics = {}
69
+ crux_source = nil
69
70
  crux = data.dig('loadingExperience', 'metrics') || {}
70
- crux.each do |k, v|
71
- crux_metrics[k] = {
72
- percentile: v['percentile'],
73
- category: v['category']
74
- }
71
+ if crux && !crux.empty?
72
+ crux_source = :url
73
+ else
74
+ crux = data.dig('originLoadingExperience', 'metrics') || {}
75
+ crux_source = :origin unless crux.empty?
75
76
  end
76
77
 
78
+ if crux && !crux.empty?
79
+ crux.each do |k, v|
80
+ crux_metrics[k] = {
81
+ percentile: v['percentile'],
82
+ category: v['category']
83
+ }
84
+ end
85
+ end
86
+
87
+ overall_category = data.dig('loadingExperience', 'overall_category') || data.dig('originLoadingExperience', 'overall_category')
88
+
77
89
  # Opportunities
78
90
  opportunities = []
79
91
  audits.each do |k, v|
@@ -103,6 +115,8 @@ module GSC
103
115
  speed_index: si
104
116
  },
105
117
  field_data: crux_metrics,
118
+ field_source: crux_source,
119
+ overall_category: overall_category,
106
120
  opportunities: opportunities.first(5)
107
121
  }
108
122
  end
data/lib/gsc/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GSC
4
- VERSION = '2.2.0'
4
+ VERSION = '2.2.3'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gsc-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.0
4
+ version: 2.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - ApollosWave LLC