@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,434 +0,0 @@
1
- /**
2
- * Directory Service
3
- * Business logic for the UCP Merchant Directory
4
- */
5
-
6
- import { eq, ilike, sql, desc, asc, and, count, SQL } from 'drizzle-orm';
7
- import { getDb, merchants, type Merchant, type NewMerchant } from '../db/index.js';
8
-
9
- // Types
10
- export interface ListMerchantsParams {
11
- page?: number;
12
- limit?: number;
13
- category?: string;
14
- country?: string;
15
- search?: string;
16
- sort?: 'score' | 'domain' | 'displayName' | 'createdAt';
17
- order?: 'asc' | 'desc';
18
- }
19
-
20
- export interface ListMerchantsResult {
21
- merchants: Merchant[];
22
- pagination: {
23
- page: number;
24
- limit: number;
25
- total: number;
26
- totalPages: number;
27
- };
28
- filters: {
29
- categories: { name: string; count: number }[];
30
- countries: { code: string; count: number }[];
31
- };
32
- }
33
-
34
- export interface SubmitMerchantParams {
35
- domain: string;
36
- displayName?: string;
37
- description?: string;
38
- logoUrl?: string;
39
- websiteUrl?: string;
40
- category?: string;
41
- countryCode?: string;
42
- }
43
-
44
- export interface DirectoryStats {
45
- totalMerchants: number;
46
- verifiedMerchants: number;
47
- avgScore: number;
48
- totalCategories: number;
49
- totalCountries: number;
50
- gradeDistribution: { grade: string; count: number }[];
51
- topCategories: { name: string; count: number }[];
52
- recentAdditions: { domain: string; displayName: string; grade: string | null; addedAt: Date }[];
53
- }
54
-
55
- export interface ValidationResult {
56
- valid: boolean;
57
- error?: string;
58
- score?: number;
59
- grade?: string;
60
- transports?: string;
61
- ucpVersion?: string;
62
- }
63
-
64
- // UCP Profile structure (minimal typing for validation)
65
- interface UcpProfile {
66
- ucp?: {
67
- version?: string;
68
- services?: Record<string, { rest?: unknown; mcp?: unknown; a2a?: unknown; embedded?: unknown }>;
69
- capabilities?: Array<{ name: string }>;
70
- };
71
- signing_keys?: unknown[];
72
- }
73
-
74
- /**
75
- * Fetch UCP profile from a URL, return null if not valid JSON
76
- */
77
- async function tryFetchProfile(url: string): Promise<UcpProfile | null> {
78
- try {
79
- const res = await fetch(url, {
80
- headers: {
81
- Accept: 'application/json',
82
- 'User-Agent': 'UCP-Directory/1.0 (https://ucptools.dev)',
83
- },
84
- signal: AbortSignal.timeout(10000),
85
- });
86
-
87
- if (!res.ok) return null;
88
-
89
- const text = await res.text();
90
- // Check if response looks like JSON (not HTML)
91
- if (text.trim().startsWith('<')) return null;
92
-
93
- return JSON.parse(text) as UcpProfile;
94
- } catch {
95
- return null;
96
- }
97
- }
98
-
99
- /**
100
- * Validate a domain's UCP profile
101
- */
102
- export async function validateDomain(domain: string): Promise<ValidationResult> {
103
- // Try both /.well-known/ucp and /.well-known/ucp.json
104
- const urls = [
105
- `https://${domain}/.well-known/ucp`,
106
- `https://${domain}/.well-known/ucp.json`,
107
- ];
108
-
109
- let profile: UcpProfile | null = null;
110
-
111
- for (const url of urls) {
112
- profile = await tryFetchProfile(url);
113
- if (profile) break;
114
- }
115
-
116
- try {
117
- if (!profile) {
118
- return { valid: false, error: 'No UCP profile found at /.well-known/ucp or /.well-known/ucp.json' };
119
- }
120
-
121
- // Basic validation
122
- if (!profile?.ucp?.version || !profile?.ucp?.services) {
123
- return { valid: false, error: 'Invalid UCP profile structure' };
124
- }
125
-
126
- // Extract transports
127
- const transports = new Set<string>();
128
- for (const svc of Object.values(profile.ucp.services || {})) {
129
- if (svc.rest) transports.add('REST');
130
- if (svc.mcp) transports.add('MCP');
131
- if (svc.a2a) transports.add('A2A');
132
- if (svc.embedded) transports.add('Embedded');
133
- }
134
-
135
- // Simple scoring
136
- let score = 50;
137
- const capabilities = profile.ucp.capabilities || [];
138
- if (capabilities.some((c) => c.name === 'dev.ucp.shopping.checkout')) score += 20;
139
- if (capabilities.some((c) => c.name === 'dev.ucp.shopping.cart')) score += 10;
140
- if (capabilities.some((c) => c.name === 'dev.ucp.shopping.order')) score += 10;
141
- if (profile.signing_keys && profile.signing_keys.length > 0) score += 10;
142
-
143
- const grade = score >= 90 ? 'A' : score >= 80 ? 'B' : score >= 70 ? 'C' : score >= 60 ? 'D' : 'F';
144
-
145
- return {
146
- valid: true,
147
- score,
148
- grade,
149
- transports: Array.from(transports).join(','),
150
- ucpVersion: profile.ucp.version,
151
- };
152
- } catch (e: unknown) {
153
- const message = e instanceof Error ? e.message : 'Failed to fetch profile';
154
- return { valid: false, error: message };
155
- }
156
- }
157
-
158
- /**
159
- * List merchants with pagination and filters
160
- */
161
- export async function listMerchants(params: ListMerchantsParams): Promise<ListMerchantsResult> {
162
- const db = getDb();
163
-
164
- const page = Math.max(1, params.page || 1);
165
- const limit = Math.min(100, Math.max(1, params.limit || 20));
166
- const offset = (page - 1) * limit;
167
-
168
- // Build where conditions
169
- const conditions: SQL[] = [eq(merchants.isPublic, true)];
170
-
171
- if (params.category) {
172
- conditions.push(eq(merchants.category, params.category));
173
- }
174
-
175
- if (params.country) {
176
- conditions.push(eq(merchants.countryCode, params.country.toUpperCase()));
177
- }
178
-
179
- if (params.search) {
180
- conditions.push(
181
- sql`(${merchants.domain} ILIKE ${`%${params.search}%`} OR ${merchants.displayName} ILIKE ${`%${params.search}%`})`
182
- );
183
- }
184
-
185
- const whereClause = and(...conditions);
186
-
187
- // Determine sort column and order
188
- const sortColumn =
189
- params.sort === 'domain'
190
- ? merchants.domain
191
- : params.sort === 'displayName'
192
- ? merchants.displayName
193
- : params.sort === 'createdAt'
194
- ? merchants.createdAt
195
- : merchants.ucpScore;
196
-
197
- const orderFn = params.order === 'asc' ? asc : desc;
198
-
199
- // Get total count
200
- const countResult = await db
201
- .select({ total: count() })
202
- .from(merchants)
203
- .where(whereClause);
204
-
205
- const total = countResult[0]?.total || 0;
206
-
207
- // Get merchants
208
- const merchantList = await db
209
- .select()
210
- .from(merchants)
211
- .where(whereClause)
212
- .orderBy(orderFn(sortColumn))
213
- .limit(limit)
214
- .offset(offset);
215
-
216
- // Get category counts
217
- const categoryResult = await db
218
- .select({
219
- category: merchants.category,
220
- count: count(),
221
- })
222
- .from(merchants)
223
- .where(and(eq(merchants.isPublic, true), sql`${merchants.category} IS NOT NULL`))
224
- .groupBy(merchants.category)
225
- .orderBy(desc(count()));
226
-
227
- // Get country counts
228
- const countryResult = await db
229
- .select({
230
- countryCode: merchants.countryCode,
231
- count: count(),
232
- })
233
- .from(merchants)
234
- .where(and(eq(merchants.isPublic, true), sql`${merchants.countryCode} IS NOT NULL`))
235
- .groupBy(merchants.countryCode)
236
- .orderBy(desc(count()));
237
-
238
- return {
239
- merchants: merchantList,
240
- pagination: {
241
- page,
242
- limit,
243
- total,
244
- totalPages: Math.ceil(total / limit),
245
- },
246
- filters: {
247
- categories: categoryResult.map((r) => ({ name: r.category!, count: r.count })),
248
- countries: countryResult.map((r) => ({ code: r.countryCode!, count: r.count })),
249
- },
250
- };
251
- }
252
-
253
- /**
254
- * Get a single merchant by domain
255
- */
256
- export async function getMerchantByDomain(domain: string): Promise<Merchant | null> {
257
- const db = getDb();
258
- const result = await db.select().from(merchants).where(eq(merchants.domain, domain.toLowerCase())).limit(1);
259
- return result[0] || null;
260
- }
261
-
262
- /**
263
- * Submit a new merchant to the directory
264
- */
265
- export async function submitMerchant(
266
- params: SubmitMerchantParams
267
- ): Promise<{ success: boolean; merchant?: Merchant; error?: string; details?: string }> {
268
- const db = getDb();
269
-
270
- // Clean domain
271
- const cleanDomain = params.domain
272
- .replace(/^https?:\/\//, '')
273
- .replace(/\/$/, '')
274
- .split('/')[0]
275
- .toLowerCase();
276
-
277
- // Check if domain already exists
278
- const existing = await getMerchantByDomain(cleanDomain);
279
- if (existing) {
280
- return {
281
- success: false,
282
- error: 'Domain already registered',
283
- details: `Merchant ID: ${existing.id}`,
284
- };
285
- }
286
-
287
- // Validate the domain has a valid UCP profile
288
- const validation = await validateDomain(cleanDomain);
289
-
290
- if (!validation.valid) {
291
- return {
292
- success: false,
293
- error: 'Invalid UCP profile',
294
- details: validation.error,
295
- };
296
- }
297
-
298
- // Insert merchant
299
- const newMerchant: NewMerchant = {
300
- domain: cleanDomain,
301
- displayName: params.displayName || cleanDomain,
302
- description: params.description || null,
303
- logoUrl: params.logoUrl || null,
304
- websiteUrl: params.websiteUrl || `https://${cleanDomain}`,
305
- category: params.category || null,
306
- countryCode: params.countryCode?.toUpperCase() || null,
307
- ucpScore: validation.score || null,
308
- ucpGrade: validation.grade || null,
309
- transports: validation.transports || null,
310
- isPublic: true,
311
- isVerified: false,
312
- lastValidatedAt: new Date(),
313
- };
314
-
315
- const result = await db.insert(merchants).values(newMerchant).returning();
316
-
317
- return {
318
- success: true,
319
- merchant: result[0],
320
- };
321
- }
322
-
323
- /**
324
- * Get directory statistics
325
- */
326
- export async function getDirectoryStats(): Promise<DirectoryStats> {
327
- const db = getDb();
328
-
329
- // Overall stats
330
- const statsResult = await db
331
- .select({
332
- totalMerchants: count(),
333
- verifiedMerchants: sql<number>`COUNT(*) FILTER (WHERE ${merchants.isVerified} = true)`,
334
- avgScore: sql<number>`COALESCE(AVG(${merchants.ucpScore}), 0)`,
335
- })
336
- .from(merchants)
337
- .where(eq(merchants.isPublic, true));
338
-
339
- // Count distinct categories and countries
340
- const categoryCountResult = await db
341
- .select({ count: sql<number>`COUNT(DISTINCT ${merchants.category})` })
342
- .from(merchants)
343
- .where(and(eq(merchants.isPublic, true), sql`${merchants.category} IS NOT NULL`));
344
-
345
- const countryCountResult = await db
346
- .select({ count: sql<number>`COUNT(DISTINCT ${merchants.countryCode})` })
347
- .from(merchants)
348
- .where(and(eq(merchants.isPublic, true), sql`${merchants.countryCode} IS NOT NULL`));
349
-
350
- // Grade distribution
351
- const gradeResult = await db
352
- .select({
353
- grade: merchants.ucpGrade,
354
- count: count(),
355
- })
356
- .from(merchants)
357
- .where(and(eq(merchants.isPublic, true), sql`${merchants.ucpGrade} IS NOT NULL`))
358
- .groupBy(merchants.ucpGrade)
359
- .orderBy(merchants.ucpGrade);
360
-
361
- // Top categories
362
- const topCategoriesResult = await db
363
- .select({
364
- name: merchants.category,
365
- count: count(),
366
- })
367
- .from(merchants)
368
- .where(and(eq(merchants.isPublic, true), sql`${merchants.category} IS NOT NULL`))
369
- .groupBy(merchants.category)
370
- .orderBy(desc(count()))
371
- .limit(10);
372
-
373
- // Recent additions
374
- const recentResult = await db
375
- .select({
376
- domain: merchants.domain,
377
- displayName: merchants.displayName,
378
- grade: merchants.ucpGrade,
379
- addedAt: merchants.createdAt,
380
- })
381
- .from(merchants)
382
- .where(eq(merchants.isPublic, true))
383
- .orderBy(desc(merchants.createdAt))
384
- .limit(5);
385
-
386
- const stats = statsResult[0];
387
-
388
- return {
389
- totalMerchants: stats?.totalMerchants || 0,
390
- verifiedMerchants: stats?.verifiedMerchants || 0,
391
- avgScore: Math.round(stats?.avgScore || 0),
392
- totalCategories: categoryCountResult[0]?.count || 0,
393
- totalCountries: countryCountResult[0]?.count || 0,
394
- gradeDistribution: gradeResult.map((r) => ({ grade: r.grade!, count: r.count })),
395
- topCategories: topCategoriesResult.map((r) => ({ name: r.name!, count: r.count })),
396
- recentAdditions: recentResult.map((r) => ({
397
- domain: r.domain,
398
- displayName: r.displayName,
399
- grade: r.grade,
400
- addedAt: r.addedAt,
401
- })),
402
- };
403
- }
404
-
405
- /**
406
- * Re-validate a merchant's UCP profile and update their record
407
- */
408
- export async function revalidateMerchant(domain: string): Promise<{ success: boolean; error?: string }> {
409
- const db = getDb();
410
-
411
- const merchant = await getMerchantByDomain(domain);
412
- if (!merchant) {
413
- return { success: false, error: 'Merchant not found' };
414
- }
415
-
416
- const validation = await validateDomain(domain);
417
-
418
- if (!validation.valid) {
419
- return { success: false, error: validation.error };
420
- }
421
-
422
- await db
423
- .update(merchants)
424
- .set({
425
- ucpScore: validation.score || null,
426
- ucpGrade: validation.grade || null,
427
- transports: validation.transports || null,
428
- lastValidatedAt: new Date(),
429
- updatedAt: new Date(),
430
- })
431
- .where(eq(merchants.domain, domain.toLowerCase()));
432
-
433
- return { success: true };
434
- }
@@ -1,140 +0,0 @@
1
- /**
2
- * Generator Types for UCP Profile Generator
3
- */
4
-
5
- import type { Environment, ProfileStatus } from './ucp-profile.js';
6
-
7
- // Merchant information
8
- export interface MerchantInfo {
9
- merchantId: string;
10
- primaryDomain: string;
11
- displayName?: string;
12
- environment?: Environment;
13
- }
14
-
15
- // Transport configuration inputs
16
- export interface TransportConfig {
17
- rest?: {
18
- endpoint: string;
19
- schemaUrl?: string; // Override default
20
- };
21
- mcp?: {
22
- endpoint: string;
23
- schemaUrl?: string;
24
- };
25
- a2a?: {
26
- agentCardUrl: string;
27
- };
28
- embedded?: {
29
- schemaUrl: string;
30
- };
31
- }
32
-
33
- // Capability selection (based on official UCP spec)
34
- export interface CapabilitySelection {
35
- checkout: boolean; // Default: true
36
- order: boolean; // Requires signing keys
37
- fulfillment: boolean; // Extension of order
38
- discount: boolean; // Extension
39
- payment?: boolean; // Payment capability
40
- buyerConsent?: boolean; // Buyer consent capability
41
- customCapabilities?: CustomCapability[];
42
- }
43
-
44
- // Custom/vendor capability
45
- export interface CustomCapability {
46
- namespace: string; // e.g., "com.myvendor"
47
- name: string; // e.g., "custom-feature"
48
- version: string;
49
- specUrl: string;
50
- schemaUrl: string;
51
- extendsCapability?: string;
52
- }
53
-
54
- // Security configuration
55
- export interface SecurityConfig {
56
- generateSigningKeys: boolean;
57
- signingKeyAlgorithm?: 'ES256' | 'RS256';
58
- uploadedPublicKey?: string; // PEM or JWK format
59
- }
60
-
61
- // Complete generator input
62
- export interface GeneratorInput {
63
- merchant: MerchantInfo;
64
- transport: TransportConfig;
65
- capabilities: CapabilitySelection;
66
- security?: SecurityConfig;
67
- ucpVersion?: string; // Override default version
68
- }
69
-
70
- // Generator output artifacts
71
- export interface GeneratorOutput {
72
- profile: object; // The UCP profile JSON
73
- profileJson: string; // Formatted JSON string
74
- installInstructions: string; // Markdown instructions
75
- validationReport?: object; // Initial validation
76
- signingKeyPair?: { // If keys were generated
77
- publicKey: object; // JWK public key
78
- privateKey: string; // PEM private key (for merchant to store securely)
79
- };
80
- }
81
-
82
- // Hosting mode options
83
- export type HostingMode = 'static' | 'edge-worker' | 'reverse-proxy';
84
-
85
- // Hosting target platforms
86
- export type HostingPlatform =
87
- | 'nginx'
88
- | 'apache'
89
- | 'vercel'
90
- | 'netlify'
91
- | 'cloudflare-worker'
92
- | 'cloudflare-pages'
93
- | 's3-cloudfront'
94
- | 'generic';
95
-
96
- // Hosting configuration
97
- export interface HostingConfig {
98
- mode: HostingMode;
99
- platform?: HostingPlatform;
100
- merchantId: string;
101
- merchantDomain: string;
102
- hostedProfileUrl?: string; // For edge/proxy modes
103
- }
104
-
105
- // Install artifact
106
- export interface InstallArtifact {
107
- filename: string;
108
- content: string;
109
- contentType: 'json' | 'javascript' | 'nginx' | 'apache' | 'markdown';
110
- description: string;
111
- }
112
-
113
- // Database models (for API service)
114
- export interface MerchantRecord {
115
- id: string;
116
- primaryDomain: string;
117
- displayName?: string;
118
- createdAt: Date;
119
- updatedAt: Date;
120
- }
121
-
122
- export interface ProfileRecord {
123
- id: string;
124
- merchantId: string;
125
- versionTag: string;
126
- ucpVersion: string;
127
- jsonBody: object;
128
- status: ProfileStatus;
129
- createdAt: Date;
130
- updatedAt: Date;
131
- }
132
-
133
- export interface ValidationRunRecord {
134
- id: string;
135
- profileId: string;
136
- mode: 'draft' | 'remote';
137
- result: object;
138
- ok: boolean;
139
- createdAt: Date;
140
- }
@@ -1,140 +0,0 @@
1
- /**
2
- * UCP (Universal Commerce Protocol) Profile Types
3
- * Based on https://ucp.dev/specification/overview/
4
- */
5
-
6
- // Current UCP version
7
- export const CURRENT_UCP_VERSION = '2026-01-11';
8
-
9
- // Transport binding types
10
- export interface RestTransport {
11
- schema: string; // OpenAPI schema URL
12
- endpoint: string; // REST API endpoint (https, no trailing slash)
13
- }
14
-
15
- export interface McpTransport {
16
- schema: string; // MCP schema URL
17
- endpoint: string; // MCP endpoint URL
18
- }
19
-
20
- export interface A2aTransport {
21
- agentCard: string; // A2A agent card URL
22
- }
23
-
24
- export interface EmbeddedTransport {
25
- schema: string; // Embedded schema URL
26
- }
27
-
28
- // Service definition with transport bindings
29
- export interface UcpService {
30
- version: string;
31
- spec: string;
32
- rest?: RestTransport;
33
- mcp?: McpTransport;
34
- a2a?: A2aTransport;
35
- embedded?: EmbeddedTransport;
36
- }
37
-
38
- // Capability definition
39
- export interface UcpCapability {
40
- name: string; // e.g., "dev.ucp.shopping.checkout"
41
- version: string; // e.g., "2026-01-11"
42
- spec: string; // Specification URL
43
- schema: string; // JSON Schema URL
44
- extends?: string; // Parent capability for extensions
45
- config?: Record<string, unknown>; // Capability-specific settings
46
- }
47
-
48
- // JWK (JSON Web Key) for signing
49
- export interface JwkKey {
50
- kty: string; // Key type (e.g., "EC", "RSA")
51
- kid: string; // Key ID
52
- use?: string; // Key use (e.g., "sig")
53
- alg?: string; // Algorithm (e.g., "ES256")
54
- crv?: string; // Curve (for EC keys)
55
- x?: string; // X coordinate (for EC keys)
56
- y?: string; // Y coordinate (for EC keys)
57
- n?: string; // Modulus (for RSA keys)
58
- e?: string; // Exponent (for RSA keys)
59
- }
60
-
61
- // Signing keys - array of JWK public keys at root level
62
- export type SigningKeys = JwkKey[];
63
-
64
- // Payment handler definition
65
- export interface PaymentHandler {
66
- id: string; // Handler identifier
67
- name: string; // Display name
68
- version: string; // Handler version (YYYY-MM-DD)
69
- spec: string; // Handler specification URL
70
- config_schema?: string; // Configuration schema URL
71
- instrument_schemas?: string[]; // Payment instrument schemas
72
- config?: Record<string, unknown>; // Handler-specific config
73
- }
74
-
75
- // Payment configuration
76
- export interface PaymentConfig {
77
- handlers: PaymentHandler[];
78
- }
79
-
80
- // Main UCP object within profile
81
- export interface UcpObject {
82
- version: string;
83
- services: Record<string, UcpService>;
84
- capabilities: UcpCapability[];
85
- }
86
-
87
- // Complete UCP Business Profile (/.well-known/ucp)
88
- export interface UcpProfile {
89
- ucp: UcpObject;
90
- payment?: PaymentConfig; // Payment handlers configuration
91
- signing_keys?: SigningKeys; // JWK public keys for webhook verification
92
- // Additional vendor extensions can be added as siblings
93
- [key: string]: unknown;
94
- }
95
-
96
- // Known capability namespaces
97
- export const CAPABILITY_NAMESPACES = {
98
- UCP_OFFICIAL: 'dev.ucp.',
99
- VENDOR_PREFIX: 'com.',
100
- } as const;
101
-
102
- // Known UCP capabilities (from official spec)
103
- export const KNOWN_CAPABILITIES = {
104
- CHECKOUT: 'dev.ucp.shopping.checkout',
105
- ORDER: 'dev.ucp.shopping.order',
106
- PAYMENT: 'dev.ucp.shopping.payment',
107
- PAYMENT_DATA: 'dev.ucp.shopping.payment_data',
108
- FULFILLMENT: 'dev.ucp.shopping.fulfillment',
109
- DISCOUNT: 'dev.ucp.shopping.discount',
110
- BUYER_CONSENT: 'dev.ucp.shopping.buyer_consent',
111
- } as const;
112
-
113
- // Known UCP services
114
- export const KNOWN_SERVICES = {
115
- SHOPPING: 'dev.ucp.shopping',
116
- } as const;
117
-
118
- // Default URLs for UCP official resources
119
- export const UCP_DEFAULTS = {
120
- SPEC_BASE: 'https://ucp.dev/specification/',
121
- SCHEMA_BASE: 'https://ucp.dev/schemas/',
122
- SERVICE_SCHEMA_BASE: 'https://ucp.dev/services/',
123
-
124
- // Default schema URLs
125
- SHOPPING_REST_SCHEMA: 'https://ucp.dev/services/shopping/rest.openapi.json',
126
- CHECKOUT_SPEC: 'https://ucp.dev/specification/checkout/',
127
- CHECKOUT_SCHEMA: 'https://ucp.dev/schemas/shopping/checkout.json',
128
- ORDER_SPEC: 'https://ucp.dev/specification/order/',
129
- ORDER_SCHEMA: 'https://ucp.dev/schemas/shopping/order.json',
130
- FULFILLMENT_SPEC: 'https://ucp.dev/specification/fulfillment/',
131
- FULFILLMENT_SCHEMA: 'https://ucp.dev/schemas/shopping/fulfillment.json',
132
- DISCOUNT_SPEC: 'https://ucp.dev/specification/discount/',
133
- DISCOUNT_SCHEMA: 'https://ucp.dev/schemas/shopping/discount.json',
134
- } as const;
135
-
136
- // Environment types
137
- export type Environment = 'production' | 'staging' | 'development';
138
-
139
- // Profile status
140
- export type ProfileStatus = 'draft' | 'published';