gsc-cli 2.0.2 → 2.2.0

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.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/AUTH.md +205 -0
  3. data/FUNDING.md +120 -0
  4. data/README.md +463 -299
  5. data/bin/gsc +29158 -4921
  6. data/dist/gsc +29158 -4921
  7. data/lib/gsc/aio_hunter.rb +343 -0
  8. data/lib/gsc/answer_synthesizer.rb +157 -0
  9. data/lib/gsc/api.rb +53 -1
  10. data/lib/gsc/auth.rb +26 -0
  11. data/lib/gsc/backlinks_manager.rb +96 -0
  12. data/lib/gsc/brand_segmenter.rb +140 -0
  13. data/lib/gsc/cache_manager.rb +806 -0
  14. data/lib/gsc/cannibalization_analyzer.rb +141 -0
  15. data/lib/gsc/canonical_chains.rb +367 -0
  16. data/lib/gsc/citation_simulator.rb +339 -0
  17. data/lib/gsc/cli/aio_hunter.rb +154 -0
  18. data/lib/gsc/cli/analytics.rb +788 -0
  19. data/lib/gsc/cli/audit.rb +1976 -0
  20. data/lib/gsc/cli/base.rb +384 -0
  21. data/lib/gsc/cli/cache.rb +266 -0
  22. data/lib/gsc/cli/canonical.rb +223 -0
  23. data/lib/gsc/cli/citation_simulator.rb +152 -0
  24. data/lib/gsc/cli/dashboard.rb +354 -0
  25. data/lib/gsc/cli/doctor.rb +129 -0
  26. data/lib/gsc/cli/eeat.rb +125 -0
  27. data/lib/gsc/cli/ga4.rb +852 -0
  28. data/lib/gsc/cli/growth.rb +650 -0
  29. data/lib/gsc/cli/hreflang.rb +164 -0
  30. data/lib/gsc/cli/image_seo.rb +162 -0
  31. data/lib/gsc/cli/indexing.rb +458 -0
  32. data/lib/gsc/cli/intent_shift.rb +125 -0
  33. data/lib/gsc/cli/keyword_value.rb +134 -0
  34. data/lib/gsc/cli/keywords.rb +795 -0
  35. data/lib/gsc/cli/landing_roi.rb +308 -0
  36. data/lib/gsc/cli/low_ctr.rb +213 -0
  37. data/lib/gsc/cli/mobile_parity.rb +150 -0
  38. data/lib/gsc/cli/report.rb +100 -0
  39. data/lib/gsc/cli/rich_results.rb +172 -0
  40. data/lib/gsc/cli/schema_generate.rb +149 -0
  41. data/lib/gsc/cli/seasonal.rb +232 -0
  42. data/lib/gsc/cli/security.rb +153 -0
  43. data/lib/gsc/cli/setup.rb +1291 -0
  44. data/lib/gsc/cli/sitemap_tree.rb +143 -0
  45. data/lib/gsc/cli/skill_pack.rb +62 -0
  46. data/lib/gsc/cli/soft_404.rb +199 -0
  47. data/lib/gsc/cli/sparkline.rb +227 -0
  48. data/lib/gsc/cli/watchdog.rb +150 -0
  49. data/lib/gsc/cli/zombie_purger.rb +208 -0
  50. data/lib/gsc/cli.rb +706 -5111
  51. data/lib/gsc/cli_advanced.rb +1513 -0
  52. data/lib/gsc/client.rb +17 -2
  53. data/lib/gsc/color.rb +16 -1
  54. data/lib/gsc/command_registry.rb +47 -9
  55. data/lib/gsc/config.rb +11 -2
  56. data/lib/gsc/content_gap.rb +112 -0
  57. data/lib/gsc/ctr_curve.rb +115 -0
  58. data/lib/gsc/decay_predictor.rb +322 -0
  59. data/lib/gsc/doctor.rb +434 -0
  60. data/lib/gsc/eeat_auditor.rb +428 -0
  61. data/lib/gsc/entity_auditor.rb +229 -0
  62. data/lib/gsc/firewall_scanner.rb +733 -0
  63. data/lib/gsc/geo_auditor.rb +368 -0
  64. data/lib/gsc/google_suggest.rb +109 -0
  65. data/lib/gsc/google_trends.rb +8 -1
  66. data/lib/gsc/heading_validator.rb +283 -0
  67. data/lib/gsc/hreflang_validator.rb +412 -0
  68. data/lib/gsc/image_seo.rb +286 -0
  69. data/lib/gsc/indexing_queue.rb +179 -0
  70. data/lib/gsc/indexnow.rb +93 -0
  71. data/lib/gsc/intent_shift.rb +188 -0
  72. data/lib/gsc/internal_links.rb +249 -0
  73. data/lib/gsc/keyword_value.rb +191 -0
  74. data/lib/gsc/landing_roi.rb +195 -0
  75. data/lib/gsc/llms_generator.rb +425 -0
  76. data/lib/gsc/low_ctr_rewriter.rb +408 -0
  77. data/lib/gsc/mobile_parity.rb +222 -0
  78. data/lib/gsc/network_tracer.rb +93 -0
  79. data/lib/gsc/open_page_rank.rb +72 -0
  80. data/lib/gsc/page_analyzer.rb +47 -7
  81. data/lib/gsc/page_comparator.rb +108 -0
  82. data/lib/gsc/page_speed.rb +110 -0
  83. data/lib/gsc/prompts.rb +38 -29
  84. data/lib/gsc/questions_harvester.rb +178 -0
  85. data/lib/gsc/report_generator.rb +461 -0
  86. data/lib/gsc/rich_results.rb +388 -0
  87. data/lib/gsc/robots_checker.rb +114 -0
  88. data/lib/gsc/schema_generator.rb +788 -0
  89. data/lib/gsc/schema_validator.rb +120 -0
  90. data/lib/gsc/seasonal_predictor.rb +381 -0
  91. data/lib/gsc/security_scanner.rb +496 -0
  92. data/lib/gsc/serp_feature_detector.rb +359 -0
  93. data/lib/gsc/serp_preview.rb +152 -0
  94. data/lib/gsc/site_crawler.rb +113 -21
  95. data/lib/gsc/sitemap_loader.rb +15 -4
  96. data/lib/gsc/sitemap_tree.rb +301 -0
  97. data/lib/gsc/skill_pack.rb +195 -0
  98. data/lib/gsc/soft_404_analyzer.rb +385 -0
  99. data/lib/gsc/sparkline.rb +171 -0
  100. data/lib/gsc/speed_correlator.rb +416 -0
  101. data/lib/gsc/striking_playbook.rb +190 -0
  102. data/lib/gsc/title_optimizer.rb +420 -0
  103. data/lib/gsc/vault.rb +260 -0
  104. data/lib/gsc/version.rb +1 -1
  105. data/lib/gsc/watchdog.rb +235 -0
  106. data/lib/gsc/zombie_purger.rb +366 -0
  107. data/lib/gsc.rb +144 -0
  108. metadata +91 -2
