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.
data/bin/gsc CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  # ==============================================================================
5
5
  # Google Search Console & Indexing API CLI Tool (Pure Ruby - Zero Gem Dependencies)
6
- # Standalone Single-File Distribution (Built from lib/gsc v2.2.0)
6
+ # Standalone Single-File Distribution (Built from lib/gsc v2.2.3)
7
7
  # ==============================================================================
8
8
 
9
9
  require 'net/http'
@@ -20,7 +20,7 @@ require 'stringio'
20
20
 
21
21
  # --- version.rb ---
22
22
  module GSC
23
- VERSION = '2.2.0'
23
+ VERSION = '2.2.3'
24
24
  end
25
25
 
26
26
  # --- color.rb ---
@@ -3116,9 +3116,9 @@ module GSC
3116
3116
 
3117
3117
  uri = URI("#{API_URL}?#{query_params.join('&')}")
3118
3118
  req = Net::HTTP::Get.new(uri)
3119
- req['User-Agent'] = 'gsc-cli/2.1'
3119
+ req['User-Agent'] = "gsc-cli/#{GSC::VERSION}"
3120
3120
 
3121
- res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true, open_timeout: 10, read_timeout: 30) do |http|
3121
+ res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true, open_timeout: 15, read_timeout: 60) do |http|
3122
3122
  http.request(req)
3123
3123
  end
3124
3124
 
@@ -3152,16 +3152,28 @@ module GSC
3152
3152
  tbt = audits.dig('total-blocking-time', 'displayValue')
3153
3153
  si = audits.dig('speed-index', 'displayValue')
3154
3154
 
3155
- # CrUX Field Data (if available)
3155
+ # CrUX Field Data (URL or Origin fallback if available)
3156
3156
  crux_metrics = {}
3157
+ crux_source = nil
3157
3158
  crux = data.dig('loadingExperience', 'metrics') || {}
3158
- crux.each do |k, v|
3159
- crux_metrics[k] = {
3160
- percentile: v['percentile'],
3161
- category: v['category']
3162
- }
3159
+ if crux && !crux.empty?
3160
+ crux_source = :url
3161
+ else
3162
+ crux = data.dig('originLoadingExperience', 'metrics') || {}
3163
+ crux_source = :origin unless crux.empty?
3163
3164
  end
3164
3165
 
3166
+ if crux && !crux.empty?
3167
+ crux.each do |k, v|
3168
+ crux_metrics[k] = {
3169
+ percentile: v['percentile'],
3170
+ category: v['category']
3171
+ }
3172
+ end
3173
+ end
3174
+
3175
+ overall_category = data.dig('loadingExperience', 'overall_category') || data.dig('originLoadingExperience', 'overall_category')
3176
+
3165
3177
  # Opportunities
3166
3178
  opportunities = []
3167
3179
  audits.each do |k, v|
@@ -3191,6 +3203,8 @@ module GSC
3191
3203
  speed_index: si
3192
3204
  },
3193
3205
  field_data: crux_metrics,
3206
+ field_source: crux_source,
3207
+ overall_category: overall_category,
3194
3208
  opportunities: opportunities.first(5)
3195
3209
  }
3196
3210
  end
@@ -21612,7 +21626,7 @@ module GSC
21612
21626
  key = input unless input.nil? || input.empty?
21613
21627
  key = existing if key.empty?
21614
21628
  else
21615
- puts "Get your Keywords Everywhere API key at: #{Color.c('https://keywordseverywhere.com/', Color::UNDERLINE)}"
21629
+ puts "Get your Keywords Everywhere API key at: #{Color.c('https://keywordseverywhere.com/?fpr=us25sg', Color::UNDERLINE)}"
21616
21630
  print "Paste your Keywords Everywhere API Key: "
21617
21631
  key = $stdin.gets&.strip
21618
21632
  end
@@ -21643,7 +21657,7 @@ module GSC
21643
21657
  puts " 2. #{Color.c('100% Free Live Google Trends (Breakout & Related terms):', Color::BLUE, Color::BOLD)}"
21644
21658
  puts " • Run: #{Color.c('gsc trends "seo audit"', Color::BLUE, Color::BOLD)}"
21645
21659
  puts " 3. #{Color.c('Top Up Credits for Automated Background CLI Lookups:', Color::GRAY, Color::BOLD)}"
21646
- puts " • Purchase credits at: https://keywordseverywhere.com/credit-packages.html"
21660
+ puts " • Purchase credits at: https://keywordseverywhere.com/credit-packages.html?fpr=us25sg"
21647
21661
  puts " • Your key is already saved; it will work immediately upon top-up.\n"
21648
21662
  else
21649
21663
  puts "\nTry searching keywords:"
@@ -21668,7 +21682,7 @@ module GSC
21668
21682
  puts "\n" + Color.c("ℹ️ You have 0 paid REST API credits remaining.", Color::YELLOW)
21669
21683
  puts "• Free Web Alternative: Check volume on keywordseverywhere.com, click 'Copy', then run: #{Color.c('gsc import clip', Color::CYAN)}"
21670
21684
  puts "• Free Live Trends: #{Color.c('gsc trends "seo audit"', Color::BLUE)}"
21671
- puts "• Top up credits at: https://keywordseverywhere.com/credit-packages.html"
21685
+ puts "• Top up credits at: https://keywordseverywhere.com/credit-packages.html?fpr=us25sg"
21672
21686
  end
21673
21687
  else
21674
21688
  if acct[:error].to_s =~ /401|Unauthorized/i
@@ -21705,7 +21719,7 @@ module GSC
21705
21719
  else
21706
21720
  puts Color.c("\n❌ Keywords Everywhere API key not configured!\n", Color::RED, Color::BOLD)
21707
21721
  puts "#{Color::BOLD}RECOMMENDATIONS:#{Color::RESET}"
