@ucptools/validator 1.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 (121) hide show
  1. package/CLAUDE.md +109 -0
  2. package/CONTRIBUTING.md +113 -0
  3. package/LICENSE +21 -0
  4. package/README.md +203 -0
  5. package/api/analyze-feed.js +140 -0
  6. package/api/badge.js +185 -0
  7. package/api/benchmark.js +177 -0
  8. package/api/directory-stats.ts +29 -0
  9. package/api/directory.ts +73 -0
  10. package/api/generate-compliance.js +143 -0
  11. package/api/generate-schema.js +457 -0
  12. package/api/generate.js +132 -0
  13. package/api/security-scan.js +133 -0
  14. package/api/simulate.js +187 -0
  15. package/api/tsconfig.json +10 -0
  16. package/api/validate.js +1351 -0
  17. package/apify-actor/.actor/actor.json +68 -0
  18. package/apify-actor/.actor/input_schema.json +32 -0
  19. package/apify-actor/APIFY-STORE-LISTING.md +412 -0
  20. package/apify-actor/Dockerfile +8 -0
  21. package/apify-actor/README.md +166 -0
  22. package/apify-actor/main.ts +111 -0
  23. package/apify-actor/package.json +17 -0
  24. package/apify-actor/src/main.js +199 -0
  25. package/docs/BRAND-IDENTITY.md +238 -0
  26. package/docs/BRAND-STYLE-GUIDE.md +356 -0
  27. package/drizzle/0000_black_king_cobra.sql +39 -0
  28. package/drizzle/meta/0000_snapshot.json +309 -0
  29. package/drizzle/meta/_journal.json +13 -0
  30. package/drizzle.config.ts +10 -0
  31. package/examples/full-profile.json +70 -0
  32. package/examples/minimal-profile.json +23 -0
  33. package/package.json +69 -0
  34. package/public/.well-known/ucp +25 -0
  35. package/public/android-chrome-192x192.png +0 -0
  36. package/public/android-chrome-512x512.png +0 -0
  37. package/public/apple-touch-icon.png +0 -0
  38. package/public/brand.css +321 -0
  39. package/public/directory.html +701 -0
  40. package/public/favicon-16x16.png +0 -0
  41. package/public/favicon-32x32.png +0 -0
  42. package/public/favicon.ico +0 -0
  43. package/public/guides/bigcommerce.html +743 -0
  44. package/public/guides/fastucp.html +838 -0
  45. package/public/guides/magento.html +779 -0
  46. package/public/guides/shopify.html +726 -0
  47. package/public/guides/squarespace.html +749 -0
  48. package/public/guides/wix.html +747 -0
  49. package/public/guides/woocommerce.html +733 -0
  50. package/public/index.html +3835 -0
  51. package/public/learn.html +396 -0
  52. package/public/logo.jpeg +0 -0
  53. package/public/og-image-icon.png +0 -0
  54. package/public/og-image.png +0 -0
  55. package/public/robots.txt +6 -0
  56. package/public/site.webmanifest +31 -0
  57. package/public/sitemap.xml +69 -0
  58. package/public/social/linkedin-banner-1128x191.png +0 -0
  59. package/public/social/temp.PNG +0 -0
  60. package/public/social/x-header-1500x500.png +0 -0
  61. package/public/verify.html +410 -0
  62. package/scripts/generate-favicons.js +44 -0
  63. package/scripts/generate-ico.js +23 -0
  64. package/scripts/generate-og-image.js +45 -0
  65. package/scripts/reset-db.ts +77 -0
  66. package/scripts/seed-db.ts +71 -0
  67. package/scripts/setup-benchmark-db.js +70 -0
  68. package/src/api/server.ts +266 -0
  69. package/src/cli/index.ts +302 -0
  70. package/src/compliance/compliance-generator.ts +452 -0
  71. package/src/compliance/index.ts +28 -0
  72. package/src/compliance/templates.ts +338 -0
  73. package/src/compliance/types.ts +170 -0
  74. package/src/db/index.ts +28 -0
  75. package/src/db/schema.ts +84 -0
  76. package/src/feed-analyzer/feed-analyzer.ts +726 -0
  77. package/src/feed-analyzer/index.ts +34 -0
  78. package/src/feed-analyzer/types.ts +354 -0
  79. package/src/generator/index.ts +7 -0
  80. package/src/generator/key-generator.ts +124 -0
  81. package/src/generator/profile-builder.ts +402 -0
  82. package/src/hosting/artifacts-generator.ts +679 -0
  83. package/src/hosting/index.ts +6 -0
  84. package/src/index.ts +105 -0
  85. package/src/security/index.ts +15 -0
  86. package/src/security/security-scanner.ts +604 -0
  87. package/src/security/types.ts +55 -0
  88. package/src/services/directory.ts +434 -0
  89. package/src/simulator/agent-simulator.ts +941 -0
  90. package/src/simulator/index.ts +7 -0
  91. package/src/simulator/types.ts +170 -0
  92. package/src/types/generator.ts +140 -0
  93. package/src/types/index.ts +7 -0
  94. package/src/types/ucp-profile.ts +140 -0
  95. package/src/types/validation.ts +89 -0
  96. package/src/validator/index.ts +194 -0
  97. package/src/validator/network-validator.ts +417 -0
  98. package/src/validator/rules-validator.ts +297 -0
  99. package/src/validator/sdk-validator.ts +330 -0
  100. package/src/validator/structural-validator.ts +476 -0
  101. package/tests/fixtures/non-compliant-profile.json +25 -0
  102. package/tests/fixtures/official-sample-profile.json +75 -0
  103. package/tests/integration/benchmark.test.ts +207 -0
  104. package/tests/integration/database.test.ts +163 -0
  105. package/tests/integration/directory-api.test.ts +268 -0
  106. package/tests/integration/simulate-api.test.ts +230 -0
  107. package/tests/integration/validate-api.test.ts +269 -0
  108. package/tests/setup.ts +15 -0
  109. package/tests/unit/agent-simulator.test.ts +575 -0
  110. package/tests/unit/compliance-generator.test.ts +374 -0
  111. package/tests/unit/directory-service.test.ts +272 -0
  112. package/tests/unit/feed-analyzer.test.ts +517 -0
  113. package/tests/unit/lint-suggestions.test.ts +423 -0
  114. package/tests/unit/official-samples.test.ts +211 -0
  115. package/tests/unit/pdf-report.test.ts +390 -0
  116. package/tests/unit/sdk-validator.test.ts +531 -0
  117. package/tests/unit/security-scanner.test.ts +410 -0
  118. package/tests/unit/validation.test.ts +390 -0
  119. package/tsconfig.json +20 -0
  120. package/vercel.json +34 -0
  121. package/vitest.config.ts +22 -0
