antigravity-seo-kit 2.0.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 (135) hide show
  1. package/.agent/agent.md +96 -0
  2. package/.agent/skills/seo/SKILL.md +153 -0
  3. package/.agent/skills/seo/references/cwv-thresholds.md +108 -0
  4. package/.agent/skills/seo/references/eeat-framework.md +214 -0
  5. package/.agent/skills/seo/references/local-schema-types.md +230 -0
  6. package/.agent/skills/seo/references/local-seo-signals.md +218 -0
  7. package/.agent/skills/seo/references/maps-api-endpoints.md +160 -0
  8. package/.agent/skills/seo/references/maps-free-apis.md +176 -0
  9. package/.agent/skills/seo/references/maps-gbp-checklist.md +150 -0
  10. package/.agent/skills/seo/references/maps-geo-grid.md +154 -0
  11. package/.agent/skills/seo/references/quality-gates.md +155 -0
  12. package/.agent/skills/seo/references/schema-types.md +118 -0
  13. package/.agent/skills/seo/schema/templates.json +213 -0
  14. package/.agent/skills/seo/scripts/analyze_visual.py +217 -0
  15. package/.agent/skills/seo/scripts/capture_screenshot.py +181 -0
  16. package/.agent/skills/seo/scripts/fetch_page.py +196 -0
  17. package/.agent/skills/seo/scripts/parse_html.py +201 -0
  18. package/.agent/skills/seo-audit/SKILL.md +278 -0
  19. package/.agent/skills/seo-competitor-pages/SKILL.md +212 -0
  20. package/.agent/skills/seo-content/SKILL.md +230 -0
  21. package/.agent/skills/seo-dataforseo/SKILL.md +418 -0
  22. package/.agent/skills/seo-geo/SKILL.md +305 -0
  23. package/.agent/skills/seo-google/SKILL.md +405 -0
  24. package/.agent/skills/seo-google/assets/templates/cwv-audit-report.md +48 -0
  25. package/.agent/skills/seo-google/assets/templates/gsc-performance-report.md +44 -0
  26. package/.agent/skills/seo-google/assets/templates/indexation-status-report.md +43 -0
  27. package/.agent/skills/seo-google/references/auth-setup.md +154 -0
  28. package/.agent/skills/seo-google/references/ga4-data-api.md +184 -0
  29. package/.agent/skills/seo-google/references/indexing-api.md +107 -0
  30. package/.agent/skills/seo-google/references/keyword-planner-api.md +66 -0
  31. package/.agent/skills/seo-google/references/nlp-api.md +55 -0
  32. package/.agent/skills/seo-google/references/pagespeed-crux-api.md +204 -0
  33. package/.agent/skills/seo-google/references/rate-limits-quotas.md +75 -0
  34. package/.agent/skills/seo-google/references/search-console-api.md +156 -0
  35. package/.agent/skills/seo-google/references/supplementary-apis.md +99 -0
  36. package/.agent/skills/seo-google/references/youtube-api.md +49 -0
  37. package/.agent/skills/seo-google/scripts/crux_history.py +321 -0
  38. package/.agent/skills/seo-google/scripts/ga4_report.py +478 -0
  39. package/.agent/skills/seo-google/scripts/google_auth.py +795 -0
  40. package/.agent/skills/seo-google/scripts/google_report.py +2273 -0
  41. package/.agent/skills/seo-google/scripts/gsc_inspect.py +340 -0
  42. package/.agent/skills/seo-google/scripts/gsc_query.py +378 -0
  43. package/.agent/skills/seo-google/scripts/indexing_notify.py +313 -0
  44. package/.agent/skills/seo-google/scripts/keyword_planner.py +297 -0
  45. package/.agent/skills/seo-google/scripts/nlp_analyze.py +309 -0
  46. package/.agent/skills/seo-google/scripts/pagespeed_check.py +649 -0
  47. package/.agent/skills/seo-google/scripts/youtube_search.py +355 -0
  48. package/.agent/skills/seo-hreflang/SKILL.md +192 -0
  49. package/.agent/skills/seo-image-gen/SKILL.md +211 -0
  50. package/.agent/skills/seo-image-gen/references/cost-tracking.md +47 -0
  51. package/.agent/skills/seo-image-gen/references/gemini-models.md +200 -0
  52. package/.agent/skills/seo-image-gen/references/mcp-tools.md +115 -0
  53. package/.agent/skills/seo-image-gen/references/post-processing.md +192 -0
  54. package/.agent/skills/seo-image-gen/references/presets.md +69 -0
  55. package/.agent/skills/seo-image-gen/references/prompt-engineering.md +411 -0
  56. package/.agent/skills/seo-image-gen/references/seo-image-presets.md +137 -0
  57. package/.agent/skills/seo-image-gen/scripts/batch.py +97 -0
  58. package/.agent/skills/seo-image-gen/scripts/cost_tracker.py +191 -0
  59. package/.agent/skills/seo-image-gen/scripts/edit.py +141 -0
  60. package/.agent/skills/seo-image-gen/scripts/generate.py +149 -0
  61. package/.agent/skills/seo-image-gen/scripts/presets.py +153 -0
  62. package/.agent/skills/seo-image-gen/scripts/setup_mcp.py +151 -0
  63. package/.agent/skills/seo-image-gen/scripts/validate_setup.py +133 -0
  64. package/.agent/skills/seo-images/SKILL.md +176 -0
  65. package/.agent/skills/seo-local/SKILL.md +381 -0
  66. package/.agent/skills/seo-maps/SKILL.md +328 -0
  67. package/.agent/skills/seo-page/SKILL.md +86 -0
  68. package/.agent/skills/seo-plan/SKILL.md +118 -0
  69. package/.agent/skills/seo-plan/assets/agency.md +175 -0
  70. package/.agent/skills/seo-plan/assets/ecommerce.md +167 -0
  71. package/.agent/skills/seo-plan/assets/generic.md +144 -0
  72. package/.agent/skills/seo-plan/assets/local-service.md +160 -0
  73. package/.agent/skills/seo-plan/assets/publisher.md +153 -0
  74. package/.agent/skills/seo-plan/assets/saas.md +135 -0
  75. package/.agent/skills/seo-programmatic/SKILL.md +171 -0
  76. package/.agent/skills/seo-schema/SKILL.md +223 -0
  77. package/.agent/skills/seo-sitemap/SKILL.md +180 -0
  78. package/.agent/skills/seo-technical/SKILL.md +211 -0
  79. package/.agent/workflows/seo-audit.md +17 -0
  80. package/.agent/workflows/seo-competitor-pages.md +12 -0
  81. package/.agent/workflows/seo-content.md +14 -0
  82. package/.agent/workflows/seo-geo.md +12 -0
  83. package/.agent/workflows/seo-google.md +12 -0
  84. package/.agent/workflows/seo-hreflang.md +12 -0
  85. package/.agent/workflows/seo-images.md +13 -0
  86. package/.agent/workflows/seo-local.md +12 -0
  87. package/.agent/workflows/seo-maps.md +11 -0
  88. package/.agent/workflows/seo-page.md +13 -0
  89. package/.agent/workflows/seo-plan.md +13 -0
  90. package/.agent/workflows/seo-programmatic.md +12 -0
  91. package/.agent/workflows/seo-schema.md +11 -0
  92. package/.agent/workflows/seo-sitemap.md +9 -0
  93. package/.agent/workflows/seo-technical.md +18 -0
  94. package/LICENSE +88 -0
  95. package/README.md +122 -0
  96. package/bin/cli.js +117 -0
  97. package/docs/ARCHITECTURE.md +218 -0
  98. package/docs/COMMANDS.md +184 -0
  99. package/docs/INSTALLATION.md +100 -0
  100. package/docs/MCP-INTEGRATION.md +153 -0
  101. package/docs/TROUBLESHOOTING.md +151 -0
  102. package/docs/superpowers/plans/2026-03-13-github-audit-fixes.md +511 -0
  103. package/extensions/banana/README.md +95 -0
  104. package/extensions/banana/docs/BANANA-SETUP.md +86 -0
  105. package/extensions/banana/install.sh +170 -0
  106. package/extensions/banana/references/cost-tracking.md +47 -0
  107. package/extensions/banana/references/gemini-models.md +200 -0
  108. package/extensions/banana/references/mcp-tools.md +115 -0
  109. package/extensions/banana/references/post-processing.md +192 -0
  110. package/extensions/banana/references/presets.md +69 -0
  111. package/extensions/banana/references/prompt-engineering.md +411 -0
  112. package/extensions/banana/references/seo-image-presets.md +137 -0
  113. package/extensions/banana/scripts/batch.py +97 -0
  114. package/extensions/banana/scripts/cost_tracker.py +191 -0
  115. package/extensions/banana/scripts/edit.py +141 -0
  116. package/extensions/banana/scripts/generate.py +149 -0
  117. package/extensions/banana/scripts/presets.py +153 -0
  118. package/extensions/banana/scripts/setup_mcp.py +151 -0
  119. package/extensions/banana/scripts/validate_setup.py +133 -0
  120. package/extensions/banana/uninstall.sh +43 -0
  121. package/extensions/dataforseo/README.md +169 -0
  122. package/extensions/dataforseo/docs/DATAFORSEO-SETUP.md +74 -0
  123. package/extensions/dataforseo/field-config.json +280 -0
  124. package/extensions/dataforseo/install.ps1 +110 -0
  125. package/extensions/dataforseo/install.sh +161 -0
  126. package/extensions/dataforseo/uninstall.ps1 +35 -0
  127. package/extensions/dataforseo/uninstall.sh +39 -0
  128. package/lib/api.js +190 -0
  129. package/lib/fingerprint.js +68 -0
  130. package/lib/installer.js +486 -0
  131. package/lib/utils.js +254 -0
  132. package/package.json +40 -0
  133. package/pyproject.toml +11 -0
  134. package/requirements-google.txt +15 -0
  135. package/requirements.txt +11 -0