21708
- puts " 1. Connect a key: #{Color.c('gsc connect ke', Color::CYAN, Color::BOLD)} (Get key at https://keywordseverywhere.com/)"
21722
+ puts " 1. Connect a key: #{Color.c('gsc connect ke', Color::CYAN, Color::BOLD)} (Get key at https://keywordseverywhere.com/?fpr=us25sg)"
21709
21723
  puts " 2. Use 100% free Google Trends (No key needed!): #{Color.c("gsc trends \"#{target}\"", Color::BLUE, Color::BOLD)}"
21710
21724
  puts " 3. Use 500 free daily web lookups: Copy web table -> #{Color.c('gsc import clip', Color::GREEN, Color::BOLD)}\n"
21711
21725
  end
@@ -21752,7 +21766,7 @@ module GSC
21752
21766
  { action: 'import_clipboard', cmd: 'gsc import clip', desc: 'Use 500 free daily web lookups on keywordseverywhere.com and import via clipboard' },
21753
21767
  { action: 'google_trends', cmd: "gsc trends \"#{target}\"", desc: '100% Free real-time search interest & rising breakout queries' },
21754
21768
  { action: 'gsc_analytics', cmd: 'gsc top-queries', desc: 'Real impressions & clicks from your active Google Search Console domain' },
21755
- { action: 'topup', url: 'https://keywordseverywhere.com/credit-packages.html', desc: 'Purchase API credits to enable automated background CLI lookups' }
21769
+ { action: 'topup', url: 'https://keywordseverywhere.com/credit-packages.html?fpr=us25sg', desc: 'Purchase API credits to enable automated background CLI lookups' }
21756
21770
  ]
21757
21771
  })
21758
21772
  return
@@ -21775,13 +21789,13 @@ module GSC
21775
21789
  puts " • High-impression queries and Page 2 striking-distance keywords:"
21776
21790
  puts " #{Color.c('gsc top-queries', Color::MAGENTA, Color::BOLD)} or #{Color.c('gsc opportunities', Color::MAGENTA, Color::BOLD)}\n"
21777
21791
  puts " 4. #{Color.c('Top Up API Credits for Automated Background CLI Lookups', Color::GRAY, Color::BOLD)}"
21778
- puts " • Purchase credits at: #{Color.c('https://keywordseverywhere.com/credit-packages.html', Color::UNDERLINE)}"
21792
+ puts " • Purchase credits at: #{Color.c('https://keywordseverywhere.com/credit-packages.html?fpr=us25sg', Color::UNDERLINE)}"
21779
21793
  puts " • Your key is already saved; it will work immediately once credited.\n"
21780
21794
  elsif err_str =~ /401|Unauthorized/i
21781
21795
  puts Color.c("\n🔑 Invalid or Expired Keywords Everywhere API Key", Color::RED, Color::BOLD)
21782
21796
  puts "Your API key was rejected by api.keywordseverywhere.com (HTTP 401).\n\n"
21783
21797
  puts "#{Color::BOLD}RECOMMENDATIONS:#{Color::RESET}"
21784
- puts " • Check or generate your API key at: #{Color.c('https://keywordseverywhere.com/first-install-addon.html', Color::UNDERLINE)}"
21798
+ puts " • Check or generate your API key at: #{Color.c('https://keywordseverywhere.com/first-install-addon.html?fpr=us25sg', Color::UNDERLINE)}"
21785
21799
  puts " • Reconnect key: #{Color.c('gsc connect ke <KEY>', Color::CYAN, Color::BOLD)}"
21786
21800
  puts " • Or use free Google Trends without any key: #{Color.c("gsc trends \"#{target}\"", Color::BLUE)}"
21787
21801
  elsif err_str =~ /429|Rate Limit/i
@@ -22833,13 +22847,61 @@ module GSC
22833
22847
  puts "╚══════════════════════════════════════════════════════════════╝"
22834
22848
 
22835
22849
  m = data[:metrics] || {}
22836
- puts "\n#{Color::BOLD}📊 CORE WEB VITALS (Lab Metrics):#{Color::RESET}"
22850
+ puts "\n#{Color::BOLD}🔬 LIGHTHOUSE LAB METRICS (Simulated #{strategy.upcase}):#{Color::RESET}"
22837
22851
  puts " • LCP (Largest Contentful Paint) : #{Color.c(m[:lcp] || 'N/A', Color::BOLD)}"
22838
22852
  puts " • FCP (First Contentful Paint) : #{Color.c(m[:fcp] || 'N/A', Color::BOLD)}"
22839
22853
  puts " • CLS (Cumulative Layout Shift) : #{Color.c(m[:cls] || 'N/A', Color::BOLD)}"
22840
22854
  puts " • TBT (Total Blocking Time) : #{Color.c(m[:tbt] || 'N/A', Color::BOLD)}"
22841
22855
  puts " • Speed Index : #{Color.c(m[:speed_index] || 'N/A', Color::BOLD)}"
22842
22856
 
