@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,34 @@
1
+ /**
2
+ * Product Feed Quality Analyzer Module
3
+ * Deep analysis of product data quality for AI agent visibility
4
+ */
5
+
6
+ export {
7
+ analyzeProductFeed,
8
+ analyzeProductFeedFromHtml,
9
+ analyzeProduct,
10
+ extractProductsFromHtml,
11
+ validateGtin,
12
+ } from './feed-analyzer.js';
13
+
14
+ export type {
15
+ ProductData,
16
+ ProductOffer,
17
+ ProductAnalysis,
18
+ QualityCheck,
19
+ FeedAnalysisResult,
20
+ FeedAnalysisInput,
21
+ CategoryScores,
22
+ Recommendation,
23
+ FeedSummary,
24
+ GtinValidation,
25
+ IssueSeverity,
26
+ CheckCategory,
27
+ } from './types.js';
28
+
29
+ export {
30
+ QUALITY_CHECKS,
31
+ VALID_AVAILABILITY_VALUES,
32
+ CATEGORY_WEIGHTS,
33
+ GRADE_THRESHOLDS,
34
+ } from './types.js';
@@ -0,0 +1,354 @@
1
+ /**
2
+ * Product Feed Quality Analyzer Types
3
+ * Deep analysis of product data quality for AI agent visibility
4
+ */
5
+
6
+ /**
7
+ * Severity levels for feed issues
8
+ */
9
+ export type IssueSeverity = 'critical' | 'warning' | 'info';
10
+
11
+ /**
12
+ * Categories of feed quality checks
13
+ */
14
+ export type CheckCategory =
15
+ | 'completeness'
16
+ | 'identifiers'
17
+ | 'images'
18
+ | 'pricing'
19
+ | 'descriptions'
20
+ | 'categories'
21
+ | 'availability';
22
+
23
+ /**
24
+ * Individual product data from Schema.org JSON-LD
25
+ */
26
+ export interface ProductData {
27
+ name?: string;
28
+ description?: string;
29
+ sku?: string;
30
+ gtin?: string;
31
+ gtin8?: string;
32
+ gtin12?: string;
33
+ gtin13?: string;
34
+ gtin14?: string;
35
+ mpn?: string;
36
+ brand?: string | { name?: string };
37
+ image?: string | string[] | { url?: string }[];
38
+ offers?: ProductOffer | ProductOffer[];
39
+ category?: string;
40
+ color?: string;
41
+ size?: string;
42
+ material?: string;
43
+ weight?: string | { value?: number; unitCode?: string };
44
+ aggregateRating?: {
45
+ ratingValue?: number;
46
+ reviewCount?: number;
47
+ ratingCount?: number;
48
+ };
49
+ review?: unknown[];
50
+ url?: string;
51
+ '@type'?: string;
52
+ }
53
+
54
+ /**
55
+ * Product offer/pricing data
56
+ */
57
+ export interface ProductOffer {
58
+ '@type'?: string;
59
+ price?: number | string;
60
+ priceCurrency?: string;
61
+ availability?: string;
62
+ url?: string;
63
+ seller?: { name?: string };
64
+ priceValidUntil?: string;
65
+ itemCondition?: string;
66
+ shippingDetails?: unknown;
67
+ }
68
+
69
+ /**
70
+ * Individual quality check result
71
+ */
72
+ export interface QualityCheck {
73
+ id: string;
74
+ name: string;
75
+ category: CheckCategory;
76
+ passed: boolean;
77
+ severity: IssueSeverity;
78
+ message: string;
79
+ details?: string;
80
+ recommendation?: string;
81
+ affectedProducts?: string[];
82
+ }
83
+
84
+ /**
85
+ * Product-level analysis result
86
+ */
87
+ export interface ProductAnalysis {
88
+ name: string;
89
+ url?: string;
90
+ sku?: string;
91
+ score: number;
92
+ issues: QualityCheck[];
93
+ attributes: {
94
+ hasName: boolean;
95
+ hasDescription: boolean;
96
+ hasSku: boolean;
97
+ hasGtin: boolean;
98
+ hasBrand: boolean;
99
+ hasImage: boolean;
100
+ hasPrice: boolean;
101
+ hasAvailability: boolean;
102
+ hasCategory: boolean;
103
+ descriptionLength: number;
104
+ imageCount: number;
105
+ };
106
+ }
107
+
108
+ /**
109
+ * Category scores breakdown
110
+ */
111
+ export interface CategoryScores {
112
+ completeness: number;
113
+ identifiers: number;
114
+ images: number;
115
+ pricing: number;
116
+ descriptions: number;
117
+ categories: number;
118
+ availability: number;
119
+ }
120
+
121
+ /**
122
+ * Overall feed analysis result
123
+ */
124
+ export interface FeedAnalysisResult {
125
+ url: string;
126
+ analyzedAt: string;
127
+ productsFound: number;
128
+ productsAnalyzed: number;
129
+ overallScore: number;
130
+ agentVisibilityScore: number;
131
+ grade: 'A' | 'B' | 'C' | 'D' | 'F';
132
+ categoryScores: CategoryScores;
133
+ issues: QualityCheck[];
134
+ topIssues: QualityCheck[];
135
+ products: ProductAnalysis[];
136
+ recommendations: Recommendation[];
137
+ summary: FeedSummary;
138
+ }
139
+
140
+ /**
141
+ * Recommendation for improving feed quality
142
+ */
143
+ export interface Recommendation {
144
+ priority: 'high' | 'medium' | 'low';
145
+ category: CheckCategory;
146
+ title: string;
147
+ description: string;
148
+ impact: string;
149
+ affectedCount: number;
150
+ }
151
+
152
+ /**
153
+ * Summary statistics for the feed
154
+ */
155
+ export interface FeedSummary {
156
+ totalProducts: number;
157
+ withName: number;
158
+ withDescription: number;
159
+ withSku: number;
160
+ withGtin: number;
161
+ withBrand: number;
162
+ withImages: number;
163
+ withPrice: number;
164
+ withAvailability: number;
165
+ withCategory: number;
166
+ averageDescriptionLength: number;
167
+ averageImageCount: number;
168
+ }
169
+
170
+ /**
171
+ * Input options for feed analysis
172
+ */
173
+ export interface FeedAnalysisInput {
174
+ url: string;
175
+ maxProducts?: number;
176
+ includeProductDetails?: boolean;
177
+ }
178
+
179
+ /**
180
+ * GTIN validation result
181
+ */
182
+ export interface GtinValidation {
183
+ isValid: boolean;
184
+ type?: 'GTIN-8' | 'GTIN-12' | 'GTIN-13' | 'GTIN-14' | 'UPC' | 'EAN';
185
+ error?: string;
186
+ }
187
+
188
+ /**
189
+ * Quality check definitions
190
+ */
191
+ export const QUALITY_CHECKS: Record<string, {
192
+ name: string;
193
+ category: CheckCategory;
194
+ severity: IssueSeverity;
195
+ description: string;
196
+ }> = {
197
+ // Completeness checks
198
+ 'missing-name': {
199
+ name: 'Missing Product Name',
200
+ category: 'completeness',
201
+ severity: 'critical',
202
+ description: 'Product name is required for AI agents to identify products',
203
+ },
204
+ 'missing-description': {
205
+ name: 'Missing Description',
206
+ category: 'completeness',
207
+ severity: 'warning',
208
+ description: 'Product description helps AI agents understand and recommend products',
209
+ },
210
+ 'short-description': {
211
+ name: 'Short Description',
212
+ category: 'descriptions',
213
+ severity: 'info',
214
+ description: 'Longer descriptions provide better context for AI agents',
215
+ },
216
+ 'missing-brand': {
217
+ name: 'Missing Brand',
218
+ category: 'completeness',
219
+ severity: 'warning',
220
+ description: 'Brand information helps with product identification and search',
221
+ },
222
+
223
+ // Identifier checks
224
+ 'missing-sku': {
225
+ name: 'Missing SKU',
226
+ category: 'identifiers',
227
+ severity: 'warning',
228
+ description: 'SKU helps uniquely identify products in your catalog',
229
+ },
230
+ 'missing-gtin': {
231
+ name: 'Missing GTIN/UPC/EAN',
232
+ category: 'identifiers',
233
+ severity: 'warning',
234
+ description: 'Global identifiers enable cross-platform product matching',
235
+ },
236
+ 'invalid-gtin': {
237
+ name: 'Invalid GTIN Format',
238
+ category: 'identifiers',
239
+ severity: 'critical',
240
+ description: 'GTIN has invalid format or check digit',
241
+ },
242
+
243
+ // Image checks
244
+ 'missing-image': {
245
+ name: 'Missing Product Image',
246
+ category: 'images',
247
+ severity: 'critical',
248
+ description: 'Product images are essential for AI shopping experiences',
249
+ },
250
+ 'single-image': {
251
+ name: 'Single Image Only',
252
+ category: 'images',
253
+ severity: 'info',
254
+ description: 'Multiple images improve product presentation',
255
+ },
256
+ 'missing-image-alt': {
257
+ name: 'Missing Image Alt Text',
258
+ category: 'images',
259
+ severity: 'info',
260
+ description: 'Alt text helps with accessibility and SEO',
261
+ },
262
+
263
+ // Pricing checks
264
+ 'missing-price': {
265
+ name: 'Missing Price',
266
+ category: 'pricing',
267
+ severity: 'critical',
268
+ description: 'Price is required for AI agents to complete purchases',
269
+ },
270
+ 'missing-currency': {
271
+ name: 'Missing Currency',
272
+ category: 'pricing',
273
+ severity: 'warning',
274
+ description: 'Currency must be specified for international commerce',
275
+ },
276
+ 'invalid-price': {
277
+ name: 'Invalid Price Format',
278
+ category: 'pricing',
279
+ severity: 'critical',
280
+ description: 'Price must be a valid number',
281
+ },
282
+
283
+ // Availability checks
284
+ 'missing-availability': {
285
+ name: 'Missing Availability',
286
+ category: 'availability',
287
+ severity: 'warning',
288
+ description: 'Availability status helps AI agents make purchase decisions',
289
+ },
290
+ 'invalid-availability': {
291
+ name: 'Invalid Availability Value',
292
+ category: 'availability',
293
+ severity: 'warning',
294
+ description: 'Availability should use Schema.org ItemAvailability values',
295
+ },
296
+
297
+ // Category checks
298
+ 'missing-category': {
299
+ name: 'Missing Category',
300
+ category: 'categories',
301
+ severity: 'info',
302
+ description: 'Product categorization improves discoverability',
303
+ },
304
+ };
305
+
306
+ /**
307
+ * Valid Schema.org availability values
308
+ */
309
+ export const VALID_AVAILABILITY_VALUES = [
310
+ 'https://schema.org/InStock',
311
+ 'https://schema.org/OutOfStock',
312
+ 'https://schema.org/PreOrder',
313
+ 'https://schema.org/PreSale',
314
+ 'https://schema.org/SoldOut',
315
+ 'https://schema.org/InStoreOnly',
316
+ 'https://schema.org/OnlineOnly',
317
+ 'https://schema.org/LimitedAvailability',
318
+ 'https://schema.org/Discontinued',
319
+ 'https://schema.org/BackOrder',
320
+ 'InStock',
321
+ 'OutOfStock',
322
+ 'PreOrder',
323
+ 'PreSale',
324
+ 'SoldOut',
325
+ 'InStoreOnly',
326
+ 'OnlineOnly',
327
+ 'LimitedAvailability',
328
+ 'Discontinued',
329
+ 'BackOrder',
330
+ ];
331
+
332
+ /**
333
+ * Scoring weights for different categories
334
+ */
335
+ export const CATEGORY_WEIGHTS: Record<CheckCategory, number> = {
336
+ completeness: 25,
337
+ identifiers: 15,
338
+ images: 20,
339
+ pricing: 20,
340
+ descriptions: 10,
341
+ categories: 5,
342
+ availability: 5,
343
+ };
344
+
345
+ /**
346
+ * Grade thresholds (aligned with all other scoring models)
347
+ */
348
+ export const GRADE_THRESHOLDS = {
349
+ A: 90,
350
+ B: 80,
351
+ C: 70,
352
+ D: 60,
353
+ F: 0,
354
+ };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * UCP Profile Generator Module
3
+ */
4
+
5
+ export { buildProfile, generateMinimalProfile } from './profile-builder.js';
6
+ export { generateSigningKeyPair, validatePublicKey } from './key-generator.js';
7
+ export type { KeyAlgorithm, KeyPairResult } from './key-generator.js';
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Signing Key Generator for UCP Webhook Signing
3
+ * Generates EC or RSA key pairs in JWK format
4
+ */
5
+
6
+ import * as jose from 'jose';
7
+ import { nanoid } from 'nanoid';
8
+ import type { JwkKey } from '../types/ucp-profile.js';
9
+
10
+ export type KeyAlgorithm = 'ES256' | 'RS256';
11
+
12
+ export interface KeyPairResult {
13
+ publicKey: JwkKey;
14
+ privateKey: string; // PEM format for merchant storage
15
+ }
16
+
17
+ /**
18
+ * Generate a new signing key pair
19
+ */
20
+ export async function generateSigningKeyPair(
21
+ algorithm: KeyAlgorithm = 'ES256'
22
+ ): Promise<KeyPairResult> {
23
+ const keyId = `ucp-${nanoid(12)}`;
24
+
25
+ if (algorithm === 'ES256') {
26
+ return generateECKeyPair(keyId);
27
+ } else {
28
+ return generateRSAKeyPair(keyId);
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Generate EC (P-256) key pair
34
+ */
35
+ async function generateECKeyPair(keyId: string): Promise<KeyPairResult> {
36
+ const { publicKey, privateKey } = await jose.generateKeyPair('ES256', {
37
+ extractable: true,
38
+ });
39
+
40
+ // Export public key as JWK
41
+ const publicJwk = await jose.exportJWK(publicKey);
42
+
43
+ // Export private key as PEM
44
+ const privatePem = await jose.exportPKCS8(privateKey);
45
+
46
+ return {
47
+ publicKey: {
48
+ kty: 'EC',
49
+ kid: keyId,
50
+ use: 'sig',
51
+ alg: 'ES256',
52
+ crv: publicJwk.crv as string,
53
+ x: publicJwk.x as string,
54
+ y: publicJwk.y as string,
55
+ },
56
+ privateKey: privatePem,
57
+ };
58
+ }
59
+
60
+ /**
61
+ * Generate RSA (2048-bit) key pair
62
+ */
63
+ async function generateRSAKeyPair(keyId: string): Promise<KeyPairResult> {
64
+ const { publicKey, privateKey } = await jose.generateKeyPair('RS256', {
65
+ extractable: true,
66
+ modulusLength: 2048,
67
+ });
68
+
69
+ // Export public key as JWK
70
+ const publicJwk = await jose.exportJWK(publicKey);
71
+
72
+ // Export private key as PEM
73
+ const privatePem = await jose.exportPKCS8(privateKey);
74
+
75
+ return {
76
+ publicKey: {
77
+ kty: 'RSA',
78
+ kid: keyId,
79
+ use: 'sig',
80
+ alg: 'RS256',
81
+ n: publicJwk.n as string,
82
+ e: publicJwk.e as string,
83
+ },
84
+ privateKey: privatePem,
85
+ };
86
+ }
87
+
88
+ /**
89
+ * Validate a JWK public key structure
90
+ */
91
+ export function validatePublicKey(key: JwkKey): string[] {
92
+ const errors: string[] = [];
93
+
94
+ if (!key.kty) {
95
+ errors.push('Missing required field: kty');
96
+ }
97
+
98
+ if (!key.kid) {
99
+ errors.push('Missing required field: kid');
100
+ }
101
+
102
+ if (key.kty === 'EC') {
103
+ if (!key.crv) errors.push('EC key missing curve (crv)');
104
+ if (!key.x) errors.push('EC key missing x coordinate');
105
+ if (!key.y) errors.push('EC key missing y coordinate');
106
+ if (key.crv && !['P-256', 'P-384', 'P-521'].includes(key.crv)) {
107
+ errors.push(`Unsupported EC curve: ${key.crv}`);
108
+ }
109
+ } else if (key.kty === 'RSA') {
110
+ if (!key.n) errors.push('RSA key missing modulus (n)');
111
+ if (!key.e) errors.push('RSA key missing exponent (e)');
112
+ } else if (key.kty) {
113
+ errors.push(`Unsupported key type: ${key.kty}`);
114
+ }
115
+
116
+ return errors;
117
+ }
118
+
119
+ /**
120
+ * Import and validate a public key from JWK
121
+ */
122
+ export async function importPublicKey(jwk: JwkKey): Promise<jose.KeyLike | Uint8Array> {
123
+ return jose.importJWK(jwk as jose.JWK, jwk.alg);
124
+ }