gsc-cli 2.1.0 → 2.2.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.
- checksums.yaml +4 -4
- data/AUTH.md +4 -1
- data/README.md +448 -408
- data/bin/gsc +28067 -5661
- data/dist/gsc +29121 -5046
- data/lib/gsc/aio_hunter.rb +343 -0
- data/lib/gsc/answer_synthesizer.rb +157 -0
- data/lib/gsc/api.rb +53 -1
- data/lib/gsc/auth.rb +26 -0
- data/lib/gsc/brand_segmenter.rb +140 -0
- data/lib/gsc/cache_manager.rb +806 -0
- data/lib/gsc/cannibalization_analyzer.rb +141 -0
- data/lib/gsc/canonical_chains.rb +367 -0
- data/lib/gsc/citation_simulator.rb +339 -0
- data/lib/gsc/cli/aio_hunter.rb +154 -0
- data/lib/gsc/cli/analytics.rb +788 -0
- data/lib/gsc/cli/audit.rb +1976 -0
- data/lib/gsc/cli/base.rb +384 -0
- data/lib/gsc/cli/cache.rb +266 -0
- data/lib/gsc/cli/canonical.rb +223 -0
- data/lib/gsc/cli/citation_simulator.rb +152 -0
- data/lib/gsc/cli/dashboard.rb +354 -0
- data/lib/gsc/cli/doctor.rb +129 -0
- data/lib/gsc/cli/eeat.rb +125 -0
- data/lib/gsc/cli/ga4.rb +852 -0
- data/lib/gsc/cli/growth.rb +650 -0
- data/lib/gsc/cli/hreflang.rb +164 -0
- data/lib/gsc/cli/image_seo.rb +162 -0
- data/lib/gsc/cli/indexing.rb +458 -0
- data/lib/gsc/cli/intent_shift.rb +125 -0
- data/lib/gsc/cli/keyword_value.rb +134 -0
- data/lib/gsc/cli/keywords.rb +795 -0
- data/lib/gsc/cli/landing_roi.rb +308 -0
- data/lib/gsc/cli/low_ctr.rb +213 -0
- data/lib/gsc/cli/mobile_parity.rb +150 -0
- data/lib/gsc/cli/report.rb +100 -0
- data/lib/gsc/cli/rich_results.rb +172 -0
- data/lib/gsc/cli/schema_generate.rb +149 -0
- data/lib/gsc/cli/seasonal.rb +232 -0
- data/lib/gsc/cli/security.rb +153 -0
- data/lib/gsc/cli/setup.rb +1291 -0
- data/lib/gsc/cli/sitemap_tree.rb +143 -0
- data/lib/gsc/cli/skill_pack.rb +62 -0
- data/lib/gsc/cli/soft_404.rb +199 -0
- data/lib/gsc/cli/sparkline.rb +227 -0
- data/lib/gsc/cli/watchdog.rb +150 -0
- data/lib/gsc/cli/zombie_purger.rb +208 -0
- data/lib/gsc/cli.rb +707 -5265
- data/lib/gsc/cli_advanced.rb +987 -44
- data/lib/gsc/client.rb +17 -2
- data/lib/gsc/color.rb +16 -1
- data/lib/gsc/command_registry.rb +47 -9
- data/lib/gsc/config.rb +2 -2
- data/lib/gsc/ctr_curve.rb +115 -0
- data/lib/gsc/decay_predictor.rb +322 -0
- data/lib/gsc/doctor.rb +434 -0
- data/lib/gsc/eeat_auditor.rb +428 -0
- data/lib/gsc/entity_auditor.rb +229 -0
- data/lib/gsc/firewall_scanner.rb +733 -0
- data/lib/gsc/geo_auditor.rb +368 -0
- data/lib/gsc/google_trends.rb +8 -1
- data/lib/gsc/heading_validator.rb +283 -0
- data/lib/gsc/hreflang_validator.rb +412 -0
- data/lib/gsc/image_seo.rb +286 -0
- data/lib/gsc/indexing_queue.rb +179 -0
- data/lib/gsc/indexnow.rb +93 -0
- data/lib/gsc/intent_shift.rb +188 -0
- data/lib/gsc/internal_links.rb +153 -36
- data/lib/gsc/keyword_value.rb +191 -0
- data/lib/gsc/landing_roi.rb +195 -0
- data/lib/gsc/llms_generator.rb +343 -22
- data/lib/gsc/low_ctr_rewriter.rb +408 -0
- data/lib/gsc/mobile_parity.rb +222 -0
- data/lib/gsc/network_tracer.rb +8 -1
- data/lib/gsc/page_analyzer.rb +47 -7
- data/lib/gsc/prompts.rb +38 -29
- data/lib/gsc/questions_harvester.rb +178 -0
- data/lib/gsc/report_generator.rb +461 -0
- data/lib/gsc/rich_results.rb +388 -0
- data/lib/gsc/robots_checker.rb +46 -15
- data/lib/gsc/schema_generator.rb +788 -0
- data/lib/gsc/schema_validator.rb +36 -38
- data/lib/gsc/seasonal_predictor.rb +381 -0
- data/lib/gsc/security_scanner.rb +496 -0
- data/lib/gsc/serp_feature_detector.rb +359 -0
- data/lib/gsc/serp_preview.rb +108 -22
- data/lib/gsc/site_crawler.rb +113 -21
- data/lib/gsc/sitemap_loader.rb +15 -4
- data/lib/gsc/sitemap_tree.rb +301 -0
- data/lib/gsc/skill_pack.rb +195 -0
- data/lib/gsc/soft_404_analyzer.rb +385 -0
- data/lib/gsc/sparkline.rb +171 -0
- data/lib/gsc/speed_correlator.rb +416 -0
- data/lib/gsc/striking_playbook.rb +190 -0
- data/lib/gsc/title_optimizer.rb +420 -0
- data/lib/gsc/vault.rb +260 -0
- data/lib/gsc/version.rb +1 -1
- data/lib/gsc/watchdog.rb +235 -0
- data/lib/gsc/zombie_purger.rb +366 -0
- data/lib/gsc.rb +118 -0
- metadata +75 -1
|
@@ -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
|
data/lib/gsc/cli/eeat.rb
ADDED
|
@@ -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
|