22857
+ fd = data[:field_data] || {}
22858
+ if fd.any?
22859
+ source_label = data[:field_source] == :origin ? "Domain-Wide Origin RUM" : "URL-Level RUM"
22860
+ overall = data[:overall_category] || "UNKNOWN"
22861
+ overall_col = overall == 'FAST' ? Color::GREEN : (overall == 'AVERAGE' ? Color::YELLOW : Color::RED)
22862
+ puts "\n#{Color::BOLD}🌐 CrUX FIELD DATA (28-Day Real User Monitoring - #{source_label}):#{Color::RESET}"
22863
+ puts " • Core Web Vitals Status : #{Color.c(overall, overall_col, Color::BOLD)}"
22864
+
22865
+ if fd['LARGEST_CONTENTFUL_PAINT_MS']
22866
+ val = "#{(fd['LARGEST_CONTENTFUL_PAINT_MS'][:percentile].to_f / 1000).round(2)} s"
22867
+ cat = fd['LARGEST_CONTENTFUL_PAINT_MS'][:category]
22868
+ col = cat == 'FAST' ? Color::GREEN : (cat == 'AVERAGE' ? Color::YELLOW : Color::RED)
22869
+ puts " • LCP (75th Percentile) : #{Color.c(val, Color::BOLD)} [#{Color.c(cat, col)}]"
22870
+ end
22871
+
22872
+ if fd['INTERACTION_TO_NEXT_PAINT']
22873
+ val = "#{fd['INTERACTION_TO_NEXT_PAINT'][:percentile]} ms"
22874
+ cat = fd['INTERACTION_TO_NEXT_PAINT'][:category]
22875
+ col = cat == 'FAST' ? Color::GREEN : (cat == 'AVERAGE' ? Color::YELLOW : Color::RED)
22876
+ puts " • INP (75th Percentile) : #{Color.c(val, Color::BOLD)} [#{Color.c(cat, col)}]"
22877
+ end
22878
+
22879
+ if fd['CUMULATIVE_LAYOUT_SHIFT_SCORE']
22880
+ val = (fd['CUMULATIVE_LAYOUT_SHIFT_SCORE'][:percentile].to_f / 100).round(3).to_s
22881
+ cat = fd['CUMULATIVE_LAYOUT_SHIFT_SCORE'][:category]
22882
+ col = cat == 'FAST' ? Color::GREEN : (cat == 'AVERAGE' ? Color::YELLOW : Color::RED)
22883
+ puts " • CLS (75th Percentile) : #{Color.c(val, Color::BOLD)} [#{Color.c(cat, col)}]"
22884
+ end
22885
+
22886
+ if fd['FIRST_CONTENTFUL_PAINT_MS']
22887
+ val = "#{(fd['FIRST_CONTENTFUL_PAINT_MS'][:percentile].to_f / 1000).round(2)} s"
22888
+ cat = fd['FIRST_CONTENTFUL_PAINT_MS'][:category]
22889
+ col = cat == 'FAST' ? Color::GREEN : (cat == 'AVERAGE' ? Color::YELLOW : Color::RED)
22890
+ puts " • FCP (75th Percentile) : #{Color.c(val, Color::BOLD)} [#{Color.c(cat, col)}]"
22891
+ end
22892
+
22893
+ if fd['EXPERIMENTAL_TIME_TO_FIRST_BYTE']
22894
+ val = "#{fd['EXPERIMENTAL_TIME_TO_FIRST_BYTE'][:percentile]} ms"
22895
+ cat = fd['EXPERIMENTAL_TIME_TO_FIRST_BYTE'][:category]
22896
+ col = cat == 'FAST' ? Color::GREEN : (cat == 'AVERAGE' ? Color::YELLOW : Color::RED)
22897
+ puts " • TTFB (75th Percentile) : #{Color.c(val, Color::BOLD)} [#{Color.c(cat, col)}]"
22898
+ end
22899
+ else
22900
+ puts "\n#{Color::BOLD}🌐 CrUX FIELD DATA (28-Day Real User Monitoring):#{Color::RESET}"
22901
+ puts " #{Color.c('ℹ️ Insufficient real-user Chrome visits over the rolling 28-day window for Google CrUX telemetry.', Color::GRAY)}"
22902
+ puts " #{Color.c(' (Google requires a minimum traffic threshold before publishing real-user CrUX field data).', Color::GRAY)}"
22903
+ end
22904
+
22843
22905
  opps = data[:opportunities] || []
22844
22906
  unless opps.empty?
22845
22907
  puts "\n#{Color::BOLD}💡 TOP SPEED OPPORTUNITIES:#{Color::RESET}"
@@ -29944,13 +30006,61 @@ module GSC
29944
30006
  puts "╚══════════════════════════════════════════════════════════════╝"
29945
30007
 
29946
30008
  m = data[:metrics] || {}
29947
- puts "\n#{Color::BOLD}📊 CORE WEB VITALS (Lab Metrics):#{Color::RESET}"
30009
+ puts "\n#{Color::BOLD}🔬 LIGHTHOUSE LAB METRICS (Simulated #{strategy.upcase}):#{Color::RESET}"
29948
30010
  puts " • LCP (Largest Contentful Paint) : #{Color.c(m[:lcp] || 'N/A', Color::BOLD)}"
29949
30011
  puts " • FCP (First Contentful Paint) : #{Color.c(m[:fcp] || 'N/A', Color::BOLD)}"
29950
30012
  puts " • CLS (Cumulative Layout Shift) : #{Color.c(m[:cls] || 'N/A', Color::BOLD)}"
29951
30013
  puts " • TBT (Total Blocking Time) : #{Color.c(m[:tbt] || 'N/A', Color::BOLD)}"
29952
30014
  puts " • Speed Index : #{Color.c(m[:speed_index] || 'N/A', Color::BOLD)}"
29953
30015
 
