@ucptools/validator 1.0.0 → 1.0.1

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 (236) hide show
  1. package/.claude/settings.local.json +60 -0
  2. package/.vercel/README.txt +11 -0
  3. package/.vercel/project.json +1 -0
  4. package/dist/cli/index.d.ts +6 -0
  5. package/dist/cli/index.d.ts.map +1 -0
  6. package/dist/cli/index.js +279 -0
  7. package/dist/cli/index.js.map +1 -0
  8. package/dist/compliance/compliance-generator.d.ts +34 -0
  9. package/dist/compliance/compliance-generator.d.ts.map +1 -0
  10. package/dist/compliance/compliance-generator.js +320 -0
  11. package/dist/compliance/compliance-generator.js.map +1 -0
  12. package/dist/compliance/index.d.ts +8 -0
  13. package/dist/compliance/index.d.ts.map +1 -0
  14. package/dist/compliance/index.js +17 -0
  15. package/dist/compliance/index.js.map +1 -0
  16. package/dist/compliance/templates.d.ts +34 -0
  17. package/dist/compliance/templates.d.ts.map +1 -0
  18. package/{src/compliance/templates.ts → dist/compliance/templates.js} +117 -155
  19. package/dist/compliance/templates.js.map +1 -0
  20. package/dist/compliance/types.d.ts +64 -0
  21. package/dist/compliance/types.d.ts.map +1 -0
  22. package/dist/compliance/types.js +64 -0
  23. package/dist/compliance/types.js.map +1 -0
  24. package/dist/db/index.d.ts +11 -0
  25. package/dist/db/index.d.ts.map +1 -0
  26. package/dist/db/index.js +63 -0
  27. package/dist/db/index.js.map +1 -0
  28. package/dist/db/schema.d.ts +444 -0
  29. package/dist/db/schema.d.ts.map +1 -0
  30. package/dist/db/schema.js +65 -0
  31. package/dist/db/schema.js.map +1 -0
  32. package/dist/feed-analyzer/feed-analyzer.d.ts +26 -0
  33. package/dist/feed-analyzer/feed-analyzer.d.ts.map +1 -0
  34. package/{src/feed-analyzer/feed-analyzer.ts → dist/feed-analyzer/feed-analyzer.js} +642 -726
  35. package/dist/feed-analyzer/feed-analyzer.js.map +1 -0
  36. package/dist/feed-analyzer/index.d.ts +8 -0
  37. package/dist/feed-analyzer/index.d.ts.map +1 -0
  38. package/dist/feed-analyzer/index.js +19 -0
  39. package/dist/feed-analyzer/index.js.map +1 -0
  40. package/dist/feed-analyzer/types.d.ts +204 -0
  41. package/dist/feed-analyzer/types.d.ts.map +1 -0
  42. package/dist/feed-analyzer/types.js +162 -0
  43. package/dist/feed-analyzer/types.js.map +1 -0
  44. package/{src/generator/index.ts → dist/generator/index.d.ts} +1 -1
  45. package/dist/generator/index.d.ts.map +1 -0
  46. package/dist/generator/index.js +13 -0
  47. package/dist/generator/index.js.map +1 -0
  48. package/dist/generator/key-generator.d.ts +24 -0
  49. package/dist/generator/key-generator.d.ts.map +1 -0
  50. package/dist/generator/key-generator.js +144 -0
  51. package/dist/generator/key-generator.js.map +1 -0
  52. package/dist/generator/profile-builder.d.ts +15 -0
  53. package/dist/generator/profile-builder.d.ts.map +1 -0
  54. package/dist/generator/profile-builder.js +338 -0
  55. package/dist/generator/profile-builder.js.map +1 -0
  56. package/dist/hosting/artifacts-generator.d.ts +10 -0
  57. package/dist/hosting/artifacts-generator.d.ts.map +1 -0
  58. package/{src/hosting/artifacts-generator.ts → dist/hosting/artifacts-generator.js} +191 -241
  59. package/dist/hosting/artifacts-generator.js.map +1 -0
  60. package/{src/hosting/index.ts → dist/hosting/index.d.ts} +1 -1
  61. package/dist/hosting/index.d.ts.map +1 -0
  62. package/dist/hosting/index.js +10 -0
  63. package/dist/hosting/index.js.map +1 -0
  64. package/dist/index.d.ts +18 -0
  65. package/dist/index.d.ts.map +1 -0
  66. package/dist/index.js +78 -0
  67. package/dist/index.js.map +1 -0
  68. package/{src/security/index.ts → dist/security/index.d.ts} +8 -15
  69. package/dist/security/index.d.ts.map +1 -0
  70. package/dist/security/index.js +12 -0
  71. package/dist/security/index.js.map +1 -0
  72. package/dist/security/security-scanner.d.ts +10 -0
  73. package/dist/security/security-scanner.d.ts.map +1 -0
  74. package/dist/security/security-scanner.js +541 -0
  75. package/dist/security/security-scanner.js.map +1 -0
  76. package/dist/security/types.d.ts +48 -0
  77. package/dist/security/types.d.ts.map +1 -0
  78. package/dist/security/types.js +21 -0
  79. package/dist/security/types.js.map +1 -0
  80. package/dist/services/directory.d.ts +104 -0
  81. package/dist/services/directory.d.ts.map +1 -0
  82. package/dist/services/directory.js +333 -0
  83. package/dist/services/directory.js.map +1 -0
  84. package/dist/simulator/agent-simulator.d.ts +69 -0
  85. package/dist/simulator/agent-simulator.d.ts.map +1 -0
  86. package/{src/simulator/agent-simulator.ts → dist/simulator/agent-simulator.js} +650 -941
  87. package/dist/simulator/agent-simulator.js.map +1 -0
  88. package/{src/simulator/index.ts → dist/simulator/index.d.ts} +7 -7
  89. package/dist/simulator/index.d.ts.map +1 -0
  90. package/dist/simulator/index.js +23 -0
  91. package/dist/simulator/index.js.map +1 -0
  92. package/{src/simulator/types.ts → dist/simulator/types.d.ts} +145 -170
  93. package/dist/simulator/types.d.ts.map +1 -0
  94. package/dist/simulator/types.js +18 -0
  95. package/dist/simulator/types.js.map +1 -0
  96. package/dist/types/generator.d.ts +106 -0
  97. package/dist/types/generator.d.ts.map +1 -0
  98. package/dist/types/generator.js +6 -0
  99. package/dist/types/generator.js.map +1 -0
  100. package/{src/types/index.ts → dist/types/index.d.ts} +1 -1
  101. package/dist/types/index.d.ts.map +1 -0
  102. package/dist/types/index.js +23 -0
  103. package/dist/types/index.js.map +1 -0
  104. package/dist/types/ucp-profile.d.ts +103 -0
  105. package/dist/types/ucp-profile.d.ts.map +1 -0
  106. package/dist/types/ucp-profile.js +45 -0
  107. package/dist/types/ucp-profile.js.map +1 -0
  108. package/dist/types/validation.d.ts +68 -0
  109. package/dist/types/validation.d.ts.map +1 -0
  110. package/dist/types/validation.js +32 -0
  111. package/dist/types/validation.js.map +1 -0
  112. package/dist/validator/index.d.ts +26 -0
  113. package/dist/validator/index.d.ts.map +1 -0
  114. package/dist/validator/index.js +161 -0
  115. package/dist/validator/index.js.map +1 -0
  116. package/dist/validator/network-validator.d.ts +28 -0
  117. package/dist/validator/network-validator.d.ts.map +1 -0
  118. package/dist/validator/network-validator.js +319 -0
  119. package/dist/validator/network-validator.js.map +1 -0
  120. package/dist/validator/rules-validator.d.ts +11 -0
  121. package/dist/validator/rules-validator.d.ts.map +1 -0
  122. package/dist/validator/rules-validator.js +257 -0
  123. package/dist/validator/rules-validator.js.map +1 -0
  124. package/dist/validator/sdk-validator.d.ts +58 -0
  125. package/dist/validator/sdk-validator.d.ts.map +1 -0
  126. package/{src/validator/sdk-validator.ts → dist/validator/sdk-validator.js} +273 -330
  127. package/dist/validator/sdk-validator.js.map +1 -0
  128. package/dist/validator/structural-validator.d.ts +11 -0
  129. package/dist/validator/structural-validator.d.ts.map +1 -0
  130. package/dist/validator/structural-validator.js +415 -0
  131. package/dist/validator/structural-validator.js.map +1 -0
  132. package/package.json +1 -1
  133. package/publish-output.txt +0 -0
  134. package/CLAUDE.md +0 -109
  135. package/api/analyze-feed.js +0 -140
  136. package/api/badge.js +0 -185
  137. package/api/benchmark.js +0 -177
  138. package/api/directory-stats.ts +0 -29
  139. package/api/directory.ts +0 -73
  140. package/api/generate-compliance.js +0 -143
  141. package/api/generate-schema.js +0 -457
  142. package/api/generate.js +0 -132
  143. package/api/security-scan.js +0 -133
  144. package/api/simulate.js +0 -187
  145. package/api/tsconfig.json +0 -10
  146. package/api/validate.js +0 -1351
  147. package/apify-actor/.actor/actor.json +0 -68
  148. package/apify-actor/.actor/input_schema.json +0 -32
  149. package/apify-actor/APIFY-STORE-LISTING.md +0 -412
  150. package/apify-actor/Dockerfile +0 -8
  151. package/apify-actor/README.md +0 -166
  152. package/apify-actor/main.ts +0 -111
  153. package/apify-actor/package.json +0 -17
  154. package/apify-actor/src/main.js +0 -199
  155. package/docs/BRAND-IDENTITY.md +0 -238
  156. package/docs/BRAND-STYLE-GUIDE.md +0 -356
  157. package/drizzle/0000_black_king_cobra.sql +0 -39
  158. package/drizzle/meta/0000_snapshot.json +0 -309
  159. package/drizzle/meta/_journal.json +0 -13
  160. package/drizzle.config.ts +0 -10
  161. package/public/.well-known/ucp +0 -25
  162. package/public/android-chrome-192x192.png +0 -0
  163. package/public/android-chrome-512x512.png +0 -0
  164. package/public/apple-touch-icon.png +0 -0
  165. package/public/brand.css +0 -321
  166. package/public/directory.html +0 -701
  167. package/public/favicon-16x16.png +0 -0
  168. package/public/favicon-32x32.png +0 -0
  169. package/public/favicon.ico +0 -0
  170. package/public/guides/bigcommerce.html +0 -743
  171. package/public/guides/fastucp.html +0 -838
  172. package/public/guides/magento.html +0 -779
  173. package/public/guides/shopify.html +0 -726
  174. package/public/guides/squarespace.html +0 -749
  175. package/public/guides/wix.html +0 -747
  176. package/public/guides/woocommerce.html +0 -733
  177. package/public/index.html +0 -3835
  178. package/public/learn.html +0 -396
  179. package/public/logo.jpeg +0 -0
  180. package/public/og-image-icon.png +0 -0
  181. package/public/og-image.png +0 -0
  182. package/public/robots.txt +0 -6
  183. package/public/site.webmanifest +0 -31
  184. package/public/sitemap.xml +0 -69
  185. package/public/social/linkedin-banner-1128x191.png +0 -0
  186. package/public/social/temp.PNG +0 -0
  187. package/public/social/x-header-1500x500.png +0 -0
  188. package/public/verify.html +0 -410
  189. package/scripts/generate-favicons.js +0 -44
  190. package/scripts/generate-ico.js +0 -23
  191. package/scripts/generate-og-image.js +0 -45
  192. package/scripts/reset-db.ts +0 -77
  193. package/scripts/seed-db.ts +0 -71
  194. package/scripts/setup-benchmark-db.js +0 -70
  195. package/src/api/server.ts +0 -266
  196. package/src/cli/index.ts +0 -302
  197. package/src/compliance/compliance-generator.ts +0 -452
  198. package/src/compliance/index.ts +0 -28
  199. package/src/compliance/types.ts +0 -170
  200. package/src/db/index.ts +0 -28
  201. package/src/db/schema.ts +0 -84
  202. package/src/feed-analyzer/index.ts +0 -34
  203. package/src/feed-analyzer/types.ts +0 -354
  204. package/src/generator/key-generator.ts +0 -124
  205. package/src/generator/profile-builder.ts +0 -402
  206. package/src/index.ts +0 -105
  207. package/src/security/security-scanner.ts +0 -604
  208. package/src/security/types.ts +0 -55
  209. package/src/services/directory.ts +0 -434
  210. package/src/types/generator.ts +0 -140
  211. package/src/types/ucp-profile.ts +0 -140
  212. package/src/types/validation.ts +0 -89
  213. package/src/validator/index.ts +0 -194
  214. package/src/validator/network-validator.ts +0 -417
  215. package/src/validator/rules-validator.ts +0 -297
  216. package/src/validator/structural-validator.ts +0 -476
  217. package/tests/fixtures/non-compliant-profile.json +0 -25
  218. package/tests/fixtures/official-sample-profile.json +0 -75
  219. package/tests/integration/benchmark.test.ts +0 -207
  220. package/tests/integration/database.test.ts +0 -163
  221. package/tests/integration/directory-api.test.ts +0 -268
  222. package/tests/integration/simulate-api.test.ts +0 -230
  223. package/tests/integration/validate-api.test.ts +0 -269
  224. package/tests/setup.ts +0 -15
  225. package/tests/unit/agent-simulator.test.ts +0 -575
  226. package/tests/unit/compliance-generator.test.ts +0 -374
  227. package/tests/unit/directory-service.test.ts +0 -272
  228. package/tests/unit/feed-analyzer.test.ts +0 -517
  229. package/tests/unit/lint-suggestions.test.ts +0 -423
  230. package/tests/unit/official-samples.test.ts +0 -211
  231. package/tests/unit/pdf-report.test.ts +0 -390
  232. package/tests/unit/sdk-validator.test.ts +0 -531
  233. package/tests/unit/security-scanner.test.ts +0 -410
  234. package/tests/unit/validation.test.ts +0 -390
  235. package/vercel.json +0 -34
  236. package/vitest.config.ts +0 -22
