@brainerce/mcp-server 3.12.1 → 3.12.3

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
@@ -1782,7 +1782,7 @@ The merchant can hide a group entirely for one variant by setting \`maxOverride:
1782
1782
 
1783
1783
  ### Restaurant features (advanced)
1784
1784
 
1785
- Allergens (informational chips), scheduled availability windows, nested combos (depth \u2264 3 via \`nestedByModifierId\`), and downsell modifiers (negative \`priceDelta\`) are all built on the same data model. See INTEGRATION-OPTIONAL.md "Restaurant / build-your-own products" for those flows.`;
1785
+ Scheduled availability windows, nested combos (depth \u2264 3 via \`nestedByModifierId\`), and downsell modifiers (negative \`priceDelta\`) are all built on the same data model. See INTEGRATION-OPTIONAL.md "Restaurant / build-your-own products" for those flows.`;
1786
1786
  }
1787
1787
  function getProductReviewsSection() {
1788
1788
  return `## Product Reviews (\u2605 ratings + JSON-LD for SEO)
@@ -3310,6 +3310,65 @@ so customers can track their shipment automatically.
3310
3310
 
3311
3311
  **SDK mode:** \`createShippingLabel()\` requires \`apiKey\` (admin mode only).`;
3312
3312
  }
3313
+ function getLoyaltySection() {
3314
+ return `# Loyalty & Rewards
3315
+
3316
+ Logged-in customers earn points on paid orders and redeem them for a one-time discount coupon at checkout.
3317
+
3318
+ > **Storefront mode only.** These methods work only with \`new BrainerceClient({ storeId })\` + a logged-in \`customerToken\`. There is **no vibe-coded route** \u2014 in \`salesChannelId: 'vc_*'\` mode they throw. Skip loyalty if you connect vibe-coded.
3319
+
3320
+ Points are earned automatically by the platform when an order is paid \u2014 there is no earn call from the storefront. Your job is to **show** balance/rewards and to **redeem**.
3321
+
3322
+ ## Status
3323
+
3324
+ \`\`\`typescript
3325
+ // requires client.setCustomerToken(auth.token)
3326
+ const status = await client.getLoyaltyStatus();
3327
+ // {
3328
+ // enrolled, pointsBalance, lifetimeEarned,
3329
+ // program: { pointsName, currencyRatio, status } | null,
3330
+ // tier: { id, name, level, pointsMultiplier } | null,
3331
+ // nextTier: { id, name, level, pointsMultiplier, qualificationType, qualificationThreshold } | null,
3332
+ // progressToNextTier, pointsToNextTier,
3333
+ // }
3334
+ \`\`\`
3335
+
3336
+ - \`program === null\` \u2192 the store has no loyalty program; hide the loyalty UI.
3337
+ - \`program.status !== 'ACTIVE'\` \u2192 program paused/draft; hide the loyalty UI.
3338
+ - \`tier\`/\`nextTier\` are \`null\` when the store hasn't configured tiers, or the customer hasn't qualified for one yet.
3339
+
3340
+ ## Enroll (only if auto-enroll is off)
3341
+
3342
+ \`\`\`typescript
3343
+ await client.enrollInLoyalty(); // same status shape; no-op if already enrolled
3344
+ \`\`\`
3345
+
3346
+ May grant a one-time "joined the program" bonus if configured \u2014 distinct from any account-signup bonus (granted automatically on registration, not by this call).
3347
+
3348
+ ## Rewards & redemption
3349
+
3350
+ \`\`\`typescript
3351
+ const rewards = await client.getAvailableRewards();
3352
+ // [{ id, name, description, pointsCost, type, discountValue, minOrderAmount, maxUsesPerUser, minTierLevel, isActive }]
3353
+ // Already filtered to rewards the customer's current tier qualifies for.
3354
+
3355
+ const { couponCode, discountType, discountValue, pointsBalance } =
3356
+ await client.redeemLoyaltyReward(rewardId);
3357
+ await client.applyCoupon(cartId, couponCode); // applies via the normal coupon engine
3358
+ \`\`\`
3359
+
3360
+ - \`reward.type\` is \`'FIXED_DISCOUNT'\` (currency amount) or \`'PERCENT_DISCOUNT'\` (0-100 percent) \u2014 the redeem result's \`discountType\` matches.
3361
+ - \`redeemLoyaltyReward\` mints a one-time coupon (\`usageLimit: 1\`). Apply it with the normal \`applyCoupon\` flow.
3362
+ - Throws if the customer has insufficient points, or their tier is below \`reward.minTierLevel\`. If minting fails, points are refunded automatically.
3363
+ - Disable a reward's redeem button when \`pointsBalance < reward.pointsCost\`.
3364
+
3365
+ ## Social share (optional)
3366
+
3367
+ \`\`\`typescript
3368
+ await client.reportSocialShare('instagram'); // platform optional, self-reported, once per customer
3369
+ \`\`\`
3370
+ `;
3371
+ }
3313
3372
  function getSectionByTopic(topic, connectionId, currency) {
3314
3373
  const cid = connectionId || "vc_YOUR_CONNECTION_ID";
3315
3374
  const cur = currency || "USD";
@@ -3336,6 +3395,8 @@ function getSectionByTopic(topic, connectionId, currency) {
3336
3395
  return getInventorySection();
3337
3396
  case "discounts":
3338
3397
  return getDiscountsSection();
3398
+ case "loyalty":
3399
+ return getLoyaltySection();
3339
3400
  case "recommendations":
3340
3401
  return getRecommendationsSection();
3341
3402
  case "reviews":
@@ -3502,6 +3563,7 @@ var GET_SDK_DOCS_SCHEMA = {
3502
3563
  "order-confirmation",
3503
3564
  "inventory",
3504
3565
  "discounts",
3566
+ "loyalty",
3505
3567
  "recommendations",
3506
3568
  "reviews",
3507
3569
  "product-customization-fields",
@@ -4839,6 +4901,63 @@ interface TaxEstimateResponse {
4839
4901
  // Non-binding by design: the authoritative tax runs at checkout against the
4840
4902
  // full shipping address. The country comes from your edge runtime
4841
4903
  // (CF-IPCountry / request.geo.country / etc.), NOT the request IP.`;
4904
+ var LOYALTY_TYPES = `// ---- Loyalty & Rewards (storefront mode only) ----
4905
+ interface LoyaltyTierSummary {
4906
+ id: string;
4907
+ name: string;
4908
+ level: number; // ordering; higher = better tier
4909
+ pointsMultiplier: number;
4910
+ }
4911
+
4912
+ interface LoyaltyNextTierSummary extends LoyaltyTierSummary {
4913
+ qualificationType: 'SPEND' | 'POINTS';
4914
+ qualificationThreshold: number;
4915
+ }
4916
+
4917
+ interface LoyaltyStatus {
4918
+ enrolled: boolean;
4919
+ pointsBalance: number; // redeemable points (pending earns excluded)
4920
+ lifetimeEarned: number;
4921
+ program: {
4922
+ pointsName: string; // e.g. "points", "coins"
4923
+ currencyRatio: number; // points needed to redeem 1 unit of store currency
4924
+ status: 'DRAFT' | 'ACTIVE' | 'PAUSED';
4925
+ } | null; // null when the store has no loyalty program
4926
+ tier: LoyaltyTierSummary | null; // null if untiered / no tiers configured
4927
+ nextTier: LoyaltyNextTierSummary | null; // null if at the top tier already
4928
+ progressToNextTier: number; // 0..1
4929
+ pointsToNextTier: number | null;
4930
+ }
4931
+
4932
+ interface LoyaltyReward {
4933
+ id: string;
4934
+ name: string;
4935
+ description: string | null;
4936
+ pointsCost: number;
4937
+ type: 'FIXED_DISCOUNT' | 'PERCENT_DISCOUNT';
4938
+ discountValue: number; // currency amount (FIXED_DISCOUNT) or 0-100 percent (PERCENT_DISCOUNT)
4939
+ minOrderAmount: number | null;
4940
+ maxUsesPerUser: number;
4941
+ isActive: boolean;
4942
+ minTierLevel: number | null; // minimum tier level required, or null for no restriction
4943
+ createdAt: string;
4944
+ updatedAt: string;
4945
+ }
4946
+
4947
+ interface RedeemRewardResult {
4948
+ couponCode: string; // one-time coupon to apply at checkout
4949
+ discountType: 'FIXED_DISCOUNT' | 'PERCENT_DISCOUNT';
4950
+ discountValue: number;
4951
+ pointsSpent: number;
4952
+ pointsBalance: number; // remaining balance after redemption
4953
+ }
4954
+
4955
+ // client.getLoyaltyStatus(): Promise<LoyaltyStatus>
4956
+ // client.enrollInLoyalty(): Promise<LoyaltyStatus>
4957
+ // client.getAvailableRewards(): Promise<LoyaltyReward[]>
4958
+ // client.redeemLoyaltyReward(rewardId: string): Promise<RedeemRewardResult>
4959
+ // client.reportSocialShare(platform?: string): Promise<{ awarded: boolean; points: number }>
4960
+ `;
4842
4961
  var TYPES_BY_DOMAIN = {
4843
4962
  products: PRODUCTS_TYPES,
4844
4963
  cart: CART_TYPES,
@@ -4851,7 +4970,8 @@ var TYPES_BY_DOMAIN = {
4851
4970
  reviews: REVIEWS_TYPES,
4852
4971
  "modifier-groups": MODIFIER_GROUPS_TYPES,
4853
4972
  content: CONTENT_TYPES,
4854
- regions: REGIONS_TYPES
4973
+ regions: REGIONS_TYPES,
4974
+ loyalty: LOYALTY_TYPES
4855
4975
  };
4856
4976
  function getTypesByDomain(domain) {
4857
4977
  if (domain === "all") {
@@ -4885,6 +5005,7 @@ var GET_TYPE_DEFINITIONS_SCHEMA = {
4885
5005
  "modifier-groups",
4886
5006
  "content",
4887
5007
  "regions",
5008
+ "loyalty",
4888
5009
  "all"
4889
5010
  ]).describe(
4890
5011
  'The domain of types to retrieve. Use "helpers" for helper function signatures and common types like StoreInfo, PaginatedResponse. Use "content" for FAQ / Footer / Header / Announcement / RichText / Page. Use "regions" for Region / TaxClass admin types.'
@@ -6331,6 +6452,9 @@ function formatCapabilities(caps) {
6331
6452
  lines.push(
6332
6453
  caps.features.hasCheckoutCustomFields ? "\u2705 Checkout custom fields configured (surcharges may apply)" : "\u274C No checkout custom fields"
6333
6454
  );
6455
+ lines.push(
6456
+ caps.features.hasLoyaltyProgram ? "\u2705 Loyalty program active (points & rewards \u2014 storefront-mode only)" : "\u274C No loyalty program"
6457
+ );
6334
6458
  lines.push("");
6335
6459
  lines.push("## Connection Settings");
6336
6460
  lines.push(
@@ -6376,6 +6500,11 @@ function formatCapabilities(caps) {
6376
6500
  if (caps.features.hasCoupons) {
6377
6501
  suggestions.push('Add a coupon input to the cart. Use get-code-example("coupon-input").');
6378
6502
  }
6503
+ if (caps.features.hasLoyaltyProgram) {
6504
+ suggestions.push(
6505
+ "A loyalty program is active. In a storefront-mode client (new BrainerceClient({ storeId })), show the logged-in customer their points balance, tier progress, and redeemable rewards, and let them redeem for a coupon at checkout. Use getLoyaltyStatus() (includes tier/nextTier/progressToNextTier), getAvailableRewards(), redeemLoyaltyReward(id), and optionally reportSocialShare(). Note: loyalty is NOT available in vibe-coded mode."
6506
+ );
6507
+ }
6379
6508
  if (caps.features.hasCheckoutCustomFields) {
6380
6509
  suggestions.push(
6381
6510
  `Checkout custom fields are configured. Build a step between shipping/pickup and payment using getCheckoutCustomFields() + setCheckoutCustomFields(). Render checkout.appliedSurcharges in the order summary so customers see what they're paying for. Use get-code-example("checkout-custom-fields").`
package/dist/bin/stdio.js CHANGED
@@ -1779,7 +1779,7 @@ The merchant can hide a group entirely for one variant by setting \`maxOverride:
1779
1779
 
1780
1780
  ### Restaurant features (advanced)
1781
1781
 
1782
- Allergens (informational chips), scheduled availability windows, nested combos (depth \u2264 3 via \`nestedByModifierId\`), and downsell modifiers (negative \`priceDelta\`) are all built on the same data model. See INTEGRATION-OPTIONAL.md "Restaurant / build-your-own products" for those flows.`;
1782
+ Scheduled availability windows, nested combos (depth \u2264 3 via \`nestedByModifierId\`), and downsell modifiers (negative \`priceDelta\`) are all built on the same data model. See INTEGRATION-OPTIONAL.md "Restaurant / build-your-own products" for those flows.`;
1783
1783
  }
1784
1784
  function getProductReviewsSection() {
1785
1785
  return `## Product Reviews (\u2605 ratings + JSON-LD for SEO)
@@ -3307,6 +3307,65 @@ so customers can track their shipment automatically.
3307
3307
 
3308
3308
  **SDK mode:** \`createShippingLabel()\` requires \`apiKey\` (admin mode only).`;
3309
3309
  }
3310
+ function getLoyaltySection() {
3311
+ return `# Loyalty & Rewards
3312
+
3313
+ Logged-in customers earn points on paid orders and redeem them for a one-time discount coupon at checkout.
3314
+
3315
+ > **Storefront mode only.** These methods work only with \`new BrainerceClient({ storeId })\` + a logged-in \`customerToken\`. There is **no vibe-coded route** \u2014 in \`salesChannelId: 'vc_*'\` mode they throw. Skip loyalty if you connect vibe-coded.
3316
+
3317
+ Points are earned automatically by the platform when an order is paid \u2014 there is no earn call from the storefront. Your job is to **show** balance/rewards and to **redeem**.
3318
+
3319
+ ## Status
3320
+
3321
+ \`\`\`typescript
3322
+ // requires client.setCustomerToken(auth.token)
3323
+ const status = await client.getLoyaltyStatus();
3324
+ // {
3325
+ // enrolled, pointsBalance, lifetimeEarned,
3326
+ // program: { pointsName, currencyRatio, status } | null,
3327
+ // tier: { id, name, level, pointsMultiplier } | null,
3328
+ // nextTier: { id, name, level, pointsMultiplier, qualificationType, qualificationThreshold } | null,
3329
+ // progressToNextTier, pointsToNextTier,
3330
+ // }
3331
+ \`\`\`
3332
+
3333
+ - \`program === null\` \u2192 the store has no loyalty program; hide the loyalty UI.
3334
+ - \`program.status !== 'ACTIVE'\` \u2192 program paused/draft; hide the loyalty UI.
3335
+ - \`tier\`/\`nextTier\` are \`null\` when the store hasn't configured tiers, or the customer hasn't qualified for one yet.
3336
+
3337
+ ## Enroll (only if auto-enroll is off)
3338
+
3339
+ \`\`\`typescript
3340
+ await client.enrollInLoyalty(); // same status shape; no-op if already enrolled
3341
+ \`\`\`
3342
+
3343
+ May grant a one-time "joined the program" bonus if configured \u2014 distinct from any account-signup bonus (granted automatically on registration, not by this call).
3344
+
3345
+ ## Rewards & redemption
3346
+
3347
+ \`\`\`typescript
3348
+ const rewards = await client.getAvailableRewards();
3349
+ // [{ id, name, description, pointsCost, type, discountValue, minOrderAmount, maxUsesPerUser, minTierLevel, isActive }]
3350
+ // Already filtered to rewards the customer's current tier qualifies for.
3351
+
3352
+ const { couponCode, discountType, discountValue, pointsBalance } =
3353
+ await client.redeemLoyaltyReward(rewardId);
3354
+ await client.applyCoupon(cartId, couponCode); // applies via the normal coupon engine
3355
+ \`\`\`
3356
+
3357
+ - \`reward.type\` is \`'FIXED_DISCOUNT'\` (currency amount) or \`'PERCENT_DISCOUNT'\` (0-100 percent) \u2014 the redeem result's \`discountType\` matches.
3358
+ - \`redeemLoyaltyReward\` mints a one-time coupon (\`usageLimit: 1\`). Apply it with the normal \`applyCoupon\` flow.
3359
+ - Throws if the customer has insufficient points, or their tier is below \`reward.minTierLevel\`. If minting fails, points are refunded automatically.
3360
+ - Disable a reward's redeem button when \`pointsBalance < reward.pointsCost\`.
3361
+
3362
+ ## Social share (optional)
3363
+
3364
+ \`\`\`typescript
3365
+ await client.reportSocialShare('instagram'); // platform optional, self-reported, once per customer
3366
+ \`\`\`
3367
+ `;
3368
+ }
3310
3369
  function getSectionByTopic(topic, connectionId, currency) {
3311
3370
  const cid = connectionId || "vc_YOUR_CONNECTION_ID";
3312
3371
  const cur = currency || "USD";
@@ -3333,6 +3392,8 @@ function getSectionByTopic(topic, connectionId, currency) {
3333
3392
  return getInventorySection();
3334
3393
  case "discounts":
3335
3394
  return getDiscountsSection();
3395
+ case "loyalty":
3396
+ return getLoyaltySection();
3336
3397
  case "recommendations":
3337
3398
  return getRecommendationsSection();
3338
3399
  case "reviews":
@@ -3499,6 +3560,7 @@ var GET_SDK_DOCS_SCHEMA = {
3499
3560
  "order-confirmation",
3500
3561
  "inventory",
3501
3562
  "discounts",
3563
+ "loyalty",
3502
3564
  "recommendations",
3503
3565
  "reviews",
3504
3566
  "product-customization-fields",
@@ -4836,6 +4898,63 @@ interface TaxEstimateResponse {
4836
4898
  // Non-binding by design: the authoritative tax runs at checkout against the
4837
4899
  // full shipping address. The country comes from your edge runtime
4838
4900
  // (CF-IPCountry / request.geo.country / etc.), NOT the request IP.`;
4901
+ var LOYALTY_TYPES = `// ---- Loyalty & Rewards (storefront mode only) ----
4902
+ interface LoyaltyTierSummary {
4903
+ id: string;
4904
+ name: string;
4905
+ level: number; // ordering; higher = better tier
4906
+ pointsMultiplier: number;
4907
+ }
4908
+
4909
+ interface LoyaltyNextTierSummary extends LoyaltyTierSummary {
4910
+ qualificationType: 'SPEND' | 'POINTS';
4911
+ qualificationThreshold: number;
4912
+ }
4913
+
4914
+ interface LoyaltyStatus {
4915
+ enrolled: boolean;
4916
+ pointsBalance: number; // redeemable points (pending earns excluded)
4917
+ lifetimeEarned: number;
4918
+ program: {
4919
+ pointsName: string; // e.g. "points", "coins"
4920
+ currencyRatio: number; // points needed to redeem 1 unit of store currency
4921
+ status: 'DRAFT' | 'ACTIVE' | 'PAUSED';
4922
+ } | null; // null when the store has no loyalty program
4923
+ tier: LoyaltyTierSummary | null; // null if untiered / no tiers configured
4924
+ nextTier: LoyaltyNextTierSummary | null; // null if at the top tier already
4925
+ progressToNextTier: number; // 0..1
4926
+ pointsToNextTier: number | null;
4927
+ }
4928
+
4929
+ interface LoyaltyReward {
4930
+ id: string;
4931
+ name: string;
4932
+ description: string | null;
4933
+ pointsCost: number;
4934
+ type: 'FIXED_DISCOUNT' | 'PERCENT_DISCOUNT';
4935
+ discountValue: number; // currency amount (FIXED_DISCOUNT) or 0-100 percent (PERCENT_DISCOUNT)
4936
+ minOrderAmount: number | null;
4937
+ maxUsesPerUser: number;
4938
+ isActive: boolean;
4939
+ minTierLevel: number | null; // minimum tier level required, or null for no restriction
4940
+ createdAt: string;
4941
+ updatedAt: string;
4942
+ }
4943
+
4944
+ interface RedeemRewardResult {
4945
+ couponCode: string; // one-time coupon to apply at checkout
4946
+ discountType: 'FIXED_DISCOUNT' | 'PERCENT_DISCOUNT';
4947
+ discountValue: number;
4948
+ pointsSpent: number;
4949
+ pointsBalance: number; // remaining balance after redemption
4950
+ }
4951
+
4952
+ // client.getLoyaltyStatus(): Promise<LoyaltyStatus>
4953
+ // client.enrollInLoyalty(): Promise<LoyaltyStatus>
4954
+ // client.getAvailableRewards(): Promise<LoyaltyReward[]>
4955
+ // client.redeemLoyaltyReward(rewardId: string): Promise<RedeemRewardResult>
4956
+ // client.reportSocialShare(platform?: string): Promise<{ awarded: boolean; points: number }>
4957
+ `;
4839
4958
  var TYPES_BY_DOMAIN = {
4840
4959
  products: PRODUCTS_TYPES,
4841
4960
  cart: CART_TYPES,
@@ -4848,7 +4967,8 @@ var TYPES_BY_DOMAIN = {
4848
4967
  reviews: REVIEWS_TYPES,
4849
4968
  "modifier-groups": MODIFIER_GROUPS_TYPES,
4850
4969
  content: CONTENT_TYPES,
4851
- regions: REGIONS_TYPES
4970
+ regions: REGIONS_TYPES,
4971
+ loyalty: LOYALTY_TYPES
4852
4972
  };
4853
4973
  function getTypesByDomain(domain) {
4854
4974
  if (domain === "all") {
@@ -4882,6 +5002,7 @@ var GET_TYPE_DEFINITIONS_SCHEMA = {
4882
5002
  "modifier-groups",
4883
5003
  "content",
4884
5004
  "regions",
5005
+ "loyalty",
4885
5006
  "all"
4886
5007
  ]).describe(
4887
5008
  'The domain of types to retrieve. Use "helpers" for helper function signatures and common types like StoreInfo, PaginatedResponse. Use "content" for FAQ / Footer / Header / Announcement / RichText / Page. Use "regions" for Region / TaxClass admin types.'
@@ -6328,6 +6449,9 @@ function formatCapabilities(caps) {
6328
6449
  lines.push(
6329
6450
  caps.features.hasCheckoutCustomFields ? "\u2705 Checkout custom fields configured (surcharges may apply)" : "\u274C No checkout custom fields"
6330
6451
  );
6452
+ lines.push(
6453
+ caps.features.hasLoyaltyProgram ? "\u2705 Loyalty program active (points & rewards \u2014 storefront-mode only)" : "\u274C No loyalty program"
6454
+ );
6331
6455
  lines.push("");
6332
6456
  lines.push("## Connection Settings");
6333
6457
  lines.push(
@@ -6373,6 +6497,11 @@ function formatCapabilities(caps) {
6373
6497
  if (caps.features.hasCoupons) {
6374
6498
  suggestions.push('Add a coupon input to the cart. Use get-code-example("coupon-input").');
6375
6499
  }
6500
+ if (caps.features.hasLoyaltyProgram) {
6501
+ suggestions.push(
6502
+ "A loyalty program is active. In a storefront-mode client (new BrainerceClient({ storeId })), show the logged-in customer their points balance, tier progress, and redeemable rewards, and let them redeem for a coupon at checkout. Use getLoyaltyStatus() (includes tier/nextTier/progressToNextTier), getAvailableRewards(), redeemLoyaltyReward(id), and optionally reportSocialShare(). Note: loyalty is NOT available in vibe-coded mode."
6503
+ );
6504
+ }
6376
6505
  if (caps.features.hasCheckoutCustomFields) {
6377
6506
  suggestions.push(
6378
6507
  `Checkout custom fields are configured. Build a step between shipping/pickup and payment using getCheckoutCustomFields() + setCheckoutCustomFields(). Render checkout.appliedSurcharges in the order summary so customers see what they're paying for. Use get-code-example("checkout-custom-fields").`
package/dist/index.d.mts CHANGED
@@ -75,6 +75,8 @@ interface StoreCapabilities {
75
75
  hasCheckoutCustomFields: boolean;
76
76
  /** True when at least one PUBLISHED Content row exists for the store. */
77
77
  hasContent?: boolean;
78
+ /** True when the store has an ACTIVE loyalty program. Storefront-mode only. */
79
+ hasLoyaltyProgram?: boolean;
78
80
  };
79
81
  }
80
82
  declare function fetchStoreCapabilities(connectionId: string, baseUrl?: string): Promise<StoreCapabilities>;
package/dist/index.d.ts CHANGED
@@ -75,6 +75,8 @@ interface StoreCapabilities {
75
75
  hasCheckoutCustomFields: boolean;
76
76
  /** True when at least one PUBLISHED Content row exists for the store. */
77
77
  hasContent?: boolean;
78
+ /** True when the store has an ACTIVE loyalty program. Storefront-mode only. */
79
+ hasLoyaltyProgram?: boolean;
78
80
  };
79
81
  }
80
82
  declare function fetchStoreCapabilities(connectionId: string, baseUrl?: string): Promise<StoreCapabilities>;
package/dist/index.js CHANGED
@@ -1805,7 +1805,7 @@ The merchant can hide a group entirely for one variant by setting \`maxOverride:
1805
1805
 
1806
1806
  ### Restaurant features (advanced)
1807
1807
 
1808
- Allergens (informational chips), scheduled availability windows, nested combos (depth \u2264 3 via \`nestedByModifierId\`), and downsell modifiers (negative \`priceDelta\`) are all built on the same data model. See INTEGRATION-OPTIONAL.md "Restaurant / build-your-own products" for those flows.`;
1808
+ Scheduled availability windows, nested combos (depth \u2264 3 via \`nestedByModifierId\`), and downsell modifiers (negative \`priceDelta\`) are all built on the same data model. See INTEGRATION-OPTIONAL.md "Restaurant / build-your-own products" for those flows.`;
1809
1809
  }
1810
1810
  function getProductReviewsSection() {
1811
1811
  return `## Product Reviews (\u2605 ratings + JSON-LD for SEO)
@@ -3333,6 +3333,65 @@ so customers can track their shipment automatically.
3333
3333
 
3334
3334
  **SDK mode:** \`createShippingLabel()\` requires \`apiKey\` (admin mode only).`;
3335
3335
  }
3336
+ function getLoyaltySection() {
3337
+ return `# Loyalty & Rewards
3338
+
3339
+ Logged-in customers earn points on paid orders and redeem them for a one-time discount coupon at checkout.
3340
+
3341
+ > **Storefront mode only.** These methods work only with \`new BrainerceClient({ storeId })\` + a logged-in \`customerToken\`. There is **no vibe-coded route** \u2014 in \`salesChannelId: 'vc_*'\` mode they throw. Skip loyalty if you connect vibe-coded.
3342
+
3343
+ Points are earned automatically by the platform when an order is paid \u2014 there is no earn call from the storefront. Your job is to **show** balance/rewards and to **redeem**.
3344
+
3345
+ ## Status
3346
+
3347
+ \`\`\`typescript
3348
+ // requires client.setCustomerToken(auth.token)
3349
+ const status = await client.getLoyaltyStatus();
3350
+ // {
3351
+ // enrolled, pointsBalance, lifetimeEarned,
3352
+ // program: { pointsName, currencyRatio, status } | null,
3353
+ // tier: { id, name, level, pointsMultiplier } | null,
3354
+ // nextTier: { id, name, level, pointsMultiplier, qualificationType, qualificationThreshold } | null,
3355
+ // progressToNextTier, pointsToNextTier,
3356
+ // }
3357
+ \`\`\`
3358
+
3359
+ - \`program === null\` \u2192 the store has no loyalty program; hide the loyalty UI.
3360
+ - \`program.status !== 'ACTIVE'\` \u2192 program paused/draft; hide the loyalty UI.
3361
+ - \`tier\`/\`nextTier\` are \`null\` when the store hasn't configured tiers, or the customer hasn't qualified for one yet.
3362
+
3363
+ ## Enroll (only if auto-enroll is off)
3364
+
3365
+ \`\`\`typescript
3366
+ await client.enrollInLoyalty(); // same status shape; no-op if already enrolled
3367
+ \`\`\`
3368
+
3369
+ May grant a one-time "joined the program" bonus if configured \u2014 distinct from any account-signup bonus (granted automatically on registration, not by this call).
3370
+
3371
+ ## Rewards & redemption
3372
+
3373
+ \`\`\`typescript
3374
+ const rewards = await client.getAvailableRewards();
3375
+ // [{ id, name, description, pointsCost, type, discountValue, minOrderAmount, maxUsesPerUser, minTierLevel, isActive }]
3376
+ // Already filtered to rewards the customer's current tier qualifies for.
3377
+
3378
+ const { couponCode, discountType, discountValue, pointsBalance } =
3379
+ await client.redeemLoyaltyReward(rewardId);
3380
+ await client.applyCoupon(cartId, couponCode); // applies via the normal coupon engine
3381
+ \`\`\`
3382
+
3383
+ - \`reward.type\` is \`'FIXED_DISCOUNT'\` (currency amount) or \`'PERCENT_DISCOUNT'\` (0-100 percent) \u2014 the redeem result's \`discountType\` matches.
3384
+ - \`redeemLoyaltyReward\` mints a one-time coupon (\`usageLimit: 1\`). Apply it with the normal \`applyCoupon\` flow.
3385
+ - Throws if the customer has insufficient points, or their tier is below \`reward.minTierLevel\`. If minting fails, points are refunded automatically.
3386
+ - Disable a reward's redeem button when \`pointsBalance < reward.pointsCost\`.
3387
+
3388
+ ## Social share (optional)
3389
+
3390
+ \`\`\`typescript
3391
+ await client.reportSocialShare('instagram'); // platform optional, self-reported, once per customer
3392
+ \`\`\`
3393
+ `;
3394
+ }
3336
3395
  function getSectionByTopic(topic, connectionId, currency) {
3337
3396
  const cid = connectionId || "vc_YOUR_CONNECTION_ID";
3338
3397
  const cur = currency || "USD";
@@ -3359,6 +3418,8 @@ function getSectionByTopic(topic, connectionId, currency) {
3359
3418
  return getInventorySection();
3360
3419
  case "discounts":
3361
3420
  return getDiscountsSection();
3421
+ case "loyalty":
3422
+ return getLoyaltySection();
3362
3423
  case "recommendations":
3363
3424
  return getRecommendationsSection();
3364
3425
  case "reviews":
@@ -3525,6 +3586,7 @@ var GET_SDK_DOCS_SCHEMA = {
3525
3586
  "order-confirmation",
3526
3587
  "inventory",
3527
3588
  "discounts",
3589
+ "loyalty",
3528
3590
  "recommendations",
3529
3591
  "reviews",
3530
3592
  "product-customization-fields",
@@ -4862,6 +4924,63 @@ interface TaxEstimateResponse {
4862
4924
  // Non-binding by design: the authoritative tax runs at checkout against the
4863
4925
  // full shipping address. The country comes from your edge runtime
4864
4926
  // (CF-IPCountry / request.geo.country / etc.), NOT the request IP.`;
4927
+ var LOYALTY_TYPES = `// ---- Loyalty & Rewards (storefront mode only) ----
4928
+ interface LoyaltyTierSummary {
4929
+ id: string;
4930
+ name: string;
4931
+ level: number; // ordering; higher = better tier
4932
+ pointsMultiplier: number;
4933
+ }
4934
+
4935
+ interface LoyaltyNextTierSummary extends LoyaltyTierSummary {
4936
+ qualificationType: 'SPEND' | 'POINTS';
4937
+ qualificationThreshold: number;
4938
+ }
4939
+
4940
+ interface LoyaltyStatus {
4941
+ enrolled: boolean;
4942
+ pointsBalance: number; // redeemable points (pending earns excluded)
4943
+ lifetimeEarned: number;
4944
+ program: {
4945
+ pointsName: string; // e.g. "points", "coins"
4946
+ currencyRatio: number; // points needed to redeem 1 unit of store currency
4947
+ status: 'DRAFT' | 'ACTIVE' | 'PAUSED';
4948
+ } | null; // null when the store has no loyalty program
4949
+ tier: LoyaltyTierSummary | null; // null if untiered / no tiers configured
4950
+ nextTier: LoyaltyNextTierSummary | null; // null if at the top tier already
4951
+ progressToNextTier: number; // 0..1
4952
+ pointsToNextTier: number | null;
4953
+ }
4954
+
4955
+ interface LoyaltyReward {
4956
+ id: string;
4957
+ name: string;
4958
+ description: string | null;
4959
+ pointsCost: number;
4960
+ type: 'FIXED_DISCOUNT' | 'PERCENT_DISCOUNT';
4961
+ discountValue: number; // currency amount (FIXED_DISCOUNT) or 0-100 percent (PERCENT_DISCOUNT)
4962
+ minOrderAmount: number | null;
4963
+ maxUsesPerUser: number;
4964
+ isActive: boolean;
4965
+ minTierLevel: number | null; // minimum tier level required, or null for no restriction
4966
+ createdAt: string;
4967
+ updatedAt: string;
4968
+ }
4969
+
4970
+ interface RedeemRewardResult {
4971
+ couponCode: string; // one-time coupon to apply at checkout
4972
+ discountType: 'FIXED_DISCOUNT' | 'PERCENT_DISCOUNT';
4973
+ discountValue: number;
4974
+ pointsSpent: number;
4975
+ pointsBalance: number; // remaining balance after redemption
4976
+ }
4977
+
4978
+ // client.getLoyaltyStatus(): Promise<LoyaltyStatus>
4979
+ // client.enrollInLoyalty(): Promise<LoyaltyStatus>
4980
+ // client.getAvailableRewards(): Promise<LoyaltyReward[]>
4981
+ // client.redeemLoyaltyReward(rewardId: string): Promise<RedeemRewardResult>
4982
+ // client.reportSocialShare(platform?: string): Promise<{ awarded: boolean; points: number }>
4983
+ `;
4865
4984
  var TYPES_BY_DOMAIN = {
4866
4985
  products: PRODUCTS_TYPES,
4867
4986
  cart: CART_TYPES,
@@ -4874,7 +4993,8 @@ var TYPES_BY_DOMAIN = {
4874
4993
  reviews: REVIEWS_TYPES,
4875
4994
  "modifier-groups": MODIFIER_GROUPS_TYPES,
4876
4995
  content: CONTENT_TYPES,
4877
- regions: REGIONS_TYPES
4996
+ regions: REGIONS_TYPES,
4997
+ loyalty: LOYALTY_TYPES
4878
4998
  };
4879
4999
  function getTypesByDomain(domain) {
4880
5000
  if (domain === "all") {
@@ -4908,6 +5028,7 @@ var GET_TYPE_DEFINITIONS_SCHEMA = {
4908
5028
  "modifier-groups",
4909
5029
  "content",
4910
5030
  "regions",
5031
+ "loyalty",
4911
5032
  "all"
4912
5033
  ]).describe(
4913
5034
  'The domain of types to retrieve. Use "helpers" for helper function signatures and common types like StoreInfo, PaginatedResponse. Use "content" for FAQ / Footer / Header / Announcement / RichText / Page. Use "regions" for Region / TaxClass admin types.'
@@ -6354,6 +6475,9 @@ function formatCapabilities(caps) {
6354
6475
  lines.push(
6355
6476
  caps.features.hasCheckoutCustomFields ? "\u2705 Checkout custom fields configured (surcharges may apply)" : "\u274C No checkout custom fields"
6356
6477
  );
6478
+ lines.push(
6479
+ caps.features.hasLoyaltyProgram ? "\u2705 Loyalty program active (points & rewards \u2014 storefront-mode only)" : "\u274C No loyalty program"
6480
+ );
6357
6481
  lines.push("");
6358
6482
  lines.push("## Connection Settings");
6359
6483
  lines.push(
@@ -6399,6 +6523,11 @@ function formatCapabilities(caps) {
6399
6523
  if (caps.features.hasCoupons) {
6400
6524
  suggestions.push('Add a coupon input to the cart. Use get-code-example("coupon-input").');
6401
6525
  }
6526
+ if (caps.features.hasLoyaltyProgram) {
6527
+ suggestions.push(
6528
+ "A loyalty program is active. In a storefront-mode client (new BrainerceClient({ storeId })), show the logged-in customer their points balance, tier progress, and redeemable rewards, and let them redeem for a coupon at checkout. Use getLoyaltyStatus() (includes tier/nextTier/progressToNextTier), getAvailableRewards(), redeemLoyaltyReward(id), and optionally reportSocialShare(). Note: loyalty is NOT available in vibe-coded mode."
6529
+ );
6530
+ }
6402
6531
  if (caps.features.hasCheckoutCustomFields) {
6403
6532
  suggestions.push(
6404
6533
  `Checkout custom fields are configured. Build a step between shipping/pickup and payment using getCheckoutCustomFields() + setCheckoutCustomFields(). Render checkout.appliedSurcharges in the order summary so customers see what they're paying for. Use get-code-example("checkout-custom-fields").`
package/dist/index.mjs CHANGED
@@ -1773,7 +1773,7 @@ The merchant can hide a group entirely for one variant by setting \`maxOverride:
1773
1773
 
1774
1774
  ### Restaurant features (advanced)
1775
1775
 
1776
- Allergens (informational chips), scheduled availability windows, nested combos (depth \u2264 3 via \`nestedByModifierId\`), and downsell modifiers (negative \`priceDelta\`) are all built on the same data model. See INTEGRATION-OPTIONAL.md "Restaurant / build-your-own products" for those flows.`;
1776
+ Scheduled availability windows, nested combos (depth \u2264 3 via \`nestedByModifierId\`), and downsell modifiers (negative \`priceDelta\`) are all built on the same data model. See INTEGRATION-OPTIONAL.md "Restaurant / build-your-own products" for those flows.`;
1777
1777
  }
1778
1778
  function getProductReviewsSection() {
1779
1779
  return `## Product Reviews (\u2605 ratings + JSON-LD for SEO)
@@ -3301,6 +3301,65 @@ so customers can track their shipment automatically.
3301
3301
 
3302
3302
  **SDK mode:** \`createShippingLabel()\` requires \`apiKey\` (admin mode only).`;
3303
3303
  }
3304
+ function getLoyaltySection() {
3305
+ return `# Loyalty & Rewards
3306
+
3307
+ Logged-in customers earn points on paid orders and redeem them for a one-time discount coupon at checkout.
3308
+
3309
+ > **Storefront mode only.** These methods work only with \`new BrainerceClient({ storeId })\` + a logged-in \`customerToken\`. There is **no vibe-coded route** \u2014 in \`salesChannelId: 'vc_*'\` mode they throw. Skip loyalty if you connect vibe-coded.
3310
+
3311
+ Points are earned automatically by the platform when an order is paid \u2014 there is no earn call from the storefront. Your job is to **show** balance/rewards and to **redeem**.
3312
+
3313
+ ## Status
3314
+
3315
+ \`\`\`typescript
3316
+ // requires client.setCustomerToken(auth.token)
3317
+ const status = await client.getLoyaltyStatus();
3318
+ // {
3319
+ // enrolled, pointsBalance, lifetimeEarned,
3320
+ // program: { pointsName, currencyRatio, status } | null,
3321
+ // tier: { id, name, level, pointsMultiplier } | null,
3322
+ // nextTier: { id, name, level, pointsMultiplier, qualificationType, qualificationThreshold } | null,
3323
+ // progressToNextTier, pointsToNextTier,
3324
+ // }
3325
+ \`\`\`
3326
+
3327
+ - \`program === null\` \u2192 the store has no loyalty program; hide the loyalty UI.
3328
+ - \`program.status !== 'ACTIVE'\` \u2192 program paused/draft; hide the loyalty UI.
3329
+ - \`tier\`/\`nextTier\` are \`null\` when the store hasn't configured tiers, or the customer hasn't qualified for one yet.
3330
+
3331
+ ## Enroll (only if auto-enroll is off)
3332
+
3333
+ \`\`\`typescript
3334
+ await client.enrollInLoyalty(); // same status shape; no-op if already enrolled
3335
+ \`\`\`
3336
+
3337
+ May grant a one-time "joined the program" bonus if configured \u2014 distinct from any account-signup bonus (granted automatically on registration, not by this call).
3338
+
3339
+ ## Rewards & redemption
3340
+
3341
+ \`\`\`typescript
3342
+ const rewards = await client.getAvailableRewards();
3343
+ // [{ id, name, description, pointsCost, type, discountValue, minOrderAmount, maxUsesPerUser, minTierLevel, isActive }]
3344
+ // Already filtered to rewards the customer's current tier qualifies for.
3345
+
3346
+ const { couponCode, discountType, discountValue, pointsBalance } =
3347
+ await client.redeemLoyaltyReward(rewardId);
3348
+ await client.applyCoupon(cartId, couponCode); // applies via the normal coupon engine
3349
+ \`\`\`
3350
+
3351
+ - \`reward.type\` is \`'FIXED_DISCOUNT'\` (currency amount) or \`'PERCENT_DISCOUNT'\` (0-100 percent) \u2014 the redeem result's \`discountType\` matches.
3352
+ - \`redeemLoyaltyReward\` mints a one-time coupon (\`usageLimit: 1\`). Apply it with the normal \`applyCoupon\` flow.
3353
+ - Throws if the customer has insufficient points, or their tier is below \`reward.minTierLevel\`. If minting fails, points are refunded automatically.
3354
+ - Disable a reward's redeem button when \`pointsBalance < reward.pointsCost\`.
3355
+
3356
+ ## Social share (optional)
3357
+
3358
+ \`\`\`typescript
3359
+ await client.reportSocialShare('instagram'); // platform optional, self-reported, once per customer
3360
+ \`\`\`
3361
+ `;
3362
+ }
3304
3363
  function getSectionByTopic(topic, connectionId, currency) {
3305
3364
  const cid = connectionId || "vc_YOUR_CONNECTION_ID";
3306
3365
  const cur = currency || "USD";
@@ -3327,6 +3386,8 @@ function getSectionByTopic(topic, connectionId, currency) {
3327
3386
  return getInventorySection();
3328
3387
  case "discounts":
3329
3388
  return getDiscountsSection();
3389
+ case "loyalty":
3390
+ return getLoyaltySection();
3330
3391
  case "recommendations":
3331
3392
  return getRecommendationsSection();
3332
3393
  case "reviews":
@@ -3493,6 +3554,7 @@ var GET_SDK_DOCS_SCHEMA = {
3493
3554
  "order-confirmation",
3494
3555
  "inventory",
3495
3556
  "discounts",
3557
+ "loyalty",
3496
3558
  "recommendations",
3497
3559
  "reviews",
3498
3560
  "product-customization-fields",
@@ -4830,6 +4892,63 @@ interface TaxEstimateResponse {
4830
4892
  // Non-binding by design: the authoritative tax runs at checkout against the
4831
4893
  // full shipping address. The country comes from your edge runtime
4832
4894
  // (CF-IPCountry / request.geo.country / etc.), NOT the request IP.`;
4895
+ var LOYALTY_TYPES = `// ---- Loyalty & Rewards (storefront mode only) ----
4896
+ interface LoyaltyTierSummary {
4897
+ id: string;
4898
+ name: string;
4899
+ level: number; // ordering; higher = better tier
4900
+ pointsMultiplier: number;
4901
+ }
4902
+
4903
+ interface LoyaltyNextTierSummary extends LoyaltyTierSummary {
4904
+ qualificationType: 'SPEND' | 'POINTS';
4905
+ qualificationThreshold: number;
4906
+ }
4907
+
4908
+ interface LoyaltyStatus {
4909
+ enrolled: boolean;
4910
+ pointsBalance: number; // redeemable points (pending earns excluded)
4911
+ lifetimeEarned: number;
4912
+ program: {
4913
+ pointsName: string; // e.g. "points", "coins"
4914
+ currencyRatio: number; // points needed to redeem 1 unit of store currency
4915
+ status: 'DRAFT' | 'ACTIVE' | 'PAUSED';
4916
+ } | null; // null when the store has no loyalty program
4917
+ tier: LoyaltyTierSummary | null; // null if untiered / no tiers configured
4918
+ nextTier: LoyaltyNextTierSummary | null; // null if at the top tier already
4919
+ progressToNextTier: number; // 0..1
4920
+ pointsToNextTier: number | null;
4921
+ }
4922
+
4923
+ interface LoyaltyReward {
4924
+ id: string;
4925
+ name: string;
4926
+ description: string | null;
4927
+ pointsCost: number;
4928
+ type: 'FIXED_DISCOUNT' | 'PERCENT_DISCOUNT';
4929
+ discountValue: number; // currency amount (FIXED_DISCOUNT) or 0-100 percent (PERCENT_DISCOUNT)
4930
+ minOrderAmount: number | null;
4931
+ maxUsesPerUser: number;
4932
+ isActive: boolean;
4933
+ minTierLevel: number | null; // minimum tier level required, or null for no restriction
4934
+ createdAt: string;
4935
+ updatedAt: string;
4936
+ }
4937
+
4938
+ interface RedeemRewardResult {
4939
+ couponCode: string; // one-time coupon to apply at checkout
4940
+ discountType: 'FIXED_DISCOUNT' | 'PERCENT_DISCOUNT';
4941
+ discountValue: number;
4942
+ pointsSpent: number;
4943
+ pointsBalance: number; // remaining balance after redemption
4944
+ }
4945
+
4946
+ // client.getLoyaltyStatus(): Promise<LoyaltyStatus>
4947
+ // client.enrollInLoyalty(): Promise<LoyaltyStatus>
4948
+ // client.getAvailableRewards(): Promise<LoyaltyReward[]>
4949
+ // client.redeemLoyaltyReward(rewardId: string): Promise<RedeemRewardResult>
4950
+ // client.reportSocialShare(platform?: string): Promise<{ awarded: boolean; points: number }>
4951
+ `;
4833
4952
  var TYPES_BY_DOMAIN = {
4834
4953
  products: PRODUCTS_TYPES,
4835
4954
  cart: CART_TYPES,
@@ -4842,7 +4961,8 @@ var TYPES_BY_DOMAIN = {
4842
4961
  reviews: REVIEWS_TYPES,
4843
4962
  "modifier-groups": MODIFIER_GROUPS_TYPES,
4844
4963
  content: CONTENT_TYPES,
4845
- regions: REGIONS_TYPES
4964
+ regions: REGIONS_TYPES,
4965
+ loyalty: LOYALTY_TYPES
4846
4966
  };
4847
4967
  function getTypesByDomain(domain) {
4848
4968
  if (domain === "all") {
@@ -4876,6 +4996,7 @@ var GET_TYPE_DEFINITIONS_SCHEMA = {
4876
4996
  "modifier-groups",
4877
4997
  "content",
4878
4998
  "regions",
4999
+ "loyalty",
4879
5000
  "all"
4880
5001
  ]).describe(
4881
5002
  'The domain of types to retrieve. Use "helpers" for helper function signatures and common types like StoreInfo, PaginatedResponse. Use "content" for FAQ / Footer / Header / Announcement / RichText / Page. Use "regions" for Region / TaxClass admin types.'
@@ -6322,6 +6443,9 @@ function formatCapabilities(caps) {
6322
6443
  lines.push(
6323
6444
  caps.features.hasCheckoutCustomFields ? "\u2705 Checkout custom fields configured (surcharges may apply)" : "\u274C No checkout custom fields"
6324
6445
  );
6446
+ lines.push(
6447
+ caps.features.hasLoyaltyProgram ? "\u2705 Loyalty program active (points & rewards \u2014 storefront-mode only)" : "\u274C No loyalty program"
6448
+ );
6325
6449
  lines.push("");
6326
6450
  lines.push("## Connection Settings");
6327
6451
  lines.push(
@@ -6367,6 +6491,11 @@ function formatCapabilities(caps) {
6367
6491
  if (caps.features.hasCoupons) {
6368
6492
  suggestions.push('Add a coupon input to the cart. Use get-code-example("coupon-input").');
6369
6493
  }
6494
+ if (caps.features.hasLoyaltyProgram) {
6495
+ suggestions.push(
6496
+ "A loyalty program is active. In a storefront-mode client (new BrainerceClient({ storeId })), show the logged-in customer their points balance, tier progress, and redeemable rewards, and let them redeem for a coupon at checkout. Use getLoyaltyStatus() (includes tier/nextTier/progressToNextTier), getAvailableRewards(), redeemLoyaltyReward(id), and optionally reportSocialShare(). Note: loyalty is NOT available in vibe-coded mode."
6497
+ );
6498
+ }
6370
6499
  if (caps.features.hasCheckoutCustomFields) {
6371
6500
  suggestions.push(
6372
6501
  `Checkout custom fields are configured. Build a step between shipping/pickup and payment using getCheckoutCustomFields() + setCheckoutCustomFields(). Render checkout.appliedSurcharges in the order summary so customers see what they're paying for. Use get-code-example("checkout-custom-fields").`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainerce/mcp-server",
3
- "version": "3.12.1",
3
+ "version": "3.12.3",
4
4
  "packageManager": "pnpm@9.0.0",
5
5
  "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.",
6
6
  "bin": {