30016
+ fd = data[:field_data] || {}
30017
+ if fd.any?
30018
+ source_label = data[:field_source] == :origin ? "Domain-Wide Origin RUM" : "URL-Level RUM"
30019
+ overall = data[:overall_category] || "UNKNOWN"
30020
+ overall_col = overall == 'FAST' ? Color::GREEN : (overall == 'AVERAGE' ? Color::YELLOW : Color::RED)
30021
+ puts "\n#{Color::BOLD}🌐 CrUX FIELD DATA (28-Day Real User Monitoring - #{source_label}):#{Color::RESET}"
30022
+ puts " • Core Web Vitals Status : #{Color.c(overall, overall_col, Color::BOLD)}"
30023
+
30024
+ if fd['LARGEST_CONTENTFUL_PAINT_MS']
30025
+ val = "#{(fd['LARGEST_CONTENTFUL_PAINT_MS'][:percentile].to_f / 1000).round(2)} s"
30026
+ cat = fd['LARGEST_CONTENTFUL_PAINT_MS'][:category]
30027
+ col = cat == 'FAST' ? Color::GREEN : (cat == 'AVERAGE' ? Color::YELLOW : Color::RED)
30028
+ puts " • LCP (75th Percentile) : #{Color.c(val, Color::BOLD)} [#{Color.c(cat, col)}]"
30029
+ end
30030
+
30031
+ if fd['INTERACTION_TO_NEXT_PAINT']
30032
+ val = "#{fd['INTERACTION_TO_NEXT_PAINT'][:percentile]} ms"
30033
+ cat = fd['INTERACTION_TO_NEXT_PAINT'][:category]
30034
+ col = cat == 'FAST' ? Color::GREEN : (cat == 'AVERAGE' ? Color::YELLOW : Color::RED)
30035
+ puts " • INP (75th Percentile) : #{Color.c(val, Color::BOLD)} [#{Color.c(cat, col)}]"
30036
+ end
30037
+
30038
+ if fd['CUMULATIVE_LAYOUT_SHIFT_SCORE']
30039
+ val = (fd['CUMULATIVE_LAYOUT_SHIFT_SCORE'][:percentile].to_f / 100).round(3).to_s
30040
+ cat = fd['CUMULATIVE_LAYOUT_SHIFT_SCORE'][:category]
30041
+ col = cat == 'FAST' ? Color::GREEN : (cat == 'AVERAGE' ? Color::YELLOW : Color::RED)
30042
+ puts " • CLS (75th Percentile) : #{Color.c(val, Color::BOLD)} [#{Color.c(cat, col)}]"
30043
+ end
30044
+
30045
+ if fd['FIRST_CONTENTFUL_PAINT_MS']
30046
+ val = "#{(fd['FIRST_CONTENTFUL_PAINT_MS'][:percentile].to_f / 1000).round(2)} s"
30047
+ cat = fd['FIRST_CONTENTFUL_PAINT_MS'][:category]
30048
+ col = cat == 'FAST' ? Color::GREEN : (cat == 'AVERAGE' ? Color::YELLOW : Color::RED)
30049
+ puts " • FCP (75th Percentile) : #{Color.c(val, Color::BOLD)} [#{Color.c(cat, col)}]"
30050
+ end
30051
+
30052
+ if fd['EXPERIMENTAL_TIME_TO_FIRST_BYTE']
30053
+ val = "#{fd['EXPERIMENTAL_TIME_TO_FIRST_BYTE'][:percentile]} ms"
30054
+ cat = fd['EXPERIMENTAL_TIME_TO_FIRST_BYTE'][:category]
30055
+ col = cat == 'FAST' ? Color::GREEN : (cat == 'AVERAGE' ? Color::YELLOW : Color::RED)
30056
+ puts " • TTFB (75th Percentile) : #{Color.c(val, Color::BOLD)} [#{Color.c(cat, col)}]"
30057
+ end
30058
+ else
30059
+ puts "\n#{Color::BOLD}🌐 CrUX FIELD DATA (28-Day Real User Monitoring):#{Color::RESET}"
30060
+ puts " #{Color.c('ℹ️ Insufficient real-user Chrome visits over the rolling 28-day window for Google CrUX telemetry.', Color::GRAY)}"
30061
+ puts " #{Color.c(' (Google requires a minimum traffic threshold before publishing real-user CrUX field data).', Color::GRAY)}"
30062
+ end
30063
+
29954
30064
  opps = data[:opportunities] || []
29955
30065
  unless opps.empty?
29956
30066
  puts "\n#{Color::BOLD}💡 TOP SPEED OPPORTUNITIES:#{Color::RESET}"
data/dist/gsc CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  # ==============================================================================
5
5
  # Google Search Console & Indexing API CLI Tool (Pure Ruby - Zero Gem Dependencies)
6
- # Standalone Single-File Distribution (Built from lib/gsc v2.2.0)
6
+ # Standalone Single-File Distribution (Built from lib/gsc v2.2.3)
7
7
  # ==============================================================================
8
8
 
9
9
  require 'net/http'
@@ -20,7 +20,7 @@ require 'stringio'
20
20
 
21
21
  # --- version.rb ---
22
22
  module GSC
23
- VERSION = '2.2.0'
23
+ VERSION = '2.2.3'
24
24
  end
25
25
 
26
26
  # --- color.rb ---
@@ -3116,9 +3116,9 @@ module GSC
3116
3116
 
3117
3117
  uri = URI("#{API_URL}?#{query_params.join('&')}")
3118
3118
  req = Net::HTTP::Get.new(uri)
3119
- req['User-Agent'] = 'gsc-cli/2.1'
3119
+ req['User-Agent'] = "gsc-cli/#{GSC::VERSION}"
3120
3120
 
3121
- res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true, open_timeout: 10, read_timeout: 30) do |http|
3121
+ res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true, open_timeout: 15, read_timeout: 60) do |http|
3122
3122
  http.request(req)
3123
3123
  end
3124
3124
 
@@ -3152,16 +3152,28 @@ module GSC
3152
3152
  tbt = audits.dig('total-blocking-time', 'displayValue')
3153
3153
  si = audits.dig('speed-index', 'displayValue')
3154
3154
 
3155
- # CrUX Field Data (if available)
3155
+ # CrUX Field Data (URL or Origin fallback if available)
3156
3156
  crux_metrics = {}
3157
+ crux_source = nil
3157
3158
  crux = data.dig('loadingExperience', 'metrics') || {}
3158
- crux.each do |k, v|
3159
- crux_metrics[k] = {
3160
- percentile: v['percentile'],
3161
- category: v['category']
3162
- }
3159
+ if crux && !crux.empty?
3160
+ crux_source = :url
3161
+ else
3162
+ crux = data.dig('originLoadingExperience', 'metrics') || {}
3163
+ crux_source = :origin unless crux.empty?
3163
3164
  end
3164
3165
 
3166
+ if crux && !crux.empty?
3167
+ crux.each do |k, v|
3168
+ crux_metrics[k] = {
3169
+ percentile: v['percentile'],
3170
+ category: v['category']
3171
+ }
3172
+ end
3173
+ end
3174
+
3175
+ overall_category = data.dig('loadingExperience', 'overall_category') || data.dig('originLoadingExperience', 'overall_category')
3176
+
3165
3177
  # Opportunities
3166
3178
  opportunities = []
3167
3179
  audits.each do |k, v|
