@brainerce/mcp-server 2.7.0 → 2.9.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/index.js CHANGED
@@ -438,6 +438,234 @@ function PayPalPaymentForm({ clientId, orderId, checkoutId }: { clientId: string
438
438
  }
439
439
  \`\`\``;
440
440
  }
441
+ function getCheckoutCustomFieldsSection() {
442
+ return `## Checkout Custom Fields (Optional Step Between Shipping & Payment)
443
+
444
+ Some stores configure **custom fields** at checkout \u2014 e.g., gift wrapping, floor number for delivery, installation date, custom engraving. Each field can add a **surcharge** to the order total. The server filters by visibility (always / shipping-only / pickup-only / cart-products) and calculates surcharges automatically.
445
+
446
+ ### When to Build This
447
+
448
+ Check capabilities first:
449
+ \`\`\`typescript
450
+ const caps = await fetch(\`/api/vc/\${connectionId}/capabilities\`).then(r => r.json());
451
+ if (caps.features.hasCheckoutCustomFields) {
452
+ // Build the step described below
453
+ }
454
+ \`\`\`
455
+
456
+ If \`hasCheckoutCustomFields\` is true, you MUST build this step or customers will be charged surcharges they never saw.
457
+
458
+ ### Where to Place It
459
+
460
+ After \`selectShippingMethod()\` (or pickup confirmation), BEFORE \`createPaymentIntent()\`. Pseudocode:
461
+
462
+ \`\`\`
463
+ address \u2192 shipping/pickup \u2192 custom-fields (NEW) \u2192 payment
464
+ \`\`\`
465
+
466
+ If the loaded fields array is empty for this checkout (visibility filters can hide all fields), **skip the step entirely** \u2014 go straight to payment with no UI flicker.
467
+
468
+ ### Loading Field Definitions
469
+
470
+ \`\`\`typescript
471
+ import type { CheckoutCustomFieldDefinition } from 'brainerce';
472
+
473
+ const fields = await client.getCheckoutCustomFields(checkoutId);
474
+ if (fields.length === 0) {
475
+ setStep('payment'); // Nothing to collect
476
+ return;
477
+ }
478
+ \`\`\`
479
+
480
+ Each \`CheckoutCustomFieldDefinition\` has:
481
+ - \`key\` \u2014 the form field name to use in your state
482
+ - \`name\` \u2014 display label
483
+ - \`description\` \u2014 helper text (render below the input)
484
+ - \`type\` \u2014 \`'TEXT' | 'TEXTAREA' | 'NUMBER' | 'BOOLEAN' | 'SELECT' | 'DATE'\`
485
+ - \`required\` \u2014 boolean
486
+ - \`minLength\` / \`maxLength\` \u2014 for TEXT/TEXTAREA
487
+ - \`minValue\` / \`maxValue\` \u2014 for NUMBER
488
+ - \`options\` \u2014 \`Array<{value, label}>\` for SELECT
489
+ - \`pricing\` \u2014 never render this; the server uses it to compute surcharges
490
+
491
+ ### Rendering All 6 Field Types
492
+
493
+ \`\`\`typescript
494
+ function CustomFieldsStep({ fields, values, onChange, onApply, loading }: {
495
+ fields: CheckoutCustomFieldDefinition[];
496
+ values: Record<string, unknown>;
497
+ onChange: (key: string, value: unknown) => void;
498
+ onApply: () => void;
499
+ loading?: boolean;
500
+ }) {
501
+ return (
502
+ <div className="space-y-4">
503
+ {fields.map((field) => {
504
+ const value = values[field.key] ?? '';
505
+ const label = (
506
+ <label className="block text-sm font-medium">
507
+ {field.name}{field.required && <span className="text-red-500"> *</span>}
508
+ </label>
509
+ );
510
+ const help = field.description && (
511
+ <p className="text-xs text-gray-500 mt-1">{field.description}</p>
512
+ );
513
+
514
+ switch (field.type) {
515
+ case 'TEXT':
516
+ return (
517
+ <div key={field.key}>
518
+ {label}
519
+ <input
520
+ type="text"
521
+ value={value as string}
522
+ onChange={(e) => onChange(field.key, e.target.value)}
523
+ required={field.required}
524
+ minLength={field.minLength ?? undefined}
525
+ maxLength={field.maxLength ?? undefined}
526
+ className="w-full border rounded px-3 py-2"
527
+ />
528
+ {help}
529
+ </div>
530
+ );
531
+ case 'TEXTAREA':
532
+ return (
533
+ <div key={field.key}>
534
+ {label}
535
+ <textarea
536
+ value={value as string}
537
+ onChange={(e) => onChange(field.key, e.target.value)}
538
+ required={field.required}
539
+ minLength={field.minLength ?? undefined}
540
+ maxLength={field.maxLength ?? undefined}
541
+ rows={3}
542
+ className="w-full border rounded px-3 py-2"
543
+ />
544
+ {help}
545
+ </div>
546
+ );
547
+ case 'NUMBER':
548
+ return (
549
+ <div key={field.key}>
550
+ {label}
551
+ <input
552
+ type="number"
553
+ value={value as number}
554
+ onChange={(e) => onChange(field.key, e.target.valueAsNumber)}
555
+ required={field.required}
556
+ min={field.minValue ?? undefined}
557
+ max={field.maxValue ?? undefined}
558
+ className="w-full border rounded px-3 py-2"
559
+ />
560
+ {help}
561
+ </div>
562
+ );
563
+ case 'BOOLEAN':
564
+ return (
565
+ <div key={field.key} className="flex items-start gap-2">
566
+ <input
567
+ type="checkbox"
568
+ checked={value === true}
569
+ onChange={(e) => onChange(field.key, e.target.checked)}
570
+ className="mt-1"
571
+ />
572
+ <div>{label}{help}</div>
573
+ </div>
574
+ );
575
+ case 'SELECT':
576
+ return (
577
+ <div key={field.key}>
578
+ {label}
579
+ <select
580
+ value={value as string}
581
+ onChange={(e) => onChange(field.key, e.target.value)}
582
+ required={field.required}
583
+ className="w-full border rounded px-3 py-2"
584
+ >
585
+ <option value="">\u2014 Select \u2014</option>
586
+ {field.options?.map((opt) => (
587
+ <option key={opt.value} value={opt.value}>{opt.label}</option>
588
+ ))}
589
+ </select>
590
+ {help}
591
+ </div>
592
+ );
593
+ case 'DATE':
594
+ return (
595
+ <div key={field.key}>
596
+ {label}
597
+ <input
598
+ type="date"
599
+ value={value as string}
600
+ onChange={(e) => onChange(field.key, e.target.value)}
601
+ required={field.required}
602
+ className="w-full border rounded px-3 py-2"
603
+ />
604
+ {help}
605
+ </div>
606
+ );
607
+ default:
608
+ return null;
609
+ }
610
+ })}
611
+
612
+ <button
613
+ type="button"
614
+ onClick={onApply}
615
+ disabled={loading}
616
+ className="w-full bg-black text-white py-3 rounded"
617
+ >
618
+ {loading ? 'Updating\u2026' : 'Continue to Payment'}
619
+ </button>
620
+ </div>
621
+ );
622
+ }
623
+ \`\`\`
624
+
625
+ ### Submitting Values & Recalculating Surcharges
626
+
627
+ \`\`\`typescript
628
+ async function applyCustomFields() {
629
+ setLoading(true);
630
+ try {
631
+ // Server validates required + min/max + coerces types + recalculates totals
632
+ const updated = await client.setCheckoutCustomFields(checkoutId, customFieldValues);
633
+ setCheckout(updated); // updated.surchargeAmount and updated.appliedSurcharges are now fresh
634
+ setStep('payment');
635
+ } catch (err) {
636
+ setError(err instanceof Error ? err.message : 'Failed to save selections');
637
+ } finally {
638
+ setLoading(false);
639
+ }
640
+ }
641
+ \`\`\`
642
+
643
+ ### Order Summary \u2014 Show the Surcharges
644
+
645
+ **Critical:** if you don't render \`appliedSurcharges\`, customers will see a higher total with no explanation. Add this to your order summary above the total row:
646
+
647
+ \`\`\`typescript
648
+ {checkout.appliedSurcharges?.length > 0 && (
649
+ <>
650
+ {checkout.appliedSurcharges.map((s) => (
651
+ <div key={s.key} className="flex justify-between text-sm text-gray-600">
652
+ <span>{s.name}</span>
653
+ <span>{formatPrice(s.amount, { currency })}</span>
654
+ </div>
655
+ ))}
656
+ </>
657
+ )}
658
+ \`\`\`
659
+
660
+ \`checkout.surchargeAmount\` is a string (use \`parseFloat()\`); \`checkout.totalAmount\` already includes it.
661
+
662
+ ### Notes
663
+
664
+ - The server filters fields by \`visibility\` (\`always\` / \`shipping\` / \`pickup\` / \`products\`) \u2014 your client just renders what \`getCheckoutCustomFields()\` returns.
665
+ - The server validates \`required\`, \`minLength\`/\`maxLength\`, \`minValue\`/\`maxValue\`, and SELECT options. Don't rely on client validation alone \u2014 surface server errors with a toast.
666
+ - If \`getCheckoutCustomFields()\` returns 0 fields, skip the step entirely. Don't render an empty UI.
667
+ `;
668
+ }
441
669
  function getPaymentProvidersSection() {
442
670
  return `## Payment Provider Detection (DO THIS FIRST on checkout page!)
443
671
 
@@ -1287,6 +1515,10 @@ ${getCheckoutFlowSection(currency)}
1287
1515
 
1288
1516
  ---
1289
1517
 
1518
+ ${getCheckoutCustomFieldsSection()}
1519
+
1520
+ ---
1521
+
1290
1522
  ${getPaymentProvidersSection()}
1291
1523
 
1292
1524
  ---
@@ -1345,6 +1577,8 @@ function getSectionByTopic(topic, connectionId, currency) {
1345
1577
  return getCartSection(cur);
1346
1578
  case "checkout":
1347
1579
  return getCheckoutFlowSection(cur);
1580
+ case "checkout-custom-fields":
1581
+ return getCheckoutCustomFieldsSection();
1348
1582
  case "payment":
1349
1583
  return getPaymentProvidersSection();
1350
1584
  case "auth":
@@ -1373,7 +1607,7 @@ function getSectionByTopic(topic, connectionId, currency) {
1373
1607
  storeType: "E-commerce store"
1374
1608
  });
1375
1609
  default:
1376
- return `Unknown topic: "${topic}". Available topics: setup, products, cart, checkout, payment, auth, order-confirmation, inventory, discounts, recommendations, tax, critical-rules, type-reference, admin, all`;
1610
+ return `Unknown topic: "${topic}". Available topics: setup, products, cart, checkout, checkout-custom-fields, payment, auth, order-confirmation, inventory, discounts, recommendations, tax, critical-rules, type-reference, admin, all`;
1377
1611
  }
1378
1612
  }
1379
1613
 
@@ -1386,6 +1620,7 @@ var GET_SDK_DOCS_SCHEMA = {
1386
1620
  "products",
1387
1621
  "cart",
1388
1622
  "checkout",
1623
+ "checkout-custom-fields",
1389
1624
  "payment",
1390
1625
  "auth",
1391
1626
  "order-confirmation",
@@ -3407,6 +3642,7 @@ import type {
3407
3642
  ShippingDestinations,
3408
3643
  PickupLocation,
3409
3644
  CheckoutBumpsResponse,
3645
+ CheckoutCustomFieldDefinition,
3410
3646
  } from 'brainerce';
3411
3647
  import { formatPrice } from 'brainerce';
3412
3648
  import { getClient } from '@/lib/brainerce';
@@ -3416,6 +3652,7 @@ import { ShippingStep } from '@/components/checkout/shipping-step';
3416
3652
  import { PaymentStep } from '@/components/checkout/payment-step';
3417
3653
  import { DeliveryMethodStep } from '@/components/checkout/delivery-method-step';
3418
3654
  import { PickupStep } from '@/components/checkout/pickup-step';
3655
+ import { CustomFieldsStep } from '@/components/checkout/custom-fields-step';
3419
3656
  import { TaxDisplay } from '@/components/checkout/tax-display';
3420
3657
  import { OrderBumpCard } from '@/components/checkout/order-bump-card';
3421
3658
  import { CouponInput } from '@/components/cart/coupon-input';
@@ -3424,7 +3661,7 @@ import { LoadingSpinner } from '@/components/shared/loading-spinner';
3424
3661
  import { useTranslations } from '@/lib/translations';
3425
3662
  import { cn } from '@/lib/utils';
3426
3663
 
3427
- type CheckoutStep = 'method' | 'address' | 'shipping' | 'pickup' | 'payment';
3664
+ type CheckoutStep = 'method' | 'address' | 'shipping' | 'pickup' | 'custom-fields' | 'payment';
3428
3665
 
3429
3666
  function CheckoutContent() {
3430
3667
  const searchParams = useSearchParams();
@@ -3457,6 +3694,9 @@ function CheckoutContent() {
3457
3694
  const [orderBumps, setOrderBumps] = useState<CheckoutBumpsResponse | null>(null);
3458
3695
  const [addedBumpIds, setAddedBumpIds] = useState<Set<string>>(new Set());
3459
3696
  const [bumpLoading, setBumpLoading] = useState<string | null>(null);
3697
+ const [customFields, setCustomFields] = useState<CheckoutCustomFieldDefinition[]>([]);
3698
+ const [customFieldValues, setCustomFieldValues] = useState<Record<string, unknown>>({});
3699
+ const [customFieldsLoading, setCustomFieldsLoading] = useState(false);
3460
3700
 
3461
3701
  // Check for returning from canceled payment
3462
3702
  const canceled = searchParams.get('canceled') === 'true';
@@ -3508,6 +3748,23 @@ function CheckoutContent() {
3508
3748
  const existing = await client.getCheckout(existingCheckoutId);
3509
3749
  setCheckout(existing);
3510
3750
 
3751
+ // Preload custom field definitions and any existing values so the
3752
+ // step indicator and "change options" affordance work on resume.
3753
+ client
3754
+ .getCheckoutCustomFields(existing.id)
3755
+ .then((fields) => {
3756
+ setCustomFields(fields);
3757
+ const existingValues = (
3758
+ existing as unknown as {
3759
+ customFieldValues?: Record<string, unknown> | null;
3760
+ }
3761
+ ).customFieldValues;
3762
+ if (existingValues) setCustomFieldValues(existingValues);
3763
+ })
3764
+ .catch(() => {
3765
+ setCustomFields([]);
3766
+ });
3767
+
3511
3768
  // Determine step based on checkout state
3512
3769
  const allDigital = existing.lineItems.every(
3513
3770
  (i) => (i.product as unknown as { isDownloadable?: boolean }).isDownloadable
@@ -3678,6 +3935,21 @@ function CheckoutContent() {
3678
3935
  }
3679
3936
  }
3680
3937
 
3938
+ // After shipping/pickup is set, decide whether to show the custom-fields step
3939
+ // or jump straight to payment. Returns the next step.
3940
+ async function loadCustomFieldsOrSkip(checkoutId: string): Promise<CheckoutStep> {
3941
+ try {
3942
+ const fields = await getClient().getCheckoutCustomFields(checkoutId);
3943
+ setCustomFields(fields);
3944
+ return fields.length > 0 ? 'custom-fields' : 'payment';
3945
+ } catch {
3946
+ // If the endpoint isn't available or fails, fall through to payment
3947
+ // rather than blocking the customer.
3948
+ setCustomFields([]);
3949
+ return 'payment';
3950
+ }
3951
+ }
3952
+
3681
3953
  // Handle shipping method selection
3682
3954
  async function handleShippingSelect(rateId: string) {
3683
3955
  if (!checkout) return;
@@ -3690,7 +3962,7 @@ function CheckoutContent() {
3690
3962
 
3691
3963
  const updated = await client.selectShippingMethod(checkout.id, rateId);
3692
3964
  setCheckout(updated);
3693
- setStep('payment');
3965
+ setStep(await loadCustomFieldsOrSkip(updated.id));
3694
3966
  } catch (err) {
3695
3967
  const message = err instanceof Error ? err.message : t('failedToSelectShipping');
3696
3968
  setError(message);
@@ -3699,6 +3971,23 @@ function CheckoutContent() {
3699
3971
  }
3700
3972
  }
3701
3973
 
3974
+ // Submit custom fields
3975
+ async function handleCustomFieldsApply() {
3976
+ if (!checkout) return;
3977
+ try {
3978
+ setCustomFieldsLoading(true);
3979
+ setError(null);
3980
+ const updated = await getClient().setCheckoutCustomFields(checkout.id, customFieldValues);
3981
+ setCheckout(updated);
3982
+ setStep('payment');
3983
+ } catch (err) {
3984
+ const message = err instanceof Error ? err.message : t('customFieldsFailed');
3985
+ setError(message);
3986
+ } finally {
3987
+ setCustomFieldsLoading(false);
3988
+ }
3989
+ }
3990
+
3702
3991
  // Handle delivery method selection
3703
3992
  async function handleDeliveryTypeSelect(method: 'shipping' | 'pickup') {
3704
3993
  if (!checkout) return;
@@ -3744,7 +4033,7 @@ function CheckoutContent() {
3744
4033
  phone: customerInfo.phone,
3745
4034
  });
3746
4035
  setCheckout(updated);
3747
- setStep('payment');
4036
+ setStep(await loadCustomFieldsOrSkip(updated.id));
3748
4037
  } catch (err) {
3749
4038
  const message = err instanceof Error ? err.message : t('failedToSelectPickup');
3750
4039
  setError(message);
@@ -3806,9 +4095,15 @@ function CheckoutContent() {
3806
4095
  );
3807
4096
  }
3808
4097
 
4098
+ const customFieldsStep =
4099
+ customFields.length > 0
4100
+ ? [{ key: 'custom-fields' as CheckoutStep, label: t('stepCustomFields') }]
4101
+ : [];
4102
+
3809
4103
  const steps: { key: CheckoutStep; label: string }[] = isAllDigital
3810
4104
  ? [
3811
4105
  { key: 'address', label: t('stepContactInfo') },
4106
+ ...customFieldsStep,
3812
4107
  { key: 'payment', label: t('stepPayment') },
3813
4108
  ]
3814
4109
  : pickupLocations.length > 0
@@ -3816,17 +4111,20 @@ function CheckoutContent() {
3816
4111
  ? [
3817
4112
  { key: 'method', label: t('stepMethod') },
3818
4113
  { key: 'pickup', label: t('stepPickup') },
4114
+ ...customFieldsStep,
3819
4115
  { key: 'payment', label: t('stepPayment') },
3820
4116
  ]
3821
4117
  : [
3822
4118
  { key: 'method', label: t('stepMethod') },
3823
4119
  { key: 'address', label: t('stepAddress') },
3824
4120
  { key: 'shipping', label: t('stepShipping') },
4121
+ ...customFieldsStep,
3825
4122
  { key: 'payment', label: t('stepPayment') },
3826
4123
  ]
3827
4124
  : [
3828
4125
  { key: 'address', label: t('stepAddress') },
3829
4126
  { key: 'shipping', label: t('stepShipping') },
4127
+ ...customFieldsStep,
3830
4128
  { key: 'payment', label: t('stepPayment') },
3831
4129
  ];
3832
4130
 
@@ -4025,19 +4323,54 @@ function CheckoutContent() {
4025
4323
  </div>
4026
4324
  )}
4027
4325
 
4326
+ {/* Custom Fields (optional, between shipping/pickup and payment) */}
4327
+ {step === 'custom-fields' && checkout && (
4328
+ <div>
4329
+ <div className="mb-4 flex items-center justify-between">
4330
+ <h2 className="text-foreground text-lg font-semibold">{t('customFieldsTitle')}</h2>
4331
+ <button
4332
+ type="button"
4333
+ onClick={() => setStep(deliveryType === 'pickup' ? 'pickup' : 'shipping')}
4334
+ className="text-primary text-sm hover:underline"
4335
+ >
4336
+ {deliveryType === 'pickup' ? t('changePickup') : t('changeShipping')}
4337
+ </button>
4338
+ </div>
4339
+ <CustomFieldsStep
4340
+ fields={customFields}
4341
+ values={customFieldValues}
4342
+ onChange={(key, value) =>
4343
+ setCustomFieldValues((prev) => ({ ...prev, [key]: value }))
4344
+ }
4345
+ onApply={handleCustomFieldsApply}
4346
+ loading={customFieldsLoading}
4347
+ />
4348
+ </div>
4349
+ )}
4350
+
4028
4351
  {/* Payment */}
4029
4352
  {step === 'payment' && checkout && (
4030
4353
  <div>
4031
4354
  <div className="mb-4 flex items-center justify-between">
4032
4355
  <h2 className="text-foreground text-lg font-semibold">{t('payment')}</h2>
4033
- {!isAllDigital && (
4356
+ {customFields.length > 0 ? (
4034
4357
  <button
4035
4358
  type="button"
4036
- onClick={() => setStep(deliveryType === 'pickup' ? 'pickup' : 'shipping')}
4359
+ onClick={() => setStep('custom-fields')}
4037
4360
  className="text-primary text-sm hover:underline"
4038
4361
  >
4039
- {deliveryType === 'pickup' ? t('changePickup') : t('changeShipping')}
4362
+ {t('changeOptions')}
4040
4363
  </button>
4364
+ ) : (
4365
+ !isAllDigital && (
4366
+ <button
4367
+ type="button"
4368
+ onClick={() => setStep(deliveryType === 'pickup' ? 'pickup' : 'shipping')}
4369
+ className="text-primary text-sm hover:underline"
4370
+ >
4371
+ {deliveryType === 'pickup' ? t('changePickup') : t('changeShipping')}
4372
+ </button>
4373
+ )
4041
4374
  )}
4042
4375
  </div>
4043
4376
 
@@ -4224,6 +4557,20 @@ function CheckoutContent() {
4224
4557
  taxBreakdown={checkout.taxBreakdown}
4225
4558
  />
4226
4559
 
4560
+ {/* Custom field surcharges (one line per applied surcharge) */}
4561
+ {checkout.appliedSurcharges && checkout.appliedSurcharges.length > 0 && (
4562
+ <>
4563
+ {checkout.appliedSurcharges.map((s) => (
4564
+ <div key={s.key} className="flex items-center justify-between">
4565
+ <span className="text-muted-foreground">{s.name}</span>
4566
+ <span className="text-foreground">
4567
+ {formatPrice(Number(s.amount), { currency }) as string}
4568
+ </span>
4569
+ </div>
4570
+ ))}
4571
+ </>
4572
+ )}
4573
+
4227
4574
  <div className="border-border mt-2 border-t pt-2">
4228
4575
  <div className="flex items-center justify-between">
4229
4576
  <span className="text-foreground font-semibold">{tc('total')}</span>
@@ -11279,8 +11626,11 @@ async function fetchStoreInfo(connectionId, baseUrl = KNOWN_API_URLS.production)
11279
11626
  } catch {
11280
11627
  throw new Error(`Invalid response from ${baseUrl}`);
11281
11628
  }
11629
+ const storeName = json.storeName || json.name || "My Store";
11630
+ const displayName = json.channelName || storeName;
11282
11631
  return {
11283
- name: json.name || json.storeName || "My Store",
11632
+ name: displayName,
11633
+ storeName,
11284
11634
  currency: json.currency || "USD",
11285
11635
  language: json.language || "en",
11286
11636
  ...json.i18n ? { i18n: json.i18n } : {}
@@ -11321,7 +11671,7 @@ function getCandidateApiUrls() {
11321
11671
 
11322
11672
  // src/tools/get-store-info.ts
11323
11673
  var GET_STORE_INFO_NAME = "get-store-info";
11324
- var GET_STORE_INFO_DESCRIPTION = "Fetch live store information from the Brainerce API using a connection ID. Returns the store name, currency, and language. Use this to personalize the store being built.";
11674
+ var GET_STORE_INFO_DESCRIPTION = "Fetch live store information from the Brainerce API using a connection ID. Returns the channel display name (what the user sees in their dashboard), parent store name, currency, and language. Use this to personalize the store being built \u2014 prefer the channel name for user-facing text since a single Brainerce store can have multiple sales channels.";
11325
11675
  var GET_STORE_INFO_SCHEMA = {
11326
11676
  connectionId: import_zod5.z.string().describe("Vibe-coded connection ID (starts with vc_)")
11327
11677
  };
@@ -11334,7 +11684,10 @@ async function handleGetStoreInfo(args) {
11334
11684
  type: "text",
11335
11685
  text: JSON.stringify(
11336
11686
  {
11687
+ // `name` is the channel display name (from the dashboard sales-channel card);
11688
+ // falls back to storeName when a channel doesn't have its own name.
11337
11689
  name: resolved.info.name,
11690
+ storeName: resolved.info.storeName,
11338
11691
  currency: resolved.info.currency,
11339
11692
  language: resolved.info.language,
11340
11693
  connectionId: args.connectionId,
@@ -11428,7 +11781,8 @@ var GET_STORE_CAPABILITIES_SCHEMA = {
11428
11781
  };
11429
11782
  function formatCapabilities(caps) {
11430
11783
  const lines = [];
11431
- lines.push(`## Store: ${caps.store.name} (${caps.store.currency})`);
11784
+ const channelSuffix = caps.store.channelName && caps.store.channelName !== caps.store.name ? ` \u2014 Channel: "${caps.store.channelName}"` : "";
11785
+ lines.push(`## Store: ${caps.store.name} (${caps.store.currency})${channelSuffix}`);
11432
11786
  lines.push(`Language: ${caps.store.language}`);
11433
11787
  if (caps.store.i18n?.enabled) {
11434
11788
  lines.push(
@@ -11461,6 +11815,9 @@ function formatCapabilities(caps) {
11461
11815
  caps.features.hasDownloadableProducts ? "\u2705 Downloadable products available" : "\u274C No downloadable products"
11462
11816
  );
11463
11817
  lines.push(caps.features.hasCoupons ? "\u2705 Coupons available" : "\u274C No coupons");
11818
+ lines.push(
11819
+ caps.features.hasCheckoutCustomFields ? "\u2705 Checkout custom fields configured (surcharges may apply)" : "\u274C No checkout custom fields"
11820
+ );
11464
11821
  lines.push("");
11465
11822
  lines.push("## Connection Settings");
11466
11823
  lines.push(
@@ -11506,6 +11863,11 @@ function formatCapabilities(caps) {
11506
11863
  if (caps.features.hasCoupons) {
11507
11864
  suggestions.push('Add a coupon input to the cart. Use get-code-example("coupon-input").');
11508
11865
  }
11866
+ if (caps.features.hasCheckoutCustomFields) {
11867
+ suggestions.push(
11868
+ `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").`
11869
+ );
11870
+ }
11509
11871
  if (caps.connection.requireEmailVerification) {
11510
11872
  suggestions.push(
11511
11873
  'Email verification is required \u2014 ALWAYS build the verify-email page. Use get-code-example("verify-email").'