@@ -0,0 +1,118 @@
1
+ <!-- Updated: 2026-02-07 -->
2
+ # Schema.org Types: Status & Recommendations (February 2026)
3
+
4
+ **Schema.org Version:** 29.4 (December 8, 2025)
5
+
6
+ ## Format Preference
7
+ Always use **JSON-LD** (`<script type="application/ld+json">`).
8
+ Google's documentation explicitly recommends JSON-LD over Microdata and RDFa.
9
+
10
+ **AI Search Note:** Content with proper schema has ~2.5× higher chance of appearing in AI-generated answers (confirmed by Google and Microsoft, March 2025).
11
+
12
+ ---
13
+
14
+ ## Active: Recommend freely
15
+
16
+ | Type | Use Case | Key Properties |
17
+ |------|----------|----------------|
18
+ | Organization | Company info | name, url, logo, contactPoint, sameAs |
19
+ | LocalBusiness | Physical businesses | name, address, telephone, openingHours, geo, priceRange |
20
+ | SoftwareApplication | Desktop/mobile apps | name, operatingSystem, applicationCategory, offers, aggregateRating |
21
+ | WebApplication | Browser-based SaaS | name, applicationCategory, offers, browserRequirements, featureList |
22
+ | Product | Physical/digital products | name, image, description, sku, brand, offers, review |
23
+ | Offer | Pricing | price, priceCurrency, availability, url, validFrom |
24
+ | Service | Service businesses | name, provider, areaServed, description, offers |
25
+ | Article | Blog posts, news | headline, author, datePublished, dateModified, image, publisher |
26
+ | BlogPosting | Blog content | Same as Article + blog-specific context |
27
+ | NewsArticle | News content | Same as Article + news-specific context |
28
+ | Review | Individual reviews | reviewRating, author, itemReviewed, reviewBody |
29
+ | AggregateRating | Rating summaries | ratingValue, reviewCount, bestRating, worstRating |
30
+ | BreadcrumbList | Navigation | itemListElement with position, name, item |
31
+ | WebSite | Site-level | name, url, potentialAction (SearchAction for sitelinks search) |
32
+ | WebPage | Page-level | name, description, datePublished, dateModified |
33
+ | Person | Author/team | name, jobTitle, url, sameAs, image, worksFor |
34
+ | ContactPage | Contact pages | name, url |
35
+ | VideoObject | Video content | name, description, thumbnailUrl, uploadDate, duration, contentUrl |
36
+ | ImageObject | Image content | contentUrl, caption, creator, copyrightHolder |
37
+ | Event | Events | name, startDate, endDate, location, organizer, offers |
38
+ | JobPosting | Job listings | title, description, datePosted, hiringOrganization, jobLocation |
39
+ | Course | Educational content | name, description, provider, hasCourseInstance |
40
+ | DiscussionForumPosting | Forum threads | headline, author, datePublished, text, url |
41
+ | ProductGroup | Variant products | name, productGroupID, variesBy, hasVariant |
42
+ | ProfilePage | Author/creator profiles | mainEntity (Person), name, url, description, sameAs |
43
+
44
+ ---
45
+
46
+ ## Restricted: Only for specific site types
47
+
48
+ | Type | Restriction | Since |
49
+ |------|------------|-------|
50
+ | FAQPage | Government and healthcare authority sites ONLY | August 2023 |
51
+
52
+ > Google severely limited FAQ rich results in August 2023. Only authoritative sources (government, health organizations) receive FAQ rich results.
53
+ >
54
+ > **GEO nuance**: FAQPage schema still benefits AI/LLM citation visibility (ChatGPT, Perplexity, Google AI Overviews), even without Google rich results.
55
+ > - **Existing FAQPage on commercial site**: Flag at Info priority, not Critical. Removal removes GEO citation upside.
56
+ > - **Adding new FAQPage**: Not recommended for Google benefit; acceptable if AI search visibility is a priority.
57
+
58
+ ---
59
+
60
+ ## Deprecated: Never recommend
61
+
62
+ | Type | Status | Since | Notes |
63
+ |------|--------|-------|-------|
64
+ | HowTo | Rich results fully removed | September 2023 | Google stopped showing how-to rich results |
65
+ | SpecialAnnouncement | Deprecated | July 31, 2025 | COVID-era schema, no longer processed |
66
+ | CourseInfo | Retired from rich results | June 2025 | Merged into Course |
67
+ | EstimatedSalary | Retired from rich results | June 2025 | No longer displayed |
68
+ | LearningVideo | Retired from rich results | June 2025 | Use VideoObject instead |
69
+ | ClaimReview | Retired from rich results | June 2025 | Fact-check markup no longer generates rich results |
70
+ | VehicleListing | Retired from rich results | June 2025 | Vehicle listing structured data discontinued |
71
+ | Book Actions | Deprecated then REVERSED | June 2025 | **Still functional as of Feb 2026**: historical note only |
72
+ | Practice Problem | Retired from rich results | Late 2025 | Educational practice problems no longer displayed |
73
+ | Dataset | Retired from rich results | Late 2025 | Dataset Search feature discontinued |
74
+
75
+ ---
76
+
77
+ ## Recent Additions (2024-2026)
78
+
79
+ | Type/Feature | Added | Notes |
80
+ |-------------|-------|-------|
81
+ | Product Certification markup | April 2025 | Energy ratings, safety certifications. Replaced EnergyConsumptionDetails. |
82
+ | ProductGroup | 2025 | E-commerce product variants with variesBy, hasVariant properties |
83
+ | ProfilePage | 2025 | Author/creator profile pages with mainEntity Person for E-E-A-T |
84
+ | DiscussionForumPosting | 2024 | For forum/community content |
85
+ | Speakable | Updated 2024 | For voice search optimization |
86
+ | LoyaltyProgram | June 2025 | Member pricing, loyalty card structured data |
87
+ | Organization-level shipping/return policies | November 2025 | Configure via Search Console without Merchant Center |
88
+ | ConferenceEvent | December 2025 | Schema.org v29.4 addition |
89
+ | PerformingArtsEvent | December 2025 | Schema.org v29.4 addition |
90
+
91
+ ## E-commerce Requirements (Updated)
92
+
93
+ | Requirement | Status | Since |
94
+ |-------------|--------|-------|
95
+ | `returnPolicyCountry` in MerchantReturnPolicy | **Required** | March 2025 |
96
+ | Product variant structured data | Expanded | 2025, includes apparel, cosmetics, electronics |
97
+
98
+ > **Note:** Content API for Shopping sunsets August 18, 2026. Migrate to Merchant API.
99
+
100
+ ---
101
+
102
+ ## Validation Checklist
103
+
104
+ For any schema block, verify:
105
+
106
+ 1. ✅ `@context` is `"https://schema.org"` (not http)
107
+ 2. ✅ `@type` is a valid, non-deprecated type
108
+ 3. ✅ All required properties are present
109
+ 4. ✅ Property values match expected data types
110
+ 5. ✅ No placeholder text (e.g., "[Business Name]")
111
+ 6. ✅ URLs are absolute, not relative
112
+ 7. ✅ Dates are in ISO 8601 format
113
+ 8. ✅ Images have valid URLs
114
+
115
+ ## Testing Tools
116
+
117
+ - [Google Rich Results Test](https://search.google.com/test/rich-results)
118
+ - [Schema.org Validator](https://validator.schema.org/)
@@ -0,0 +1,213 @@
1
+ {
2
+ "templates": [
3
+ {
4
+ "type": "VideoObject",
5
+ "description": "Video content pages with thumbnails, duration, and playback URLs. Enables video rich results in Google Search.",
6
+ "template": {
7
+ "@context": "https://schema.org",
8
+ "@type": "VideoObject",
9
+ "name": "[Video Title]",
10
+ "description": "[Video Description]",
11
+ "thumbnailUrl": "[Thumbnail Image URL]",
12
+ "uploadDate": "[YYYY-MM-DD]",
13
+ "duration": "[ISO 8601 Duration, e.g. PT1H30M]",
14
+ "contentUrl": "[Direct Video File URL]",
15
+ "embedUrl": "[Embed Player URL]",
16
+ "publisher": {
17
+ "@type": "Organization",
18
+ "name": "[Publisher Name]",
19
+ "logo": {
20
+ "@type": "ImageObject",
21
+ "url": "[Logo URL]"
22
+ }
23
+ }
24
+ }
25
+ },
26
+ {
27
+ "type": "BroadcastEvent",
28
+ "description": "Live streaming content for LIVE badge in Google Search results. Requires VideoObject and isLiveBroadcast flag.",
29
+ "template": {
30
+ "@context": "https://schema.org",
31
+ "@type": "VideoObject",
32
+ "name": "[Live Stream Title]",
33
+ "description": "[Live Stream Description]",
34
+ "thumbnailUrl": "[Thumbnail Image URL]",
35
+ "uploadDate": "[YYYY-MM-DD]",
36
+ "contentUrl": "[Stream URL]",
37
+ "embedUrl": "[Embed Player URL]",
38
+ "publication": {
39
+ "@type": "BroadcastEvent",
40
+ "isLiveBroadcast": true,
41
+ "startDate": "[YYYY-MM-DDTHH:MM:SSZ]",
42
+ "endDate": "[YYYY-MM-DDTHH:MM:SSZ]"
43
+ }
44
+ }
45
+ },
46
+ {
47
+ "type": "Clip",
48
+ "description": "Key moments or chapters within a video. Enables key moments rich results with timestamp links.",
49
+ "template": {
50
+ "@context": "https://schema.org",
51
+ "@type": "VideoObject",
52
+ "name": "[Video Title]",
53
+ "description": "[Video Description]",
54
+ "thumbnailUrl": "[Thumbnail Image URL]",
55
+ "uploadDate": "[YYYY-MM-DD]",
56
+ "contentUrl": "[Direct Video File URL]",
57
+ "hasPart": [
58
+ {
59
+ "@type": "Clip",
60
+ "name": "[Clip Title]",
61
+ "startOffset": 0,
62
+ "endOffset": 120,
63
+ "url": "[Video URL with timestamp, e.g. ?t=0]"
64
+ },
65
+ {
66
+ "@type": "Clip",
67
+ "name": "[Next Clip Title]",
68
+ "startOffset": 120,
69
+ "endOffset": 300,
70
+ "url": "[Video URL with timestamp, e.g. ?t=120]"
71
+ }
72
+ ]
73
+ }
74
+ },
75
+ {
76
+ "type": "SeekToAction",
77
+ "description": "Enable seek functionality in video rich results. Allows users to jump to specific timestamps from search.",
78
+ "template": {
79
+ "@context": "https://schema.org",
80
+ "@type": "VideoObject",
81
+ "name": "[Video Title]",
82
+ "description": "[Video Description]",
83
+ "thumbnailUrl": "[Thumbnail Image URL]",
84
+ "uploadDate": "[YYYY-MM-DD]",
85
+ "contentUrl": "[Direct Video File URL]",
86
+ "potentialAction": {
87
+ "@type": "SeekToAction",
88
+ "target": "[Video URL]?t={seek_to_second_number}",
89
+ "startOffset-input": "required name=seek_to_second_number"
90
+ }
91
+ }
92
+ },
93
+ {
94
+ "type": "SoftwareSourceCode",
95
+ "description": "Open source and code repository pages. Describes programming language, platform, and repository location.",
96
+ "template": {
97
+ "@context": "https://schema.org",
98
+ "@type": "SoftwareSourceCode",
99
+ "name": "[Repository Name]",
100
+ "description": "[Repository Description]",
101
+ "codeRepository": "[Repository URL, e.g. https://github.com/org/repo]",
102
+ "programmingLanguage": "[Language, e.g. Python]",
103
+ "runtimePlatform": "[Platform, e.g. Node.js]",
104
+ "author": {
105
+ "@type": "Person",
106
+ "name": "[Author Name]"
107
+ },
108
+ "license": "[License URL, e.g. https://opensource.org/licenses/MIT]",
109
+ "dateCreated": "[YYYY-MM-DD]",
110
+ "dateModified": "[YYYY-MM-DD]"
111
+ }
112
+ },
113
+ {
114
+ "type": "ProductGroup",
115
+ "description": "E-commerce product variants grouped by attributes like size, color. Enables variant-aware rich results with variesBy and hasVariant properties.",
116
+ "template": {
117
+ "@context": "https://schema.org",
118
+ "@type": "ProductGroup",
119
+ "name": "[Product Name]",
120
+ "description": "[Product group description]",
121
+ "productGroupID": "[product-group-id]",
122
+ "variesBy": ["https://schema.org/size", "https://schema.org/color"],
123
+ "hasVariant": [
124
+ {
125
+ "@type": "Product",
126
+ "name": "[Variant - Red, Large]",
127
+ "sku": "[SKU-001]",
128
+ "color": "[Red]",
129
+ "size": "[Large]",
130
+ "offers": {
131
+ "@type": "Offer",
132
+ "price": "[29.99]",
133
+ "priceCurrency": "USD",
134
+ "availability": "https://schema.org/InStock"
135
+ }
136
+ }
137
+ ]
138
+ }
139
+ },
140
+ {
141
+ "type": "ProfilePage",
142
+ "description": "Author, creator, or team member profile pages. Enhances E-E-A-T signals with mainEntity Person markup and sameAs links.",
143
+ "template": {
144
+ "@context": "https://schema.org",
145
+ "@type": "ProfilePage",
146
+ "mainEntity": {
147
+ "@type": "Person",
148
+ "name": "[Author Name]",
149
+ "url": "[Profile URL]",
150
+ "description": "[Author bio and expertise summary]",
151
+ "sameAs": [
152
+ "[Twitter URL]",
153
+ "[LinkedIn URL]"
154
+ ]
155
+ }
156
+ }
157
+ },
158
+ {
159
+ "type": "Certification",
160
+ "description": "Product certifications (Energy Star, safety, organic, etc.). Replaced EnergyConsumptionDetails in April 2025.",
161
+ "template": {
162
+ "@context": "https://schema.org",
163
+ "@type": "Product",
164
+ "name": "[Product Name]",
165
+ "hasCertification": {
166
+ "@type": "Certification",
167
+ "certificationIdentification": "[Certification Name, e.g. Energy Star]",
168
+ "issuedBy": {
169
+ "@type": "Organization",
170
+ "name": "[Issuing Organization, e.g. EPA]"
171
+ }
172
+ }
173
+ }
174
+ },
175
+ {
176
+ "type": "OfferShippingDetails",
177
+ "description": "Shipping and delivery information for e-commerce products. Includes shipping rate, handling time, and transit time.",
178
+ "template": {
179
+ "@context": "https://schema.org",
180
+ "@type": "Product",
181
+ "name": "[Product Name]",
182
+ "offers": {
183
+ "@type": "Offer",
184
+ "price": "[Price]",
185
+ "priceCurrency": "USD",
186
+ "shippingDetails": {
187
+ "@type": "OfferShippingDetails",
188
+ "shippingRate": {
189
+ "@type": "MonetaryAmount",
190
+ "value": "[0]",
191
+ "currency": "USD"
192
+ },
193
+ "deliveryTime": {
194
+ "@type": "ShippingDeliveryTime",
195
+ "handlingTime": {
196
+ "@type": "QuantitativeValue",
197
+ "minValue": 0,
198
+ "maxValue": 1,
199
+ "unitCode": "DAY"
200
+ },
201
+ "transitTime": {
202
+ "@type": "QuantitativeValue",
203
+ "minValue": 1,
204
+ "maxValue": 5,
205
+ "unitCode": "DAY"
206
+ }
207
+ }
208
+ }
209
+ }
210
+ }
211
+ }
212
+ ]
213
+ }
@@ -0,0 +1,217 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Analyze visual aspects of a web page using Playwright.
4
+
5
+ Usage:
6
+ python analyze_visual.py https://example.com
7
+ """
8
+
9
+ import argparse
10
+ import ipaddress
11
+ import json
12
+ import socket
13
+ import sys
14
+ from urllib.parse import ParseResult, urlparse
15
+
16
+ try:
17
+ from playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeout
18
+ except ImportError:
19
+ print("Error: playwright required. Install with: pip install playwright && playwright install chromium")
20
+ sys.exit(1)
21
+
22
+
23
+ def normalize_url(url: str) -> tuple[str, ParseResult]:
24
+ """Normalize URL and return (url, parsed_url)."""
25
+ parsed = urlparse(url)
26
+ if not parsed.scheme:
27
+ url = f"https://{url}"
28
+ parsed = urlparse(url)
29
+
30
+ if parsed.scheme not in ("http", "https"):
31
+ raise ValueError(f"Invalid URL scheme: {parsed.scheme}")
32
+ if not parsed.hostname:
33
+ raise ValueError("Invalid URL: missing hostname")
34
+
35
+ return url, parsed
36
+
37
+
38
+ def analyze_visual(url: str, timeout: int = 30000) -> dict:
39
+ """
40
+ Analyze visual aspects of a web page.
41
+
42
+ Args:
43
+ url: URL to analyze
44
+ timeout: Page load timeout in milliseconds
45
+
46
+ Returns:
47
+ Dictionary with visual analysis results
48
+ """
49
+ result = {
50
+ "url": url,
51
+ "above_fold": {
52
+ "h1_visible": False,
53
+ "cta_visible": False,
54
+ "hero_image": None,
55
+ },
56
+ "mobile": {
57
+ "viewport_meta": False,
58
+ "horizontal_scroll": False,
59
+ "touch_targets_ok": True,
60
+ },
61
+ "layout": {
62
+ "overlapping_elements": [],
63
+ "text_overflow": [],
64
+ },
65
+ "fonts": {
66
+ "base_size": None,
67
+ "readable": True,
68
+ },
69
+ "error": None,
70
+ }
71
+
72
+ try:
73
+ url, parsed = normalize_url(url)
74
+ result["url"] = url
75
+ except ValueError as e:
76
+ result["error"] = str(e)
77
+ return result
78
+
79
+ # SSRF prevention: block private/internal IPs
80
+ try:
81
+ resolved_ip = socket.gethostbyname(parsed.hostname)
82
+ ip = ipaddress.ip_address(resolved_ip)
83
+ if ip.is_private or ip.is_loopback or ip.is_reserved:
84
+ result["error"] = f"Blocked: URL resolves to private/internal IP ({resolved_ip})"
85
+ return result
86
+ except socket.gaierror:
87
+ pass
88
+
89
+ try:
90
+ with sync_playwright() as p:
91
+ browser = p.chromium.launch(headless=True)
92
+
93
+ # Desktop analysis
94
+ desktop = browser.new_context(viewport={"width": 1920, "height": 1080})
95
+ page = desktop.new_page()
96
+ page.goto(url, wait_until="networkidle", timeout=timeout)
97
+
98
+ # Check H1 visibility above fold
99
+ h1 = page.query_selector("h1")
100
+ if h1:
101
+ box = h1.bounding_box()
102
+ if box and box["y"] < 1080:
103
+ result["above_fold"]["h1_visible"] = True
104
+
105
+ # Check for CTA buttons above fold
106
+ cta_selectors = [
107
+ "a[href*='signup']",
108
+ "a[href*='contact']",
109
+ "a[href*='demo']",
110
+ "button:has-text('Get Started')",
111
+ "button:has-text('Sign Up')",
112
+ "button:has-text('Contact')",
113
+ ".cta",
114
+ "[class*='cta']",
115
+ ]
116
+ for selector in cta_selectors:
117
+ try:
118
+ cta = page.query_selector(selector)
119
+ if cta:
120
+ box = cta.bounding_box()
121
+ if box and box["y"] < 1080:
122
+ result["above_fold"]["cta_visible"] = True
123
+ break
124
+ except Exception:
125
+ pass
126
+
127
+ # Check hero image
128
+ hero_selectors = [
129
+ ".hero img",
130
+ "[class*='hero'] img",
131
+ "header img",
132
+ "main img:first-of-type",
133
+ ]
134
+ for selector in hero_selectors:
135
+ try:
136
+ hero = page.query_selector(selector)
137
+ if hero:
138
+ src = hero.get_attribute("src")
139
+ if src:
140
+ result["above_fold"]["hero_image"] = src
141
+ break
142
+ except Exception:
143
+ pass
144
+
145
+ desktop.close()
146
+
147
+ # Mobile analysis
148
+ mobile = browser.new_context(viewport={"width": 375, "height": 812})
149
+ page = mobile.new_page()
150
+ page.goto(url, wait_until="networkidle", timeout=timeout)
151
+
152
+ # Check viewport meta
153
+ viewport_meta = page.query_selector('meta[name="viewport"]')
154
+ result["mobile"]["viewport_meta"] = viewport_meta is not None
155
+
156
+ # Check for horizontal scroll
157
+ scroll_width = page.evaluate("document.documentElement.scrollWidth")
158
+ viewport_width = page.evaluate("window.innerWidth")
159
+ result["mobile"]["horizontal_scroll"] = scroll_width > viewport_width
160
+
161
+ # Check font size
162
+ base_font_size = page.evaluate("""
163
+ () => {
164
+ const body = document.body;
165
+ const style = window.getComputedStyle(body);
166
+ return parseFloat(style.fontSize);
167
+ }
168
+ """)
169
+ result["fonts"]["base_size"] = base_font_size
170
+ result["fonts"]["readable"] = base_font_size >= 16
171
+
172
+ mobile.close()
173
+ browser.close()
174
+
175
+ except PlaywrightTimeout:
176
+ result["error"] = f"Page load timed out after {timeout}ms"
177
+ except Exception as e:
178
+ result["error"] = str(e)
179
+
180
+ return result
181
+
182
+
183
+ def main():
184
+ parser = argparse.ArgumentParser(description="Analyze visual aspects of a web page")
185
+ parser.add_argument("url", help="URL to analyze")
186
+ parser.add_argument("--timeout", "-t", type=int, default=30000, help="Timeout in ms")
187
+ parser.add_argument("--json", "-j", action="store_true", help="Output as JSON")
188
+
189
+ args = parser.parse_args()
190
+
191
+ result = analyze_visual(args.url, timeout=args.timeout)
192
+
193
+ if args.json:
194
+ print(json.dumps(result, indent=2))
195
+ else:
196
+ print("Visual Analysis Results")
197
+ print("=" * 40)
198
+
199
+ print("\nAbove the Fold:")
200
+ print(f" H1 Visible: {'✓' if result['above_fold']['h1_visible'] else '✗'}")
201
+ print(f" CTA Visible: {'✓' if result['above_fold']['cta_visible'] else '✗'}")
202
+ print(f" Hero Image: {result['above_fold']['hero_image'] or 'None found'}")
203
+
204
+ print("\nMobile Responsiveness:")
205
+ print(f" Viewport Meta: {'✓' if result['mobile']['viewport_meta'] else '✗'}")
206
+ print(f" Horizontal Scroll: {'✗ (problem)' if result['mobile']['horizontal_scroll'] else '✓'}")
207
+
208
+ print("\nTypography:")
209
+ print(f" Base Font Size: {result['fonts']['base_size']}px")
210
+ print(f" Readable (≥16px): {'✓' if result['fonts']['readable'] else '✗'}")
211
+
212
+ if result["error"]:
213
+ print(f"\nError: {result['error']}")
214
+
215
+
216
+ if __name__ == "__main__":
217
+ main()