@@ -3191,6 +3203,8 @@ module GSC
3191
3203
  speed_index: si
3192
3204
  },
3193
3205
  field_data: crux_metrics,
3206
+ field_source: crux_source,
3207
+ overall_category: overall_category,
3194
3208
  opportunities: opportunities.first(5)
3195
3209
  }
3196
3210
  end
@@ -21612,7 +21626,7 @@ module GSC
21612
21626
  key = input unless input.nil? || input.empty?
21613
21627
  key = existing if key.empty?
21614
21628
  else
21615
- puts "Get your Keywords Everywhere API key at: #{Color.c('https://keywordseverywhere.com/', Color::UNDERLINE)}"
21629
+ puts "Get your Keywords Everywhere API key at: #{Color.c('https://keywordseverywhere.com/?fpr=us25sg', Color::UNDERLINE)}"
21616
21630
  print "Paste your Keywords Everywhere API Key: "
21617
21631
  key = $stdin.gets&.strip
21618
21632
  end
@@ -21643,7 +21657,7 @@ module GSC
21643
21657
  puts " 2. #{Color.c('100% Free Live Google Trends (Breakout & Related terms):', Color::BLUE, Color::BOLD)}"
21644
21658
  puts " • Run: #{Color.c('gsc trends "seo audit"', Color::BLUE, Color::BOLD)}"
21645
21659
  puts " 3. #{Color.c('Top Up Credits for Automated Background CLI Lookups:', Color::GRAY, Color::BOLD)}"
21646
- puts " • Purchase credits at: https://keywordseverywhere.com/credit-packages.html"
21660
+ puts " • Purchase credits at: https://keywordseverywhere.com/credit-packages.html?fpr=us25sg"
21647
21661
  puts " • Your key is already saved; it will work immediately upon top-up.\n"
21648
21662
  else
21649
21663
  puts "\nTry searching keywords:"
@@ -21668,7 +21682,7 @@ module GSC
21668
21682
  puts "\n" + Color.c("ℹ️ You have 0 paid REST API credits remaining.", Color::YELLOW)
21669
21683
  puts "• Free Web Alternative: Check volume on keywordseverywhere.com, click 'Copy', then run: #{Color.c('gsc import clip', Color::CYAN)}"
21670
21684
  puts "• Free Live Trends: #{Color.c('gsc trends "seo audit"', Color::BLUE)}"
21671
- puts "• Top up credits at: https://keywordseverywhere.com/credit-packages.html"
21685
+ puts "• Top up credits at: https://keywordseverywhere.com/credit-packages.html?fpr=us25sg"
21672
21686
  end
21673
21687
  else
21674
21688
  if acct[:error].to_s =~ /401|Unauthorized/i
@@ -21705,7 +21719,7 @@ module GSC
21705
21719
  else
21706
21720
  puts Color.c("\n❌ Keywords Everywhere API key not configured!\n", Color::RED, Color::BOLD)
21707
21721
  puts "#{Color::BOLD}RECOMMENDATIONS:#{Color::RESET}"
21708
- puts " 1. Connect a key: #{Color.c('gsc connect ke', Color::CYAN, Color::BOLD)} (Get key at https://keywordseverywhere.com/)"
21722
+ puts " 1. Connect a key: #{Color.c('gsc connect ke', Color::CYAN, Color::BOLD)} (Get key at https://keywordseverywhere.com/?fpr=us25sg)"
21709
21723
  puts " 2. Use 100% free Google Trends (No key needed!): #{Color.c("gsc trends \"#{target}\"", Color::BLUE, Color::BOLD)}"
21710
21724
  puts " 3. Use 500 free daily web lookups: Copy web table -> #{Color.c('gsc import clip', Color::GREEN, Color::BOLD)}\n"
21711
21725
  end
@@ -21752,7 +21766,7 @@ module GSC
21752
21766
  { action: 'import_clipboard', cmd: 'gsc import clip', desc: 'Use 500 free daily web lookups on keywordseverywhere.com and import via clipboard' },
21753
21767
  { action: 'google_trends', cmd: "gsc trends \"#{target}\"", desc: '100% Free real-time search interest & rising breakout queries' },
21754
21768
  { action: 'gsc_analytics', cmd: 'gsc top-queries', desc: 'Real impressions & clicks from your active Google Search Console domain' },
21755
- { action: 'topup', url: 'https://keywordseverywhere.com/credit-packages.html', desc: 'Purchase API credits to enable automated background CLI lookups' }
21769
+ { action: 'topup', url: 'https://keywordseverywhere.com/credit-packages.html?fpr=us25sg', desc: 'Purchase API credits to enable automated background CLI lookups' }
21756
21770
  ]
21757
21771
  })
21758
21772
  return
@@ -21775,13 +21789,13 @@ module GSC
21775
21789
  puts " • High-impression queries and Page 2 striking-distance keywords:"
21776
21790
  puts " #{Color.c('gsc top-queries', Color::MAGENTA, Color::BOLD)} or #{Color.c('gsc opportunities', Color::MAGENTA, Color::BOLD)}\n"
21777
21791
  puts " 4. #{Color.c('Top Up API Credits for Automated Background CLI Lookups', Color::GRAY, Color::BOLD)}"
21778
- puts " • Purchase credits at: #{Color.c('https://keywordseverywhere.com/credit-packages.html', Color::UNDERLINE)}"
21792
+ puts " • Purchase credits at: #{Color.c('https://keywordseverywhere.com/credit-packages.html?fpr=us25sg', Color::UNDERLINE)}"
21779
21793
  puts " • Your key is already saved; it will work immediately once credited.\n"
21780
21794
  elsif err_str =~ /401|Unauthorized/i
21781
21795
  puts Color.c("\n🔑 Invalid or Expired Keywords Everywhere API Key", Color::RED, Color::BOLD)
21782
21796
  puts "Your API key was rejected by api.keywordseverywhere.com (HTTP 401).\n\n"
21783
21797
  puts "#{Color::BOLD}RECOMMENDATIONS:#{Color::RESET}"
