@brainerce/mcp-server 3.11.1 → 3.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/http.js CHANGED
@@ -2537,8 +2537,9 @@ await admin.createTaxRate({ name: 'VAT (Food)', rate: 0, country: 'GB', taxClass
2537
2537
  await admin.mergeTaxClasses(food.id, standardClassId);
2538
2538
  \`\`\`
2539
2539
 
2540
- Storefront (public, no apiKey \u2014 \`storeId\` mode): \`getStoreTaxClasses()\` lists
2541
- the store's classes (storefront-safe fields only) for a transparency badge.
2540
+ Storefront (public, no apiKey \u2014 \`storeId\` mode OR vibe-coded mode \`vc_*\`, gated
2541
+ on the \`products:read\` scope every connection already has): \`getStoreTaxClasses()\`
2542
+ lists the store's classes (storefront-safe fields only) for a transparency badge.
2542
2543
 
2543
2544
  For a buyer-facing tax preview on PDP / PLP / cart, \`estimateTax({ country,
2544
2545
  subtotal })\` returns the tax portion at the Standard rate for the buyer's
@@ -2560,8 +2561,10 @@ A region binds countries \u2192 currency + tax-display mode + enabled payment
2560
2561
  providers. Manage them via the SDK (scopes \`regions:read\` / \`regions:write\`).
2561
2562
  \`detectRegion\` is a pure client-side helper to map a buyer's country to a
2562
2563
  region; pass the resolved \`regionId\` to \`createCheckout\` to associate the
2563
- checkout with a region (recorded for reporting + provider scoping; currency
2564
- follows the cart until FX price conversion lands).
2564
+ checkout with a region (recorded for reporting + provider scoping). FX-at-checkout:
2565
+ presentment-enabled regions (Stripe today) charge in the region currency and the
2566
+ checkout response carries a \`presentment\` overlay with the charged amounts;
2567
+ otherwise the checkout is charged in the store base currency.
2565
2568
 
2566
2569
  \`\`\`typescript
2567
2570
  const eu = await admin.createRegion({
@@ -2574,9 +2577,12 @@ const { data: regions } = await admin.getRegions();
2574
2577
  const region = admin.detectRegion('DE', regions); // \u2192 eu, else default, else null
2575
2578
  \`\`\`
2576
2579
 
2577
- Storefront (public, no apiKey \u2014 \`storeId\` mode): \`getStoreRegions()\` lists
2578
- active regions, \`getStoreRegion(id)\` returns one region + its payment providers.
2579
- Pair with \`detectRegion\` to pick the buyer's region for currency display.
2580
+ Storefront (public, no apiKey \u2014 \`storeId\` mode OR vibe-coded mode \`vc_*\`, gated
2581
+ on the \`products:read\` scope every connection already has): \`getStoreRegions()\`
2582
+ lists active regions, \`getStoreRegion(id)\` returns one region + its payment
2583
+ providers. \`getAutoRegion(country)\` and \`estimateTax({ country, subtotal })\` work
2584
+ in both modes too. Pair with \`detectRegion\` to pick the buyer's region for
2585
+ currency display.
2580
2586
 
2581
2587
  \`\`\`typescript
2582
2588
  const store = new BrainerceClient({ storeId: 'store_123' });
@@ -3200,6 +3206,38 @@ Rules:
3200
3206
  count updates when the bot adds items.
3201
3207
  `;
3202
3208
  }
3209
+ function getStorefrontAnalyticsSection(connectionId) {
3210
+ return `## Storefront Traffic Analytics (visits, countries, sources)
3211
+
3212
+ Brainerce can track cookieless storefront traffic \u2014 how many people visit the
3213
+ store, from which countries, and from which traffic sources \u2014 and surface it in
3214
+ the merchant dashboard alongside sales (so the merchant gets a REAL conversion
3215
+ rate: visits \u2192 orders).
3216
+
3217
+ Add ONE line to the storefront's root layout (\`<head>\` or end of \`<body>\`):
3218
+
3219
+ \`\`\`html
3220
+ <script defer src="https://api.brainerce.com/t.js" data-channel="${connectionId}"></script>
3221
+ \`\`\`
3222
+
3223
+ That's it \u2014 the pixel auto-sends a pageview beacon on load and on SPA route
3224
+ changes. Stores scaffolded with \`create-brainerce-store\` already include it.
3225
+
3226
+ Privacy: the pixel is cookieless and stores NO personal data \u2014 no cookie, no
3227
+ cross-site/cross-day tracking, and the visitor IP is resolved to a country
3228
+ server-side then discarded. No cookie-consent banner is required for it under
3229
+ GDPR/ePrivacy.
3230
+
3231
+ CSP: if the storefront sets a Content-Security-Policy, allow the script + beacon:
3232
+ - \`script-src\` \u2026 \`https://api.brainerce.com\`
3233
+ - \`connect-src\` \u2026 \`https://api.brainerce.com\`
3234
+
3235
+ The merchant can toggle tracking per sales channel in the dashboard. The data
3236
+ shows up under Dashboard \u2192 Analytics (visits over time, by country, by source,
3237
+ by device) and can also be asked of the AI assistant ("how many visits did I get
3238
+ this month and from where?").
3239
+ `;
3240
+ }
3203
3241
  function getSectionByTopic(topic, connectionId, currency) {
3204
3242
  const cid = connectionId || "vc_YOUR_CONNECTION_ID";
3205
3243
  const cur = currency || "USD";
@@ -3256,6 +3294,9 @@ function getSectionByTopic(topic, connectionId, currency) {
3256
3294
  return getBlogSection();
3257
3295
  case "storefront-bot":
3258
3296
  return getStorefrontBotSection(cid);
3297
+ case "analytics":
3298
+ case "traffic-analytics":
3299
+ return getStorefrontAnalyticsSection(cid);
3259
3300
  case "all":
3260
3301
  return [
3261
3302
  "# Brainerce SDK \u2014 full topic dump",
@@ -3324,6 +3365,10 @@ function getSectionByTopic(topic, connectionId, currency) {
3324
3365
  "",
3325
3366
  "---",
3326
3367
  "",
3368
+ getStorefrontAnalyticsSection(cid),
3369
+ "",
3370
+ "---",
3371
+ "",
3327
3372
  getProductCustomizationFieldsSection(),
3328
3373
  "",
3329
3374
  "---",
@@ -3393,6 +3438,7 @@ var GET_SDK_DOCS_SCHEMA = {
3393
3438
  "inquiries",
3394
3439
  "content",
3395
3440
  "storefront-bot",
3441
+ "analytics",
3396
3442
  "all"
3397
3443
  ]).describe("The SDK documentation topic to retrieve"),
3398
3444
  salesChannelId: import_zod.z.string().optional().describe("Sales channel ID (starts with vc_). Used to personalize setup code."),
@@ -3430,7 +3476,7 @@ interface Product {
3430
3476
  priceMin?: string | null; // Lowest variant price (VARIABLE products) \u2014 same as basePrice for VARIABLE, kept for back-compat and JSON-LD range display.
3431
3477
  priceMax?: string | null; // Highest variant price (VARIABLE products). Use with priceMin for "\u20AA49 \u2013 \u20AA199" range.
3432
3478
  priceVaries?: boolean; // true when variant prices differ \u2014 show "\u20AA49 \u2013 \u20AA199" range.
3433
- // FX DISPLAY overlay (PRD \xA723). Set only when getProducts({ regionId }) was called AND region.currency !== store.currency. basePrice/salePrice above stay in store currency \u2014 these are additive, display-only. The buyer is still charged in store currency at checkout; payment provider handles customer-side FX.
3479
+ // FX DISPLAY overlay (PRD \xA723). Set only when getProducts({ regionId }) was called AND region.currency !== store.currency. basePrice/salePrice above stay in store currency \u2014 these are additive. For presentment-enabled regions (Stripe) the displayPrice uses the same buffered rate the checkout charges (browsed == checkout); for display-only regions it is mid-market and the buyer is charged in store currency at checkout (payment provider handles customer-side FX).
3434
3480
  displayPrice?: string; // basePrice \xD7 daily FX rate, in displayCurrency.
3435
3481
  displaySalePrice?: string; // salePrice \xD7 rate (if salePrice present).
3436
3482
  displayPriceMin?: string; // priceMin \xD7 rate (VARIABLE products).
@@ -3684,7 +3730,7 @@ interface Checkout {
3684
3730
  status: CheckoutStatus;
3685
3731
  email?: string | null;
3686
3732
  customerId?: string | null;
3687
- regionId?: string | null; // multi-region: recorded for reporting + provider scoping (currency follows the cart until FX lands)
3733
+ regionId?: string | null; // multi-region: recorded for reporting + provider scoping. FX-at-checkout: presentment-enabled regions (Stripe) charge in the region currency \u2014 see the presentment field below; else charged in store base currency
3688
3734
  shippingAddress?: CheckoutAddress | null;
3689
3735
  billingAddress?: CheckoutAddress | null;
3690
3736
  shippingRateId?: string | null;
@@ -3704,6 +3750,18 @@ interface Checkout {
3704
3750
  itemCount: number;
3705
3751
  availableShippingRates?: ShippingRate[];
3706
3752
  reservation?: ReservationInfo;
3753
+ // FX-at-checkout: present ONLY when the region charges in its own currency (presentment-enabled \u2014 Stripe today). The amounts the buyer is actually CHARGED, in presentment.currency; presentment.total equals the payment intent amount. Render these (not total/currency above) when present. Absent = charged in store base currency. The fields above stay the base reference.
3754
+ presentment?: {
3755
+ currency: string; // ISO-4217 charged currency, e.g. "EUR"
3756
+ subtotal: string;
3757
+ discountAmount: string;
3758
+ shippingAmount: string;
3759
+ taxAmount: string;
3760
+ surchargeAmount: string;
3761
+ total: string; // equals the charged amount, to the cent
3762
+ fxChargingRate: string; // base\u2192presentment rate (buffer included)
3763
+ fxBufferPercent: string | null;
3764
+ };
3707
3765
  createdAt: string;
3708
3766
  updatedAt: string;
3709
3767
  }
@@ -4081,10 +4139,22 @@ interface CartAppliedDiscount {
4081
4139
  }
4082
4140
 
4083
4141
  // Recommendation types
4142
+ interface RecommendationVariant {
4143
+ id: string; name?: string; price?: string; salePrice?: string;
4144
+ attributes?: Record<string, string>; image?: ProductImage | string; inventory?: InventoryInfo;
4145
+ }
4084
4146
  interface ProductRecommendation {
4085
- id: string; name: string; slug: string; basePrice: string; salePrice?: string;
4147
+ id: string; name: string; slug: string;
4148
+ // For a VARIABLE target, basePrice is the pinned variant's price (when
4149
+ // targetVariantId is set) or the "from {min variant}" range \u2014 never 0.
4150
+ basePrice: string; salePrice?: string;
4086
4151
  images: ProductImage[]; type: 'SIMPLE' | 'VARIABLE'; inventory?: InventoryInfo;
4087
4152
  relationType: string;
4153
+ // Variant context (VARIABLE targets):
4154
+ targetVariantId?: string | null; // merchant-pinned variant, or null
4155
+ requiresVariantSelection?: boolean; // true \u2192 customer must pick from variants
4156
+ pinnedVariant?: { id: string; name?: string; attributes?: Record<string, string> } | null;
4157
+ variants?: RecommendationVariant[]; // present when requiresVariantSelection
4088
4158
  }
4089
4159
  interface ProductRecommendationsResponse {
4090
4160
  crossSells: ProductRecommendation[];
@@ -4118,6 +4188,12 @@ interface CartBundleOfferOfferedProduct {
4118
4188
  salePrice: string | null;
4119
4189
  images: Array<{ url: string }>;
4120
4190
  type: string;
4191
+ // Variant context (VARIABLE offered products):
4192
+ variantId?: string | null; // merchant-pinned variant for this slot
4193
+ requiresVariantSelection?: boolean; // true \u2192 customer must pick from variants
4194
+ pinnedVariant?: { id: string; name?: string; attributes?: Record<string, string> } | null;
4195
+ variants?: RecommendationVariant[]; // present when requiresVariantSelection
4196
+ // originalPrice = pinned/cheapest variant price for VARIABLE (not a 0 parent base).
4121
4197
  originalPrice: string;
4122
4198
  discountedPrice: string;
4123
4199
  }
@@ -4601,10 +4677,12 @@ interface UpdateRegionDto extends Partial<CreateRegionDto> { isActive?: boolean
4601
4677
  // client.detectRegion(country, regions): Region | null \u2014 pure, no network.
4602
4678
  // \u2192 region whose countries includes the code, else the default region, else null.
4603
4679
  // Pass the resolved regionId to createCheckout to associate the checkout with a
4604
- // region (recorded for reporting + provider scoping; currency follows the cart
4605
- // until FX price conversion lands).
4680
+ // region (recorded for reporting + provider scoping). FX-at-checkout:
4681
+ // presentment-enabled regions (Stripe) charge in the region currency and the
4682
+ // response carries a presentment overlay; else charged in store base currency.
4606
4683
  //
4607
- // Storefront (public, no apiKey \u2014 storeId mode):
4684
+ // Storefront (public, no apiKey \u2014 storeId mode OR vibe-coded mode, salesChannelId 'vc_*';
4685
+ // the vc routes are gated on products:read, which every connection already has):
4608
4686
  interface PublicRegion {
4609
4687
  id: string; name: string; slug: string; currency: string;
4610
4688
  countries: string[]; taxInclusive: boolean; isDefault: boolean;
@@ -4656,7 +4734,8 @@ interface AssignTaxClassDto { productIds?: string[]; variantIds?: string[]; cate
4656
4734
  // deleteTaxClass(id), setDefaultTaxClass(id), assignTaxClass(id, dto),
4657
4735
  // mergeTaxClasses(id, targetId)
4658
4736
  //
4659
- // SDK methods (storefront, public \u2014 storeId mode, no apiKey):
4737
+ // SDK methods (storefront, public \u2014 storeId mode OR vibe-coded mode 'vc_*', no apiKey;
4738
+ // the vc routes are gated on products:read, which every connection already has):
4660
4739
  // getStoreRegions(), getStoreRegion(id), getAutoRegion(country)
4661
4740
  // getStoreTaxClasses(), estimateTax({ country?, subtotal })
4662
4741
  //
package/dist/bin/stdio.js CHANGED
@@ -2534,8 +2534,9 @@ await admin.createTaxRate({ name: 'VAT (Food)', rate: 0, country: 'GB', taxClass
2534
2534
  await admin.mergeTaxClasses(food.id, standardClassId);
2535
2535
  \`\`\`
2536
2536
 
2537
- Storefront (public, no apiKey \u2014 \`storeId\` mode): \`getStoreTaxClasses()\` lists
2538
- the store's classes (storefront-safe fields only) for a transparency badge.
2537
+ Storefront (public, no apiKey \u2014 \`storeId\` mode OR vibe-coded mode \`vc_*\`, gated
2538
+ on the \`products:read\` scope every connection already has): \`getStoreTaxClasses()\`
2539
+ lists the store's classes (storefront-safe fields only) for a transparency badge.
2539
2540
 
2540
2541
  For a buyer-facing tax preview on PDP / PLP / cart, \`estimateTax({ country,
2541
2542
  subtotal })\` returns the tax portion at the Standard rate for the buyer's
@@ -2557,8 +2558,10 @@ A region binds countries \u2192 currency + tax-display mode + enabled payment
2557
2558
  providers. Manage them via the SDK (scopes \`regions:read\` / \`regions:write\`).
2558
2559
  \`detectRegion\` is a pure client-side helper to map a buyer's country to a
2559
2560
  region; pass the resolved \`regionId\` to \`createCheckout\` to associate the
2560
- checkout with a region (recorded for reporting + provider scoping; currency
2561
- follows the cart until FX price conversion lands).
2561
+ checkout with a region (recorded for reporting + provider scoping). FX-at-checkout:
2562
+ presentment-enabled regions (Stripe today) charge in the region currency and the
2563
+ checkout response carries a \`presentment\` overlay with the charged amounts;
2564
+ otherwise the checkout is charged in the store base currency.
2562
2565
 
2563
2566
  \`\`\`typescript
2564
2567
  const eu = await admin.createRegion({
@@ -2571,9 +2574,12 @@ const { data: regions } = await admin.getRegions();
2571
2574
  const region = admin.detectRegion('DE', regions); // \u2192 eu, else default, else null
2572
2575
  \`\`\`
2573
2576
 
2574
- Storefront (public, no apiKey \u2014 \`storeId\` mode): \`getStoreRegions()\` lists
2575
- active regions, \`getStoreRegion(id)\` returns one region + its payment providers.
2576
- Pair with \`detectRegion\` to pick the buyer's region for currency display.
2577
+ Storefront (public, no apiKey \u2014 \`storeId\` mode OR vibe-coded mode \`vc_*\`, gated
2578
+ on the \`products:read\` scope every connection already has): \`getStoreRegions()\`
2579
+ lists active regions, \`getStoreRegion(id)\` returns one region + its payment
2580
+ providers. \`getAutoRegion(country)\` and \`estimateTax({ country, subtotal })\` work
2581
+ in both modes too. Pair with \`detectRegion\` to pick the buyer's region for
2582
+ currency display.
2577
2583
 
2578
2584
  \`\`\`typescript
2579
2585
  const store = new BrainerceClient({ storeId: 'store_123' });
@@ -3197,6 +3203,38 @@ Rules:
3197
3203
  count updates when the bot adds items.
3198
3204
  `;
3199
3205
  }
3206
+ function getStorefrontAnalyticsSection(connectionId) {
3207
+ return `## Storefront Traffic Analytics (visits, countries, sources)
3208
+
3209
+ Brainerce can track cookieless storefront traffic \u2014 how many people visit the
3210
+ store, from which countries, and from which traffic sources \u2014 and surface it in
3211
+ the merchant dashboard alongside sales (so the merchant gets a REAL conversion
3212
+ rate: visits \u2192 orders).
3213
+
3214
+ Add ONE line to the storefront's root layout (\`<head>\` or end of \`<body>\`):
3215
+
3216
+ \`\`\`html
3217
+ <script defer src="https://api.brainerce.com/t.js" data-channel="${connectionId}"></script>
3218
+ \`\`\`
3219
+
3220
+ That's it \u2014 the pixel auto-sends a pageview beacon on load and on SPA route
3221
+ changes. Stores scaffolded with \`create-brainerce-store\` already include it.
3222
+
3223
+ Privacy: the pixel is cookieless and stores NO personal data \u2014 no cookie, no
3224
+ cross-site/cross-day tracking, and the visitor IP is resolved to a country
3225
+ server-side then discarded. No cookie-consent banner is required for it under
3226
+ GDPR/ePrivacy.
3227
+
3228
+ CSP: if the storefront sets a Content-Security-Policy, allow the script + beacon:
3229
+ - \`script-src\` \u2026 \`https://api.brainerce.com\`
3230
+ - \`connect-src\` \u2026 \`https://api.brainerce.com\`
3231
+
3232
+ The merchant can toggle tracking per sales channel in the dashboard. The data
3233
+ shows up under Dashboard \u2192 Analytics (visits over time, by country, by source,
3234
+ by device) and can also be asked of the AI assistant ("how many visits did I get
3235
+ this month and from where?").
3236
+ `;
3237
+ }
3200
3238
  function getSectionByTopic(topic, connectionId, currency) {
3201
3239
  const cid = connectionId || "vc_YOUR_CONNECTION_ID";
3202
3240
  const cur = currency || "USD";
@@ -3253,6 +3291,9 @@ function getSectionByTopic(topic, connectionId, currency) {
3253
3291
  return getBlogSection();
3254
3292
  case "storefront-bot":
3255
3293
  return getStorefrontBotSection(cid);
3294
+ case "analytics":
3295
+ case "traffic-analytics":
3296
+ return getStorefrontAnalyticsSection(cid);
3256
3297
  case "all":
3257
3298
  return [
3258
3299
  "# Brainerce SDK \u2014 full topic dump",
@@ -3321,6 +3362,10 @@ function getSectionByTopic(topic, connectionId, currency) {
3321
3362
  "",
3322
3363
  "---",
3323
3364
  "",
3365
+ getStorefrontAnalyticsSection(cid),
3366
+ "",
3367
+ "---",
3368
+ "",
3324
3369
  getProductCustomizationFieldsSection(),
3325
3370
  "",
3326
3371
  "---",
@@ -3390,6 +3435,7 @@ var GET_SDK_DOCS_SCHEMA = {
3390
3435
  "inquiries",
3391
3436
  "content",
3392
3437
  "storefront-bot",
3438
+ "analytics",
3393
3439
  "all"
3394
3440
  ]).describe("The SDK documentation topic to retrieve"),
3395
3441
  salesChannelId: import_zod.z.string().optional().describe("Sales channel ID (starts with vc_). Used to personalize setup code."),
@@ -3427,7 +3473,7 @@ interface Product {
3427
3473
  priceMin?: string | null; // Lowest variant price (VARIABLE products) \u2014 same as basePrice for VARIABLE, kept for back-compat and JSON-LD range display.
3428
3474
  priceMax?: string | null; // Highest variant price (VARIABLE products). Use with priceMin for "\u20AA49 \u2013 \u20AA199" range.
3429
3475
  priceVaries?: boolean; // true when variant prices differ \u2014 show "\u20AA49 \u2013 \u20AA199" range.
3430
- // FX DISPLAY overlay (PRD \xA723). Set only when getProducts({ regionId }) was called AND region.currency !== store.currency. basePrice/salePrice above stay in store currency \u2014 these are additive, display-only. The buyer is still charged in store currency at checkout; payment provider handles customer-side FX.
3476
+ // FX DISPLAY overlay (PRD \xA723). Set only when getProducts({ regionId }) was called AND region.currency !== store.currency. basePrice/salePrice above stay in store currency \u2014 these are additive. For presentment-enabled regions (Stripe) the displayPrice uses the same buffered rate the checkout charges (browsed == checkout); for display-only regions it is mid-market and the buyer is charged in store currency at checkout (payment provider handles customer-side FX).
3431
3477
  displayPrice?: string; // basePrice \xD7 daily FX rate, in displayCurrency.
3432
3478
  displaySalePrice?: string; // salePrice \xD7 rate (if salePrice present).
3433
3479
  displayPriceMin?: string; // priceMin \xD7 rate (VARIABLE products).
@@ -3681,7 +3727,7 @@ interface Checkout {
3681
3727
  status: CheckoutStatus;
3682
3728
  email?: string | null;
3683
3729
  customerId?: string | null;
3684
- regionId?: string | null; // multi-region: recorded for reporting + provider scoping (currency follows the cart until FX lands)
3730
+ regionId?: string | null; // multi-region: recorded for reporting + provider scoping. FX-at-checkout: presentment-enabled regions (Stripe) charge in the region currency \u2014 see the presentment field below; else charged in store base currency
3685
3731
  shippingAddress?: CheckoutAddress | null;
3686
3732
  billingAddress?: CheckoutAddress | null;
3687
3733
  shippingRateId?: string | null;
@@ -3701,6 +3747,18 @@ interface Checkout {
3701
3747
  itemCount: number;
3702
3748
  availableShippingRates?: ShippingRate[];
3703
3749
  reservation?: ReservationInfo;
3750
+ // FX-at-checkout: present ONLY when the region charges in its own currency (presentment-enabled \u2014 Stripe today). The amounts the buyer is actually CHARGED, in presentment.currency; presentment.total equals the payment intent amount. Render these (not total/currency above) when present. Absent = charged in store base currency. The fields above stay the base reference.
3751
+ presentment?: {
3752
+ currency: string; // ISO-4217 charged currency, e.g. "EUR"
3753
+ subtotal: string;
3754
+ discountAmount: string;
3755
+ shippingAmount: string;
3756
+ taxAmount: string;
3757
+ surchargeAmount: string;
3758
+ total: string; // equals the charged amount, to the cent
3759
+ fxChargingRate: string; // base\u2192presentment rate (buffer included)
3760
+ fxBufferPercent: string | null;
3761
+ };
3704
3762
  createdAt: string;
3705
3763
  updatedAt: string;
3706
3764
  }
@@ -4078,10 +4136,22 @@ interface CartAppliedDiscount {
4078
4136
  }
4079
4137
 
4080
4138
  // Recommendation types
4139
+ interface RecommendationVariant {
4140
+ id: string; name?: string; price?: string; salePrice?: string;
4141
+ attributes?: Record<string, string>; image?: ProductImage | string; inventory?: InventoryInfo;
4142
+ }
4081
4143
  interface ProductRecommendation {
4082
- id: string; name: string; slug: string; basePrice: string; salePrice?: string;
4144
+ id: string; name: string; slug: string;
4145
+ // For a VARIABLE target, basePrice is the pinned variant's price (when
4146
+ // targetVariantId is set) or the "from {min variant}" range \u2014 never 0.
4147
+ basePrice: string; salePrice?: string;
4083
4148
  images: ProductImage[]; type: 'SIMPLE' | 'VARIABLE'; inventory?: InventoryInfo;
4084
4149
  relationType: string;
4150
+ // Variant context (VARIABLE targets):
4151
+ targetVariantId?: string | null; // merchant-pinned variant, or null
4152
+ requiresVariantSelection?: boolean; // true \u2192 customer must pick from variants
4153
+ pinnedVariant?: { id: string; name?: string; attributes?: Record<string, string> } | null;
4154
+ variants?: RecommendationVariant[]; // present when requiresVariantSelection
4085
4155
  }
4086
4156
  interface ProductRecommendationsResponse {
4087
4157
  crossSells: ProductRecommendation[];
@@ -4115,6 +4185,12 @@ interface CartBundleOfferOfferedProduct {
4115
4185
  salePrice: string | null;
4116
4186
  images: Array<{ url: string }>;
4117
4187
  type: string;
4188
+ // Variant context (VARIABLE offered products):
4189
+ variantId?: string | null; // merchant-pinned variant for this slot
4190
+ requiresVariantSelection?: boolean; // true \u2192 customer must pick from variants
4191
+ pinnedVariant?: { id: string; name?: string; attributes?: Record<string, string> } | null;
4192
+ variants?: RecommendationVariant[]; // present when requiresVariantSelection
4193
+ // originalPrice = pinned/cheapest variant price for VARIABLE (not a 0 parent base).
4118
4194
  originalPrice: string;
4119
4195
  discountedPrice: string;
4120
4196
  }
@@ -4598,10 +4674,12 @@ interface UpdateRegionDto extends Partial<CreateRegionDto> { isActive?: boolean
4598
4674
  // client.detectRegion(country, regions): Region | null \u2014 pure, no network.
4599
4675
  // \u2192 region whose countries includes the code, else the default region, else null.
4600
4676
  // Pass the resolved regionId to createCheckout to associate the checkout with a
4601
- // region (recorded for reporting + provider scoping; currency follows the cart
4602
- // until FX price conversion lands).
4677
+ // region (recorded for reporting + provider scoping). FX-at-checkout:
4678
+ // presentment-enabled regions (Stripe) charge in the region currency and the
4679
+ // response carries a presentment overlay; else charged in store base currency.
4603
4680
  //
4604
- // Storefront (public, no apiKey \u2014 storeId mode):
4681
+ // Storefront (public, no apiKey \u2014 storeId mode OR vibe-coded mode, salesChannelId 'vc_*';
4682
+ // the vc routes are gated on products:read, which every connection already has):
4605
4683
  interface PublicRegion {
4606
4684
  id: string; name: string; slug: string; currency: string;
4607
4685
  countries: string[]; taxInclusive: boolean; isDefault: boolean;
@@ -4653,7 +4731,8 @@ interface AssignTaxClassDto { productIds?: string[]; variantIds?: string[]; cate
4653
4731
  // deleteTaxClass(id), setDefaultTaxClass(id), assignTaxClass(id, dto),
4654
4732
  // mergeTaxClasses(id, targetId)
4655
4733
  //
4656
- // SDK methods (storefront, public \u2014 storeId mode, no apiKey):
4734
+ // SDK methods (storefront, public \u2014 storeId mode OR vibe-coded mode 'vc_*', no apiKey;
4735
+ // the vc routes are gated on products:read, which every connection already has):
4657
4736
  // getStoreRegions(), getStoreRegion(id), getAutoRegion(country)
4658
4737
  // getStoreTaxClasses(), estimateTax({ country?, subtotal })
4659
4738
  //
package/dist/index.js CHANGED
@@ -2560,8 +2560,9 @@ await admin.createTaxRate({ name: 'VAT (Food)', rate: 0, country: 'GB', taxClass
2560
2560
  await admin.mergeTaxClasses(food.id, standardClassId);
2561
2561
  \`\`\`
2562
2562
 
2563
- Storefront (public, no apiKey \u2014 \`storeId\` mode): \`getStoreTaxClasses()\` lists
2564
- the store's classes (storefront-safe fields only) for a transparency badge.
2563
+ Storefront (public, no apiKey \u2014 \`storeId\` mode OR vibe-coded mode \`vc_*\`, gated
2564
+ on the \`products:read\` scope every connection already has): \`getStoreTaxClasses()\`
2565
+ lists the store's classes (storefront-safe fields only) for a transparency badge.
2565
2566
 
2566
2567
  For a buyer-facing tax preview on PDP / PLP / cart, \`estimateTax({ country,
2567
2568
  subtotal })\` returns the tax portion at the Standard rate for the buyer's
@@ -2583,8 +2584,10 @@ A region binds countries \u2192 currency + tax-display mode + enabled payment
2583
2584
  providers. Manage them via the SDK (scopes \`regions:read\` / \`regions:write\`).
2584
2585
  \`detectRegion\` is a pure client-side helper to map a buyer's country to a
2585
2586
  region; pass the resolved \`regionId\` to \`createCheckout\` to associate the
2586
- checkout with a region (recorded for reporting + provider scoping; currency
2587
- follows the cart until FX price conversion lands).
2587
+ checkout with a region (recorded for reporting + provider scoping). FX-at-checkout:
2588
+ presentment-enabled regions (Stripe today) charge in the region currency and the
2589
+ checkout response carries a \`presentment\` overlay with the charged amounts;
2590
+ otherwise the checkout is charged in the store base currency.
2588
2591
 
2589
2592
  \`\`\`typescript
2590
2593
  const eu = await admin.createRegion({
@@ -2597,9 +2600,12 @@ const { data: regions } = await admin.getRegions();
2597
2600
  const region = admin.detectRegion('DE', regions); // \u2192 eu, else default, else null
2598
2601
  \`\`\`
2599
2602
 
2600
- Storefront (public, no apiKey \u2014 \`storeId\` mode): \`getStoreRegions()\` lists
2601
- active regions, \`getStoreRegion(id)\` returns one region + its payment providers.
2602
- Pair with \`detectRegion\` to pick the buyer's region for currency display.
2603
+ Storefront (public, no apiKey \u2014 \`storeId\` mode OR vibe-coded mode \`vc_*\`, gated
2604
+ on the \`products:read\` scope every connection already has): \`getStoreRegions()\`
2605
+ lists active regions, \`getStoreRegion(id)\` returns one region + its payment
2606
+ providers. \`getAutoRegion(country)\` and \`estimateTax({ country, subtotal })\` work
2607
+ in both modes too. Pair with \`detectRegion\` to pick the buyer's region for
2608
+ currency display.
2603
2609
 
2604
2610
  \`\`\`typescript
2605
2611
  const store = new BrainerceClient({ storeId: 'store_123' });
@@ -3223,6 +3229,38 @@ Rules:
3223
3229
  count updates when the bot adds items.
3224
3230
  `;
3225
3231
  }
3232
+ function getStorefrontAnalyticsSection(connectionId) {
3233
+ return `## Storefront Traffic Analytics (visits, countries, sources)
3234
+
3235
+ Brainerce can track cookieless storefront traffic \u2014 how many people visit the
3236
+ store, from which countries, and from which traffic sources \u2014 and surface it in
3237
+ the merchant dashboard alongside sales (so the merchant gets a REAL conversion
3238
+ rate: visits \u2192 orders).
3239
+
3240
+ Add ONE line to the storefront's root layout (\`<head>\` or end of \`<body>\`):
3241
+
3242
+ \`\`\`html
3243
+ <script defer src="https://api.brainerce.com/t.js" data-channel="${connectionId}"></script>
3244
+ \`\`\`
3245
+
3246
+ That's it \u2014 the pixel auto-sends a pageview beacon on load and on SPA route
3247
+ changes. Stores scaffolded with \`create-brainerce-store\` already include it.
3248
+
3249
+ Privacy: the pixel is cookieless and stores NO personal data \u2014 no cookie, no
3250
+ cross-site/cross-day tracking, and the visitor IP is resolved to a country
3251
+ server-side then discarded. No cookie-consent banner is required for it under
3252
+ GDPR/ePrivacy.
3253
+
3254
+ CSP: if the storefront sets a Content-Security-Policy, allow the script + beacon:
3255
+ - \`script-src\` \u2026 \`https://api.brainerce.com\`
3256
+ - \`connect-src\` \u2026 \`https://api.brainerce.com\`
3257
+
3258
+ The merchant can toggle tracking per sales channel in the dashboard. The data
3259
+ shows up under Dashboard \u2192 Analytics (visits over time, by country, by source,
3260
+ by device) and can also be asked of the AI assistant ("how many visits did I get
3261
+ this month and from where?").
3262
+ `;
3263
+ }
3226
3264
  function getSectionByTopic(topic, connectionId, currency) {
3227
3265
  const cid = connectionId || "vc_YOUR_CONNECTION_ID";
3228
3266
  const cur = currency || "USD";
@@ -3279,6 +3317,9 @@ function getSectionByTopic(topic, connectionId, currency) {
3279
3317
  return getBlogSection();
3280
3318
  case "storefront-bot":
3281
3319
  return getStorefrontBotSection(cid);
3320
+ case "analytics":
3321
+ case "traffic-analytics":
3322
+ return getStorefrontAnalyticsSection(cid);
3282
3323
  case "all":
3283
3324
  return [
3284
3325
  "# Brainerce SDK \u2014 full topic dump",
@@ -3347,6 +3388,10 @@ function getSectionByTopic(topic, connectionId, currency) {
3347
3388
  "",
3348
3389
  "---",
3349
3390
  "",
3391
+ getStorefrontAnalyticsSection(cid),
3392
+ "",
3393
+ "---",
3394
+ "",
3350
3395
  getProductCustomizationFieldsSection(),
3351
3396
  "",
3352
3397
  "---",
@@ -3416,6 +3461,7 @@ var GET_SDK_DOCS_SCHEMA = {
3416
3461
  "inquiries",
3417
3462
  "content",
3418
3463
  "storefront-bot",
3464
+ "analytics",
3419
3465
  "all"
3420
3466
  ]).describe("The SDK documentation topic to retrieve"),
3421
3467
  salesChannelId: import_zod.z.string().optional().describe("Sales channel ID (starts with vc_). Used to personalize setup code."),
@@ -3453,7 +3499,7 @@ interface Product {
3453
3499
  priceMin?: string | null; // Lowest variant price (VARIABLE products) \u2014 same as basePrice for VARIABLE, kept for back-compat and JSON-LD range display.
3454
3500
  priceMax?: string | null; // Highest variant price (VARIABLE products). Use with priceMin for "\u20AA49 \u2013 \u20AA199" range.
3455
3501
  priceVaries?: boolean; // true when variant prices differ \u2014 show "\u20AA49 \u2013 \u20AA199" range.
3456
- // FX DISPLAY overlay (PRD \xA723). Set only when getProducts({ regionId }) was called AND region.currency !== store.currency. basePrice/salePrice above stay in store currency \u2014 these are additive, display-only. The buyer is still charged in store currency at checkout; payment provider handles customer-side FX.
3502
+ // FX DISPLAY overlay (PRD \xA723). Set only when getProducts({ regionId }) was called AND region.currency !== store.currency. basePrice/salePrice above stay in store currency \u2014 these are additive. For presentment-enabled regions (Stripe) the displayPrice uses the same buffered rate the checkout charges (browsed == checkout); for display-only regions it is mid-market and the buyer is charged in store currency at checkout (payment provider handles customer-side FX).
3457
3503
  displayPrice?: string; // basePrice \xD7 daily FX rate, in displayCurrency.
3458
3504
  displaySalePrice?: string; // salePrice \xD7 rate (if salePrice present).
3459
3505
  displayPriceMin?: string; // priceMin \xD7 rate (VARIABLE products).
@@ -3707,7 +3753,7 @@ interface Checkout {
3707
3753
  status: CheckoutStatus;
3708
3754
  email?: string | null;
3709
3755
  customerId?: string | null;
3710
- regionId?: string | null; // multi-region: recorded for reporting + provider scoping (currency follows the cart until FX lands)
3756
+ regionId?: string | null; // multi-region: recorded for reporting + provider scoping. FX-at-checkout: presentment-enabled regions (Stripe) charge in the region currency \u2014 see the presentment field below; else charged in store base currency
3711
3757
  shippingAddress?: CheckoutAddress | null;
3712
3758
  billingAddress?: CheckoutAddress | null;
3713
3759
  shippingRateId?: string | null;
@@ -3727,6 +3773,18 @@ interface Checkout {
3727
3773
  itemCount: number;
3728
3774
  availableShippingRates?: ShippingRate[];
3729
3775
  reservation?: ReservationInfo;
3776
+ // FX-at-checkout: present ONLY when the region charges in its own currency (presentment-enabled \u2014 Stripe today). The amounts the buyer is actually CHARGED, in presentment.currency; presentment.total equals the payment intent amount. Render these (not total/currency above) when present. Absent = charged in store base currency. The fields above stay the base reference.
3777
+ presentment?: {
3778
+ currency: string; // ISO-4217 charged currency, e.g. "EUR"
3779
+ subtotal: string;
3780
+ discountAmount: string;
3781
+ shippingAmount: string;
3782
+ taxAmount: string;
3783
+ surchargeAmount: string;
3784
+ total: string; // equals the charged amount, to the cent
3785
+ fxChargingRate: string; // base\u2192presentment rate (buffer included)
3786
+ fxBufferPercent: string | null;
3787
+ };
3730
3788
  createdAt: string;
3731
3789
  updatedAt: string;
3732
3790
  }
@@ -4104,10 +4162,22 @@ interface CartAppliedDiscount {
4104
4162
  }
4105
4163
 
4106
4164
  // Recommendation types
4165
+ interface RecommendationVariant {
4166
+ id: string; name?: string; price?: string; salePrice?: string;
4167
+ attributes?: Record<string, string>; image?: ProductImage | string; inventory?: InventoryInfo;
4168
+ }
4107
4169
  interface ProductRecommendation {
4108
- id: string; name: string; slug: string; basePrice: string; salePrice?: string;
4170
+ id: string; name: string; slug: string;
4171
+ // For a VARIABLE target, basePrice is the pinned variant's price (when
4172
+ // targetVariantId is set) or the "from {min variant}" range \u2014 never 0.
4173
+ basePrice: string; salePrice?: string;
4109
4174
  images: ProductImage[]; type: 'SIMPLE' | 'VARIABLE'; inventory?: InventoryInfo;
4110
4175
  relationType: string;
4176
+ // Variant context (VARIABLE targets):
4177
+ targetVariantId?: string | null; // merchant-pinned variant, or null
4178
+ requiresVariantSelection?: boolean; // true \u2192 customer must pick from variants
4179
+ pinnedVariant?: { id: string; name?: string; attributes?: Record<string, string> } | null;
4180
+ variants?: RecommendationVariant[]; // present when requiresVariantSelection
4111
4181
  }
4112
4182
  interface ProductRecommendationsResponse {
4113
4183
  crossSells: ProductRecommendation[];
@@ -4141,6 +4211,12 @@ interface CartBundleOfferOfferedProduct {
4141
4211
  salePrice: string | null;
4142
4212
  images: Array<{ url: string }>;
4143
4213
  type: string;
4214
+ // Variant context (VARIABLE offered products):
4215
+ variantId?: string | null; // merchant-pinned variant for this slot
4216
+ requiresVariantSelection?: boolean; // true \u2192 customer must pick from variants
4217
+ pinnedVariant?: { id: string; name?: string; attributes?: Record<string, string> } | null;
4218
+ variants?: RecommendationVariant[]; // present when requiresVariantSelection
4219
+ // originalPrice = pinned/cheapest variant price for VARIABLE (not a 0 parent base).
4144
4220
  originalPrice: string;
4145
4221
  discountedPrice: string;
4146
4222
  }
@@ -4624,10 +4700,12 @@ interface UpdateRegionDto extends Partial<CreateRegionDto> { isActive?: boolean
4624
4700
  // client.detectRegion(country, regions): Region | null \u2014 pure, no network.
4625
4701
  // \u2192 region whose countries includes the code, else the default region, else null.
4626
4702
  // Pass the resolved regionId to createCheckout to associate the checkout with a
4627
- // region (recorded for reporting + provider scoping; currency follows the cart
4628
- // until FX price conversion lands).
4703
+ // region (recorded for reporting + provider scoping). FX-at-checkout:
4704
+ // presentment-enabled regions (Stripe) charge in the region currency and the
4705
+ // response carries a presentment overlay; else charged in store base currency.
4629
4706
  //
4630
- // Storefront (public, no apiKey \u2014 storeId mode):
4707
+ // Storefront (public, no apiKey \u2014 storeId mode OR vibe-coded mode, salesChannelId 'vc_*';
4708
+ // the vc routes are gated on products:read, which every connection already has):
4631
4709
  interface PublicRegion {
4632
4710
  id: string; name: string; slug: string; currency: string;
4633
4711
  countries: string[]; taxInclusive: boolean; isDefault: boolean;
@@ -4679,7 +4757,8 @@ interface AssignTaxClassDto { productIds?: string[]; variantIds?: string[]; cate
4679
4757
  // deleteTaxClass(id), setDefaultTaxClass(id), assignTaxClass(id, dto),
4680
4758
  // mergeTaxClasses(id, targetId)
4681
4759
  //
4682
- // SDK methods (storefront, public \u2014 storeId mode, no apiKey):
4760
+ // SDK methods (storefront, public \u2014 storeId mode OR vibe-coded mode 'vc_*', no apiKey;
4761
+ // the vc routes are gated on products:read, which every connection already has):
4683
4762
  // getStoreRegions(), getStoreRegion(id), getAutoRegion(country)
4684
4763
  // getStoreTaxClasses(), estimateTax({ country?, subtotal })
4685
4764
  //
package/dist/index.mjs CHANGED
@@ -2528,8 +2528,9 @@ await admin.createTaxRate({ name: 'VAT (Food)', rate: 0, country: 'GB', taxClass
2528
2528
  await admin.mergeTaxClasses(food.id, standardClassId);
2529
2529
  \`\`\`
2530
2530
 
2531
- Storefront (public, no apiKey \u2014 \`storeId\` mode): \`getStoreTaxClasses()\` lists
2532
- the store's classes (storefront-safe fields only) for a transparency badge.
2531
+ Storefront (public, no apiKey \u2014 \`storeId\` mode OR vibe-coded mode \`vc_*\`, gated
2532
+ on the \`products:read\` scope every connection already has): \`getStoreTaxClasses()\`
2533
+ lists the store's classes (storefront-safe fields only) for a transparency badge.
2533
2534
 
2534
2535
  For a buyer-facing tax preview on PDP / PLP / cart, \`estimateTax({ country,
2535
2536
  subtotal })\` returns the tax portion at the Standard rate for the buyer's
@@ -2551,8 +2552,10 @@ A region binds countries \u2192 currency + tax-display mode + enabled payment
2551
2552
  providers. Manage them via the SDK (scopes \`regions:read\` / \`regions:write\`).
2552
2553
  \`detectRegion\` is a pure client-side helper to map a buyer's country to a
2553
2554
  region; pass the resolved \`regionId\` to \`createCheckout\` to associate the
2554
- checkout with a region (recorded for reporting + provider scoping; currency
2555
- follows the cart until FX price conversion lands).
2555
+ checkout with a region (recorded for reporting + provider scoping). FX-at-checkout:
2556
+ presentment-enabled regions (Stripe today) charge in the region currency and the
2557
+ checkout response carries a \`presentment\` overlay with the charged amounts;
2558
+ otherwise the checkout is charged in the store base currency.
2556
2559
 
2557
2560
  \`\`\`typescript
2558
2561
  const eu = await admin.createRegion({
@@ -2565,9 +2568,12 @@ const { data: regions } = await admin.getRegions();
2565
2568
  const region = admin.detectRegion('DE', regions); // \u2192 eu, else default, else null
2566
2569
  \`\`\`
2567
2570
 
2568
- Storefront (public, no apiKey \u2014 \`storeId\` mode): \`getStoreRegions()\` lists
2569
- active regions, \`getStoreRegion(id)\` returns one region + its payment providers.
2570
- Pair with \`detectRegion\` to pick the buyer's region for currency display.
2571
+ Storefront (public, no apiKey \u2014 \`storeId\` mode OR vibe-coded mode \`vc_*\`, gated
2572
+ on the \`products:read\` scope every connection already has): \`getStoreRegions()\`
2573
+ lists active regions, \`getStoreRegion(id)\` returns one region + its payment
2574
+ providers. \`getAutoRegion(country)\` and \`estimateTax({ country, subtotal })\` work
2575
+ in both modes too. Pair with \`detectRegion\` to pick the buyer's region for
2576
+ currency display.
2571
2577
 
2572
2578
  \`\`\`typescript
2573
2579
  const store = new BrainerceClient({ storeId: 'store_123' });
@@ -3191,6 +3197,38 @@ Rules:
3191
3197
  count updates when the bot adds items.
3192
3198
  `;
3193
3199
  }
3200
+ function getStorefrontAnalyticsSection(connectionId) {
3201
+ return `## Storefront Traffic Analytics (visits, countries, sources)
3202
+
3203
+ Brainerce can track cookieless storefront traffic \u2014 how many people visit the
3204
+ store, from which countries, and from which traffic sources \u2014 and surface it in
3205
+ the merchant dashboard alongside sales (so the merchant gets a REAL conversion
3206
+ rate: visits \u2192 orders).
3207
+
3208
+ Add ONE line to the storefront's root layout (\`<head>\` or end of \`<body>\`):
3209
+
3210
+ \`\`\`html
3211
+ <script defer src="https://api.brainerce.com/t.js" data-channel="${connectionId}"></script>
3212
+ \`\`\`
3213
+
3214
+ That's it \u2014 the pixel auto-sends a pageview beacon on load and on SPA route
3215
+ changes. Stores scaffolded with \`create-brainerce-store\` already include it.
3216
+
3217
+ Privacy: the pixel is cookieless and stores NO personal data \u2014 no cookie, no
3218
+ cross-site/cross-day tracking, and the visitor IP is resolved to a country
3219
+ server-side then discarded. No cookie-consent banner is required for it under
3220
+ GDPR/ePrivacy.
3221
+
3222
+ CSP: if the storefront sets a Content-Security-Policy, allow the script + beacon:
3223
+ - \`script-src\` \u2026 \`https://api.brainerce.com\`
3224
+ - \`connect-src\` \u2026 \`https://api.brainerce.com\`
3225
+
3226
+ The merchant can toggle tracking per sales channel in the dashboard. The data
3227
+ shows up under Dashboard \u2192 Analytics (visits over time, by country, by source,
3228
+ by device) and can also be asked of the AI assistant ("how many visits did I get
3229
+ this month and from where?").
3230
+ `;
3231
+ }
3194
3232
  function getSectionByTopic(topic, connectionId, currency) {
3195
3233
  const cid = connectionId || "vc_YOUR_CONNECTION_ID";
3196
3234
  const cur = currency || "USD";
@@ -3247,6 +3285,9 @@ function getSectionByTopic(topic, connectionId, currency) {
3247
3285
  return getBlogSection();
3248
3286
  case "storefront-bot":
3249
3287
  return getStorefrontBotSection(cid);
3288
+ case "analytics":
3289
+ case "traffic-analytics":
3290
+ return getStorefrontAnalyticsSection(cid);
3250
3291
  case "all":
3251
3292
  return [
3252
3293
  "# Brainerce SDK \u2014 full topic dump",
@@ -3315,6 +3356,10 @@ function getSectionByTopic(topic, connectionId, currency) {
3315
3356
  "",
3316
3357
  "---",
3317
3358
  "",
3359
+ getStorefrontAnalyticsSection(cid),
3360
+ "",
3361
+ "---",
3362
+ "",
3318
3363
  getProductCustomizationFieldsSection(),
3319
3364
  "",
3320
3365
  "---",
@@ -3384,6 +3429,7 @@ var GET_SDK_DOCS_SCHEMA = {
3384
3429
  "inquiries",
3385
3430
  "content",
3386
3431
  "storefront-bot",
3432
+ "analytics",
3387
3433
  "all"
3388
3434
  ]).describe("The SDK documentation topic to retrieve"),
3389
3435
  salesChannelId: z.string().optional().describe("Sales channel ID (starts with vc_). Used to personalize setup code."),
@@ -3421,7 +3467,7 @@ interface Product {
3421
3467
  priceMin?: string | null; // Lowest variant price (VARIABLE products) \u2014 same as basePrice for VARIABLE, kept for back-compat and JSON-LD range display.
3422
3468
  priceMax?: string | null; // Highest variant price (VARIABLE products). Use with priceMin for "\u20AA49 \u2013 \u20AA199" range.
3423
3469
  priceVaries?: boolean; // true when variant prices differ \u2014 show "\u20AA49 \u2013 \u20AA199" range.
3424
- // FX DISPLAY overlay (PRD \xA723). Set only when getProducts({ regionId }) was called AND region.currency !== store.currency. basePrice/salePrice above stay in store currency \u2014 these are additive, display-only. The buyer is still charged in store currency at checkout; payment provider handles customer-side FX.
3470
+ // FX DISPLAY overlay (PRD \xA723). Set only when getProducts({ regionId }) was called AND region.currency !== store.currency. basePrice/salePrice above stay in store currency \u2014 these are additive. For presentment-enabled regions (Stripe) the displayPrice uses the same buffered rate the checkout charges (browsed == checkout); for display-only regions it is mid-market and the buyer is charged in store currency at checkout (payment provider handles customer-side FX).
3425
3471
  displayPrice?: string; // basePrice \xD7 daily FX rate, in displayCurrency.
3426
3472
  displaySalePrice?: string; // salePrice \xD7 rate (if salePrice present).
3427
3473
  displayPriceMin?: string; // priceMin \xD7 rate (VARIABLE products).
@@ -3675,7 +3721,7 @@ interface Checkout {
3675
3721
  status: CheckoutStatus;
3676
3722
  email?: string | null;
3677
3723
  customerId?: string | null;
3678
- regionId?: string | null; // multi-region: recorded for reporting + provider scoping (currency follows the cart until FX lands)
3724
+ regionId?: string | null; // multi-region: recorded for reporting + provider scoping. FX-at-checkout: presentment-enabled regions (Stripe) charge in the region currency \u2014 see the presentment field below; else charged in store base currency
3679
3725
  shippingAddress?: CheckoutAddress | null;
3680
3726
  billingAddress?: CheckoutAddress | null;
3681
3727
  shippingRateId?: string | null;
@@ -3695,6 +3741,18 @@ interface Checkout {
3695
3741
  itemCount: number;
3696
3742
  availableShippingRates?: ShippingRate[];
3697
3743
  reservation?: ReservationInfo;
3744
+ // FX-at-checkout: present ONLY when the region charges in its own currency (presentment-enabled \u2014 Stripe today). The amounts the buyer is actually CHARGED, in presentment.currency; presentment.total equals the payment intent amount. Render these (not total/currency above) when present. Absent = charged in store base currency. The fields above stay the base reference.
3745
+ presentment?: {
3746
+ currency: string; // ISO-4217 charged currency, e.g. "EUR"
3747
+ subtotal: string;
3748
+ discountAmount: string;
3749
+ shippingAmount: string;
3750
+ taxAmount: string;
3751
+ surchargeAmount: string;
3752
+ total: string; // equals the charged amount, to the cent
3753
+ fxChargingRate: string; // base\u2192presentment rate (buffer included)
3754
+ fxBufferPercent: string | null;
3755
+ };
3698
3756
  createdAt: string;
3699
3757
  updatedAt: string;
3700
3758
  }
@@ -4072,10 +4130,22 @@ interface CartAppliedDiscount {
4072
4130
  }
4073
4131
 
4074
4132
  // Recommendation types
4133
+ interface RecommendationVariant {
4134
+ id: string; name?: string; price?: string; salePrice?: string;
4135
+ attributes?: Record<string, string>; image?: ProductImage | string; inventory?: InventoryInfo;
4136
+ }
4075
4137
  interface ProductRecommendation {
4076
- id: string; name: string; slug: string; basePrice: string; salePrice?: string;
4138
+ id: string; name: string; slug: string;
4139
+ // For a VARIABLE target, basePrice is the pinned variant's price (when
4140
+ // targetVariantId is set) or the "from {min variant}" range \u2014 never 0.
4141
+ basePrice: string; salePrice?: string;
4077
4142
  images: ProductImage[]; type: 'SIMPLE' | 'VARIABLE'; inventory?: InventoryInfo;
4078
4143
  relationType: string;
4144
+ // Variant context (VARIABLE targets):
4145
+ targetVariantId?: string | null; // merchant-pinned variant, or null
4146
+ requiresVariantSelection?: boolean; // true \u2192 customer must pick from variants
4147
+ pinnedVariant?: { id: string; name?: string; attributes?: Record<string, string> } | null;
4148
+ variants?: RecommendationVariant[]; // present when requiresVariantSelection
4079
4149
  }
4080
4150
  interface ProductRecommendationsResponse {
4081
4151
  crossSells: ProductRecommendation[];
@@ -4109,6 +4179,12 @@ interface CartBundleOfferOfferedProduct {
4109
4179
  salePrice: string | null;
4110
4180
  images: Array<{ url: string }>;
4111
4181
  type: string;
4182
+ // Variant context (VARIABLE offered products):
4183
+ variantId?: string | null; // merchant-pinned variant for this slot
4184
+ requiresVariantSelection?: boolean; // true \u2192 customer must pick from variants
4185
+ pinnedVariant?: { id: string; name?: string; attributes?: Record<string, string> } | null;
4186
+ variants?: RecommendationVariant[]; // present when requiresVariantSelection
4187
+ // originalPrice = pinned/cheapest variant price for VARIABLE (not a 0 parent base).
4112
4188
  originalPrice: string;
4113
4189
  discountedPrice: string;
4114
4190
  }
@@ -4592,10 +4668,12 @@ interface UpdateRegionDto extends Partial<CreateRegionDto> { isActive?: boolean
4592
4668
  // client.detectRegion(country, regions): Region | null \u2014 pure, no network.
4593
4669
  // \u2192 region whose countries includes the code, else the default region, else null.
4594
4670
  // Pass the resolved regionId to createCheckout to associate the checkout with a
4595
- // region (recorded for reporting + provider scoping; currency follows the cart
4596
- // until FX price conversion lands).
4671
+ // region (recorded for reporting + provider scoping). FX-at-checkout:
4672
+ // presentment-enabled regions (Stripe) charge in the region currency and the
4673
+ // response carries a presentment overlay; else charged in store base currency.
4597
4674
  //
4598
- // Storefront (public, no apiKey \u2014 storeId mode):
4675
+ // Storefront (public, no apiKey \u2014 storeId mode OR vibe-coded mode, salesChannelId 'vc_*';
4676
+ // the vc routes are gated on products:read, which every connection already has):
4599
4677
  interface PublicRegion {
4600
4678
  id: string; name: string; slug: string; currency: string;
4601
4679
  countries: string[]; taxInclusive: boolean; isDefault: boolean;
@@ -4647,7 +4725,8 @@ interface AssignTaxClassDto { productIds?: string[]; variantIds?: string[]; cate
4647
4725
  // deleteTaxClass(id), setDefaultTaxClass(id), assignTaxClass(id, dto),
4648
4726
  // mergeTaxClasses(id, targetId)
4649
4727
  //
4650
- // SDK methods (storefront, public \u2014 storeId mode, no apiKey):
4728
+ // SDK methods (storefront, public \u2014 storeId mode OR vibe-coded mode 'vc_*', no apiKey;
4729
+ // the vc routes are gated on products:read, which every connection already has):
4651
4730
  // getStoreRegions(), getStoreRegion(id), getAutoRegion(country)
4652
4731
  // getStoreTaxClasses(), estimateTax({ country?, subtotal })
4653
4732
  //
package/package.json CHANGED
@@ -1,53 +1,53 @@
1
- {
2
- "name": "@brainerce/mcp-server",
3
- "version": "3.11.1",
4
- "description": "Framework-agnostic domain knowledge API for Brainerce. Provides SDK docs, types, business flows, critical rules, and store capabilities to AI tools like Lovable, Cursor, Bolt, v0, and Claude Code. Does NOT provide framework-specific boilerplate — clients build in whatever framework fits.",
5
- "bin": {
6
- "brainerce-mcp": "dist/bin/stdio.js"
7
- },
8
- "main": "dist/index.js",
9
- "module": "dist/index.mjs",
10
- "types": "dist/index.d.ts",
11
- "exports": {
12
- ".": {
13
- "types": "./dist/index.d.ts",
14
- "require": "./dist/index.js",
15
- "import": "./dist/index.mjs"
16
- }
17
- },
18
- "files": [
19
- "dist",
20
- "README.md"
21
- ],
22
- "scripts": {
23
- "build": "tsup",
24
- "dev": "tsup --watch",
25
- "start:stdio": "node dist/bin/stdio.js",
26
- "start:http": "node dist/bin/http.js",
27
- "prepublishOnly": "npm run build"
28
- },
29
- "dependencies": {
30
- "@modelcontextprotocol/sdk": "^1.27.1",
31
- "zod": "^3.24.0"
32
- },
33
- "devDependencies": {
34
- "@types/node": "^22.0.0",
35
- "tsup": "^8.0.0",
36
- "typescript": "^5.3.0"
37
- },
38
- "engines": {
39
- "node": ">=18"
40
- },
41
- "keywords": [
42
- "brainerce",
43
- "mcp",
44
- "model-context-protocol",
45
- "ai",
46
- "ecommerce",
47
- "lovable",
48
- "cursor",
49
- "claude-code",
50
- "vibe-coding"
51
- ],
52
- "license": "MIT"
53
- }
1
+ {
2
+ "name": "@brainerce/mcp-server",
3
+ "version": "3.12.0",
4
+ "description": "Framework-agnostic domain knowledge API for Brainerce. Provides SDK docs, types, business flows, critical rules, and store capabilities to AI tools like Lovable, Cursor, Bolt, v0, and Claude Code. Does NOT provide framework-specific boilerplate — clients build in whatever framework fits.",
5
+ "bin": {
6
+ "brainerce-mcp": "dist/bin/stdio.js"
7
+ },
8
+ "main": "dist/index.js",
9
+ "module": "dist/index.mjs",
10
+ "types": "dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "require": "./dist/index.js",
15
+ "import": "./dist/index.mjs"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "README.md"
21
+ ],
22
+ "scripts": {
23
+ "build": "tsup",
24
+ "dev": "tsup --watch",
25
+ "start:stdio": "node dist/bin/stdio.js",
26
+ "start:http": "node dist/bin/http.js",
27
+ "prepublishOnly": "npm run build"
28
+ },
29
+ "dependencies": {
30
+ "@modelcontextprotocol/sdk": "^1.27.1",
31
+ "zod": "^3.24.0"
32
+ },
33
+ "devDependencies": {
34
+ "@types/node": "^22.0.0",
35
+ "tsup": "^8.0.0",
36
+ "typescript": "^5.3.0"
37
+ },
38
+ "engines": {
39
+ "node": ">=18"
40
+ },
41
+ "keywords": [
42
+ "brainerce",
43
+ "mcp",
44
+ "model-context-protocol",
45
+ "ai",
46
+ "ecommerce",
47
+ "lovable",
48
+ "cursor",
49
+ "claude-code",
50
+ "vibe-coding"
51
+ ],
52
+ "license": "MIT"
53
+ }