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.
- package/.agent/agent.md +96 -0
- package/.agent/skills/seo/SKILL.md +153 -0
- package/.agent/skills/seo/references/cwv-thresholds.md +108 -0
- package/.agent/skills/seo/references/eeat-framework.md +214 -0
- package/.agent/skills/seo/references/local-schema-types.md +230 -0
- package/.agent/skills/seo/references/local-seo-signals.md +218 -0
- package/.agent/skills/seo/references/maps-api-endpoints.md +160 -0
- package/.agent/skills/seo/references/maps-free-apis.md +176 -0
- package/.agent/skills/seo/references/maps-gbp-checklist.md +150 -0
- package/.agent/skills/seo/references/maps-geo-grid.md +154 -0
- package/.agent/skills/seo/references/quality-gates.md +155 -0
- package/.agent/skills/seo/references/schema-types.md +118 -0
- package/.agent/skills/seo/schema/templates.json +213 -0
- package/.agent/skills/seo/scripts/analyze_visual.py +217 -0
- package/.agent/skills/seo/scripts/capture_screenshot.py +181 -0
- package/.agent/skills/seo/scripts/fetch_page.py +196 -0
- package/.agent/skills/seo/scripts/parse_html.py +201 -0
- package/.agent/skills/seo-audit/SKILL.md +278 -0
- package/.agent/skills/seo-competitor-pages/SKILL.md +212 -0
- package/.agent/skills/seo-content/SKILL.md +230 -0
- package/.agent/skills/seo-dataforseo/SKILL.md +418 -0
- package/.agent/skills/seo-geo/SKILL.md +305 -0
- package/.agent/skills/seo-google/SKILL.md +405 -0
- package/.agent/skills/seo-google/assets/templates/cwv-audit-report.md +48 -0
- package/.agent/skills/seo-google/assets/templates/gsc-performance-report.md +44 -0
- package/.agent/skills/seo-google/assets/templates/indexation-status-report.md +43 -0
- package/.agent/skills/seo-google/references/auth-setup.md +154 -0
- package/.agent/skills/seo-google/references/ga4-data-api.md +184 -0
- package/.agent/skills/seo-google/references/indexing-api.md +107 -0
- package/.agent/skills/seo-google/references/keyword-planner-api.md +66 -0
- package/.agent/skills/seo-google/references/nlp-api.md +55 -0
- package/.agent/skills/seo-google/references/pagespeed-crux-api.md +204 -0
- package/.agent/skills/seo-google/references/rate-limits-quotas.md +75 -0
- package/.agent/skills/seo-google/references/search-console-api.md +156 -0
- package/.agent/skills/seo-google/references/supplementary-apis.md +99 -0
- package/.agent/skills/seo-google/references/youtube-api.md +49 -0
- package/.agent/skills/seo-google/scripts/crux_history.py +321 -0
- package/.agent/skills/seo-google/scripts/ga4_report.py +478 -0
- package/.agent/skills/seo-google/scripts/google_auth.py +795 -0
- package/.agent/skills/seo-google/scripts/google_report.py +2273 -0
- package/.agent/skills/seo-google/scripts/gsc_inspect.py +340 -0
- package/.agent/skills/seo-google/scripts/gsc_query.py +378 -0
- package/.agent/skills/seo-google/scripts/indexing_notify.py +313 -0
- package/.agent/skills/seo-google/scripts/keyword_planner.py +297 -0
- package/.agent/skills/seo-google/scripts/nlp_analyze.py +309 -0
- package/.agent/skills/seo-google/scripts/pagespeed_check.py +649 -0
- package/.agent/skills/seo-google/scripts/youtube_search.py +355 -0
- package/.agent/skills/seo-hreflang/SKILL.md +192 -0
- package/.agent/skills/seo-image-gen/SKILL.md +211 -0
- package/.agent/skills/seo-image-gen/references/cost-tracking.md +47 -0
- package/.agent/skills/seo-image-gen/references/gemini-models.md +200 -0
- package/.agent/skills/seo-image-gen/references/mcp-tools.md +115 -0
- package/.agent/skills/seo-image-gen/references/post-processing.md +192 -0
- package/.agent/skills/seo-image-gen/references/presets.md +69 -0
- package/.agent/skills/seo-image-gen/references/prompt-engineering.md +411 -0
- package/.agent/skills/seo-image-gen/references/seo-image-presets.md +137 -0
- package/.agent/skills/seo-image-gen/scripts/batch.py +97 -0
- package/.agent/skills/seo-image-gen/scripts/cost_tracker.py +191 -0
- package/.agent/skills/seo-image-gen/scripts/edit.py +141 -0
- package/.agent/skills/seo-image-gen/scripts/generate.py +149 -0
- package/.agent/skills/seo-image-gen/scripts/presets.py +153 -0
- package/.agent/skills/seo-image-gen/scripts/setup_mcp.py +151 -0
- package/.agent/skills/seo-image-gen/scripts/validate_setup.py +133 -0
- package/.agent/skills/seo-images/SKILL.md +176 -0
- package/.agent/skills/seo-local/SKILL.md +381 -0
- package/.agent/skills/seo-maps/SKILL.md +328 -0
- package/.agent/skills/seo-page/SKILL.md +86 -0
- package/.agent/skills/seo-plan/SKILL.md +118 -0
- package/.agent/skills/seo-plan/assets/agency.md +175 -0
- package/.agent/skills/seo-plan/assets/ecommerce.md +167 -0
- package/.agent/skills/seo-plan/assets/generic.md +144 -0
- package/.agent/skills/seo-plan/assets/local-service.md +160 -0
- package/.agent/skills/seo-plan/assets/publisher.md +153 -0
- package/.agent/skills/seo-plan/assets/saas.md +135 -0
- package/.agent/skills/seo-programmatic/SKILL.md +171 -0
- package/.agent/skills/seo-schema/SKILL.md +223 -0
- package/.agent/skills/seo-sitemap/SKILL.md +180 -0
- package/.agent/skills/seo-technical/SKILL.md +211 -0
- package/.agent/workflows/seo-audit.md +17 -0
- package/.agent/workflows/seo-competitor-pages.md +12 -0
- package/.agent/workflows/seo-content.md +14 -0
- package/.agent/workflows/seo-geo.md +12 -0
- package/.agent/workflows/seo-google.md +12 -0
- package/.agent/workflows/seo-hreflang.md +12 -0
- package/.agent/workflows/seo-images.md +13 -0
- package/.agent/workflows/seo-local.md +12 -0
- package/.agent/workflows/seo-maps.md +11 -0
- package/.agent/workflows/seo-page.md +13 -0
- package/.agent/workflows/seo-plan.md +13 -0
- package/.agent/workflows/seo-programmatic.md +12 -0
- package/.agent/workflows/seo-schema.md +11 -0
- package/.agent/workflows/seo-sitemap.md +9 -0
- package/.agent/workflows/seo-technical.md +18 -0
- package/LICENSE +88 -0
- package/README.md +122 -0
- package/bin/cli.js +117 -0
- package/docs/ARCHITECTURE.md +218 -0
- package/docs/COMMANDS.md +184 -0
- package/docs/INSTALLATION.md +100 -0
- package/docs/MCP-INTEGRATION.md +153 -0
- package/docs/TROUBLESHOOTING.md +151 -0
- package/docs/superpowers/plans/2026-03-13-github-audit-fixes.md +511 -0
- package/extensions/banana/README.md +95 -0
- package/extensions/banana/docs/BANANA-SETUP.md +86 -0
- package/extensions/banana/install.sh +170 -0
- package/extensions/banana/references/cost-tracking.md +47 -0
- package/extensions/banana/references/gemini-models.md +200 -0
- package/extensions/banana/references/mcp-tools.md +115 -0
- package/extensions/banana/references/post-processing.md +192 -0
- package/extensions/banana/references/presets.md +69 -0
- package/extensions/banana/references/prompt-engineering.md +411 -0
- package/extensions/banana/references/seo-image-presets.md +137 -0
- package/extensions/banana/scripts/batch.py +97 -0
- package/extensions/banana/scripts/cost_tracker.py +191 -0
- package/extensions/banana/scripts/edit.py +141 -0
- package/extensions/banana/scripts/generate.py +149 -0
- package/extensions/banana/scripts/presets.py +153 -0
- package/extensions/banana/scripts/setup_mcp.py +151 -0
- package/extensions/banana/scripts/validate_setup.py +133 -0
- package/extensions/banana/uninstall.sh +43 -0
- package/extensions/dataforseo/README.md +169 -0
- package/extensions/dataforseo/docs/DATAFORSEO-SETUP.md +74 -0
- package/extensions/dataforseo/field-config.json +280 -0
- package/extensions/dataforseo/install.ps1 +110 -0
- package/extensions/dataforseo/install.sh +161 -0
- package/extensions/dataforseo/uninstall.ps1 +35 -0
- package/extensions/dataforseo/uninstall.sh +39 -0
- package/lib/api.js +190 -0
- package/lib/fingerprint.js +68 -0
- package/lib/installer.js +486 -0
- package/lib/utils.js +254 -0
- package/package.json +40 -0
- package/pyproject.toml +11 -0
- package/requirements-google.txt +15 -0
- 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()
|