@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.
- package/LICENSE +58 -0
- package/README.md +42 -0
- package/apps/storefront/astro.config.mjs +66 -0
- package/apps/storefront/dev-plugins/dev-loop-state.mjs +147 -0
- package/apps/storefront/dev-plugins/tenant-hot-reload.mjs +238 -0
- package/apps/storefront/env.d.ts +162 -0
- package/apps/storefront/migrations/0000_baseline.sql +54 -0
- package/apps/storefront/migrations/0001_products_fts.sql +9 -0
- package/apps/storefront/migrations/README.md +54 -0
- package/apps/storefront/migrations/meta/0000_snapshot.json +347 -0
- package/apps/storefront/migrations/meta/0001_snapshot.json +347 -0
- package/apps/storefront/migrations/meta/_journal.json +20 -0
- package/apps/storefront/package.json +42 -0
- package/apps/storefront/public/brand/northwind-logo-dark.svg +4 -0
- package/apps/storefront/public/brand/northwind-logo-light.svg +4 -0
- package/apps/storefront/public/brand/northwind-og.svg +13 -0
- package/apps/storefront/public/favicon.svg +5 -0
- package/apps/storefront/public/js/dashboard-team.js +147 -0
- package/apps/storefront/public/js/dashboard-vendor-search.js +115 -0
- package/apps/storefront/public/shared/commerce-chrome.css +369 -0
- package/apps/storefront/src/components/Badge.astro +34 -0
- package/apps/storefront/src/components/Breadcrumbs.astro +45 -0
- package/apps/storefront/src/components/CollectionCard.astro +56 -0
- package/apps/storefront/src/components/EmptyState.astro +55 -0
- package/apps/storefront/src/components/Img.astro +68 -0
- package/apps/storefront/src/components/ProductCard.astro +228 -0
- package/apps/storefront/src/components/Section.astro +39 -0
- package/apps/storefront/src/components/Seo.astro +70 -0
- package/apps/storefront/src/components/WidgetFrame.astro +34 -0
- package/apps/storefront/src/components/admin/AdminAIWorkflowsTab.astro +531 -0
- package/apps/storefront/src/components/admin/AdminCustomersTab.astro +597 -0
- package/apps/storefront/src/components/admin/AdminFoxyCartSettingsPanel.astro +227 -0
- package/apps/storefront/src/components/admin/AdminFulfillmentTab.astro +465 -0
- package/apps/storefront/src/components/admin/AdminProductsTab.astro +901 -0
- package/apps/storefront/src/components/admin/AdminReportingTab.astro +435 -0
- package/apps/storefront/src/components/admin/AdminSettingsTab.astro +782 -0
- package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +1223 -0
- package/apps/storefront/src/components/auth/AuthBadge.astro +44 -0
- package/apps/storefront/src/components/chrome/AnnouncementBar.astro +108 -0
- package/apps/storefront/src/components/chrome/MegaMenu.astro +26 -0
- package/apps/storefront/src/components/chrome/NavDropdown.astro +201 -0
- package/apps/storefront/src/components/chrome/SiteFooter.astro +201 -0
- package/apps/storefront/src/components/chrome/SiteHeader.astro +234 -0
- package/apps/storefront/src/components/chrome/UtilityNav.astro +79 -0
- package/apps/storefront/src/components/commerce/ArticleCard.astro +242 -0
- package/apps/storefront/src/components/commerce/BrandGrid.astro +175 -0
- package/apps/storefront/src/components/commerce/FoxyLoader.astro +23 -0
- package/apps/storefront/src/components/commerce/FoxySidecartStyles.astro +61 -0
- package/apps/storefront/src/components/commerce/PriceDisplay.astro +91 -0
- package/apps/storefront/src/components/commerce/ProductCarousel.astro +197 -0
- package/apps/storefront/src/components/commerce/ProductGrid.astro +75 -0
- package/apps/storefront/src/components/commerce/RatingStars.astro +58 -0
- package/apps/storefront/src/components/commerce/ReviewList.astro +54 -0
- package/apps/storefront/src/components/commerce/ReviewSummary.astro +44 -0
- package/apps/storefront/src/components/commerce/TagFilterCloud.astro +149 -0
- package/apps/storefront/src/components/compliance/AdultSignatureNotice.astro +37 -0
- package/apps/storefront/src/components/compliance/ComplianceNotice.astro +99 -0
- package/apps/storefront/src/components/compliance/ExciseTaxNotice.astro +45 -0
- package/apps/storefront/src/components/compliance/NicotineWarning.astro +33 -0
- package/apps/storefront/src/components/compliance/PactActNotice.astro +35 -0
- package/apps/storefront/src/components/compliance/Prop65Warning.astro +80 -0
- package/apps/storefront/src/components/compliance/PurchaseLimitNotice.astro +38 -0
- package/apps/storefront/src/components/compliance/ShippingRestrictionNotice.astro +35 -0
- package/apps/storefront/src/components/compliance/StateEligibilityNotice.astro +53 -0
- package/apps/storefront/src/components/home/Hero.astro +164 -0
- package/apps/storefront/src/components/islands/AgeGate.tsx +109 -0
- package/apps/storefront/src/components/islands/ImageGallery.tsx +237 -0
- package/apps/storefront/src/components/islands/IsolatedAgeGate.tsx +73 -0
- package/apps/storefront/src/components/islands/MobileNav.tsx +239 -0
- package/apps/storefront/src/components/islands/QuickView.tsx +138 -0
- package/apps/storefront/src/components/islands/RecentlyViewed.tsx +122 -0
- package/apps/storefront/src/components/islands/SavedList.tsx +99 -0
- package/apps/storefront/src/components/islands/SearchTypeahead.tsx +248 -0
- package/apps/storefront/src/components/islands/VariantSelector.tsx +375 -0
- package/apps/storefront/src/components/marketing/CardGrid.astro +72 -0
- package/apps/storefront/src/components/marketing/CodeSample.astro +20 -0
- package/apps/storefront/src/components/marketing/CtaBand.astro +24 -0
- package/apps/storefront/src/components/marketing/Faq.astro +37 -0
- package/apps/storefront/src/components/marketing/Integrations.astro +63 -0
- package/apps/storefront/src/components/marketing/MarketingBlocks.astro +53 -0
- package/apps/storefront/src/components/marketing/MarketingCtas.astro +48 -0
- package/apps/storefront/src/components/marketing/MarketingHero.astro +72 -0
- package/apps/storefront/src/components/marketing/ProofStrip.astro +37 -0
- package/apps/storefront/src/components/marketing/SplitCompare.astro +53 -0
- package/apps/storefront/src/components/marketing/Steps.astro +36 -0
- package/apps/storefront/src/components/marketing/Testimonials.astro +38 -0
- package/apps/storefront/src/components/marketing/TrustBar.astro +22 -0
- package/apps/storefront/src/components/membership/IllustrativeLabel.astro +56 -0
- package/apps/storefront/src/components/membership/TierCards.astro +164 -0
- package/apps/storefront/src/components/membership/TierComparisonTable.astro +148 -0
- package/apps/storefront/src/components/plp/ActiveFilters.astro +79 -0
- package/apps/storefront/src/components/plp/FacetSidebar.astro +199 -0
- package/apps/storefront/src/components/plp/Pagination.astro +99 -0
- package/apps/storefront/src/components/plp/SortSelect.astro +64 -0
- package/apps/storefront/src/components/style-guide/StyleGuideNav.astro +95 -0
- package/apps/storefront/src/components/subscription/HowItWorksSteps.astro +67 -0
- package/apps/storefront/src/components/subscription/ManageSubscriptionEntry.astro +67 -0
- package/apps/storefront/src/components/subscription/SavingsExplainer.astro +101 -0
- package/apps/storefront/src/config/adminTargetTenant.ts +28 -0
- package/apps/storefront/src/config/devTenantSeed.ts +78 -0
- package/apps/storefront/src/config/devTenants.ts +16 -0
- package/apps/storefront/src/config/kvTenantResolver.ts +87 -0
- package/apps/storefront/src/config/resolver.ts +102 -0
- package/apps/storefront/src/config/tenants.ts +87 -0
- package/apps/storefront/src/layouts/Layout.astro +368 -0
- package/apps/storefront/src/lib/admin/README.md +122 -0
- package/apps/storefront/src/lib/admin/ai/approvalMode.ts +21 -0
- package/apps/storefront/src/lib/admin/ai/askEnvelope.ts +48 -0
- package/apps/storefront/src/lib/admin/ai/audit.ts +14 -0
- package/apps/storefront/src/lib/admin/ai/index.ts +6 -0
- package/apps/storefront/src/lib/admin/ai/operationalRuns.ts +155 -0
- package/apps/storefront/src/lib/admin/ai/recommendation.ts +20 -0
- package/apps/storefront/src/lib/admin/checkoutStyleInput.ts +172 -0
- package/apps/storefront/src/lib/admin/checkoutStyleTarget.ts +65 -0
- package/apps/storefront/src/lib/admin/envelope.ts +10 -0
- package/apps/storefront/src/lib/admin/evidence/evidenceReference.ts +32 -0
- package/apps/storefront/src/lib/admin/memoizeByEnvelope.ts +27 -0
- package/apps/storefront/src/lib/admin/orderClient.ts +146 -0
- package/apps/storefront/src/lib/admin/seed.ts +54 -0
- package/apps/storefront/src/lib/admin/services/mock/aiWorkflows.ts +59 -0
- package/apps/storefront/src/lib/admin/services/mock/customers.ts +86 -0
- package/apps/storefront/src/lib/admin/services/mock/evidence.ts +99 -0
- package/apps/storefront/src/lib/admin/services/mock/foxyCartSettings.ts +78 -0
- package/apps/storefront/src/lib/admin/services/mock/fulfillment.ts +62 -0
- package/apps/storefront/src/lib/admin/services/mock/index.ts +12 -0
- package/apps/storefront/src/lib/admin/services/mock/orders.ts +184 -0
- package/apps/storefront/src/lib/admin/services/mock/products.ts +105 -0
- package/apps/storefront/src/lib/admin/services/mock/reporting.ts +51 -0
- package/apps/storefront/src/lib/admin/services/mock/settings.ts +70 -0
- package/apps/storefront/src/lib/admin/services/mock/tenants.ts +29 -0
- package/apps/storefront/src/lib/admin/services/orders.ts +300 -0
- package/apps/storefront/src/lib/admin/viewmodels/aiWorkflows.ts +67 -0
- package/apps/storefront/src/lib/admin/viewmodels/customers.ts +56 -0
- package/apps/storefront/src/lib/admin/viewmodels/fulfillment.ts +49 -0
- package/apps/storefront/src/lib/admin/viewmodels/orders.ts +92 -0
- package/apps/storefront/src/lib/admin/viewmodels/products.ts +74 -0
- package/apps/storefront/src/lib/admin/viewmodels/reporting.ts +34 -0
- package/apps/storefront/src/lib/admin/viewmodels/settings.ts +64 -0
- package/apps/storefront/src/lib/adminTenantEnvelope.ts +90 -0
- package/apps/storefront/src/lib/analytics/index.ts +47 -0
- package/apps/storefront/src/lib/auth/brokerAssertion.ts +136 -0
- package/apps/storefront/src/lib/auth/devAuth.ts +90 -0
- package/apps/storefront/src/lib/auth/gatePage.ts +81 -0
- package/apps/storefront/src/lib/auth/identityToken.ts +341 -0
- package/apps/storefront/src/lib/auth/loginGate.ts +119 -0
- package/apps/storefront/src/lib/auth/route.ts +146 -0
- package/apps/storefront/src/lib/auth/session.ts +203 -0
- package/apps/storefront/src/lib/auth/totAccessClient.ts +503 -0
- package/apps/storefront/src/lib/basePath.ts +41 -0
- package/apps/storefront/src/lib/cfImage.ts +55 -0
- package/apps/storefront/src/lib/chrome/model.ts +244 -0
- package/apps/storefront/src/lib/chrome/parseConfig.ts +34 -0
- package/apps/storefront/src/lib/colors.ts +97 -0
- package/apps/storefront/src/lib/commerce/foxyCartSettings.ts +253 -0
- package/apps/storefront/src/lib/content-edit/applyDiff.ts +91 -0
- package/apps/storefront/src/lib/content-edit/client.ts +230 -0
- package/apps/storefront/src/lib/content-edit/index.ts +14 -0
- package/apps/storefront/src/lib/content-edit/route.ts +25 -0
- package/apps/storefront/src/lib/content-edit/types.ts +19 -0
- package/apps/storefront/src/lib/cspStaticHashes.ts +24 -0
- package/apps/storefront/src/lib/d1/catalog.ts +366 -0
- package/apps/storefront/src/lib/dashboard/staffAdmission.ts +43 -0
- package/apps/storefront/src/lib/dashboard/staffVendorAccess.ts +449 -0
- package/apps/storefront/src/lib/dashboard/tenantSelection.ts +136 -0
- package/apps/storefront/src/lib/dashboard/vendorQuery.ts +61 -0
- package/apps/storefront/src/lib/demoGate.ts +113 -0
- package/apps/storefront/src/lib/dev/activityIngestToken.ts +78 -0
- package/apps/storefront/src/lib/dev/activityStore.ts +72 -0
- package/apps/storefront/src/lib/dev/ai-widget-client.js +308 -0
- package/apps/storefront/src/lib/dev/apiBase.ts +14 -0
- package/apps/storefront/src/lib/dev/cliSignInCode.ts +155 -0
- package/apps/storefront/src/lib/dev/devDraft.ts +133 -0
- package/apps/storefront/src/lib/dev/hostedCockpit.ts +169 -0
- package/apps/storefront/src/lib/dev/livePreview.ts +77 -0
- package/apps/storefront/src/lib/dev/runtimeStore.ts +120 -0
- package/apps/storefront/src/lib/dev/storeStatus.ts +142 -0
- package/apps/storefront/src/lib/diag/redact.ts +20 -0
- package/apps/storefront/src/lib/edgeCache.ts +128 -0
- package/apps/storefront/src/lib/email/magicLinkInviteEmail.ts +62 -0
- package/apps/storefront/src/lib/env.ts +60 -0
- package/apps/storefront/src/lib/fixtures/sampleProducts.ts +130 -0
- package/apps/storefront/src/lib/format.ts +38 -0
- package/apps/storefront/src/lib/foxyCommerce.ts +49 -0
- package/apps/storefront/src/lib/imageAttrs.ts +55 -0
- package/apps/storefront/src/lib/jsonld.ts +166 -0
- package/apps/storefront/src/lib/maintenance.ts +71 -0
- package/apps/storefront/src/lib/membership/model.ts +97 -0
- package/apps/storefront/src/lib/memoryKv.ts +32 -0
- package/apps/storefront/src/lib/messaging/messagesClient.ts +143 -0
- package/apps/storefront/src/lib/placeholder.ts +99 -0
- package/apps/storefront/src/lib/previewReload.ts +125 -0
- package/apps/storefront/src/lib/priceBuckets.ts +33 -0
- package/apps/storefront/src/lib/rawChrome.ts +505 -0
- package/apps/storefront/src/lib/rawMarketingHtml.ts +136 -0
- package/apps/storefront/src/lib/search/index.ts +74 -0
- package/apps/storefront/src/lib/sections.ts +129 -0
- package/apps/storefront/src/lib/securityHeaders.ts +31 -0
- package/apps/storefront/src/lib/storyblok/content-model.ts +289 -0
- package/apps/storefront/src/lib/storyblok/provider.ts +607 -0
- package/apps/storefront/src/lib/subscription/model.ts +68 -0
- package/apps/storefront/src/lib/tenantRouting.ts +91 -0
- package/apps/storefront/src/lib/tot/ToTClient.ts +177 -0
- package/apps/storefront/src/lib/tot/d1Client.ts +62 -0
- package/apps/storefront/src/lib/tot/query.ts +249 -0
- package/apps/storefront/src/lib/tot/totClientInterface.ts +30 -0
- package/apps/storefront/src/lib/url.ts +142 -0
- package/apps/storefront/src/lib/wishlist.ts +63 -0
- package/apps/storefront/src/middleware/index.ts +455 -0
- package/apps/storefront/src/pages/404.astro +61 -0
- package/apps/storefront/src/pages/[...slug].astro +90 -0
- package/apps/storefront/src/pages/admin.astro +1675 -0
- package/apps/storefront/src/pages/api/admin/orders/[id].ts +53 -0
- package/apps/storefront/src/pages/api/admin/orders.ts +71 -0
- package/apps/storefront/src/pages/api/auth/logout.ts +21 -0
- package/apps/storefront/src/pages/api/auth/magic-exchange.ts +107 -0
- package/apps/storefront/src/pages/api/auth/send.ts +85 -0
- package/apps/storefront/src/pages/api/auth/verify.ts +135 -0
- package/apps/storefront/src/pages/api/cache-purge.ts +44 -0
- package/apps/storefront/src/pages/api/checkout-style.css.ts +43 -0
- package/apps/storefront/src/pages/api/cli/policy.ts +70 -0
- package/apps/storefront/src/pages/api/content-edit/ai-edit.ts +164 -0
- package/apps/storefront/src/pages/api/csp-report.ts +25 -0
- package/apps/storefront/src/pages/api/dashboard/enter-vendor.ts +124 -0
- package/apps/storefront/src/pages/api/dashboard/vendor-search.ts +120 -0
- package/apps/storefront/src/pages/api/dev/activity.ts +124 -0
- package/apps/storefront/src/pages/api/dev/cli-signin-code.ts +73 -0
- package/apps/storefront/src/pages/api/dev/diff.ts +68 -0
- package/apps/storefront/src/pages/api/dev/file.ts +57 -0
- package/apps/storefront/src/pages/api/dev/locate.ts +125 -0
- package/apps/storefront/src/pages/api/dev/publish.ts +128 -0
- package/apps/storefront/src/pages/api/dev/tree.ts +70 -0
- package/apps/storefront/src/pages/api/dev/write.ts +116 -0
- package/apps/storefront/src/pages/api/newsletter.ts +104 -0
- package/apps/storefront/src/pages/api/request-access.ts +172 -0
- package/apps/storefront/src/pages/auth/login.astro +149 -0
- package/apps/storefront/src/pages/auth/magic.astro +105 -0
- package/apps/storefront/src/pages/capabilities.astro +292 -0
- package/apps/storefront/src/pages/collections/[handle].astro +177 -0
- package/apps/storefront/src/pages/collections/index.astro +59 -0
- package/apps/storefront/src/pages/dashboard/[appDomain]/index.astro +100 -0
- package/apps/storefront/src/pages/dashboard/[appDomain]/team.astro +154 -0
- package/apps/storefront/src/pages/dashboard/index.astro +160 -0
- package/apps/storefront/src/pages/index.astro +290 -0
- package/apps/storefront/src/pages/llms.txt.ts +92 -0
- package/apps/storefront/src/pages/products/[handle].astro +402 -0
- package/apps/storefront/src/pages/robots.txt.ts +24 -0
- package/apps/storefront/src/pages/saved.astro +35 -0
- package/apps/storefront/src/pages/search.astro +150 -0
- package/apps/storefront/src/pages/sitemap.xml.ts +78 -0
- package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +1280 -0
- package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +229 -0
- package/apps/storefront/src/pages/style-guide/[tenant]/guide/[theme].astro +144 -0
- package/apps/storefront/src/pages/style-guide/[tenant]/index.astro +116 -0
- package/apps/storefront/src/pages/style-guide/index.astro +98 -0
- package/apps/storefront/src/pages/tenants/[id]/[...path].ts +57 -0
- package/apps/storefront/src/styles/global.css +292 -0
- package/apps/storefront/src/themes/reference.ts +68 -0
- package/apps/storefront/src/themes/schema.ts +111 -0
- package/apps/storefront/src/themes/tenants/index.ts +51 -0
- package/apps/storefront/tsconfig.json +16 -0
- package/apps/storefront/vitest.config.ts +22 -0
- package/package.json +26 -0
- package/packages/public-runtime/LICENSE +58 -0
- package/packages/public-runtime/package.json +22 -0
- package/packages/public-runtime/src/binary-path.ts +68 -0
- package/packages/public-runtime/src/capabilities.ts +89 -0
- package/packages/public-runtime/src/catalog-api.ts +81 -0
- package/packages/public-runtime/src/catalog-d1.ts +192 -0
- package/packages/public-runtime/src/checkout-style-publish.ts +200 -0
- package/packages/public-runtime/src/checkout-style.ts +375 -0
- package/packages/public-runtime/src/csp.ts +109 -0
- package/packages/public-runtime/src/customization-preview.ts +197 -0
- package/packages/public-runtime/src/customization-reconcile.ts +63 -0
- package/packages/public-runtime/src/customization-runtime.ts +157 -0
- package/packages/public-runtime/src/customization-scripts.ts +64 -0
- package/packages/public-runtime/src/customization-versioning.ts +95 -0
- package/packages/public-runtime/src/foxy-orders.ts +136 -0
- package/packages/public-runtime/src/foxy.ts +196 -0
- package/packages/public-runtime/src/hot-standby-readiness.ts +140 -0
- package/packages/public-runtime/src/index.ts +34 -0
- package/packages/public-runtime/src/product.ts +159 -0
- package/packages/public-runtime/src/search.ts +45 -0
- package/packages/public-runtime/src/tenant-assets.ts +444 -0
- package/packages/public-runtime/src/tenant.ts +384 -0
- package/packages/public-runtime/tsconfig.json +8 -0
- package/packages/public-runtime/vitest.config.ts +8 -0
- package/pnpm-workspace.yaml +7 -0
- package/scripts/build/copy-tenant-assets.mjs +69 -0
- package/scripts/dev/ai-edit.mjs +183 -0
- package/scripts/dev/checkout-watch.mjs +116 -0
- package/scripts/dev/file-browser.mjs +159 -0
- package/scripts/dev/file-writer.mjs +141 -0
- package/scripts/dev/git-status.mjs +171 -0
- package/scripts/dev/locate-handler.mjs +163 -0
- package/scripts/dev/locate.mjs +112 -0
- package/scripts/dev/port-check.mjs +48 -0
- package/scripts/dev/safe-path.mjs +109 -0
- package/scripts/dev/shot.mjs +46 -0
- package/scripts/dev/tenant-source-root.mjs +76 -0
- package/scripts/dev/unified-diff.mjs +81 -0
- package/scripts/tot-dev.mjs +376 -0
- package/tenants/README.md +14 -0
- package/tenants/home/content/chrome.json +7 -0
- package/tenants/home/content/home.html +58 -0
- package/tenants/home/content/home.json +11 -0
- package/tenants/home/public/js/storefront.js +210 -0
- package/tenants/home/public/logo-wordmark.png +0 -0
- package/tenants/home/public/pages/storefront.css +212 -0
- package/tenants/home/theme.json +6 -0
- 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
|
+
];
|