@tokenoftrust/storefront-runner 1.3.2

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 (310) hide show
  1. package/LICENSE +58 -0
  2. package/README.md +42 -0
  3. package/apps/storefront/astro.config.mjs +66 -0
  4. package/apps/storefront/dev-plugins/dev-loop-state.mjs +147 -0
  5. package/apps/storefront/dev-plugins/tenant-hot-reload.mjs +238 -0
  6. package/apps/storefront/env.d.ts +162 -0
  7. package/apps/storefront/migrations/0000_baseline.sql +54 -0
  8. package/apps/storefront/migrations/0001_products_fts.sql +9 -0
  9. package/apps/storefront/migrations/README.md +54 -0
  10. package/apps/storefront/migrations/meta/0000_snapshot.json +347 -0
  11. package/apps/storefront/migrations/meta/0001_snapshot.json +347 -0
  12. package/apps/storefront/migrations/meta/_journal.json +20 -0
  13. package/apps/storefront/package.json +42 -0
  14. package/apps/storefront/public/brand/northwind-logo-dark.svg +4 -0
  15. package/apps/storefront/public/brand/northwind-logo-light.svg +4 -0
  16. package/apps/storefront/public/brand/northwind-og.svg +13 -0
  17. package/apps/storefront/public/favicon.svg +5 -0
  18. package/apps/storefront/public/js/dashboard-team.js +147 -0
  19. package/apps/storefront/public/js/dashboard-vendor-search.js +115 -0
  20. package/apps/storefront/public/shared/commerce-chrome.css +369 -0
  21. package/apps/storefront/src/components/Badge.astro +34 -0
  22. package/apps/storefront/src/components/Breadcrumbs.astro +45 -0
  23. package/apps/storefront/src/components/CollectionCard.astro +56 -0
  24. package/apps/storefront/src/components/EmptyState.astro +55 -0
  25. package/apps/storefront/src/components/Img.astro +68 -0
  26. package/apps/storefront/src/components/ProductCard.astro +228 -0
  27. package/apps/storefront/src/components/Section.astro +39 -0
  28. package/apps/storefront/src/components/Seo.astro +70 -0
  29. package/apps/storefront/src/components/WidgetFrame.astro +34 -0
  30. package/apps/storefront/src/components/admin/AdminAIWorkflowsTab.astro +531 -0
  31. package/apps/storefront/src/components/admin/AdminCustomersTab.astro +597 -0
  32. package/apps/storefront/src/components/admin/AdminFoxyCartSettingsPanel.astro +227 -0
  33. package/apps/storefront/src/components/admin/AdminFulfillmentTab.astro +465 -0
  34. package/apps/storefront/src/components/admin/AdminProductsTab.astro +901 -0
  35. package/apps/storefront/src/components/admin/AdminReportingTab.astro +435 -0
  36. package/apps/storefront/src/components/admin/AdminSettingsTab.astro +782 -0
  37. package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +1223 -0
  38. package/apps/storefront/src/components/auth/AuthBadge.astro +44 -0
  39. package/apps/storefront/src/components/chrome/AnnouncementBar.astro +108 -0
  40. package/apps/storefront/src/components/chrome/MegaMenu.astro +26 -0
  41. package/apps/storefront/src/components/chrome/NavDropdown.astro +201 -0
  42. package/apps/storefront/src/components/chrome/SiteFooter.astro +201 -0
  43. package/apps/storefront/src/components/chrome/SiteHeader.astro +234 -0
  44. package/apps/storefront/src/components/chrome/UtilityNav.astro +79 -0
  45. package/apps/storefront/src/components/commerce/ArticleCard.astro +242 -0
  46. package/apps/storefront/src/components/commerce/BrandGrid.astro +175 -0
  47. package/apps/storefront/src/components/commerce/FoxyLoader.astro +23 -0
  48. package/apps/storefront/src/components/commerce/FoxySidecartStyles.astro +61 -0
  49. package/apps/storefront/src/components/commerce/PriceDisplay.astro +91 -0
  50. package/apps/storefront/src/components/commerce/ProductCarousel.astro +197 -0
  51. package/apps/storefront/src/components/commerce/ProductGrid.astro +75 -0
  52. package/apps/storefront/src/components/commerce/RatingStars.astro +58 -0
  53. package/apps/storefront/src/components/commerce/ReviewList.astro +54 -0
  54. package/apps/storefront/src/components/commerce/ReviewSummary.astro +44 -0
  55. package/apps/storefront/src/components/commerce/TagFilterCloud.astro +149 -0
  56. package/apps/storefront/src/components/compliance/AdultSignatureNotice.astro +37 -0
  57. package/apps/storefront/src/components/compliance/ComplianceNotice.astro +99 -0
  58. package/apps/storefront/src/components/compliance/ExciseTaxNotice.astro +45 -0
  59. package/apps/storefront/src/components/compliance/NicotineWarning.astro +33 -0
  60. package/apps/storefront/src/components/compliance/PactActNotice.astro +35 -0
  61. package/apps/storefront/src/components/compliance/Prop65Warning.astro +80 -0
  62. package/apps/storefront/src/components/compliance/PurchaseLimitNotice.astro +38 -0
  63. package/apps/storefront/src/components/compliance/ShippingRestrictionNotice.astro +35 -0
  64. package/apps/storefront/src/components/compliance/StateEligibilityNotice.astro +53 -0
  65. package/apps/storefront/src/components/home/Hero.astro +164 -0
  66. package/apps/storefront/src/components/islands/AgeGate.tsx +109 -0
  67. package/apps/storefront/src/components/islands/ImageGallery.tsx +237 -0
  68. package/apps/storefront/src/components/islands/IsolatedAgeGate.tsx +73 -0
  69. package/apps/storefront/src/components/islands/MobileNav.tsx +239 -0
  70. package/apps/storefront/src/components/islands/QuickView.tsx +138 -0
  71. package/apps/storefront/src/components/islands/RecentlyViewed.tsx +122 -0
  72. package/apps/storefront/src/components/islands/SavedList.tsx +99 -0
  73. package/apps/storefront/src/components/islands/SearchTypeahead.tsx +248 -0
  74. package/apps/storefront/src/components/islands/VariantSelector.tsx +375 -0
  75. package/apps/storefront/src/components/marketing/CardGrid.astro +72 -0
  76. package/apps/storefront/src/components/marketing/CodeSample.astro +20 -0
  77. package/apps/storefront/src/components/marketing/CtaBand.astro +24 -0
  78. package/apps/storefront/src/components/marketing/Faq.astro +37 -0
  79. package/apps/storefront/src/components/marketing/Integrations.astro +63 -0
  80. package/apps/storefront/src/components/marketing/MarketingBlocks.astro +53 -0
  81. package/apps/storefront/src/components/marketing/MarketingCtas.astro +48 -0
  82. package/apps/storefront/src/components/marketing/MarketingHero.astro +72 -0
  83. package/apps/storefront/src/components/marketing/ProofStrip.astro +37 -0
  84. package/apps/storefront/src/components/marketing/SplitCompare.astro +53 -0
  85. package/apps/storefront/src/components/marketing/Steps.astro +36 -0
  86. package/apps/storefront/src/components/marketing/Testimonials.astro +38 -0
  87. package/apps/storefront/src/components/marketing/TrustBar.astro +22 -0
  88. package/apps/storefront/src/components/membership/IllustrativeLabel.astro +56 -0
  89. package/apps/storefront/src/components/membership/TierCards.astro +164 -0
  90. package/apps/storefront/src/components/membership/TierComparisonTable.astro +148 -0
  91. package/apps/storefront/src/components/plp/ActiveFilters.astro +79 -0
  92. package/apps/storefront/src/components/plp/FacetSidebar.astro +199 -0
  93. package/apps/storefront/src/components/plp/Pagination.astro +99 -0
  94. package/apps/storefront/src/components/plp/SortSelect.astro +64 -0
  95. package/apps/storefront/src/components/style-guide/StyleGuideNav.astro +95 -0
  96. package/apps/storefront/src/components/subscription/HowItWorksSteps.astro +67 -0
  97. package/apps/storefront/src/components/subscription/ManageSubscriptionEntry.astro +67 -0
  98. package/apps/storefront/src/components/subscription/SavingsExplainer.astro +101 -0
  99. package/apps/storefront/src/config/adminTargetTenant.ts +28 -0
  100. package/apps/storefront/src/config/devTenantSeed.ts +78 -0
  101. package/apps/storefront/src/config/devTenants.ts +16 -0
  102. package/apps/storefront/src/config/kvTenantResolver.ts +87 -0
  103. package/apps/storefront/src/config/resolver.ts +102 -0
  104. package/apps/storefront/src/config/tenants.ts +87 -0
  105. package/apps/storefront/src/layouts/Layout.astro +368 -0
  106. package/apps/storefront/src/lib/admin/README.md +122 -0
  107. package/apps/storefront/src/lib/admin/ai/approvalMode.ts +21 -0
  108. package/apps/storefront/src/lib/admin/ai/askEnvelope.ts +48 -0
  109. package/apps/storefront/src/lib/admin/ai/audit.ts +14 -0
  110. package/apps/storefront/src/lib/admin/ai/index.ts +6 -0
  111. package/apps/storefront/src/lib/admin/ai/operationalRuns.ts +155 -0
  112. package/apps/storefront/src/lib/admin/ai/recommendation.ts +20 -0
  113. package/apps/storefront/src/lib/admin/checkoutStyleInput.ts +172 -0
  114. package/apps/storefront/src/lib/admin/checkoutStyleTarget.ts +65 -0
  115. package/apps/storefront/src/lib/admin/envelope.ts +10 -0
  116. package/apps/storefront/src/lib/admin/evidence/evidenceReference.ts +32 -0
  117. package/apps/storefront/src/lib/admin/memoizeByEnvelope.ts +27 -0
  118. package/apps/storefront/src/lib/admin/orderClient.ts +146 -0
  119. package/apps/storefront/src/lib/admin/seed.ts +54 -0
  120. package/apps/storefront/src/lib/admin/services/mock/aiWorkflows.ts +59 -0
  121. package/apps/storefront/src/lib/admin/services/mock/customers.ts +86 -0
  122. package/apps/storefront/src/lib/admin/services/mock/evidence.ts +99 -0
  123. package/apps/storefront/src/lib/admin/services/mock/foxyCartSettings.ts +78 -0
  124. package/apps/storefront/src/lib/admin/services/mock/fulfillment.ts +62 -0
  125. package/apps/storefront/src/lib/admin/services/mock/index.ts +12 -0
  126. package/apps/storefront/src/lib/admin/services/mock/orders.ts +184 -0
  127. package/apps/storefront/src/lib/admin/services/mock/products.ts +105 -0
  128. package/apps/storefront/src/lib/admin/services/mock/reporting.ts +51 -0
  129. package/apps/storefront/src/lib/admin/services/mock/settings.ts +70 -0
  130. package/apps/storefront/src/lib/admin/services/mock/tenants.ts +29 -0
  131. package/apps/storefront/src/lib/admin/services/orders.ts +300 -0
  132. package/apps/storefront/src/lib/admin/viewmodels/aiWorkflows.ts +67 -0
  133. package/apps/storefront/src/lib/admin/viewmodels/customers.ts +56 -0
  134. package/apps/storefront/src/lib/admin/viewmodels/fulfillment.ts +49 -0
  135. package/apps/storefront/src/lib/admin/viewmodels/orders.ts +92 -0
  136. package/apps/storefront/src/lib/admin/viewmodels/products.ts +74 -0
  137. package/apps/storefront/src/lib/admin/viewmodels/reporting.ts +34 -0
  138. package/apps/storefront/src/lib/admin/viewmodels/settings.ts +64 -0
  139. package/apps/storefront/src/lib/adminTenantEnvelope.ts +90 -0
  140. package/apps/storefront/src/lib/analytics/index.ts +47 -0
  141. package/apps/storefront/src/lib/auth/brokerAssertion.ts +136 -0
  142. package/apps/storefront/src/lib/auth/devAuth.ts +90 -0
  143. package/apps/storefront/src/lib/auth/gatePage.ts +81 -0
  144. package/apps/storefront/src/lib/auth/identityToken.ts +341 -0
  145. package/apps/storefront/src/lib/auth/loginGate.ts +119 -0
  146. package/apps/storefront/src/lib/auth/route.ts +146 -0
  147. package/apps/storefront/src/lib/auth/session.ts +203 -0
  148. package/apps/storefront/src/lib/auth/totAccessClient.ts +503 -0
  149. package/apps/storefront/src/lib/basePath.ts +41 -0
  150. package/apps/storefront/src/lib/cfImage.ts +55 -0
  151. package/apps/storefront/src/lib/chrome/model.ts +244 -0
  152. package/apps/storefront/src/lib/chrome/parseConfig.ts +34 -0
  153. package/apps/storefront/src/lib/colors.ts +97 -0
  154. package/apps/storefront/src/lib/commerce/foxyCartSettings.ts +253 -0
  155. package/apps/storefront/src/lib/content-edit/applyDiff.ts +91 -0
  156. package/apps/storefront/src/lib/content-edit/client.ts +230 -0
  157. package/apps/storefront/src/lib/content-edit/index.ts +14 -0
  158. package/apps/storefront/src/lib/content-edit/route.ts +25 -0
  159. package/apps/storefront/src/lib/content-edit/types.ts +19 -0
  160. package/apps/storefront/src/lib/cspStaticHashes.ts +24 -0
  161. package/apps/storefront/src/lib/d1/catalog.ts +366 -0
  162. package/apps/storefront/src/lib/dashboard/staffAdmission.ts +43 -0
  163. package/apps/storefront/src/lib/dashboard/staffVendorAccess.ts +449 -0
  164. package/apps/storefront/src/lib/dashboard/tenantSelection.ts +136 -0
  165. package/apps/storefront/src/lib/dashboard/vendorQuery.ts +61 -0
  166. package/apps/storefront/src/lib/demoGate.ts +113 -0
  167. package/apps/storefront/src/lib/dev/activityIngestToken.ts +78 -0
  168. package/apps/storefront/src/lib/dev/activityStore.ts +72 -0
  169. package/apps/storefront/src/lib/dev/ai-widget-client.js +308 -0
  170. package/apps/storefront/src/lib/dev/apiBase.ts +14 -0
  171. package/apps/storefront/src/lib/dev/cliSignInCode.ts +155 -0
  172. package/apps/storefront/src/lib/dev/devDraft.ts +133 -0
  173. package/apps/storefront/src/lib/dev/hostedCockpit.ts +169 -0
  174. package/apps/storefront/src/lib/dev/livePreview.ts +77 -0
  175. package/apps/storefront/src/lib/dev/runtimeStore.ts +120 -0
  176. package/apps/storefront/src/lib/dev/storeStatus.ts +142 -0
  177. package/apps/storefront/src/lib/diag/redact.ts +20 -0
  178. package/apps/storefront/src/lib/edgeCache.ts +128 -0
  179. package/apps/storefront/src/lib/email/magicLinkInviteEmail.ts +62 -0
  180. package/apps/storefront/src/lib/env.ts +60 -0
  181. package/apps/storefront/src/lib/fixtures/sampleProducts.ts +130 -0
  182. package/apps/storefront/src/lib/format.ts +38 -0
  183. package/apps/storefront/src/lib/foxyCommerce.ts +49 -0
  184. package/apps/storefront/src/lib/imageAttrs.ts +55 -0
  185. package/apps/storefront/src/lib/jsonld.ts +166 -0
  186. package/apps/storefront/src/lib/maintenance.ts +71 -0
  187. package/apps/storefront/src/lib/membership/model.ts +97 -0
  188. package/apps/storefront/src/lib/memoryKv.ts +32 -0
  189. package/apps/storefront/src/lib/messaging/messagesClient.ts +143 -0
  190. package/apps/storefront/src/lib/placeholder.ts +99 -0
  191. package/apps/storefront/src/lib/previewReload.ts +125 -0
  192. package/apps/storefront/src/lib/priceBuckets.ts +33 -0
  193. package/apps/storefront/src/lib/rawChrome.ts +505 -0
  194. package/apps/storefront/src/lib/rawMarketingHtml.ts +136 -0
  195. package/apps/storefront/src/lib/search/index.ts +74 -0
  196. package/apps/storefront/src/lib/sections.ts +129 -0
  197. package/apps/storefront/src/lib/securityHeaders.ts +31 -0
  198. package/apps/storefront/src/lib/storyblok/content-model.ts +289 -0
  199. package/apps/storefront/src/lib/storyblok/provider.ts +607 -0
  200. package/apps/storefront/src/lib/subscription/model.ts +68 -0
  201. package/apps/storefront/src/lib/tenantRouting.ts +91 -0
  202. package/apps/storefront/src/lib/tot/ToTClient.ts +177 -0
  203. package/apps/storefront/src/lib/tot/d1Client.ts +62 -0
  204. package/apps/storefront/src/lib/tot/query.ts +249 -0
  205. package/apps/storefront/src/lib/tot/totClientInterface.ts +30 -0
  206. package/apps/storefront/src/lib/url.ts +142 -0
  207. package/apps/storefront/src/lib/wishlist.ts +63 -0
  208. package/apps/storefront/src/middleware/index.ts +455 -0
  209. package/apps/storefront/src/pages/404.astro +61 -0
  210. package/apps/storefront/src/pages/[...slug].astro +90 -0
  211. package/apps/storefront/src/pages/admin.astro +1675 -0
  212. package/apps/storefront/src/pages/api/admin/orders/[id].ts +53 -0
  213. package/apps/storefront/src/pages/api/admin/orders.ts +71 -0
  214. package/apps/storefront/src/pages/api/auth/logout.ts +21 -0
  215. package/apps/storefront/src/pages/api/auth/magic-exchange.ts +107 -0
  216. package/apps/storefront/src/pages/api/auth/send.ts +85 -0
  217. package/apps/storefront/src/pages/api/auth/verify.ts +135 -0
  218. package/apps/storefront/src/pages/api/cache-purge.ts +44 -0
  219. package/apps/storefront/src/pages/api/checkout-style.css.ts +43 -0
  220. package/apps/storefront/src/pages/api/cli/policy.ts +70 -0
  221. package/apps/storefront/src/pages/api/content-edit/ai-edit.ts +164 -0
  222. package/apps/storefront/src/pages/api/csp-report.ts +25 -0
  223. package/apps/storefront/src/pages/api/dashboard/enter-vendor.ts +124 -0
  224. package/apps/storefront/src/pages/api/dashboard/vendor-search.ts +120 -0
  225. package/apps/storefront/src/pages/api/dev/activity.ts +124 -0
  226. package/apps/storefront/src/pages/api/dev/cli-signin-code.ts +73 -0
  227. package/apps/storefront/src/pages/api/dev/diff.ts +68 -0
  228. package/apps/storefront/src/pages/api/dev/file.ts +57 -0
  229. package/apps/storefront/src/pages/api/dev/locate.ts +125 -0
  230. package/apps/storefront/src/pages/api/dev/publish.ts +128 -0
  231. package/apps/storefront/src/pages/api/dev/tree.ts +70 -0
  232. package/apps/storefront/src/pages/api/dev/write.ts +116 -0
  233. package/apps/storefront/src/pages/api/newsletter.ts +104 -0
  234. package/apps/storefront/src/pages/api/request-access.ts +172 -0
  235. package/apps/storefront/src/pages/auth/login.astro +149 -0
  236. package/apps/storefront/src/pages/auth/magic.astro +105 -0
  237. package/apps/storefront/src/pages/capabilities.astro +292 -0
  238. package/apps/storefront/src/pages/collections/[handle].astro +177 -0
  239. package/apps/storefront/src/pages/collections/index.astro +59 -0
  240. package/apps/storefront/src/pages/dashboard/[appDomain]/index.astro +100 -0
  241. package/apps/storefront/src/pages/dashboard/[appDomain]/team.astro +154 -0
  242. package/apps/storefront/src/pages/dashboard/index.astro +160 -0
  243. package/apps/storefront/src/pages/index.astro +290 -0
  244. package/apps/storefront/src/pages/llms.txt.ts +92 -0
  245. package/apps/storefront/src/pages/products/[handle].astro +402 -0
  246. package/apps/storefront/src/pages/robots.txt.ts +24 -0
  247. package/apps/storefront/src/pages/saved.astro +35 -0
  248. package/apps/storefront/src/pages/search.astro +150 -0
  249. package/apps/storefront/src/pages/sitemap.xml.ts +78 -0
  250. package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +1280 -0
  251. package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +229 -0
  252. package/apps/storefront/src/pages/style-guide/[tenant]/guide/[theme].astro +144 -0
  253. package/apps/storefront/src/pages/style-guide/[tenant]/index.astro +116 -0
  254. package/apps/storefront/src/pages/style-guide/index.astro +98 -0
  255. package/apps/storefront/src/pages/tenants/[id]/[...path].ts +57 -0
  256. package/apps/storefront/src/styles/global.css +292 -0
  257. package/apps/storefront/src/themes/reference.ts +68 -0
  258. package/apps/storefront/src/themes/schema.ts +111 -0
  259. package/apps/storefront/src/themes/tenants/index.ts +51 -0
  260. package/apps/storefront/tsconfig.json +16 -0
  261. package/apps/storefront/vitest.config.ts +22 -0
  262. package/package.json +26 -0
  263. package/packages/public-runtime/LICENSE +58 -0
  264. package/packages/public-runtime/package.json +22 -0
  265. package/packages/public-runtime/src/binary-path.ts +68 -0
  266. package/packages/public-runtime/src/capabilities.ts +89 -0
  267. package/packages/public-runtime/src/catalog-api.ts +81 -0
  268. package/packages/public-runtime/src/catalog-d1.ts +192 -0
  269. package/packages/public-runtime/src/checkout-style-publish.ts +200 -0
  270. package/packages/public-runtime/src/checkout-style.ts +375 -0
  271. package/packages/public-runtime/src/csp.ts +109 -0
  272. package/packages/public-runtime/src/customization-preview.ts +197 -0
  273. package/packages/public-runtime/src/customization-reconcile.ts +63 -0
  274. package/packages/public-runtime/src/customization-runtime.ts +157 -0
  275. package/packages/public-runtime/src/customization-scripts.ts +64 -0
  276. package/packages/public-runtime/src/customization-versioning.ts +95 -0
  277. package/packages/public-runtime/src/foxy-orders.ts +136 -0
  278. package/packages/public-runtime/src/foxy.ts +196 -0
  279. package/packages/public-runtime/src/hot-standby-readiness.ts +140 -0
  280. package/packages/public-runtime/src/index.ts +34 -0
  281. package/packages/public-runtime/src/product.ts +159 -0
  282. package/packages/public-runtime/src/search.ts +45 -0
  283. package/packages/public-runtime/src/tenant-assets.ts +444 -0
  284. package/packages/public-runtime/src/tenant.ts +384 -0
  285. package/packages/public-runtime/tsconfig.json +8 -0
  286. package/packages/public-runtime/vitest.config.ts +8 -0
  287. package/pnpm-workspace.yaml +7 -0
  288. package/scripts/build/copy-tenant-assets.mjs +69 -0
  289. package/scripts/dev/ai-edit.mjs +183 -0
  290. package/scripts/dev/checkout-watch.mjs +116 -0
  291. package/scripts/dev/file-browser.mjs +159 -0
  292. package/scripts/dev/file-writer.mjs +141 -0
  293. package/scripts/dev/git-status.mjs +171 -0
  294. package/scripts/dev/locate-handler.mjs +163 -0
  295. package/scripts/dev/locate.mjs +112 -0
  296. package/scripts/dev/port-check.mjs +48 -0
  297. package/scripts/dev/safe-path.mjs +109 -0
  298. package/scripts/dev/shot.mjs +46 -0
  299. package/scripts/dev/tenant-source-root.mjs +76 -0
  300. package/scripts/dev/unified-diff.mjs +81 -0
  301. package/scripts/tot-dev.mjs +376 -0
  302. package/tenants/README.md +14 -0
  303. package/tenants/home/content/chrome.json +7 -0
  304. package/tenants/home/content/home.html +58 -0
  305. package/tenants/home/content/home.json +11 -0
  306. package/tenants/home/public/js/storefront.js +210 -0
  307. package/tenants/home/public/logo-wordmark.png +0 -0
  308. package/tenants/home/public/pages/storefront.css +212 -0
  309. package/tenants/home/theme.json +6 -0
  310. package/tsconfig.base.json +20 -0
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Apply a unified diff to a source string — the missing half of the content_edit wire
3
+ * contract. tot-mcp's `content_edit` returns per-file unified DIFFS (`--- a/…` /
4
+ * `+++ b/…` / `@@` hunks), not updated contents; to materialize a draft artifact
5
+ * (decision `draft-overlay-same-origin`) the storefront must re-apply the diff to the
6
+ * exact content it sent.
7
+ *
8
+ * STRICT + FAIL-CLOSED by design: every hunk's context and deletion lines must match
9
+ * the source exactly at the stated position (no fuzz, no offset search). Any mismatch
10
+ * throws {@link DiffApplyError} and the caller writes NOTHING — a wrong-base apply must
11
+ * never silently corrupt a draft. This is safe because we apply to the same string we
12
+ * handed content_edit moments earlier; a mismatch means the model produced a bad diff.
13
+ */
14
+
15
+ export class DiffApplyError extends Error {
16
+ constructor(message: string) {
17
+ super(message);
18
+ this.name = "DiffApplyError";
19
+ }
20
+ }
21
+
22
+ interface Hunk {
23
+ /** 1-based start line in the ORIGINAL content (0 allowed for empty-file inserts). */
24
+ oldStart: number;
25
+ /** Body lines: " " context, "-" deletion, "+" addition. */
26
+ lines: string[];
27
+ }
28
+
29
+ const HUNK_RE = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
30
+
31
+ function parseHunks(diff: string): Hunk[] {
32
+ const hunks: Hunk[] = [];
33
+ let current: Hunk | null = null;
34
+ for (const raw of diff.split("\n")) {
35
+ const m = HUNK_RE.exec(raw);
36
+ if (m) {
37
+ current = { oldStart: parseInt(m[1] ?? "0", 10), lines: [] };
38
+ hunks.push(current);
39
+ continue;
40
+ }
41
+ if (!current) continue; // header lines (--- / +++ / diff --git) before the first hunk
42
+ if (raw === "" || raw === "\") continue;
43
+ const tag = raw[0];
44
+ if (tag === " " || tag === "-" || tag === "+") current.lines.push(raw);
45
+ else current = null; // trailing junk / next file header — stop collecting
46
+ }
47
+ return hunks;
48
+ }
49
+
50
+ /**
51
+ * Apply `diff` (unified format) to `source`; returns the patched string. Throws
52
+ * {@link DiffApplyError} on any malformed hunk or context mismatch. A diff with no
53
+ * hunks returns the source unchanged.
54
+ */
55
+ export function applyUnifiedDiff(source: string, diff: string): string {
56
+ const hunks = parseHunks(diff);
57
+ if (hunks.length === 0) return source;
58
+
59
+ const src = source.split("\n");
60
+ const out: string[] = [];
61
+ // Cursor into src (0-based) of the next line not yet copied to out.
62
+ let cursor = 0;
63
+
64
+ for (const hunk of hunks) {
65
+ // oldStart is 1-based; a 0 means "insert into an empty file" (keep cursor).
66
+ const start = Math.max(hunk.oldStart - 1, 0);
67
+ if (start < cursor) throw new DiffApplyError("hunks overlap or are out of order");
68
+ if (start > src.length) throw new DiffApplyError("hunk start beyond end of content");
69
+ out.push(...src.slice(cursor, start));
70
+ cursor = start;
71
+
72
+ for (const line of hunk.lines) {
73
+ const tag = line[0];
74
+ const text = line.slice(1);
75
+ if (tag === "+") {
76
+ out.push(text);
77
+ continue;
78
+ }
79
+ // context (" ") and deletion ("-") must match the source exactly.
80
+ if (cursor >= src.length || src[cursor] !== text) {
81
+ throw new DiffApplyError(
82
+ `context mismatch at line ${cursor + 1}: expected ${JSON.stringify(text)}, found ${JSON.stringify(src[cursor] ?? "<eof>")}`,
83
+ );
84
+ }
85
+ if (tag === " ") out.push(text);
86
+ cursor++;
87
+ }
88
+ }
89
+ out.push(...src.slice(cursor));
90
+ return out.join("\n");
91
+ }
@@ -0,0 +1,230 @@
1
+ /**
2
+ * JsonRpcContentEditClient — the storefront's server-to-server caller for
3
+ * tot-mcp's `content_edit` tool (the inline-AI edit loop, WS5 G1). Mirrors
4
+ * {@link JsonRpcChangeClient} exactly (same transport, same session-validation,
5
+ * same tool-result unwrap, same typed errors) — reusing the forge seam's
6
+ * exported transport + error types so there is ONE wire contract, not two.
7
+ *
8
+ * AUTH SHAPE (G4 design note, `pending beta/WS2 confirmation`): the storefront
9
+ * backend calls `content_edit` as the TENANT OPERATOR (`credential_validate`
10
+ * with storefront's own TOT_API_KEY/TOT_SECRET_KEY — the SAME credential
11
+ * change/accept already uses), not as the individual signed-in developer.
12
+ * Developer-level authorization is the caller's job (`/dev`'s own
13
+ * `readViewerSession()` + capability check, BEFORE this client is ever
14
+ * constructed) — this client only proves "a legitimate storefront backend is
15
+ * asking", the same trust boundary `/api/changes/accept` already relies on.
16
+ */
17
+ import {
18
+ FetchTransport,
19
+ ForgeAuthError,
20
+ ForgeConfigError,
21
+ ForgeTransportError,
22
+ SESSION_HEADER,
23
+ type McpTransport,
24
+ type JsonRpcRequest,
25
+ type TotCredentials,
26
+ type TransportResponse,
27
+ } from "@/lib/forge";
28
+ import { mapForgeError } from "@/lib/forge/errors.js";
29
+ import { readEnv } from "@/lib/env";
30
+ import type { ContentEditFile, ContentEditResult } from "./types.js";
31
+
32
+ export const CONTENT_EDIT_TOOL = {
33
+ validate: "credential_validate",
34
+ edit: "content_edit",
35
+ } as const;
36
+
37
+ export { SESSION_HEADER } from "@/lib/forge";
38
+
39
+ // --- MCP tool-result envelope (mirrors the change client) -------------------
40
+
41
+ interface McpToolResult {
42
+ content?: Array<{ type?: string; text?: string }>;
43
+ structuredContent?: unknown;
44
+ isError?: boolean;
45
+ }
46
+
47
+ function parseTextContent(content: McpToolResult["content"]): unknown {
48
+ const text = content?.find((c) => c.type === "text" || c.text != null)?.text;
49
+ if (text == null) return undefined;
50
+ try {
51
+ return JSON.parse(text);
52
+ } catch {
53
+ return text;
54
+ }
55
+ }
56
+
57
+ function unwrapToolResult<T>(result: unknown): T {
58
+ const env = (result ?? {}) as McpToolResult;
59
+ const payload =
60
+ env.structuredContent !== undefined
61
+ ? env.structuredContent
62
+ : parseTextContent(env.content);
63
+
64
+ if (env.isError) {
65
+ const p = (payload ?? {}) as Record<string, unknown>;
66
+ throw mapForgeError({
67
+ code: typeof p.code === "number" ? p.code : undefined,
68
+ message: typeof p.message === "string" ? p.message : "content_edit tool error",
69
+ data: (p.data as Record<string, unknown>) ?? p,
70
+ });
71
+ }
72
+ return payload as T;
73
+ }
74
+
75
+ // --- Config -------------------------------------------------------------
76
+
77
+ export interface JsonRpcContentEditClientConfig {
78
+ /** MCP base URL (the `POST /mcp` endpoint). */
79
+ baseUrl: string;
80
+ /** Storefront's ToT operator credentials for `credential_validate`. */
81
+ credentials: TotCredentials;
82
+ /** Transport override (defaults to fetch). Injected by tests. */
83
+ transport?: McpTransport;
84
+ }
85
+
86
+ // --- The client -----------------------------------------------------------
87
+
88
+ export class JsonRpcContentEditClient {
89
+ private readonly baseUrl: string;
90
+ private readonly credentials: TotCredentials;
91
+ private readonly transport: McpTransport;
92
+ private nextId = 1;
93
+ /** Cached session validation — validate once, reuse (lazily). */
94
+ private sessionPromise?: Promise<string | undefined>;
95
+
96
+ constructor(config: JsonRpcContentEditClientConfig) {
97
+ if (!config.baseUrl) {
98
+ throw new ForgeConfigError("content-edit: MCP_BASE_URL is not configured");
99
+ }
100
+ if (!config.credentials?.apiKey) {
101
+ throw new ForgeConfigError(
102
+ "content-edit: storefront ToT credentials are not configured",
103
+ );
104
+ }
105
+ this.baseUrl = config.baseUrl;
106
+ this.credentials = config.credentials;
107
+ this.transport = config.transport ?? new FetchTransport(config.baseUrl);
108
+ }
109
+
110
+ async editContent(
111
+ prompt: string,
112
+ files: readonly ContentEditFile[],
113
+ ): Promise<ContentEditResult> {
114
+ const sessionId = await this.ensureSession();
115
+ const result = await this.rpc(
116
+ "tools/call",
117
+ { name: CONTENT_EDIT_TOOL.edit, arguments: { prompt, files } },
118
+ sessionId,
119
+ );
120
+ return unwrapToolResult<ContentEditResult>(result);
121
+ }
122
+
123
+ // --- internals (mirror JsonRpcChangeClient) --------------------------------
124
+
125
+ private ensureSession(): Promise<string | undefined> {
126
+ this.sessionPromise ??= this.validateSession();
127
+ return this.sessionPromise;
128
+ }
129
+
130
+ private async validateSession(): Promise<string | undefined> {
131
+ const args = this.credentialArgs();
132
+ let res: TransportResponse;
133
+ try {
134
+ res = await this.rpcRaw(
135
+ "tools/call",
136
+ { name: CONTENT_EDIT_TOOL.validate, arguments: args },
137
+ undefined,
138
+ );
139
+ } catch (err) {
140
+ this.sessionPromise = undefined; // let a later call retry
141
+ throw err;
142
+ }
143
+ if (res.body.error) {
144
+ this.sessionPromise = undefined;
145
+ throw new ForgeAuthError(
146
+ res.body.error.message ?? "content-edit: credential validation failed",
147
+ );
148
+ }
149
+ try {
150
+ const validated = unwrapToolResult<{ session?: string; sessionId?: string }>(
151
+ res.body.result,
152
+ );
153
+ return (
154
+ res.sessionId ?? validated?.session ?? validated?.sessionId ?? undefined
155
+ );
156
+ } catch (err) {
157
+ this.sessionPromise = undefined;
158
+ throw err instanceof ForgeAuthError
159
+ ? err
160
+ : new ForgeAuthError("content-edit: credential validation was rejected", {
161
+ cause: err,
162
+ });
163
+ }
164
+ }
165
+
166
+ /** Build the credential payload; omits absent secrets. Never logged. */
167
+ private credentialArgs(): Record<string, unknown> {
168
+ const c = this.credentials;
169
+ const out: Record<string, unknown> = { apiKey: c.apiKey };
170
+ if (c.secretKey) out.secretKey = c.secretKey;
171
+ if (c.accessToken) out.accessToken = c.accessToken;
172
+ return out;
173
+ }
174
+
175
+ private async rpc(
176
+ method: string,
177
+ params: unknown,
178
+ sessionId: string | undefined,
179
+ ): Promise<unknown> {
180
+ const res = await this.rpcRaw(method, params, sessionId);
181
+ if (res.body.error) throw mapForgeError(res.body.error);
182
+ return res.body.result;
183
+ }
184
+
185
+ private async rpcRaw(
186
+ method: string,
187
+ params: unknown,
188
+ sessionId: string | undefined,
189
+ ): Promise<TransportResponse> {
190
+ const headers: Record<string, string> = {};
191
+ if (sessionId) headers[SESSION_HEADER] = sessionId;
192
+ const body: JsonRpcRequest = {
193
+ jsonrpc: "2.0",
194
+ id: this.nextId++,
195
+ method,
196
+ params,
197
+ };
198
+ try {
199
+ return await this.transport.post({ body, headers });
200
+ } catch (err) {
201
+ if (err instanceof ForgeTransportError) throw err;
202
+ throw new ForgeTransportError(
203
+ `content-edit: transport failure calling ${method}`,
204
+ { cause: err },
205
+ );
206
+ }
207
+ }
208
+ }
209
+
210
+ // --- Factory ----------------------------------------------------------------
211
+
212
+ /**
213
+ * Build a {@link JsonRpcContentEditClient} from the runtime env — the SAME env
214
+ * the change client reads (`MCP_BASE_URL` + storefront's own ToT credentials).
215
+ * Throws {@link ForgeConfigError} when the base URL or api key is missing.
216
+ * Secrets are read via {@link readEnv} and never logged.
217
+ */
218
+ export async function createContentEditClient(
219
+ overrides: Partial<JsonRpcContentEditClientConfig> = {},
220
+ ): Promise<JsonRpcContentEditClient> {
221
+ const baseUrl = overrides.baseUrl ?? (await readEnv("MCP_BASE_URL")) ?? "";
222
+ const credentials =
223
+ overrides.credentials ??
224
+ ({
225
+ apiKey: (await readEnv("TOT_API_KEY")) ?? "",
226
+ secretKey: await readEnv("TOT_SECRET_KEY"),
227
+ accessToken: await readEnv("TOT_ACCESS_TOKEN"),
228
+ } satisfies TotCredentials);
229
+ return new JsonRpcContentEditClient({ ...overrides, baseUrl, credentials });
230
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * content_edit integration — public barrel. Import the typed client and its
3
+ * route helper from here (`@/lib/content-edit`) so call sites never reach into
4
+ * individual files.
5
+ */
6
+ export type { ContentEditFile, ContentEditFileDiff, ContentEditResult } from "./types.js";
7
+ export {
8
+ JsonRpcContentEditClient,
9
+ createContentEditClient,
10
+ CONTENT_EDIT_TOOL,
11
+ SESSION_HEADER,
12
+ } from "./client.js";
13
+ export type { JsonRpcContentEditClientConfig } from "./client.js";
14
+ export { getContentEditClient } from "./route.js";
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Route helper for `/api/content-edit/ai-edit` — mirrors `@/lib/change/route.ts`'s
3
+ * `getChangeClient`: build the env-configured client, or a 503 Response when
4
+ * the MCP integration isn't configured (missing `MCP_BASE_URL`/credentials).
5
+ */
6
+ import { ForgeError } from "@/lib/forge";
7
+ import { createContentEditClient } from "./client.js";
8
+ import { json } from "@/lib/grants/route.js";
9
+
10
+ /**
11
+ * Build a content-edit client over the real MCP from env, or return a 503
12
+ * Response when the integration is not configured. Never leaks secret values.
13
+ */
14
+ export async function getContentEditClient(): Promise<
15
+ Awaited<ReturnType<typeof createContentEditClient>> | Response
16
+ > {
17
+ try {
18
+ return await createContentEditClient();
19
+ } catch (err) {
20
+ if (err instanceof ForgeError && err.kind === "config") {
21
+ return json({ error: "content_edit integration not configured" }, 503);
22
+ }
23
+ throw err;
24
+ }
25
+ }
@@ -0,0 +1,19 @@
1
+ /** Wire shape for tot-mcp's `content_edit` tool — mirrors tot-mcp's own
2
+ * ContentFile/ContentFileDiff/ContentEditResult (src/services/content-editor.ts). */
3
+
4
+ export interface ContentEditFile {
5
+ /** e.g. "content/home.html" or "theme.json" — content_edit's own allowlist. */
6
+ path: string;
7
+ content: string;
8
+ }
9
+
10
+ export interface ContentEditFileDiff {
11
+ path: string;
12
+ /** Unified diff (`--- a/<path>` / `+++ b/<path>`) against the given content. */
13
+ diff: string;
14
+ }
15
+
16
+ export interface ContentEditResult {
17
+ diffs: ContentEditFileDiff[];
18
+ summary: string;
19
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * CSP hashes for the **static** inline `<script>`/`<style>` that Astro emits in
3
+ * the production build — the `client:load|idle|only` hydration directive
4
+ * loaders, the `<astro-island>` definition, the `astro-island{display:contents}`
5
+ * style, and bundled Layout scripts. These are identical across tenants and
6
+ * requests (no dynamic data), so hashes (not nonces) are the correct, strict
7
+ * tool for them.
8
+ *
9
+ * They change only when Astro or our bundled scripts change (e.g. an Astro
10
+ * upgrade). A drift check re-extracts them from a fresh build and fails if this
11
+ * list is stale — see `scripts/check-csp-hashes.mjs`. Regenerate with:
12
+ * node scripts/check-csp-hashes.mjs --print
13
+ */
14
+ export const STATIC_SCRIPT_HASHES: readonly string[] = [
15
+ "'sha256-BF0290pkb3jxQsE7z00xR8Imp8X34FLC88L0lkMnrGw='",
16
+ "'sha256-SaCkFfPruIdTXT8/97JArQmGxiJAL2o4bBDvSgJ5y3Q='",
17
+ "'sha256-QzWFZi+FLIx23tnm9SBU4aEgx4x8DsuASP07mfqol/c='",
18
+ "'sha256-YicfxUFBkKrQapZ/tzZKlaKTdNHgwVfv7y2Wa0NC6/k='",
19
+ "'sha256-eIXWvAmxkr251LJZkjniEK5LcPF3NkapbJepohwYRIc='",
20
+ ];
21
+
22
+ export const STATIC_STYLE_HASHES: readonly string[] = [
23
+ "'sha256-vv9IoKo7BSLbWcUHr3tNmfNVmm5L/9Cfn2H6LMk7/ow='",
24
+ ];