@@ -0,0 +1,410 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <!-- Google Analytics -->
5
+ <script async src="https://www.googletagmanager.com/gtag/js?id=G-J5JSHV7H1E"></script>
6
+ <script>
7
+ window.dataLayer = window.dataLayer || [];
8
+ function gtag(){dataLayer.push(arguments);}
9
+ gtag('js', new Date());
10
+ gtag('config', 'G-J5JSHV7H1E');
11
+ </script>
12
+ <!-- Ahrefs Analytics -->
13
+ <script src="https://analytics.ahrefs.com/analytics.js" data-key="w/KDij6w81HzA8oXaw60JA" async></script>
14
+ <meta charset="UTF-8">
15
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
16
+ <title>AI Commerce Verification Results | Universal Commerce Protocol (UCP) Score | UCP.tools</title>
17
+ <meta name="description" content="View AI Commerce readiness verification results. Check Universal Commerce Protocol (UCP) compliance score, Schema.org validation, and Google AI Mode compatibility for any e-commerce store.">
18
+ <meta name="keywords" content="UCP Verification, AI Commerce Score, Universal Commerce Protocol, Schema.org Validation, AI Shopping Agent, Google AI Mode, E-commerce Readiness">
19
+ <meta name="robots" content="index, follow">
20
+ <link rel="canonical" href="https://ucptools.dev/verify">
21
+ <link rel="icon" type="image/x-icon" href="/favicon.ico">
22
+ <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
23
+ <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
24
+
25
+ <!-- Open Graph / Facebook -->
26
+ <meta property="og:type" content="website">
27
+ <meta property="og:url" content="https://ucptools.dev/verify">
28
+ <meta property="og:title" content="AI Commerce Verification Results | UCP.tools">
29
+ <meta property="og:description" content="View AI Commerce readiness score and Universal Commerce Protocol (UCP) validation results for e-commerce stores.">
30
+ <meta property="og:image" content="https://ucptools.dev/og-image.png">
31
+ <meta property="og:site_name" content="UCP.tools">
32
+
33
+ <!-- Twitter Card -->
34
+ <meta name="twitter:card" content="summary_large_image">
35
+ <meta name="twitter:url" content="https://ucptools.dev/verify">
36
+ <meta name="twitter:title" content="AI Commerce Verification Results | UCP.tools">
37
+ <meta name="twitter:description" content="Check Universal Commerce Protocol (UCP) compliance and AI shopping agent readiness score.">
38
+ <meta name="twitter:image" content="https://ucptools.dev/og-image.png">
39
+
40
+ <!-- JSON-LD Structured Data -->
41
+ <script type="application/ld+json">
42
+ {
43
+ "@context": "https://schema.org",
44
+ "@type": "WebPage",
45
+ "name": "AI Commerce Verification Results",
46
+ "description": "View Universal Commerce Protocol (UCP) verification results and AI Commerce readiness scores for e-commerce stores.",
47
+ "url": "https://ucptools.dev/verify",
48
+ "isPartOf": {
49
+ "@type": "WebSite",
50
+ "name": "UCP.tools",
51
+ "url": "https://ucptools.dev/"
52
+ },
53
+ "breadcrumb": {
54
+ "@type": "BreadcrumbList",
55
+ "itemListElement": [
56
+ {"@type": "ListItem", "position": 1, "name": "Home", "item": "https://ucptools.dev/"},
57
+ {"@type": "ListItem", "position": 2, "name": "Verification Results", "item": "https://ucptools.dev/verify"}
58
+ ]
59
+ }
60
+ }
61
+ </script>
62
+ <style>
63
+ :root {
64
+ --brand-blue: #2E86AB;
65
+ --brand-teal: #36B5A2;
66
+ --brand-green: #47C97A;
67
+ --brand-gradient: linear-gradient(135deg, #2E86AB 0%, #36B5A2 50%, #47C97A 100%);
68
+ --color-dark: #1A2B3C;
69
+ --color-medium: #5A6978;
70
+ --color-light: #94A3B8;
71
+ --color-border: #E2E8F0;
72
+ --color-background: #F8FAFC;
73
+ --color-card: #FFFFFF;
74
+ --grade-a-bg: #DCFCE7; --grade-a-text: #16A34A;
75
+ --grade-b-bg: #DBEAFE; --grade-b-text: #2563EB;
76
+ --grade-c-bg: #FEF9C3; --grade-c-text: #CA8A04;
77
+ --grade-d-bg: #FED7AA; --grade-d-text: #EA580C;
78
+ --grade-f-bg: #FEE2E2; --grade-f-text: #DC2626;
79
+ }
80
+ * { box-sizing: border-box; margin: 0; padding: 0; }
81
+ body {
82
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
83
+ background: var(--color-background);
84
+ color: var(--color-dark);
85
+ line-height: 1.6;
86
+ min-height: 100vh;
87
+ display: flex;
88
+ flex-direction: column;
89
+ }
90
+ .container { max-width: 800px; margin: 0 auto; padding: 0 20px; }
91
+ header {
92
+ background: var(--color-card);
93
+ border-bottom: 1px solid var(--color-border);
94
+ padding: 16px 0;
95
+ }
96
+ .header-inner { display: flex; justify-content: space-between; align-items: center; }
97
+ .logo {
98
+ display: flex; align-items: center; gap: 12px;
99
+ font-size: 24px; font-weight: 700; text-decoration: none;
100
+ }
101
+ .logo-icon { width: 40px; height: 40px; border-radius: 8px; }
102
+ .logo-text {
103
+ background: var(--brand-gradient);
104
+ -webkit-background-clip: text; -webkit-text-fill-color: transparent;
105
+ }
106
+ main { flex: 1; padding: 60px 20px; }
107
+ .verify-card {
108
+ background: var(--color-card);
109
+ border-radius: 16px;
110
+ padding: 40px;
111
+ box-shadow: 0 4px 20px rgba(0,0,0,0.1);
112
+ text-align: center;
113
+ }
114
+ .grade-circle {
115
+ width: 140px; height: 140px;
116
+ border-radius: 50%;
117
+ display: flex; align-items: center; justify-content: center;
118
+ margin: 0 auto 24px;
119
+ font-size: 56px; font-weight: 800;
120
+ box-shadow: 0 8px 24px rgba(0,0,0,0.15);
121
+ color: white;
122
+ }
123
+ .grade-A { background: linear-gradient(135deg, #22c55e, #16a34a); }
124
+ .grade-B { background: linear-gradient(135deg, #3b82f6, #2563eb); }
125
+ .grade-C { background: linear-gradient(135deg, #eab308, #ca8a04); }
126
+ .grade-D { background: linear-gradient(135deg, #f97316, #ea580c); }
127
+ .grade-F { background: linear-gradient(135deg, #ef4444, #dc2626); }
128
+ .domain-name {
129
+ font-size: 24px; font-weight: 600;
130
+ color: var(--color-dark); margin-bottom: 8px;
131
+ }
132
+ .readiness-label {
133
+ display: inline-block;
134
+ padding: 8px 20px; border-radius: 20px;
135
+ font-weight: 600; font-size: 14px;
136
+ margin-bottom: 24px;
137
+ }
138
+ .score-detail {
139
+ font-size: 36px; font-weight: 700;
140
+ background: var(--brand-gradient);
141
+ -webkit-background-clip: text; -webkit-text-fill-color: transparent;
142
+ margin-bottom: 8px;
143
+ }
144
+ .verified-date { color: var(--color-medium); font-size: 14px; margin-bottom: 32px; }
145
+ .stats-grid {
146
+ display: grid; grid-template-columns: repeat(3, 1fr);
147
+ gap: 16px; margin-bottom: 32px;
148
+ text-align: center;
149
+ }
150
+ .stat-item {
151
+ padding: 16px;
152
+ background: var(--color-background);
153
+ border-radius: 8px;
154
+ }
155
+ .stat-value { font-size: 24px; font-weight: 700; color: var(--color-dark); }
156
+ .stat-label { font-size: 12px; color: var(--color-medium); }
157
+ .cta-section {
158
+ padding-top: 24px;
159
+ border-top: 1px solid var(--color-border);
160
+ }
161
+ .btn {
162
+ display: inline-block;
163
+ padding: 14px 28px;
164
+ background: var(--brand-gradient);
165
+ color: white;
166
+ border: none; border-radius: 8px;
167
+ font-size: 16px; font-weight: 600;
168
+ cursor: pointer; text-decoration: none;
169
+ transition: transform 0.2s, box-shadow 0.2s;
170
+ }
171
+ .btn:hover {
172
+ transform: translateY(-1px);
173
+ box-shadow: 0 4px 12px rgba(46, 134, 171, 0.4);
174
+ }
175
+ .loading {
176
+ text-align: center;
177
+ padding: 60px 20px;
178
+ color: var(--color-medium);
179
+ }
180
+ .spinner {
181
+ width: 40px; height: 40px;
182
+ border: 3px solid var(--color-border);
183
+ border-top-color: var(--brand-blue);
184
+ border-radius: 50%;
185
+ animation: spin 1s linear infinite;
186
+ margin: 0 auto 16px;
187
+ }
188
+ @keyframes spin { to { transform: rotate(360deg); } }
189
+ footer {
190
+ padding: 24px 20px;
191
+ text-align: center;
192
+ color: var(--color-medium);
193
+ border-top: 1px solid var(--color-border);
194
+ background: var(--color-card);
195
+ font-size: 14px;
196
+ }
197
+ footer a { color: var(--brand-blue); text-decoration: none; }
198
+ .error-message {
199
+ background: #FEE2E2;
200
+ color: #DC2626;
201
+ padding: 20px;
202
+ border-radius: 8px;
203
+ margin-bottom: 24px;
204
+ }
205
+ .issues-summary {
206
+ text-align: left;
207
+ margin-top: 24px;
208
+ padding: 20px;
209
+ background: var(--color-background);
210
+ border-radius: 8px;
211
+ }
212
+ .issue-category {
213
+ margin-bottom: 16px;
214
+ }
215
+ .issue-category h4 {
216
+ font-size: 14px;
217
+ color: var(--color-dark);
218
+ margin-bottom: 8px;
219
+ display: flex;
220
+ align-items: center;
221
+ gap: 8px;
222
+ }
223
+ .issue-badge {
224
+ font-size: 11px;
225
+ padding: 2px 8px;
226
+ border-radius: 10px;
227
+ font-weight: 600;
228
+ }
229
+ .issue-badge.errors { background: #FEE2E2; color: #DC2626; }
230
+ .issue-badge.warnings { background: #FEF9C3; color: #CA8A04; }
231
+ .issue-badge.ok { background: #DCFCE7; color: #16A34A; }
232
+ @media (max-width: 600px) {
233
+ .stats-grid { grid-template-columns: 1fr; }
234
+ .verify-card { padding: 24px; }
235
+ }
236
+ </style>
237
+ </head>
238
+ <body>
239
+ <header>
240
+ <div class="container header-inner">
241
+ <a href="/" class="logo">
242
+ <img src="/logo.jpeg" alt="UCP.tools" class="logo-icon">
243
+ <span class="logo-text">UCP.tools</span>
244
+ </a>
245
+ <a href="/" class="btn" style="padding: 10px 20px; font-size: 14px;">Check Your Store</a>
246
+ </div>
247
+ </header>
248
+
249
+ <main>
250
+ <div class="container">
251
+ <div id="loading" class="loading">
252
+ <div class="spinner"></div>
253
+ <p>Verifying AI Commerce readiness...</p>
254
+ </div>
255
+
256
+ <div id="result" class="verify-card" style="display: none;"></div>
257
+ </div>
258
+ </main>
259
+
260
+ <footer>
261
+ <div class="container">
262
+ <p><a href="/">UCP.tools</a> - Free AI Commerce Readiness Checker</p>
263
+ <p style="margin-top: 4px;">Powered by Universal Commerce Protocol</p>
264
+ </div>
265
+ </footer>
266
+
267
+ <script>
268
+ async function verifyDomain() {
269
+ const params = new URLSearchParams(window.location.search);
270
+ const domain = params.get('domain') || params.get('d');
271
+
272
+ if (!domain) {
273
+ document.getElementById('loading').style.display = 'none';
274
+ document.getElementById('result').style.display = 'block';
275
+ document.getElementById('result').innerHTML = `
276
+ <div class="error-message">
277
+ <strong>No domain specified</strong>
278
+ <p>Add ?domain=yourstore.com to the URL</p>
279
+ </div>
280
+ <a href="/" class="btn">Check Your Store</a>
281
+ `;
282
+ return;
283
+ }
284
+
285
+ const cleanDomain = domain.replace(/^https?:\/\//, '').replace(/\/$/, '').split('/')[0];
286
+ document.title = `${cleanDomain} - AI Commerce Verification`;
287
+
288
+ try {
289
+ const res = await fetch('/api/validate', {
290
+ method: 'POST',
291
+ headers: { 'Content-Type': 'application/json' },
292
+ body: JSON.stringify({ domain: cleanDomain })
293
+ });
294
+
295
+ const data = await res.json();
296
+
297
+ const grade = data.ai_readiness?.grade || 'F';
298
+ const score = data.ai_readiness?.score || 0;
299
+ const label = data.ai_readiness?.label || 'Not Ready';
300
+ const level = data.ai_readiness?.level || 'not_ready';
301
+
302
+ const levelColors = {
303
+ 'ready': { bg: '#DCFCE7', text: '#16A34A' },
304
+ 'partial': { bg: '#FEF9C3', text: '#CA8A04' },
305
+ 'limited': { bg: '#FED7AA', text: '#EA580C' },
306
+ 'not_ready': { bg: '#FEE2E2', text: '#DC2626' }
307
+ };
308
+ const colors = levelColors[level] || levelColors.not_ready;
309
+
310
+ const ucpErrors = data.ucp?.issues?.filter(i => i.severity === 'error').length || 0;
311
+ const schemaErrors = data.schema?.issues?.filter(i => i.severity === 'error').length || 0;
312
+ const productCompleteness = data.product_quality?.completeness || 0;
313
+
314
+ const ucpWarnings = data.ucp?.issues?.filter(i => i.severity === 'warn').length || 0;
315
+ const schemaWarnings = data.schema?.issues?.filter(i => i.severity === 'warn').length || 0;
316
+ const returnPolicies = data.schema?.stats?.returnPolicies || 0;
317
+
318
+ document.getElementById('loading').style.display = 'none';
319
+ document.getElementById('result').style.display = 'block';
320
+ document.getElementById('result').innerHTML = `
321
+ <div class="grade-circle grade-${grade}">${grade}</div>
322
+ <div class="domain-name">${cleanDomain}</div>
323
+ <div class="readiness-label" style="background: ${colors.bg}; color: ${colors.text};">${label}</div>
324
+ <div class="score-detail">${score}/100</div>
325
+ <div class="verified-date">Verified ${new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })}</div>
326
+
327
+ ${data.benchmark ? `
328
+ <div style="margin: 20px 0; padding: 16px; background: linear-gradient(135deg, rgba(99,102,241,0.1), rgba(168,85,247,0.1)); border-radius: 12px; border: 1px solid rgba(99,102,241,0.2);">
329
+ <div style="font-size: 28px; font-weight: 800; color: #6366F1;">${data.benchmark.percentile}%</div>
330
+ <div style="font-size: 14px; color: var(--color-medium);">better than ${data.benchmark.total_sites_analyzed.toLocaleString()} sites analyzed</div>
331
+ </div>
332
+ ` : ''}
333
+
334
+ <div class="stats-grid">
335
+ <div class="stat-item">
336
+ <div class="stat-value" style="color: ${data.ucp?.found ? '#16A34A' : '#DC2626'};">${data.ucp?.found ? '&#10003;' : '&#10005;'}</div>
337
+ <div class="stat-label">UCP Profile</div>
338
+ </div>
339
+ <div class="stat-item">
340
+ <div class="stat-value">${data.schema?.stats?.products || 0}</div>
341
+ <div class="stat-label">Products</div>
342
+ </div>
343
+ <div class="stat-item">
344
+ <div class="stat-value" style="color: ${productCompleteness >= 80 ? '#16A34A' : productCompleteness >= 60 ? '#CA8A04' : '#DC2626'};">${productCompleteness}%</div>
345
+ <div class="stat-label">Completeness</div>
346
+ </div>
347
+ </div>
348
+
349
+ <div class="issues-summary">
350
+ <div class="issue-category">
351
+ <h4>
352
+ <span style="margin-right: 8px;">&#128279;</span> UCP Profile
353
+ ${ucpErrors === 0 && data.ucp?.found
354
+ ? '<span class="issue-badge ok">&#10003; Valid</span>'
355
+ : ucpErrors > 0
356
+ ? '<span class="issue-badge errors">' + ucpErrors + ' errors</span>'
357
+ : '<span class="issue-badge warnings">Not found</span>'
358
+ }
359
+ </h4>
360
+ </div>
361
+ <div class="issue-category">
362
+ <h4>
363
+ <span style="margin-right: 8px;">&#128202;</span> Schema.org
364
+ ${schemaErrors === 0
365
+ ? '<span class="issue-badge ok">&#10003; Valid</span>'
366
+ : '<span class="issue-badge errors">' + schemaErrors + ' issues</span>'
367
+ }
368
+ </h4>
369
+ </div>
370
+ <div class="issue-category">
371
+ <h4>
372
+ <span style="margin-right: 8px;">&#128230;</span> Return Policy
373
+ ${returnPolicies > 0
374
+ ? '<span class="issue-badge ok">&#10003; Found</span>'
375
+ : '<span class="issue-badge warnings">Missing</span>'
376
+ }
377
+ </h4>
378
+ </div>
379
+ </div>
380
+
381
+ <div class="cta-section">
382
+ <p style="margin-bottom: 16px; color: var(--color-medium); font-size: 15px;">
383
+ ${score >= 70
384
+ ? '&#127881; Great job! Your store is ready for AI shopping agents.'
385
+ : '&#128736; Improve your score to be ready for AI shopping agents.'
386
+ }
387
+ </p>
388
+ <div style="display: flex; gap: 12px; justify-content: center; flex-wrap: wrap;">
389
+ <a href="/?domain=${encodeURIComponent(cleanDomain)}" class="btn">View Full Report</a>
390
+ <a href="/" class="btn" style="background: var(--color-background); color: var(--color-dark); border: 1px solid var(--color-border);">Check Another Store</a>
391
+ </div>
392
+ </div>
393
+ `;
394
+ } catch (err) {
395
+ document.getElementById('loading').style.display = 'none';
396
+ document.getElementById('result').style.display = 'block';
397
+ document.getElementById('result').innerHTML = `
398
+ <div class="error-message">
399
+ <strong>Verification failed</strong>
400
+ <p>${err.message}</p>
401
+ </div>
402
+ <a href="/" class="btn">Try Again</a>
403
+ `;
404
+ }
405
+ }
406
+
407
+ verifyDomain();
408
+ </script>
409
+ </body>
410
+ </html>
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Generate favicons and app icons from logo
3
+ *
4
+ * Run: npm install sharp && node scripts/generate-favicons.js
5
+ */
6
+
7
+ const sharp = require('sharp');
8
+ const path = require('path');
9
+ const fs = require('fs');
10
+
11
+ const inputLogo = path.join(__dirname, '../public/logo.jpeg');
12
+ const outputDir = path.join(__dirname, '../public');
13
+
14
+ const sizes = [
15
+ { name: 'favicon-16x16.png', size: 16 },
16
+ { name: 'favicon-32x32.png', size: 32 },
17
+ { name: 'apple-touch-icon.png', size: 180 },
18
+ { name: 'android-chrome-192x192.png', size: 192 },
19
+ { name: 'android-chrome-512x512.png', size: 512 },
20
+ { name: 'og-image-icon.png', size: 256 },
21
+ ];
22
+
23
+ async function generateFavicons() {
24
+ console.log('Generating favicons from:', inputLogo);
25
+
26
+ for (const { name, size } of sizes) {
27
+ const output = path.join(outputDir, name);
28
+ await sharp(inputLogo)
29
+ .resize(size, size, { fit: 'cover' })
30
+ .png()
31
+ .toFile(output);
32
+ console.log(`Created: ${name} (${size}x${size})`);
33
+ }
34
+
35
+ // Generate ICO file (requires ico-endec or similar)
36
+ // For now, we'll create a 32x32 PNG that browsers can use
37
+ console.log('\nNote: For favicon.ico, use an online converter or:');
38
+ console.log(' npm install png-to-ico');
39
+ console.log(' Then convert favicon-32x32.png to favicon.ico');
40
+
41
+ console.log('\nDone! Favicons generated in:', outputDir);
42
+ }
43
+
44
+ generateFavicons().catch(console.error);
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Generate favicon.ico from PNG files
3
+ *
4
+ * Run: node scripts/generate-ico.js
5
+ */
6
+
7
+ const { imagesToIco } = require('png-to-ico');
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ const publicDir = path.join(__dirname, '../public');
12
+
13
+ async function generateIco() {
14
+ const images = [
15
+ fs.readFileSync(path.join(publicDir, 'favicon-16x16.png')),
16
+ fs.readFileSync(path.join(publicDir, 'favicon-32x32.png'))
17
+ ];
18
+ const buf = await imagesToIco(images);
19
+ fs.writeFileSync(path.join(publicDir, 'favicon.ico'), buf);
20
+ console.log('Created: favicon.ico');
21
+ }
22
+
23
+ generateIco().catch(console.error);
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Generate Open Graph image (1200x630) for social sharing
3
+ *
4
+ * Run: npm install sharp && node scripts/generate-og-image.js
5
+ */
6
+
7
+ const sharp = require('sharp');
8
+ const path = require('path');
9
+
10
+ const outputDir = path.join(__dirname, '../public');
11
+
12
+ async function generateOGImage() {
13
+ // Create a simple OG image with brand gradient and logo
14
+ const width = 1200;
15
+ const height = 630;
16
+
17
+ // Create SVG with brand gradient background and text
18
+ const svg = `
19
+ <svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
20
+ <defs>
21
+ <linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
22
+ <stop offset="0%" style="stop-color:#2E86AB;stop-opacity:1" />
23
+ <stop offset="50%" style="stop-color:#36B5A2;stop-opacity:1" />
24
+ <stop offset="100%" style="stop-color:#47C97A;stop-opacity:1" />
25
+ </linearGradient>
26
+ </defs>
27
+ <rect width="100%" height="100%" fill="url(#bg)"/>
28
+ <text x="600" y="250" font-family="Arial, sans-serif" font-size="72" font-weight="bold" fill="white" text-anchor="middle">UCP.tools</text>
29
+ <text x="600" y="350" font-family="Arial, sans-serif" font-size="36" fill="rgba(255,255,255,0.9)" text-anchor="middle">Universal Commerce Protocol</text>
30
+ <text x="600" y="420" font-family="Arial, sans-serif" font-size="32" fill="rgba(255,255,255,0.8)" text-anchor="middle">Profile Validator &amp; Generator</text>
31
+ <text x="600" y="530" font-family="Arial, sans-serif" font-size="24" fill="rgba(255,255,255,0.7)" text-anchor="middle">Get ready for AI-powered commerce</text>
32
+ </svg>
33
+ `;
34
+
35
+ const output = path.join(outputDir, 'og-image.png');
36
+
37
+ await sharp(Buffer.from(svg))
38
+ .png()
39
+ .toFile(output);
40
+
41
+ console.log('Created: og-image.png (1200x630)');
42
+ console.log('Output:', output);
43
+ }
44
+
45
+ generateOGImage().catch(console.error);
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Reset database script - drops existing tables and prepares for Drizzle migrations
3
+ * Run with: npx tsx scripts/reset-db.ts
4
+ */
5
+
6
+ import pg from 'pg';
7
+
8
+ const { Pool } = pg;
9
+
10
+ async function resetDatabase() {
11
+ const pool = new Pool({
12
+ connectionString: process.env.DATABASE_URL,
13
+ ssl: { rejectUnauthorized: false }
14
+ });
15
+
16
+ try {
17
+ console.log('šŸ” Inspecting current database schema...\n');
18
+
19
+ // Get all tables
20
+ const tablesResult = await pool.query(`
21
+ SELECT table_name
22
+ FROM information_schema.tables
23
+ WHERE table_schema = 'public'
24
+ AND table_type = 'BASE TABLE'
25
+ ORDER BY table_name;
26
+ `);
27
+
28
+ console.log('Current tables:');
29
+ if (tablesResult.rows.length === 0) {
30
+ console.log(' (no tables found)');
31
+ } else {
32
+ for (const row of tablesResult.rows) {
33
+ // Get column info for each table
34
+ const columnsResult = await pool.query(`
35
+ SELECT column_name, data_type, is_nullable, column_default
36
+ FROM information_schema.columns
37
+ WHERE table_schema = 'public' AND table_name = $1
38
+ ORDER BY ordinal_position;
39
+ `, [row.table_name]);
40
+
41
+ console.log(`\n šŸ“‹ ${row.table_name}:`);
42
+ for (const col of columnsResult.rows) {
43
+ console.log(` - ${col.column_name}: ${col.data_type} ${col.is_nullable === 'NO' ? 'NOT NULL' : ''}`);
44
+ }
45
+
46
+ // Get row count
47
+ const countResult = await pool.query(`SELECT COUNT(*) as count FROM "${row.table_name}"`);
48
+ console.log(` (${countResult.rows[0].count} rows)`);
49
+ }
50
+ }
51
+
52
+ console.log('\n\nšŸ—‘ļø Dropping all existing tables...\n');
53
+
54
+ // Drop tables in correct order (handle dependencies)
55
+ const dropStatements = [
56
+ 'DROP TABLE IF EXISTS merchants CASCADE',
57
+ 'DROP TABLE IF EXISTS benchmark_stats CASCADE',
58
+ 'DROP TABLE IF EXISTS benchmark_summary CASCADE',
59
+ 'DROP TABLE IF EXISTS drizzle_migrations CASCADE', // Drizzle migration tracking table
60
+ ];
61
+
62
+ for (const stmt of dropStatements) {
63
+ console.log(` Executing: ${stmt}`);
64
+ await pool.query(stmt);
65
+ }
66
+
67
+ console.log('\nāœ… Database reset complete. Ready for Drizzle migrations.\n');
68
+
69
+ } catch (error) {
70
+ console.error('āŒ Error:', error);
71
+ process.exit(1);
72
+ } finally {
73
+ await pool.end();
74
+ }
75
+ }
76
+
77
+ resetDatabase();
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Seed database with initial data
3
+ * Run with: npx tsx scripts/seed-db.ts
4
+ */
5
+
6
+ import pg from 'pg';
7
+
8
+ const { Pool } = pg;
9
+
10
+ async function seedDatabase() {
11
+ const pool = new Pool({
12
+ connectionString: process.env.DATABASE_URL,
13
+ ssl: { rejectUnauthorized: false }
14
+ });
15
+
16
+ try {
17
+ console.log('🌱 Seeding database...\n');
18
+
19
+ // Verify tables exist
20
+ const tablesResult = await pool.query(`
21
+ SELECT table_name
22
+ FROM information_schema.tables
23
+ WHERE table_schema = 'public'
24
+ AND table_type = 'BASE TABLE'
25
+ ORDER BY table_name;
26
+ `);
27
+
28
+ console.log('āœ… Tables created:');
29
+ for (const row of tablesResult.rows) {
30
+ console.log(` - ${row.table_name}`);
31
+ }
32
+
33
+ // Initialize score buckets (0-10, 10-20, ..., 90-100)
34
+ console.log('\nšŸ“Š Initializing benchmark score buckets...');
35
+ for (let bucket = 0; bucket <= 100; bucket += 10) {
36
+ await pool.query(`
37
+ INSERT INTO benchmark_stats (score_bucket, count)
38
+ VALUES ($1, 0)
39
+ ON CONFLICT (score_bucket) DO NOTHING;
40
+ `, [bucket]);
41
+ }
42
+ console.log(' Score buckets 0-100 initialized');
43
+
44
+ // Initialize summary row
45
+ console.log('\nšŸ“ˆ Initializing benchmark summary...');
46
+ await pool.query(`
47
+ INSERT INTO benchmark_summary (id, total_validations, avg_score)
48
+ VALUES (1, 0, 0)
49
+ ON CONFLICT (id) DO NOTHING;
50
+ `);
51
+ console.log(' Summary row initialized');
52
+
53
+ // Verify data
54
+ const statsCount = await pool.query('SELECT COUNT(*) as count FROM benchmark_stats');
55
+ const summaryCount = await pool.query('SELECT COUNT(*) as count FROM benchmark_summary');
56
+ const merchantsCount = await pool.query('SELECT COUNT(*) as count FROM merchants');
57
+
58
+ console.log('\nāœ… Database seeded successfully:');
59
+ console.log(` - benchmark_stats: ${statsCount.rows[0].count} rows`);
60
+ console.log(` - benchmark_summary: ${summaryCount.rows[0].count} rows`);
61
+ console.log(` - merchants: ${merchantsCount.rows[0].count} rows`);
62
+
63
+ } catch (error) {
64
+ console.error('āŒ Error:', error);
65
+ process.exit(1);
66
+ } finally {
67
+ await pool.end();
68
+ }
69
+ }
70
+
71
+ seedDatabase();