@@ -1,140 +0,0 @@
1
- /**
2
- * Vercel Serverless Function: Product Feed Quality Analyzer
3
- * POST /api/analyze-feed
4
- *
5
- * Analyzes product feed quality for AI agent visibility.
6
- *
7
- * Request body:
8
- * {
9
- * "url": "https://example.com/products",
10
- * "maxProducts": 50,
11
- * "includeProductDetails": true
12
- * }
13
- *
14
- * GET /api/analyze-feed?url=https://example.com/products
15
- * Quick analysis with default options.
16
- */
17
-
18
- export default async function handler(req, res) {
19
- // Handle CORS
20
- res.setHeader('Access-Control-Allow-Origin', '*');
21
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
22
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
23
-
24
- if (req.method === 'OPTIONS') {
25
- return res.status(200).end();
26
- }
27
-
28
- // Extract URL from query (GET) or body (POST)
29
- let url;
30
- let maxProducts = 50;
31
- let includeProductDetails = true;
32
-
33
- if (req.method === 'GET') {
34
- url = req.query.url;
35
- if (req.query.maxProducts) {
36
- maxProducts = parseInt(req.query.maxProducts, 10);
37
- }
38
- if (req.query.includeProductDetails === 'false') {
39
- includeProductDetails = false;
40
- }
41
- } else if (req.method === 'POST') {
42
- const body = req.body || {};
43
- url = body.url;
44
- maxProducts = body.maxProducts || 50;
45
- includeProductDetails = body.includeProductDetails !== false;
46
- } else {
47
- return res.status(405).json({
48
- error: 'Method not allowed',
49
- message: 'Use GET or POST to analyze a product feed',
50
- });
51
- }
52
-
53
- // Validate URL
54
- if (!url) {
55
- return res.status(400).json({
56
- error: 'Missing URL',
57
- message: 'Please provide a URL to analyze',
58
- });
59
- }
60
-
61
- try {
62
- new URL(url);
63
- } catch {
64
- return res.status(400).json({
65
- error: 'Invalid URL',
66
- message: 'Please provide a valid URL',
67
- });
68
- }
69
-
70
- // Validate maxProducts
71
- if (maxProducts < 1 || maxProducts > 100) {
72
- return res.status(400).json({
73
- error: 'Invalid maxProducts',
74
- message: 'maxProducts must be between 1 and 100',
75
- });
76
- }
77
-
78
- try {
79
- // Fetch the page content
80
- const controller = new AbortController();
81
- const timeoutId = setTimeout(() => controller.abort(), 30000);
82
-
83
- const response = await fetch(url, {
84
- headers: {
85
- 'User-Agent': 'UCP-Tools Feed Analyzer/1.0 (https://ucp.day)',
86
- 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
87
- },
88
- signal: controller.signal,
89
- });
90
-
91
- clearTimeout(timeoutId);
92
-
93
- if (!response.ok) {
94
- return res.status(400).json({
95
- error: 'Failed to fetch URL',
96
- message: `Server returned ${response.status}: ${response.statusText}`,
97
- });
98
- }
99
-
100
- const html = await response.text();
101
-
102
- // Import and run the analyzer
103
- const { analyzeProductFeedFromHtml } = await import('../src/feed-analyzer/index.js');
104
-
105
- const result = analyzeProductFeedFromHtml(html, url, {
106
- maxProducts,
107
- includeProductDetails,
108
- });
109
-
110
- // Check if any products were found
111
- if (result.productsFound === 0) {
112
- return res.status(200).json({
113
- success: true,
114
- warning: 'No products found',
115
- message: 'No Schema.org Product markup was found on this page. Make sure your page includes JSON-LD structured data with @type: "Product".',
116
- ...result,
117
- });
118
- }
119
-
120
- return res.status(200).json({
121
- success: true,
122
- ...result,
123
- });
124
-
125
- } catch (error) {
126
- console.error('Feed analysis error:', error);
127
-
128
- if (error.name === 'AbortError') {
129
- return res.status(408).json({
130
- error: 'Request timeout',
131
- message: 'The request took too long. The target server may be slow or unresponsive.',
132
- });
133
- }
134
-
135
- return res.status(500).json({
136
- error: 'Analysis failed',
137
- message: error.message || 'An unexpected error occurred',
138
- });
139
- }
140
- }
package/api/badge.js DELETED
@@ -1,185 +0,0 @@
1
- /**
2
- * Vercel Serverless Function: Generate AI Commerce Ready Badge
3
- * GET /api/badge?domain=example.com&style=flat
4
- *
5
- * Returns an SVG badge showing the AI readiness grade
6
- */
7
-
8
- const GRADE_COLORS = {
9
- A: { bg: '#16A34A', text: '#DCFCE7' },
10
- B: { bg: '#2563EB', text: '#DBEAFE' },
11
- C: { bg: '#CA8A04', text: '#FEF9C3' },
12
- D: { bg: '#EA580C', text: '#FED7AA' },
13
- F: { bg: '#DC2626', text: '#FEE2E2' },
14
- };
15
-
16
- const READINESS_LABELS = {
17
- A: 'AI Commerce Ready',
18
- B: 'Mostly Ready',
19
- C: 'Partially Ready',
20
- D: 'Limited Readiness',
21
- F: 'Not Ready',
22
- };
23
-
24
- function generateFlatBadge(grade, domain, score) {
25
- const colors = GRADE_COLORS[grade] || GRADE_COLORS.F;
26
- const label = READINESS_LABELS[grade] || 'Not Ready';
27
-
28
- return `<svg xmlns="http://www.w3.org/2000/svg" width="180" height="20" role="img" aria-label="AI Commerce: ${grade}">
29
- <title>AI Commerce: Grade ${grade} - ${label}</title>
30
- <linearGradient id="s" x2="0" y2="100%">
31
- <stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
32
- <stop offset="1" stop-opacity=".1"/>
33
- </linearGradient>
34
- <clipPath id="r">
35
- <rect width="180" height="20" rx="3" fill="#fff"/>
36
- </clipPath>
37
- <g clip-path="url(#r)">
38
- <rect width="95" height="20" fill="#555"/>
39
- <rect x="95" width="85" height="20" fill="${colors.bg}"/>
40
- <rect width="180" height="20" fill="url(#s)"/>
41
- </g>
42
- <g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="11">
43
- <text x="48.5" y="14">AI Commerce</text>
44
- <text x="137.5" y="14" font-weight="bold">${grade} ${score}/100</text>
45
- </g>
46
- </svg>`;
47
- }
48
-
49
- function generateFlatSquareBadge(grade, domain, score) {
50
- const colors = GRADE_COLORS[grade] || GRADE_COLORS.F;
51
- const label = READINESS_LABELS[grade] || 'Not Ready';
52
-
53
- return `<svg xmlns="http://www.w3.org/2000/svg" width="180" height="20" role="img" aria-label="AI Commerce: ${grade}">
54
- <title>AI Commerce: Grade ${grade} - ${label}</title>
55
- <g shape-rendering="crispEdges">
56
- <rect width="95" height="20" fill="#555"/>
57
- <rect x="95" width="85" height="20" fill="${colors.bg}"/>
58
- </g>
59
- <g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="11">
60
- <text x="48.5" y="14">AI Commerce</text>
61
- <text x="137.5" y="14" font-weight="bold">${grade} ${score}/100</text>
62
- </g>
63
- </svg>`;
64
- }
65
-
66
- function generateLargeBadge(grade, domain, score) {
67
- const colors = GRADE_COLORS[grade] || GRADE_COLORS.F;
68
- const label = READINESS_LABELS[grade] || 'Not Ready';
69
-
70
- return `<svg xmlns="http://www.w3.org/2000/svg" width="200" height="80" role="img" aria-label="AI Commerce Ready: ${grade}">
71
- <title>AI Commerce: Grade ${grade} - ${label}</title>
72
- <defs>
73
- <linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
74
- <stop offset="0%" style="stop-color:#2E86AB;stop-opacity:1" />
75
- <stop offset="50%" style="stop-color:#36B5A2;stop-opacity:1" />
76
- <stop offset="100%" style="stop-color:#47C97A;stop-opacity:1" />
77
- </linearGradient>
78
- </defs>
79
- <rect width="200" height="80" rx="8" fill="#1A2B3C"/>
80
- <rect x="2" y="2" width="196" height="76" rx="6" fill="none" stroke="url(#grad)" stroke-width="2"/>
81
-
82
- <!-- Grade circle -->
83
- <circle cx="40" cy="40" r="25" fill="${colors.bg}"/>
84
- <text x="40" y="47" text-anchor="middle" font-family="Verdana,Geneva,sans-serif" font-size="24" font-weight="bold" fill="#fff">${grade}</text>
85
-
86
- <!-- Text -->
87
- <text x="75" y="30" font-family="Verdana,Geneva,sans-serif" font-size="11" fill="#94A3B8">AI Commerce</text>
88
- <text x="75" y="48" font-family="Verdana,Geneva,sans-serif" font-size="14" font-weight="bold" fill="#fff">${label}</text>
89
- <text x="75" y="65" font-family="Verdana,Geneva,sans-serif" font-size="10" fill="#94A3B8">Score: ${score}/100 • ucptools.dev</text>
90
- </svg>`;
91
- }
92
-
93
- function generateMiniBadge(grade) {
94
- const colors = GRADE_COLORS[grade] || GRADE_COLORS.F;
95
-
96
- return `<svg xmlns="http://www.w3.org/2000/svg" width="40" height="20" role="img" aria-label="Grade ${grade}">
97
- <title>AI Commerce Grade ${grade}</title>
98
- <rect width="40" height="20" rx="3" fill="${colors.bg}"/>
99
- <text x="20" y="14" text-anchor="middle" font-family="Verdana,Geneva,sans-serif" font-size="11" font-weight="bold" fill="#fff">${grade}</text>
100
- </svg>`;
101
- }
102
-
103
- export default async function handler(req, res) {
104
- // CORS headers
105
- res.setHeader('Access-Control-Allow-Origin', '*');
106
- res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
107
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
108
- res.setHeader('Cache-Control', 'public, max-age=3600'); // Cache for 1 hour
109
-
110
- if (req.method === 'OPTIONS') {
111
- return res.status(200).end();
112
- }
113
-
114
- if (req.method !== 'GET') {
115
- return res.status(405).json({ error: 'Method not allowed' });
116
- }
117
-
118
- const { domain, style = 'flat', grade: staticGrade, score: staticScore } = req.query;
119
-
120
- // If static grade provided (for previews), use that
121
- if (staticGrade) {
122
- const grade = staticGrade.toUpperCase();
123
- const score = parseInt(staticScore) || (grade === 'A' ? 95 : grade === 'B' ? 82 : grade === 'C' ? 71 : grade === 'D' ? 55 : 30);
124
-
125
- res.setHeader('Content-Type', 'image/svg+xml');
126
-
127
- switch (style) {
128
- case 'flat-square':
129
- return res.send(generateFlatSquareBadge(grade, '', score));
130
- case 'large':
131
- return res.send(generateLargeBadge(grade, '', score));
132
- case 'mini':
133
- return res.send(generateMiniBadge(grade));
134
- default:
135
- return res.send(generateFlatBadge(grade, '', score));
136
- }
137
- }
138
-
139
- if (!domain) {
140
- return res.status(400).json({ error: 'Missing required parameter: domain' });
141
- }
142
-
143
- const cleanDomain = domain.replace(/^https?:\/\//, '').replace(/\/$/, '').split('/')[0];
144
-
145
- try {
146
- // Fetch validation data from our own API
147
- const baseUrl = process.env.VERCEL_URL
148
- ? `https://${process.env.VERCEL_URL}`
149
- : 'https://ucptools.dev';
150
-
151
- const validateRes = await fetch(`${baseUrl}/api/validate`, {
152
- method: 'POST',
153
- headers: { 'Content-Type': 'application/json' },
154
- body: JSON.stringify({ domain: cleanDomain }),
155
- });
156
-
157
- if (!validateRes.ok) {
158
- throw new Error('Validation failed');
159
- }
160
-
161
- const data = await validateRes.json();
162
- const grade = data.ai_readiness?.grade || 'F';
163
- const score = data.ai_readiness?.score || 0;
164
-
165
- res.setHeader('Content-Type', 'image/svg+xml');
166
-
167
- switch (style) {
168
- case 'flat-square':
169
- return res.send(generateFlatSquareBadge(grade, cleanDomain, score));
170
- case 'large':
171
- return res.send(generateLargeBadge(grade, cleanDomain, score));
172
- case 'mini':
173
- return res.send(generateMiniBadge(grade));
174
- default:
175
- return res.send(generateFlatBadge(grade, cleanDomain, score));
176
- }
177
- } catch (error) {
178
- // Return a gray "unknown" badge on error
179
- res.setHeader('Content-Type', 'image/svg+xml');
180
- return res.send(`<svg xmlns="http://www.w3.org/2000/svg" width="180" height="20" role="img">
181
- <rect width="180" height="20" rx="3" fill="#9CA3AF"/>
182
- <text x="90" y="14" text-anchor="middle" font-family="Verdana,Geneva,sans-serif" font-size="11" fill="#fff">AI Commerce • Unknown</text>
183
- </svg>`);
184
- }
185
- }
package/api/benchmark.js DELETED
@@ -1,177 +0,0 @@
1
- /**
2
- * Vercel Serverless Function: Benchmark Statistics
3
- *
4
- * POST /api/benchmark - Record a new validation score
5
- * GET /api/benchmark - Get benchmark statistics and percentile
6
- *
7
- * Privacy: Only stores aggregate statistics, not individual domains
8
- */
9
-
10
- import pg from 'pg';
11
-
12
- const { Pool } = pg;
13
-
14
- let pool = null;
15
-
16
- function getPool() {
17
- if (!pool) {
18
- pool = new Pool({
19
- connectionString: process.env.DATABASE_URL,
20
- ssl: { rejectUnauthorized: false },
21
- max: 5,
22
- idleTimeoutMillis: 30000,
23
- });
24
- }
25
- return pool;
26
- }
27
-
28
- /**
29
- * Get the score bucket for a given score (0, 10, 20, ..., 100)
30
- */
31
- function getScoreBucket(score) {
32
- return Math.floor(score / 10) * 10;
33
- }
34
-
35
- /**
36
- * Calculate percentile for a given score based on distribution
37
- */
38
- async function calculatePercentile(pool, score) {
39
- const result = await pool.query(`
40
- SELECT
41
- score_bucket,
42
- count,
43
- SUM(count) OVER (ORDER BY score_bucket) as cumulative
44
- FROM benchmark_stats
45
- ORDER BY score_bucket
46
- `);
47
-
48
- const summary = await pool.query('SELECT total_validations FROM benchmark_summary WHERE id = 1');
49
- const total = summary.rows[0]?.total_validations || 0;
50
-
51
- if (total === 0) {
52
- return 50; // Default to 50th percentile if no data
53
- }
54
-
55
- const scoreBucket = getScoreBucket(score);
56
- let belowCount = 0;
57
-
58
- for (const row of result.rows) {
59
- if (row.score_bucket < scoreBucket) {
60
- belowCount = row.cumulative;
61
- } else if (row.score_bucket === scoreBucket) {
62
- // For the current bucket, count half of it (assume uniform distribution within bucket)
63
- belowCount = (row.cumulative - row.count) + Math.floor(row.count / 2);
64
- break;
65
- }
66
- }
67
-
68
- return Math.round((belowCount / total) * 100);
69
- }
70
-
71
- /**
72
- * Record a new validation score
73
- */
74
- async function recordScore(pool, score) {
75
- const bucket = getScoreBucket(score);
76
-
77
- // Increment the bucket count
78
- await pool.query(`
79
- UPDATE benchmark_stats
80
- SET count = count + 1
81
- WHERE score_bucket = $1
82
- `, [bucket]);
83
-
84
- // Update summary statistics
85
- await pool.query(`
86
- UPDATE benchmark_summary
87
- SET
88
- total_validations = total_validations + 1,
89
- avg_score = (avg_score * total_validations + $1) / (total_validations + 1),
90
- updated_at = NOW()
91
- WHERE id = 1
92
- `, [score]);
93
- }
94
-
95
- /**
96
- * Get benchmark statistics
97
- */
98
- async function getStats(pool) {
99
- const summary = await pool.query('SELECT * FROM benchmark_summary WHERE id = 1');
100
- const distribution = await pool.query('SELECT score_bucket, count FROM benchmark_stats ORDER BY score_bucket');
101
-
102
- const stats = summary.rows[0] || { total_validations: 0, avg_score: 0 };
103
-
104
- return {
105
- total_validations: stats.total_validations,
106
- avg_score: Math.round(stats.avg_score * 10) / 10,
107
- distribution: distribution.rows.reduce((acc, row) => {
108
- acc[row.score_bucket] = row.count;
109
- return acc;
110
- }, {}),
111
- updated_at: stats.updated_at,
112
- };
113
- }
114
-
115
- export default async function handler(req, res) {
116
- // CORS headers
117
- res.setHeader('Access-Control-Allow-Origin', '*');
118
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
119
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
120
-
121
- if (req.method === 'OPTIONS') {
122
- return res.status(200).end();
123
- }
124
-
125
- if (!process.env.DATABASE_URL) {
126
- return res.status(500).json({ error: 'Database not configured' });
127
- }
128
-
129
- const pool = getPool();
130
-
131
- try {
132
- if (req.method === 'POST') {
133
- // Record a new score
134
- const { score } = req.body;
135
-
136
- if (typeof score !== 'number' || score < 0 || score > 100) {
137
- return res.status(400).json({ error: 'Invalid score. Must be a number between 0 and 100.' });
138
- }
139
-
140
- await recordScore(pool, score);
141
- const percentile = await calculatePercentile(pool, score);
142
- const stats = await getStats(pool);
143
-
144
- return res.status(200).json({
145
- recorded: true,
146
- percentile,
147
- stats: {
148
- total_validations: stats.total_validations,
149
- avg_score: stats.avg_score,
150
- }
151
- });
152
-
153
- } else if (req.method === 'GET') {
154
- // Get benchmark statistics
155
- const score = req.query.score ? parseInt(req.query.score, 10) : null;
156
- const stats = await getStats(pool);
157
-
158
- const response = {
159
- stats,
160
- };
161
-
162
- // If score provided, calculate percentile
163
- if (score !== null && !isNaN(score) && score >= 0 && score <= 100) {
164
- response.percentile = await calculatePercentile(pool, score);
165
- }
166
-
167
- return res.status(200).json(response);
168
-
169
- } else {
170
- return res.status(405).json({ error: 'Method not allowed' });
171
- }
172
-
173
- } catch (error) {
174
- console.error('Benchmark API error:', error);
175
- return res.status(500).json({ error: 'Internal server error' });
176
- }
177
- }
@@ -1,29 +0,0 @@
1
- /**
2
- * Vercel Serverless Function: Directory Statistics
3
- * GET /api/directory-stats
4
- */
5
-
6
- import type { VercelRequest, VercelResponse } from '@vercel/node';
7
- import { getDirectoryStats } from '../src/services/directory.js';
8
-
9
- export default async function handler(req: VercelRequest, res: VercelResponse) {
10
- res.setHeader('Access-Control-Allow-Origin', '*');
11
- res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
12
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
13
-
14
- if (req.method === 'OPTIONS') {
15
- return res.status(200).end();
16
- }
17
-
18
- if (req.method !== 'GET') {
19
- return res.status(405).json({ error: 'Method not allowed' });
20
- }
21
-
22
- try {
23
- const stats = await getDirectoryStats();
24
- return res.status(200).json(stats);
25
- } catch (error: any) {
26
- console.error('Directory stats error:', error);
27
- return res.status(500).json({ error: 'Internal server error' });
28
- }
29
- }
package/api/directory.ts DELETED
@@ -1,73 +0,0 @@
1
- /**
2
- * Vercel Serverless Function: UCP Merchant Directory
3
- *
4
- * GET /api/directory - List merchants with pagination and filters
5
- * POST /api/directory - Submit a new merchant to the directory
6
- */
7
-
8
- import type { VercelRequest, VercelResponse } from '@vercel/node';
9
- import { listMerchants, submitMerchant } from '../src/services/directory.js';
10
-
11
- export default async function handler(req: VercelRequest, res: VercelResponse) {
12
- // CORS handled by vercel.json, but keep for local dev
13
- res.setHeader('Access-Control-Allow-Origin', '*');
14
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
15
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
16
-
17
- if (req.method === 'OPTIONS') {
18
- return res.status(200).end();
19
- }
20
-
21
- try {
22
- if (req.method === 'GET') {
23
- const result = await listMerchants({
24
- page: parseInt(req.query.page as string) || 1,
25
- limit: parseInt(req.query.limit as string) || 20,
26
- category: req.query.category as string,
27
- country: req.query.country as string,
28
- search: req.query.search as string,
29
- sort: req.query.sort as 'score' | 'domain' | 'displayName' | 'createdAt',
30
- order: req.query.order as 'asc' | 'desc',
31
- });
32
-
33
- return res.status(200).json(result);
34
- }
35
-
36
- if (req.method === 'POST') {
37
- const { domain, displayName, description, logoUrl, websiteUrl, category, countryCode } = req.body;
38
-
39
- if (!domain) {
40
- return res.status(400).json({ error: 'Missing required field: domain' });
41
- }
42
-
43
- const result = await submitMerchant({
44
- domain,
45
- displayName,
46
- description,
47
- logoUrl,
48
- websiteUrl,
49
- category,
50
- countryCode,
51
- });
52
-
53
- if (!result.success) {
54
- return res.status(400).json({
55
- error: result.error,
56
- details: result.details,
57
- });
58
- }
59
-
60
- return res.status(201).json({
61
- success: true,
62
- message: 'Merchant added to directory',
63
- merchant: result.merchant,
64
- directoryUrl: `https://ucptools.dev/directory#${result.merchant?.domain}`,
65
- });
66
- }
67
-
68
- return res.status(405).json({ error: 'Method not allowed' });
69
- } catch (error: any) {
70
- console.error('Directory API error:', error);
71
- return res.status(500).json({ error: 'Internal server error' });
72
- }
73
- }