21784
- puts " • Check or generate your API key at: #{Color.c('https://keywordseverywhere.com/first-install-addon.html', Color::UNDERLINE)}"
21798
+ puts " • Check or generate your API key at: #{Color.c('https://keywordseverywhere.com/first-install-addon.html?fpr=us25sg', Color::UNDERLINE)}"
21785
21799
  puts " • Reconnect key: #{Color.c('gsc connect ke <KEY>', Color::CYAN, Color::BOLD)}"
21786
21800
  puts " • Or use free Google Trends without any key: #{Color.c("gsc trends \"#{target}\"", Color::BLUE)}"
21787
21801
  elsif err_str =~ /429|Rate Limit/i
@@ -22833,13 +22847,61 @@ module GSC
22833
22847
  puts "╚══════════════════════════════════════════════════════════════╝"
22834
22848
 
22835
22849
  m = data[:metrics] || {}
22836
- puts "\n#{Color::BOLD}📊 CORE WEB VITALS (Lab Metrics):#{Color::RESET}"
22850
+ puts "\n#{Color::BOLD}🔬 LIGHTHOUSE LAB METRICS (Simulated #{strategy.upcase}):#{Color::RESET}"
22837
22851
  puts " • LCP (Largest Contentful Paint) : #{Color.c(m[:lcp] || 'N/A', Color::BOLD)}"
22838
22852
  puts " • FCP (First Contentful Paint) : #{Color.c(m[:fcp] || 'N/A', Color::BOLD)}"
22839
22853
  puts " • CLS (Cumulative Layout Shift) : #{Color.c(m[:cls] || 'N/A', Color::BOLD)}"
22840
22854
  puts " • TBT (Total Blocking Time) : #{Color.c(m[:tbt] || 'N/A', Color::BOLD)}"
22841
22855
  puts " • Speed Index : #{Color.c(m[:speed_index] || 'N/A', Color::BOLD)}"
22842
22856
 
22857
+ fd = data[:field_data] || {}
22858
+ if fd.any?
22859
+ source_label = data[:field_source] == :origin ? "Domain-Wide Origin RUM" : "URL-Level RUM"
22860
+ overall = data[:overall_category] || "UNKNOWN"
22861
+ overall_col = overall == 'FAST' ? Color::GREEN : (overall == 'AVERAGE' ? Color::YELLOW : Color::RED)
22862
+ puts "\n#{Color::BOLD}🌐 CrUX FIELD DATA (28-Day Real User Monitoring - #{source_label}):#{Color::RESET}"
22863
+ puts " • Core Web Vitals Status : #{Color.c(overall, overall_col, Color::BOLD)}"
22864
+
22865
+ if fd['LARGEST_CONTENTFUL_PAINT_MS']
22866
+ val = "#{(fd['LARGEST_CONTENTFUL_PAINT_MS'][:percentile].to_f / 1000).round(2)} s"
22867
+ cat = fd['LARGEST_CONTENTFUL_PAINT_MS'][:category]
22868
+ col = cat == 'FAST' ? Color::GREEN : (cat == 'AVERAGE' ? Color::YELLOW : Color::RED)
22869
+ puts " • LCP (75th Percentile) : #{Color.c(val, Color::BOLD)} [#{Color.c(cat, col)}]"
22870
+ end
22871
+
22872
+ if fd['INTERACTION_TO_NEXT_PAINT']
22873
+ val = "#{fd['INTERACTION_TO_NEXT_PAINT'][:percentile]} ms"
22874
+ cat = fd['INTERACTION_TO_NEXT_PAINT'][:category]
22875
+ col = cat == 'FAST' ? Color::GREEN : (cat == 'AVERAGE' ? Color::YELLOW : Color::RED)
22876
+ puts " • INP (75th Percentile) : #{Color.c(val, Color::BOLD)} [#{Color.c(cat, col)}]"
22877
+ end
22878
+
22879
+ if fd['CUMULATIVE_LAYOUT_SHIFT_SCORE']
22880
+ val = (fd['CUMULATIVE_LAYOUT_SHIFT_SCORE'][:percentile].to_f / 100).round(3).to_s
22881
+ cat = fd['CUMULATIVE_LAYOUT_SHIFT_SCORE'][:category]
22882
+ col = cat == 'FAST' ? Color::GREEN : (cat == 'AVERAGE' ? Color::YELLOW : Color::RED)
22883
+ puts " • CLS (75th Percentile) : #{Color.c(val, Color::BOLD)} [#{Color.c(cat, col)}]"
22884
+ end
22885
+
22886
+ if fd['FIRST_CONTENTFUL_PAINT_MS']
22887
+ val = "#{(fd['FIRST_CONTENTFUL_PAINT_MS'][:percentile].to_f / 1000).round(2)} s"
22888
+ cat = fd['FIRST_CONTENTFUL_PAINT_MS'][:category]
22889
+ col = cat == 'FAST' ? Color::GREEN : (cat == 'AVERAGE' ? Color::YELLOW : Color::RED)
22890
+ puts " • FCP (75th Percentile) : #{Color.c(val, Color::BOLD)} [#{Color.c(cat, col)}]"
22891
+ end
22892
+
22893
+ if fd['EXPERIMENTAL_TIME_TO_FIRST_BYTE']
22894
+ val = "#{fd['EXPERIMENTAL_TIME_TO_FIRST_BYTE'][:percentile]} ms"
22895
+ cat = fd['EXPERIMENTAL_TIME_TO_FIRST_BYTE'][:category]
22896
+ col = cat == 'FAST' ? Color::GREEN : (cat == 'AVERAGE' ? Color::YELLOW : Color::RED)
22897
+ puts " • TTFB (75th Percentile) : #{Color.c(val, Color::BOLD)} [#{Color.c(cat, col)}]"
22898
+ end
22899
+ else
22900
+ puts "\n#{Color::BOLD}🌐 CrUX FIELD DATA (28-Day Real User Monitoring):#{Color::RESET}"
22901
+ puts " #{Color.c('ℹ️ Insufficient real-user Chrome visits over the rolling 28-day window for Google CrUX telemetry.', Color::GRAY)}"
22902
+ puts " #{Color.c(' (Google requires a minimum traffic threshold before publishing real-user CrUX field data).', Color::GRAY)}"
22903
+ end
22904
+
22843
22905
  opps = data[:opportunities] || []