@@ -0,0 +1,354 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'shellwords'
4
+
5
+ module GSC
6
+ class CLI
7
+ module Dashboard
8
+ module_function
9
+
10
+ def run(options = {})
11
+ puts Base::BANNER
12
+
13
+ domains = Base.fetch_available_domains
14
+ active = Config.default_domain
15
+ if (active.nil? || active.empty?) && domains.any?
16
+ active = Config.set_default_domain(domains.first)
17
+ end
18
+
19
+ if active
20
+ puts "Active Target Domain: #{Color.c(active, Color::GREEN, Color::BOLD)}"
21
+ else
22
+ puts Color.c("No active domain configured. Run 'connect' to configure.", Color::YELLOW)
23
+ end
24
+ puts
25
+
26
+ if domains.any?
27
+ puts "#{Color::BOLD}🌐 Verified Domains (Type number 1-#{domains.size} to switch):#{Color::RESET}"
28
+ domains.each_with_index do |dom, idx|
29
+ marker = (dom == active) ? " #{Color.c('👈 [ACTIVE]', Color::GREEN, Color::BOLD)}" : ""
30
+ ga4_id = Config.ga4_property_id(dom)
31
+ ga4_str = ga4_id ? Color.c("[GA4: #{ga4_id}]", Color::MAGENTA) : ""
32
+ puts " #{Color.c("[#{idx + 1}]", Color::CYAN, Color::BOLD)} #{dom.ljust(26)} #{ga4_str}#{marker}"
33
+ end
34
+ puts
35
+ end
36
+
37
+ print_main_menu_shortcuts
38
+
39
+ current_group = nil
40
+
41
+ loop do
42
+ current_dom = Config.default_domain || 'no-domain'
43
+ prompt_label = current_group ? "gsc [#{current_dom}] (#{current_group})> " : "gsc [#{current_dom}]> "
44
+ print "#{Color::BOLD}#{Color::CYAN}#{prompt_label}#{Color::RESET}"
45
+
46
+ raw = $stdin.gets
47
+ break if raw.nil?
48
+
49
+ input = raw.strip
50
+ next if input.empty?
51
+
52
+ # Universal quit
53
+ break if %w[q quit exit bye :q q!].include?(input.downcase)
54
+
55
+ # Return to main menu from sub-group
56
+ if %w[b back .. main].include?(input.downcase)
57
+ if current_group
58
+ current_group = nil
59
+ puts Color.c("↩ Back to main menu.\n", Color::DIM)
60
+ print_main_menu_shortcuts
61
+ end
62
+ next
63
+ end
64
+
65
+ # Show help/menu
66
+ if %w[? help menu commands].include?(input.downcase) || input == ' '
67
+ case current_group
68
+ when :keywords then print_keywords_menu
69
+ when :analytics then print_analytics_menu
70
+ when :ga4 then print_ga4_menu
71
+ when :indexing then print_indexing_menu
72
+ when :setup then print_setup_menu
73
+ else
74
+ print_main_menu_shortcuts
75
+ print_commands_help
76
+ end
77
+ next
78
+ end
79
+
80
+ # Number: domain switch
81
+ if input =~ /^\d+$/
82
+ CLI.run([input, '--in-dashboard'])
83
+ puts
84
+ next
85
+ end
86
+
87
+ # Group switches from main menu
88
+ if current_group.nil?
89
+ case input.downcase
90
+ when 'k', 'keywords'
91
+ current_group = :keywords
92
+ print_keywords_menu
93
+ next
94
+ when 'a', 'analytics'
95
+ current_group = :analytics
96
+ print_analytics_menu
97
+ next
98
+ when 'g', 'ga4', 'traffic'
99
+ current_group = :ga4
100
+ print_ga4_menu
101
+ next
102
+ when 'i', 'indexing', 'sitemaps'
103
+ current_group = :indexing
104
+ print_indexing_menu
105
+ next
106
+ when 's', 'setup', 'config'
107
+ current_group = :setup
108
+ print_setup_menu
109
+ next
110
+ end
111
+ end
112
+
113
+ # Built-in clear
114
+ if %w[clear cls].include?(input.downcase)
115
+ print "\e[H\e[2J"
116
+ next
117
+ end
118
+
119
+ # Domains list
120
+ if %w[domains list].include?(input.downcase)
121
+ doms = Base.fetch_available_domains
122
+ puts "\n🌐 Verified Domains:"
123
+ doms.each_with_index do |d, idx|
124
+ marker = (d == Config.default_domain) ? " #{Color.c('👈 [ACTIVE]', Color::GREEN, Color::BOLD)}" : ""
125
+ puts " #{Color.c("[#{idx + 1}]", Color::CYAN, Color::BOLD)} #{d}#{marker}"
126
+ end
127
+ puts
128
+ next
129
+ end
130
+
131
+ cmd_args = Shellwords.split(input) rescue input.split
132
+ first = cmd_args[0].downcase
133
+
134
+ # Sub-menu and global shortcut resolution
135
+ resolved_cmd = case first
136
+ when 'p', 'perf' then 'performance'
137
+ when 'tq', 'queries', 'query' then 'top-queries'
138
+ when 'b', 'brand', 'brand-split' then 'brand'
139
+ when 'ctr', 'curve', 'ctr-curve', 'gain' then 'ctr-curve'
140
+ when 'tp', 'pages' then 'top-pages'
141
+ when 'o', 'opp' then 'opportunities'
142
+ when 'u', 'under', 'low' then 'underperformers'
143
+ when 'c', 'conflicts', 'cann' then 'cannibalization'
144
+ when 'd', 'decay' then 'decay'
145
+ when 'aud', 'audit' then 'audit'
146
+ when 'tr', 'trends' then 'trends'
147
+ when 'kp', 'planner' then 'planner'
148
+ when 'ke' then 'ke'
149
+ when 'imp', 'import', 'pi' then 'import'
150
+ when 'cr', 'credits', 'kec' then 'ke-credits'
151
+ when 'sv', 'saved', 'research' then 'saved'
152
+ when 'chk', 'check' then 'check'
153
+ when 'pb', 'prompts', 'prompt', 'playbooks', 'playbook' then 'prompts'
154
+ when 'r', 'live' then 'realtime'
155
+ when 'ga', 'bounce' then 'ga4'
156
+ when 'co', 'corr' then 'correlation'
157
+ when 'ad', 'ads' then 'ads'
158
+ when 'ch', 'channels' then 'channels'
159
+ when 'ins', 'inspect' then 'inspect'
160
+ when 'idx', 'index' then 'index'
161
+ when 'ib', 'queue', 'index-batch', 'batch-index' then 'index-batch'
162
+ when 'rm', 'remove' then 'remove'
163
+ when 'map', 'sitemaps' then 'sitemaps-list'
164
+ when 'sub', 'sitemaps-submit' then 'sitemaps-submit'
165
+ when 'bi', 'inspect-sitemap' then 'inspect-sitemap'
166
+ when 'z', 'zombies' then 'zombies'
167
+ when 'cg', 'connect-ga4' then 'connect-ga4'
168
+ when 'w', 'where' then 'where'
169
+ when 'v', 'version' then 'version'
170
+ else first
171
+ end
172
+
173
+ if first == 'ck'
174
+ cmd_args = ['connect', 'ke'] + cmd_args[1..]
175
+ else
176
+ cmd_args[0] = resolved_cmd
177
+ end
178
+
179
+ begin
180
+ CLI.run(cmd_args + ['--in-dashboard'])
181
+ rescue Interrupt
182
+ puts "\n(Action cancelled)"
183
+ rescue StandardError => e
184
+ puts Color.c("❌ Error: #{e.message}", Color::RED)
185
+ end
186
+
187
+ puts
188
+ end
189
+
190
+ puts Color.c("\n👋 Exited interactive mode.", Color::DIM)
191
+ end
192
+
193
+ def print_main_menu_shortcuts
194
+ puts "#{Color::BOLD}📂 Command Groups (Type letter to explore sub-shortcuts):#{Color::RESET}"
195
+ puts " #{Color.c('[k]', Color::CYAN, Color::BOLD)} Keywords & Trends #{Color.c('[a]', Color::YELLOW, Color::BOLD)} Search Analytics #{Color.c('[g]', Color::MAGENTA, Color::BOLD)} GA4 & Realtime"
196
+ puts " #{Color.c('[i]', Color::GREEN, Color::BOLD)} Indexing & Sitemaps #{Color.c('[s]', Color::BLUE, Color::BOLD)} Setup & Config #{Color.c('[?]', Color::WHITE, Color::BOLD)} All Commands"
197
+ puts
198
+ puts "#{Color::BOLD}⚡ Quick Shortcuts (Run directly from any prompt):#{Color::RESET}"
199
+ puts " #{Color.c('[pb]', Color::MAGENTA, Color::BOLD)} AI Playbooks #{Color.c('[p]', Color::YELLOW, Color::BOLD)} Performance #{Color.c('[tq]', Color::YELLOW, Color::BOLD)} Top Queries #{Color.c('[tp]', Color::YELLOW, Color::BOLD)} Top Pages"
200
+ puts " #{Color.c('[o]', Color::YELLOW, Color::BOLD)} Opportunities #{Color.c('[u]', Color::YELLOW, Color::BOLD)} Underperf #{Color.c('[aud]', Color::CYAN, Color::BOLD)} Full Audit #{Color.c('[r]', Color::GREEN, Color::BOLD)} GA4 Realtime"
201
+ puts " #{Color.c('[tr]', Color::CYAN, Color::BOLD)} Trends <query> #{Color.c('[kp]', Color::CYAN, Color::BOLD)} Planner <kw> #{Color.c('[ke]', Color::YELLOW, Color::BOLD)} Keywords <kw> #{Color.c('[imp]', Color::CYAN, Color::BOLD)} Import File"
202
+ puts " #{Color.c('[q]', Color::GRAY, Color::BOLD)} Quit (or exit)"
203
+ puts
204
+ end
205
+
206
+ def print_keywords_menu
207
+ puts "\n#{Color::BOLD}📈 GOOGLE TRENDS & KEYWORD INTELLIGENCE:#{Color::RESET}"
208
+ puts " #{Color.c('[tr] trends <query>', Color::CYAN, Color::BOLD)} Real-time Google Trends 5-year/12-month demand & velocity"
209
+ puts " #{Color.c('[kp] planner <seed>', Color::CYAN, Color::BOLD)} Free autocomplete keyword expander & intent classifier"
210
+ puts " #{Color.c('[ke] ke <query>', Color::YELLOW, Color::BOLD)} Keywords Everywhere exact monthly volume, CPC & competition"
211
+ puts " #{Color.c('[imp] import <file>', Color::CYAN, Color::BOLD)} Import Keywords Everywhere TSV/CSV or Google Ads export"
212
+ puts " #{Color.c('[sv] saved', Color::GREEN, Color::BOLD)} List saved domain keyword research snapshots"
213
+ puts " #{Color.c('[chk] check [id]', Color::GREEN, Color::BOLD)} Re-check saved keywords against live Search Console rankings"
214
+ puts " #{Color.c('[cr] ke-credits', Color::YELLOW, Color::BOLD)} Check remaining Keywords Everywhere account balance"
215
+ puts " #{Color.c('[ck] connect ke [key]', Color::GREEN, Color::BOLD)} Connect Keywords Everywhere API key"
216
+ puts " #{Color.c('[b] back', Color::DIM, Color::BOLD)} Return to main menu"
217
+ puts " #{Color.c('[q] quit', Color::GRAY, Color::BOLD)} Exit gsc"
218
+ puts
219
+ end
220
+
221
+ def print_analytics_menu
222
+ puts "\n#{Color::BOLD}📊 SEARCH ANALYTICS & MONITORING:#{Color::RESET}"
223
+ puts " #{Color.c('[p] performance', Color::YELLOW, Color::BOLD)} Executive dashboard: Totals, Devices, Countries, Snippets, Queries"
224
+ puts " #{Color.c('[tq] top-queries', Color::YELLOW, Color::BOLD)} Top search queries, rankings, impressions, CTR, and position"
225
+ puts " #{Color.c('[tp] top-pages', Color::YELLOW, Color::BOLD)} Top landing pages driving clicks and impressions"
226
+ puts " #{Color.c('[o] opportunities', Color::YELLOW, Color::BOLD)} Striking-distance Page 2 queries (Pos 7–20) to push to Top 3"
227
+ puts " #{Color.c('[u] underperformers', Color::YELLOW, Color::BOLD)} Top 10 ranking queries with low CTR (title & meta win hooks)"
228
+ puts " #{Color.c('[c] cannibalization', Color::RED, Color::BOLD)} Detect multiple internal URLs competing for the same search query"
229
+ puts " #{Color.c('[d] decay', Color::RED, Color::BOLD)} 28-day period-over-period trend analysis (decaying vs surging)"
230
+ puts " #{Color.c('[aud] audit', Color::CYAN, Color::BOLD)} Automated 4-step comprehensive SEO & indexing health audit"
231
+ puts " #{Color.c('[b] back', Color::DIM, Color::BOLD)} Return to main menu"
232
+ puts " #{Color.c('[q] quit', Color::GRAY, Color::BOLD)} Exit gsc"
233
+ puts
234
+ end
235
+
236
+ def print_ga4_menu
237
+ puts "\n#{Color::BOLD}📈 GOOGLE ANALYTICS 4 (GA4) & POST-CLICK BEHAVIOR:#{Color::RESET}"
238
+ puts " #{Color.c('[r] realtime [--watch]', Color::GREEN, Color::BOLD)} Live active visitors, pages, and countries right now"
239
+ puts " #{Color.c('[ga] ga4 [--organic]', Color::MAGENTA, Color::BOLD)} Landing page bounce rates, engagement rates, sessions, duration"
240
+ puts " #{Color.c('[co] correlation', Color::MAGENTA, Color::BOLD)} Merge GSC keyword rankings with GA4 bounce rates per landing page"
241
+ puts " #{Color.c('[ad] ads', Color::MAGENTA, Color::BOLD)} Google Ads campaign performance (Clicks, Cost, CPC, CPA, Conv)"
242
+ puts " #{Color.c('[ch] channels', Color::MAGENTA, Color::BOLD)} Omnichannel acquisition breakdown (Organic, Paid, Direct, Referral)"
243
+ puts " #{Color.c('[cg] connect-ga4', Color::GREEN, Color::BOLD)} Interactive GA4 linking wizard"
244
+ puts " #{Color.c('[b] back', Color::DIM, Color::BOLD)} Return to main menu"
245
+ puts " #{Color.c('[q] quit', Color::GRAY, Color::BOLD)} Exit gsc"
246
+ puts
247
+ end
248
+
249
+ def print_indexing_menu
250
+ puts "\n#{Color::BOLD}🔍 GOOGLEBOT INDEXING & SITEMAPS:#{Color::RESET}"
251
+ puts " #{Color.c('[ins] inspect <url>', Color::CYAN, Color::BOLD)} Live Search Console URL inspection (verdict, coverage, canonical)"
252
+ puts " #{Color.c('[idx] index <url>', Color::GREEN, Color::BOLD)} Notify Googlebot to crawl/index a URL immediately (URL_UPDATED)"
253
+ puts " #{Color.c('[rm] remove <url>', Color::RED, Color::BOLD)} Notify Googlebot a URL has been deleted (URL_DELETED)"
254
+ puts " #{Color.c('[map] sitemaps', Color::MAGENTA, Color::BOLD)} List submitted sitemaps in Search Console and crawl status"
255
+ puts " #{Color.c('[sub] sitemaps-submit <url>', Color::MAGENTA, Color::BOLD)} Submit new XML sitemap to Google Search Console"
256
+ puts " #{Color.c('[bi] inspect-sitemap <file>', Color::CYAN, Color::BOLD)} Bulk inspect all sitemap URLs in XML file"
257
+ puts " #{Color.c('[z] zombies <sitemap>', Color::RED, Color::BOLD)} Detect 90-day zero-impression pages wasting crawl budget"
258
+ puts " #{Color.c('[b] back', Color::DIM, Color::BOLD)} Return to main menu"
259
+ puts " #{Color.c('[q] quit', Color::GRAY, Color::BOLD)} Exit gsc"
260
+ puts
261
+ end
262
+
263
+ def print_setup_menu
264
+ puts "\n#{Color::BOLD}⚙️ SETUP, DOMAINS & CONFIGURATION:#{Color::RESET}"
265
+ puts " #{Color.c('[1-N] <num>', Color::CYAN, Color::BOLD)} Switch active domain by number"
266
+ puts " #{Color.c('[c] connect', Color::GREEN, Color::BOLD)} 1-Click Setup Wizard: auto-detects key or drag & drop"
267
+ puts " #{Color.c('[cg] connect-ga4', Color::GREEN, Color::BOLD)} Interactive GA4 linking wizard"
268
+ puts " #{Color.c('[ck] connect ke [key]', Color::GREEN, Color::BOLD)} Connect Keywords Everywhere API key"
269
+ puts " #{Color.c('[o] open', Color::GREEN, Color::BOLD)} Reveal configuration directory (~/.config/gsc) in Finder"
270
+ puts " #{Color.c('[w] where', Color::GREEN, Color::BOLD)} Inspect CLI installation path, active credentials, and config"
271
+ puts " #{Color.c('[v] version', Color::GREEN, Color::BOLD)} Show version and runtime environment"
272
+ puts " #{Color.c('[u] update', Color::GREEN, Color::BOLD)} Fetch latest release from GitHub and self-update"
273
+ puts " #{Color.c('[b] back', Color::DIM, Color::BOLD)} Return to main menu"
274
+ puts " #{Color.c('[q] quit', Color::GRAY, Color::BOLD)} Exit gsc"
275
+ puts
276
+ end
277
+
278
+ def print_commands_help
279
+ puts <<~COMMANDS
280
+
281
+ #{Color::BOLD}SETUP, CONFIGURATION & DOMAIN SWITCHING:#{Color::RESET}
282
+ #{Color.c('connect', Color::GREEN)} 1-Click Setup Wizard: auto-detects key or drag & drop
283
+ #{Color.c('open', Color::GREEN)} Reveal config folder (~/.config/gsc) in Finder
284
+ #{Color.c('use <domain>', Color::GREEN)} Set default active domain (saved in ~/.config/gsc/config.json)
285
+ #{Color.c('where', Color::GREEN)} Show CLI location, active credentials, and config path
286
+ #{Color.c('config', Color::GREEN)} View or update current CLI configuration
287
+ #{Color.c('domains', Color::GREEN)} List all verified Search Console properties (highlights active)
288
+ #{Color.c('version', Color::GREEN)} Show CLI version and runtime environment
289
+ #{Color.c('update', Color::GREEN)} Fetch latest version from GitHub and self-update
290
+
291
+ #{Color::BOLD}GOOGLE TRENDS & KEYWORD INTELLIGENCE:#{Color::RESET}
292
+ #{Color.c('trends <keyword>', Color::CYAN).ljust(38)} Real-time Google Trends search demand, velocity & breakouts
293
+ #{Color.c('planner <seed>', Color::CYAN).ljust(38)} Autocomplete expansion, search intent & opportunity scoring
294
+ #{Color.c('planner-import <file>', Color::CYAN).ljust(38)} Import Google Ads Keyword Planner CSV/TSV export & score
295
+ #{Color.c('ke <keyword|file>', Color::YELLOW).ljust(38)} Keywords Everywhere exact monthly volume, CPC & competition
296
+ #{Color.c('ke-credits', Color::YELLOW).ljust(38)} Check remaining Keywords Everywhere account balance
297
+ #{Color.c('connect ke [key]', Color::GREEN).ljust(38)} Connect Keywords Everywhere API key (~/.config/gsc/config.json)
298
+
299
+ #{Color::BOLD}SEARCH ANALYTICS & MONITORING:#{Color::RESET}
300
+ #{Color.c('performance', Color::YELLOW)} Executive dashboard: Totals, Devices, Countries, Snippets, Queries, Pages, Cities
301
+ #{Color.c('top-queries', Color::YELLOW)} Top search queries, impressions, CTR, and average rankings
302
+ #{Color.c('top-pages', Color::YELLOW)} Top indexed pages driving clicks & impressions
303
+ #{Color.c('devices', Color::YELLOW)} Device breakdown (Desktop, Mobile, Tablet) with clicks share
304
+ #{Color.c('countries', Color::YELLOW)} Geographic search demand by country (with flags & CTR)
305
+ #{Color.c('cities', Color::YELLOW)} Top visitor cities, volume, bounce rates & retention (via GA4)
306
+ #{Color.c('snippets', Color::YELLOW)} Search appearance & rich snippets (Reviews, Products, FAQs)
307
+ #{Color.c('audit', Color::CYAN)} Automated 4-step SEO & Indexing Health Check
308
+
309
+ #{Color::BOLD}ENTERPRISE GROWTH & AUDIT INTELLIGENCE:#{Color::RESET}
310
+ #{Color.c('opportunities', Color::YELLOW)} Striking-distance queries (Pos 7–20) to push to Page 1 / Top 3
311
+ #{Color.c('underperformers', Color::YELLOW)} High-ranking queries (Top 10) with low CTR (title tag wins)
312
+ #{Color.c('cannibalization', Color::RED)} Detect multiple internal URLs competing for the same query
313
+ #{Color.c('decay', Color::RED)} Period-over-period decay detection (decaying vs surging)
314
+ #{Color.c('brand', Color::GREEN)} Brand vs non-brand search query segmentation & traffic share
315
+ #{Color.c('ctr-curve', Color::CYAN)} Expected SERP CTR curve & click gain simulator
316
+ #{Color.c('strike', Color::YELLOW)} Striking-distance tactical playbook (title rewrites & headings)
317
+ #{Color.c('answer <query>', Color::CYAN)} Synthesize 40-60w direct answer for Featured Snippets
318
+ #{Color.c('geo <url>', Color::GREEN)} Generative Engine Optimization (GEO/AEO) citability audit
319
+ #{Color.c('firewall [url]', Color::YELLOW)} AI Bot & Cloudflare WAF firewall blockade scanner
320
+ #{Color.c('titles [url]', Color::CYAN)} Title tag length & pixel width overflow optimizer
321
+ #{Color.c('headings <url>', Color::CYAN)} Heading hierarchy tree validator (H1-H6 depth & skipping)
322
+ #{Color.c('orphans [url]', Color::MAGENTA)} Internal link equity & orphan page rescue engine
323
+ #{Color.c('speed-correlate', Color::GREEN)} Correlate Core Web Vitals (LCP/CLS) with real GSC traffic
324
+
325
+ #{Color::BOLD}GOOGLE INDEXING API & SITEMAPS:#{Color::RESET}
326
+ #{Color.c('inspect <url>', Color::CYAN)} Live Google index check (coverage, canonical, crawl date)
327
+ #{Color.c('index <url>', Color::GREEN)} Notify Googlebot to crawl/index URL immediately
328
+ #{Color.c('index-batch [action]', Color::GREEN)} Automated 200 URL/day quota queue manager
329
+ #{Color.c('remove <url>', Color::RED)} Notify Googlebot a page has been removed
330
+ #{Color.c('sitemaps-list', Color::MAGENTA)} List submitted sitemaps in Search Console
331
+ #{Color.c('sitemaps-submit <url>', Color::MAGENTA)} Submit new XML sitemap to Google Search Console
332
+ #{Color.c('inspect-sitemap <file>', Color::CYAN)} Bulk inspect indexation status for all sitemap URLs
333
+ #{Color.c('index-sitemap <file>', Color::GREEN)} Batch submit all sitemap URLs to Google Indexing API
334
+ #{Color.c('zombies <sitemap>', Color::RED)} Find zero-impression crawl waste pages over 90 days
335
+
336
+ #{Color::BOLD}LOCAL CACHE & OFFLINE INTELLIGENCE:#{Color::RESET}
337
+ #{Color.c('cache status', Color::GREEN)} Inspect local SQLite query cache size & snapshots
338
+ #{Color.c('cache warm', Color::GREEN)} Warm local cache from GSC API
339
+ #{Color.c('cache query <term>', Color::GREEN)} Sub-10ms offline query search with filtering & sorting
340
+ #{Color.c('cache pages <term>', Color::GREEN)} Sub-10ms offline landing page search
341
+ #{Color.c('cache diff', Color::CYAN)} Offline snapshot winner/loser delta comparator
342
+ #{Color.c('cache clear', Color::RED)} Clear local database cache
343
+
344
+ #{Color::BOLD}GOOGLE ANALYTICS 4 (GA4):#{Color::RESET}
345
+ #{Color.c('realtime', Color::GREEN)} Stream live active visitors and active paths
346
+ #{Color.c('pages', Color::MAGENTA)} Top landing pages with bounce rates, views & duration
347
+ #{Color.c('sources', Color::MAGENTA)} Traffic acquisition channels (Organic, Direct, Social)
348
+ #{Color.c('correlation', Color::MAGENTA)} Merge GSC rankings with GA4 bounce rates
349
+ #{Color.c('ads', Color::MAGENTA)} Google Ads campaign performance
350
+ COMMANDS
351
+ end
352
+ end
353
+ end
354
+ end
@@ -0,0 +1,129 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require 'json'
5
+ require_relative 'base'
6
+ require_relative '../doctor'
7
+ require_relative '../color'
8
+
9
+ module GSC
10
+ class CLI
11
+ module Doctor
12
+ module_function
13
+
14
+ def run(command, target, extra, options, api = nil, site_url = nil, hostname = nil)
15
+ puts "🩺 Running zero-dependency architecture & system certification..." unless options[:json]
16
+
17
+ res = GSC::Doctor.diagnose(options)
18
+
19
+ if options[:json]
20
+ puts JSON.pretty_generate(res)
21
+ if options[:strict] && (!res[:certification][:certified_zero_gem] || res[:certification][:score] < 100.0)
22
+ exit 1
23
+ end
24
+ return
25
+ end
26
+
27
+ render_terminal(res, options)
28
+
29
+ if options[:strict] && (!res[:certification][:certified_zero_gem] || res[:certification][:score] < 100.0)
30
+ puts "\n#{Color.red("✖ STRICT AUDIT FAILED: Architecture certification score is #{res[:certification][:score]}/100.")}"
31
+ exit 1
32
+ end
33
+ end
34
+
35
+ def render_terminal(res, options)
36
+ cert = res[:certification]
37
+ purity = res[:purity]
38
+ cold = res[:cold_start]
39
+ crypto = res[:crypto]
40
+ env = res[:environment]
41
+
42
+ grade_color = case cert[:grade]
43
+ when 'A+', 'A' then Color.green("#{cert[:score]}/100 (Grade #{cert[:grade]} - #{cert[:status].to_s.upcase})")
44
+ when 'B', 'C' then Color.yellow("#{cert[:score]}/100 (Grade #{cert[:grade]} - #{cert[:status].to_s.upcase})")
45
+ else Color.red("#{cert[:score]}/100 (Grade #{cert[:grade]} - #{cert[:status].to_s.upcase})")
46
+ end
47
+
48
+ puts "\n#{Color.bold("═══════════════════════════════════════════════════════════════════════════")}"
49
+ puts "#{Color.bold("🩺 GSC ZERO-DEPENDENCY ARCHITECTURE & SYSTEM CERTIFICATION")}"
50
+ puts "#{Color.bold("═══════════════════════════════════════════════════════════════════════════")}"
51
+
52
+ puts " • Certification Score: [ #{Color.bold(grade_color)} ]"
53
+ puts " • Runtime Environment: #{Color.bold(res[:ruby_version])}"
54
+ puts " • Zero-Gem Compliance: #{cert[:certified_zero_gem] ? Color.green("✅ 100% PURE RUBY STDLIB CERTIFIED") : Color.red("❌ DISALLOWED GEMS FOUND")}"
55
+ puts "#{Color.bold("───────────────────────────────────────────────────────────────────────────")}\n"
56
+
57
+ # 1. Codebase Purity Card
58
+ puts "#{Color.bold("1. CODEBASE PURITY & DEPENDENCY AUDIT:")}"
59
+ puts " • Ruby Files Scanned: #{Color.cyan(purity[:files_scanned].to_s)} files in lib/ and bin/"
60
+ puts " • Third-Party Gem Count: #{purity[:unapproved_requires].empty? ? Color.green("0 gems (100% Zero-Gem Purity)") : Color.red("#{purity[:unapproved_requires].size} external gems detected!")}"
61
+ puts " • Stdlib Packages Used: #{purity[:stdlib_packages].join(', ')}"
62
+ if purity[:optional_accelerators].any?
63
+ puts " • Optional Accelerators: #{Color.yellow(purity[:optional_accelerators].join(', '))} (Dynamic load with graceful fallback)"
64
+ end
65
+ if purity[:unapproved_requires].any?
66
+ puts " • #{Color.red("Disallowed Requires:")}"
67
+ purity[:unapproved_requires].each do |gem_name, occurrences|
68
+ occurrences.each do |occ|
69
+ puts " ✖ #{Color.red(gem_name)} in #{occ[:file]}:#{occ[:line]}"
70
+ end
71
+ end
72
+ end
73
+
74
+ # 2. Cold-Start Performance Card
75
+ puts "\n#{Color.bold("2. COLD-START EXECUTION PERFORMANCE:")}"
76
+ cold_badge = cold[:average_ms] < 50.0 ? Color.green("⚡ INSTANT (#{cold[:average_ms]}ms)") : Color.cyan("#{cold[:average_ms]}ms")
77
+ puts " • Cold-Start Latency: [ #{Color.bold(cold_badge)} ] (Target: < #{cold[:target_ms]}ms)"
78
+ puts " • Bootstrap Benchmark: Iterations: #{cold[:iterations]} | Samples: #{cold[:measurements_ms].join('ms, ')}ms"
79
+
80
+ # 3. Cryptographic Suite Card
81
+ puts "\n#{Color.bold("3. CRYPTOGRAPHIC & TLS 1.3 CAPABILITIES:")}"
82
+ puts " • OpenSSL Version: #{crypto[:openssl_version]}"
83
+ puts " • AES-256-GCM Cipher: #{crypto[:aes_gcm_available] ? Color.green("✅ Operational (Round-trip verified)") : Color.red("❌ Unavailable")}"
84
+ puts " • PBKDF2 Key Derivation: #{crypto[:pbkdf2_available] ? Color.green("✅ Operational (SHA256 PBKDF2)") : Color.red("❌ Unavailable")}"
85
+ puts " • TLS Handshake Context: #{crypto[:tls_context_ready] ? Color.green("✅ Operational (TLS 1.2 & 1.3)") : Color.red("❌ Unavailable")}"
86
+ puts " • System CA Cert Store: #{crypto[:ca_cert_store_valid] ? Color.green("✅ Operational") : Color.yellow("⚠️ Default store notice")}"
87
+
88
+ # 4. Security & Permissions Card
89
+ puts "\n#{Color.bold("4. ENVIRONMENT & CREDENTIAL ISOLATION:")}"
90
+ puts " • Config Directory: #{env[:config_dir]} (#{env[:config_dir_exists] ? Color.green("Found") : Color.yellow("Not yet created")})"
91
+ puts " • Directory Permissions: #{env[:permissions_secure] ? Color.green("✅ Secure (Strict POSIX 0700/0600)") : Color.yellow("⚠️ Loose permissions detected")}"
92
+ if env[:permissions_fixed].any?
93
+ puts " • #{Color.green("Repaired Permissions:")}"
94
+ env[:permissions_fixed].each do |fix|
95
+ puts " ✔ #{fix}"
96
+ end
97
+ end
98
+ puts " • GSC Credentials: #{env[:credentials_configured] ? Color.green("✅ Configured") : Color.yellow("⚠️ Not configured (Run `gsc connect`)")}"
99
+ if env[:active_domain]
100
+ puts " • Active Domain Link: #{Color.cyan(env[:active_domain])}"
101
+ end
102
+
103
+ # 5. System Helpers Card
104
+ puts "\n#{Color.bold("5. SYSTEM CLI HELPERS & IPC STATUS:")}"
105
+ env[:system_helpers].each do |tool, info|
106
+ status_str = info[:available] ? Color.green("✅ Available (#{info[:path]})") : Color.yellow("⚠️ Not in PATH (Optional)")
107
+ puts " • #{tool.ljust(11)}: #{status_str}"
108
+ end
109
+
110
+ # Remediations
111
+ if res[:remediations].any?
112
+ puts "\n#{Color.bold("───────────────────────────────────────────────────────────────────────────")}"
113
+ puts "#{Color.bold("RECOMMENDED REMEDIATIONS:")}"
114
+ res[:remediations].each do |rem|
115
+ puts " 👉 #{Color.yellow(rem)}"
116
+ end
117
+ end
118
+
119
+ puts "\n#{Color.bold("═══════════════════════════════════════════════════════════════════════════")}"
120
+ if cert[:score] >= 95.0
121
+ puts "#{Color.green(Color.bold("✔ SYSTEM FULLY CERTIFIED: Zero dependencies, sub-100ms cold start, secure."))}"
122
+ else
123
+ puts "#{Color.yellow("Notice: Address non-optimal items above to reach 100/100 certification.")}"
124
+ end
125
+ puts "#{Color.bold("═══════════════════════════════════════════════════════════════════════════")}\n"
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,125 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require 'json'
5
+ require_relative 'base'
6
+ require_relative '../eeat_auditor'
7
+ require_relative '../color'
8
+
9
+ module GSC
10
+ class CLI
11
+ module Eeat
12
+ module_function
13
+
14
+ def run(command, target, extra, options, api = nil, site_url = nil, hostname = nil)
15
+ target_url = target || (hostname ? "https://#{hostname}" : nil)
16
+ unless target_url
17
+ if File.exist?('public/index.html')
18
+ target_url = 'public/index.html'
19
+ elsif File.exist?('index.html')
20
+ target_url = 'index.html'
21
+ else
22
+ msg = "Please provide a target URL or HTML file: gsc eeat <url|file>"
23
+ if options[:json]
24
+ puts JSON.pretty_generate({ error: msg })
25
+ else
26
+ puts Color.red("❌ Error: #{msg}")
27
+ end
28
+ return
29
+ end
30
+ end
31
+
32
+ puts "🧠 Auditing E-E-A-T author credentials & trust signals for #{Color.cyan(target_url)}..." unless options[:json]
33
+
34
+ res = GSC::EeatAuditor.audit(target_url, options)
35
+
36
+ if options[:json]
37
+ puts JSON.pretty_generate(res)
38
+ return
39
+ end
40
+
41
+ render_terminal(res)
42
+
43
+ if options[:csv]
44
+ export_csv(res, options[:csv])
45
+ end
46
+ end
47
+
48
+ def render_terminal(res)
49
+ puts "\n#{Color.bold("═══════════════════════════════════════════════════════════════════════════")}"
50
+ puts "#{Color.bold("🧠 AUTHOR E-E-A-T & CREDENTIAL SIGNAL AUDITOR (Google Helpful Content)")}"
51
+ puts "#{Color.bold("═══════════════════════════════════════════════════════════════════════════")}"
52
+
53
+ score = res[:health_score]
54
+ grade_color = case res[:grade]
55
+ when 'A+', 'A' then Color.green(res[:grade])
56
+ when 'B' then Color.cyan(res[:grade])
57
+ when 'C' then Color.yellow(res[:grade])
58
+ else Color.red(res[:grade])
59
+ end
60
+
61
+ puts " • E-E-A-T Trust Score: [ #{Color.bold(grade_color)} ] #{Color.bold("#{score}/100")}"
62
+ puts " • Target URL / Source: #{res[:url]}"
63
+ puts " • Author Name: #{res[:author_name] ? Color.bold(res[:author_name]) : Color.red("Not Detected")}"
64
+ puts " • Verified Credentials: #{res[:credentials].any? ? Color.green(res[:credentials].join(', ')) : Color.yellow("None Detected")}"
65
+ puts " • Editorial Reviewer: #{res[:reviewer_name] ? Color.green(res[:reviewer_name]) : Color.gray("None")}"
66
+ puts " • Publication / Modified Dates: #{res[:date_published] || 'N/A'} / #{res[:date_modified] || 'N/A'}"
67
+ puts " • Authoritative Citations: #{Color.bold(res[:citations_count].to_s)} (.gov, .edu, DOI, studies)"
68
+ puts "#{Color.bold("───────────────────────────────────────────────────────────────────────────")}"
69
+
70
+ puts " Audited Signal Components:"
71
+ format_signal("Author Byline & Name", !res[:author_name].nil?)
72
+ format_signal("Structured Schema (Person)", !res[:issues].include?(:missing_person_schema))
73
+ format_signal("Professional Credentials", res[:credentials].any?)
74
+ format_signal("Social Proof (LinkedIn/Wiki)", !res[:issues].include?(:missing_social_proof))
75
+ format_signal("Editorial Review / Fact-Check", !res[:reviewer_name].nil?)
76
+ format_signal("Freshness Timestamps", !res[:issues].include?(:missing_dates) && !res[:issues].include?(:missing_date_modified))
77
+ format_signal("Authoritative Citations", res[:citations_count] > 0)
78
+ format_signal("Editorial Transparency Policy", !res[:issues].include?(:missing_editorial_disclosure))
79
+ puts "#{Color.bold("───────────────────────────────────────────────────────────────────────────")}\n"
80
+
81
+ if res[:prescriptions].any?
82
+ puts "#{Color.bold("PRIORITIZED E-E-A-T RECOVERY PRESCRIPTIONS:")}"
83
+ res[:prescriptions].each_with_index do |p, idx|
84
+ puts " #{idx + 1}. 💡 #{p}"
85
+ end
86
+ puts
87
+ end
88
+
89
+ if res[:schema_fix_snippet]
90
+ puts "#{Color.bold("1-CLICK SCHEMA.ORG JSON-LD CREDENTIAL FIX:")}"
91
+ puts "```json"
92
+ puts res[:schema_fix_snippet]
93
+ puts "```\n"
94
+ end
95
+
96
+ puts "#{Color.bold("═══════════════════════════════════════════════════════════════════════════")}\n"
97
+ end
98
+
99
+ def format_signal(label, status)
100
+ badge = status ? Color.green("✅ VERIFIED") : Color.red("❌ MISSING ")
101
+ printf " %-32s %s\n", label, badge
102
+ end
103
+
104
+ def export_csv(res, file_path)
105
+ headers = %w[URL Score Grade Author Credentials Reviewer Published Modified Citations Issues]
106
+ rows = [
107
+ [
108
+ res[:url],
109
+ res[:health_score],
110
+ res[:grade],
111
+ res[:author_name] || '',
112
+ res[:credentials].join(';'),
113
+ res[:reviewer_name] || '',
114
+ res[:date_published] || '',
115
+ res[:date_modified] || '',
116
+ res[:citations_count],
117
+ res[:issues].join('|')
118
+ ]
119
+ ]
120
+ Base.write_csv(file_path, headers, rows)
121
+ puts Color.cyan("📁 Successfully exported E-E-A-T audit to #{file_path}")
122
+ end
123
+ end
124
+ end
125
+ end