22844
22906
  unless opps.empty?
22845
22907
  puts "\n#{Color::BOLD}💡 TOP SPEED OPPORTUNITIES:#{Color::RESET}"
@@ -29944,13 +30006,61 @@ module GSC
29944
30006
  puts "╚══════════════════════════════════════════════════════════════╝"
29945
30007
 
29946
30008
  m = data[:metrics] || {}
29947
- puts "\n#{Color::BOLD}📊 CORE WEB VITALS (Lab Metrics):#{Color::RESET}"
30009
+ puts "\n#{Color::BOLD}🔬 LIGHTHOUSE LAB METRICS (Simulated #{strategy.upcase}):#{Color::RESET}"
29948
30010
  puts " • LCP (Largest Contentful Paint) : #{Color.c(m[:lcp] || 'N/A', Color::BOLD)}"
29949
30011
  puts " • FCP (First Contentful Paint) : #{Color.c(m[:fcp] || 'N/A', Color::BOLD)}"
29950
30012
  puts " • CLS (Cumulative Layout Shift) : #{Color.c(m[:cls] || 'N/A', Color::BOLD)}"
29951
30013
  puts " • TBT (Total Blocking Time) : #{Color.c(m[:tbt] || 'N/A', Color::BOLD)}"
29952
30014
  puts " • Speed Index : #{Color.c(m[:speed_index] || 'N/A', Color::BOLD)}"
29953
30015
 
30016
+ fd = data[:field_data] || {}
30017
+ if fd.any?
30018
+ source_label = data[:field_source] == :origin ? "Domain-Wide Origin RUM" : "URL-Level RUM"
30019
+ overall = data[:overall_category] || "UNKNOWN"
30020
+ overall_col = overall == 'FAST' ? Color::GREEN : (overall == 'AVERAGE' ? Color::YELLOW : Color::RED)
30021
+ puts "\n#{Color::BOLD}🌐 CrUX FIELD DATA (28-Day Real User Monitoring - #{source_label}):#{Color::RESET}"
30022
+ puts " • Core Web Vitals Status : #{Color.c(overall, overall_col, Color::BOLD)}"
30023
+
30024
+ if fd['LARGEST_CONTENTFUL_PAINT_MS']
30025
+ val = "#{(fd['LARGEST_CONTENTFUL_PAINT_MS'][:percentile].to_f / 1000).round(2)} s"
30026
+ cat = fd['LARGEST_CONTENTFUL_PAINT_MS'][:category]
30027
+ col = cat == 'FAST' ? Color::GREEN : (cat == 'AVERAGE' ? Color::YELLOW : Color::RED)
30028
+ puts " • LCP (75th Percentile) : #{Color.c(val, Color::BOLD)} [#{Color.c(cat, col)}]"
30029
+ end
30030
+
30031
+ if fd['INTERACTION_TO_NEXT_PAINT']
30032
+ val = "#{fd['INTERACTION_TO_NEXT_PAINT'][:percentile]} ms"
30033
+ cat = fd['INTERACTION_TO_NEXT_PAINT'][:category]
30034
+ col = cat == 'FAST' ? Color::GREEN : (cat == 'AVERAGE' ? Color::YELLOW : Color::RED)
30035
+ puts " • INP (75th Percentile) : #{Color.c(val, Color::BOLD)} [#{Color.c(cat, col)}]"
30036
+ end
30037
+
30038
+ if fd['CUMULATIVE_LAYOUT_SHIFT_SCORE']
30039
+ val = (fd['CUMULATIVE_LAYOUT_SHIFT_SCORE'][:percentile].to_f / 100).round(3).to_s
30040
+ cat = fd['CUMULATIVE_LAYOUT_SHIFT_SCORE'][:category]
30041
+ col = cat == 'FAST' ? Color::GREEN : (cat == 'AVERAGE' ? Color::YELLOW : Color::RED)
30042
+ puts " • CLS (75th Percentile) : #{Color.c(val, Color::BOLD)} [#{Color.c(cat, col)}]"
30043
+ end
30044
+
30045
+ if fd['FIRST_CONTENTFUL_PAINT_MS']
30046
+ val = "#{(fd['FIRST_CONTENTFUL_PAINT_MS'][:percentile].to_f / 1000).round(2)} s"
30047
+ cat = fd['FIRST_CONTENTFUL_PAINT_MS'][:category]
30048
+ col = cat == 'FAST' ? Color::GREEN : (cat == 'AVERAGE' ? Color::YELLOW : Color::RED)
30049
+ puts " • FCP (75th Percentile) : #{Color.c(val, Color::BOLD)} [#{Color.c(cat, col)}]"
30050
+ end
30051
+
30052
+ if fd['EXPERIMENTAL_TIME_TO_FIRST_BYTE']
30053
+ val = "#{fd['EXPERIMENTAL_TIME_TO_FIRST_BYTE'][:percentile]} ms"
30054
+ cat = fd['EXPERIMENTAL_TIME_TO_FIRST_BYTE'][:category]
30055
+ col = cat == 'FAST' ? Color::GREEN : (cat == 'AVERAGE' ? Color::YELLOW : Color::RED)
30056
+ puts " • TTFB (75th Percentile) : #{Color.c(val, Color::BOLD)} [#{Color.c(cat, col)}]"
30057
+ end
30058
+ else
30059
+ puts "\n#{Color::BOLD}🌐 CrUX FIELD DATA (28-Day Real User Monitoring):#{Color::RESET}"
30060
+ puts " #{Color.c('ℹ️ Insufficient real-user Chrome visits over the rolling 28-day window for Google CrUX telemetry.', Color::GRAY)}"
30061
+ puts " #{Color.c(' (Google requires a minimum traffic threshold before publishing real-user CrUX field data).', Color::GRAY)}"
30062
+ end
30063
+
29954
30064
  opps = data[:opportunities] || []
29955
30065
  unless opps.empty?
29956
30066
  puts "\n#{Color::BOLD}💡 TOP SPEED OPPORTUNITIES:#{Color::RESET}"
data/lib/gsc/cli/audit.rb CHANGED
@@ -160,13 +160,61 @@ module GSC
160
160
  puts "╚══════════════════════════════════════════════════════════════╝"
161
161
 
162
162
  m = data[:metrics] || {}
163
- puts "\n#{Color::BOLD}📊 CORE WEB VITALS (Lab Metrics):#{Color::RESET}"
163
+ puts "\n#{Color::BOLD}🔬 LIGHTHOUSE LAB METRICS (Simulated #{strategy.upcase}):#{Color::RESET}"
164
164
  puts " • LCP (Largest Contentful Paint) : #{Color.c(m[:lcp] || 'N/A', Color::BOLD)}"
165
165
  puts " • FCP (First Contentful Paint) : #{Color.c(m[:fcp] || 'N/A', Color::BOLD)}"
166
166
  puts " • CLS (Cumulative Layout Shift) : #{Color.c(m[:cls] || 'N/A', Color::BOLD)}"
167
167
  puts " • TBT (Total Blocking Time) : #{Color.c(m[:tbt] || 'N/A', Color::BOLD)}"
168
168
  puts " • Speed Index : #{Color.c(m[:speed_index] || 'N/A', Color::BOLD)}"
169
169
 
170
+ fd = data[:field_data] || {}
171
+ if fd.any?
172
+ source_label = data[:field_source] == :origin ? "Domain-Wide Origin RUM" : "URL-Level RUM"
173
+ overall = data[:overall_category] || "UNKNOWN"
174
+ overall_col = overall == 'FAST' ? Color::GREEN : (overall == 'AVERAGE' ? Color::YELLOW : Color::RED)
175
+ puts "\n#{Color::BOLD}🌐 CrUX FIELD DATA (28-Day Real User Monitoring - #{source_label}):#{Color::RESET}"
176
+ puts " • Core Web Vitals Status : #{Color.c(overall, overall_col, Color::BOLD)}"
177
+
178
+ if fd['LARGEST_CONTENTFUL_PAINT_MS']
179
+ val = "#{(fd['LARGEST_CONTENTFUL_PAINT_MS'][:percentile].to_f / 1000).round(2)} s"
180
+ cat = fd['LARGEST_CONTENTFUL_PAINT_MS'][:category]
181
+ col = cat == 'FAST' ? Color::GREEN : (cat == 'AVERAGE' ? Color::YELLOW : Color::RED)
182
+ puts " • LCP (75th Percentile) : #{Color.c(val, Color::BOLD)} [#{Color.c(cat, col)}]"
183
+ end
184
+
185
+ if fd['INTERACTION_TO_NEXT_PAINT']
186
+ val = "#{fd['INTERACTION_TO_NEXT_PAINT'][:percentile]} ms"
187
+ cat = fd['INTERACTION_TO_NEXT_PAINT'][:category]
188
+ col = cat == 'FAST' ? Color::GREEN : (cat == 'AVERAGE' ? Color::YELLOW : Color::RED)
189
+ puts " • INP (75th Percentile) : #{Color.c(val, Color::BOLD)} [#{Color.c(cat, col)}]"
190
+ end
191
+
192
+ if fd['CUMULATIVE_LAYOUT_SHIFT_SCORE']
193
+ val = (fd['CUMULATIVE_LAYOUT_SHIFT_SCORE'][:percentile].to_f / 100).round(3).to_s
194
+ cat = fd['CUMULATIVE_LAYOUT_SHIFT_SCORE'][:category]
195
+ col = cat == 'FAST' ? Color::GREEN : (cat == 'AVERAGE' ? Color::YELLOW : Color::RED)
196
+ puts " • CLS (75th Percentile) : #{Color.c(val, Color::BOLD)} [#{Color.c(cat, col)}]"
197
+ end
198
+
199
+ if fd['FIRST_CONTENTFUL_PAINT_MS']
200
+ val = "#{(fd['FIRST_CONTENTFUL_PAINT_MS'][:percentile].to_f / 1000).round(2)} s"
201
+ cat = fd['FIRST_CONTENTFUL_PAINT_MS'][:category]
202
+ col = cat == 'FAST' ? Color::GREEN : (cat == 'AVERAGE' ? Color::YELLOW : Color::RED)
203
+ puts " • FCP (75th Percentile) : #{Color.c(val, Color::BOLD)} [#{Color.c(cat, col)}]"
204
+ end
205
+
206
+ if fd['EXPERIMENTAL_TIME_TO_FIRST_BYTE']
207
+ val = "#{fd['EXPERIMENTAL_TIME_TO_FIRST_BYTE'][:percentile]} ms"
208
+ cat = fd['EXPERIMENTAL_TIME_TO_FIRST_BYTE'][:category]
209
+ col = cat == 'FAST' ? Color::GREEN : (cat == 'AVERAGE' ? Color::YELLOW : Color::RED)
210
+ puts " • TTFB (75th Percentile) : #{Color.c(val, Color::BOLD)} [#{Color.c(cat, col)}]"
211
+ end
212
+ else
213
+ puts "\n#{Color::BOLD}🌐 CrUX FIELD DATA (28-Day Real User Monitoring):#{Color::RESET}"
214
+ puts " #{Color.c('ℹ️ Insufficient real-user Chrome visits over the rolling 28-day window for Google CrUX telemetry.', Color::GRAY)}"
215
+ puts " #{Color.c(' (Google requires a minimum traffic threshold before publishing real-user CrUX field data).', Color::GRAY)}"
216
+ end
217
+
170
218
  opps = data[:opportunities] || []
171
219
  unless opps.empty?
172
220
  puts "\n#{Color::BOLD}💡 TOP SPEED OPPORTUNITIES:#{Color::RESET}"