@brainerce/mcp-server 2.7.0 → 2.8.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
@@ -411,6 +411,234 @@ function PayPalPaymentForm({ clientId, orderId, checkoutId }: { clientId: string
411
411
  }
412
412
  \`\`\``;
413
413
  }
414
+ function getCheckoutCustomFieldsSection() {
415
+ return `## Checkout Custom Fields (Optional Step Between Shipping & Payment)
416
+
417
+ 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.
418
+
419
+ ### When to Build This
420
+
421
+ Check capabilities first:
422
+ \`\`\`typescript
423
+ const caps = await fetch(\`/api/vc/\${connectionId}/capabilities\`).then(r => r.json());
424
+ if (caps.features.hasCheckoutCustomFields) {
425
+ // Build the step described below
426
+ }
427
+ \`\`\`
428
+
429
+ If \`hasCheckoutCustomFields\` is true, you MUST build this step or customers will be charged surcharges they never saw.
430
+
431
+ ### Where to Place It
432
+
433
+ After \`selectShippingMethod()\` (or pickup confirmation), BEFORE \`createPaymentIntent()\`. Pseudocode:
434
+
435
+ \`\`\`
436
+ address \u2192 shipping/pickup \u2192 custom-fields (NEW) \u2192 payment
437
+ \`\`\`
438
+
439
+ 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.
440
+
441
+ ### Loading Field Definitions
442
+
443
+ \`\`\`typescript
444
+ import type { CheckoutCustomFieldDefinition } from 'brainerce';
445
+
446
+ const fields = await client.getCheckoutCustomFields(checkoutId);
447
+ if (fields.length === 0) {
448
+ setStep('payment'); // Nothing to collect
449
+ return;
450
+ }
451
+ \`\`\`
452
+
453
+ Each \`CheckoutCustomFieldDefinition\` has:
454
+ - \`key\` \u2014 the form field name to use in your state
455
+ - \`name\` \u2014 display label
456
+ - \`description\` \u2014 helper text (render below the input)
457
+ - \`type\` \u2014 \`'TEXT' | 'TEXTAREA' | 'NUMBER' | 'BOOLEAN' | 'SELECT' | 'DATE'\`
458
+ - \`required\` \u2014 boolean
459
+ - \`minLength\` / \`maxLength\` \u2014 for TEXT/TEXTAREA
460
+ - \`minValue\` / \`maxValue\` \u2014 for NUMBER
461
+ - \`options\` \u2014 \`Array<{value, label}>\` for SELECT
462
+ - \`pricing\` \u2014 never render this; the server uses it to compute surcharges
463
+
464
+ ### Rendering All 6 Field Types
465
+
466
+ \`\`\`typescript
467
+ function CustomFieldsStep({ fields, values, onChange, onApply, loading }: {
468
+ fields: CheckoutCustomFieldDefinition[];
469
+ values: Record<string, unknown>;
470
+ onChange: (key: string, value: unknown) => void;
471
+ onApply: () => void;
472
+ loading?: boolean;
473
+ }) {
474
+ return (
475
+ <div className="space-y-4">
476
+ {fields.map((field) => {
477
+ const value = values[field.key] ?? '';
478
+ const label = (
479
+ <label className="block text-sm font-medium">
480
+ {field.name}{field.required && <span className="text-red-500"> *</span>}
481
+ </label>
482
+ );
483
+ const help = field.description && (
484
+ <p className="text-xs text-gray-500 mt-1">{field.description}</p>
485
+ );
486
+
487
+ switch (field.type) {
488
+ case 'TEXT':
489
+ return (
490
+ <div key={field.key}>
491
+ {label}
492
+ <input
493
+ type="text"
494
+ value={value as string}
495
+ onChange={(e) => onChange(field.key, e.target.value)}
496
+ required={field.required}
497
+ minLength={field.minLength ?? undefined}
498
+ maxLength={field.maxLength ?? undefined}
499
+ className="w-full border rounded px-3 py-2"
500
+ />
501
+ {help}
502
+ </div>
503
+ );
504
+ case 'TEXTAREA':
505
+ return (
506
+ <div key={field.key}>
507
+ {label}
508
+ <textarea
509
+ value={value as string}
510
+ onChange={(e) => onChange(field.key, e.target.value)}
511
+ required={field.required}
512
+ minLength={field.minLength ?? undefined}
513
+ maxLength={field.maxLength ?? undefined}
514
+ rows={3}
515
+ className="w-full border rounded px-3 py-2"
516
+ />
517
+ {help}
518
+ </div>
519
+ );
520
+ case 'NUMBER':
521
+ return (
522
+ <div key={field.key}>
523
+ {label}
524
+ <input
525
+ type="number"
526
+ value={value as number}
527
+ onChange={(e) => onChange(field.key, e.target.valueAsNumber)}
528
+ required={field.required}
529
+ min={field.minValue ?? undefined}
530
+ max={field.maxValue ?? undefined}
531
+ className="w-full border rounded px-3 py-2"
532
+ />
533
+ {help}
534
+ </div>
535
+ );
536
+ case 'BOOLEAN':
537
+ return (
538
+ <div key={field.key} className="flex items-start gap-2">
539
+ <input
540
+ type="checkbox"
541
+ checked={value === true}
542
+ onChange={(e) => onChange(field.key, e.target.checked)}
543
+ className="mt-1"
544
+ />
545
+ <div>{label}{help}</div>
546
+ </div>
547
+ );
548
+ case 'SELECT':
549
+ return (
550
+ <div key={field.key}>
551
+ {label}
552
+ <select
553
+ value={value as string}
554
+ onChange={(e) => onChange(field.key, e.target.value)}
555
+ required={field.required}
556
+ className="w-full border rounded px-3 py-2"
557
+ >
558
+ <option value="">\u2014 Select \u2014</option>
559
+ {field.options?.map((opt) => (
560
+ <option key={opt.value} value={opt.value}>{opt.label}</option>
561
+ ))}
562
+ </select>
563
+ {help}
564
+ </div>
565
+ );
566
+ case 'DATE':
567
+ return (
568
+ <div key={field.key}>
569
+ {label}
570
+ <input
571
+ type="date"
572
+ value={value as string}
573
+ onChange={(e) => onChange(field.key, e.target.value)}
574
+ required={field.required}
575
+ className="w-full border rounded px-3 py-2"
576
+ />
577
+ {help}
578
+ </div>
579
+ );
580
+ default:
581
+ return null;
582
+ }
583
+ })}
584
+
585
+ <button
586
+ type="button"
587
+ onClick={onApply}
588
+ disabled={loading}
589
+ className="w-full bg-black text-white py-3 rounded"
590
+ >
591
+ {loading ? 'Updating\u2026' : 'Continue to Payment'}
592
+ </button>
593
+ </div>
594
+ );
595
+ }
596
+ \`\`\`
597
+
598
+ ### Submitting Values & Recalculating Surcharges
599
+
600
+ \`\`\`typescript
601
+ async function applyCustomFields() {
602
+ setLoading(true);
603
+ try {
604
+ // Server validates required + min/max + coerces types + recalculates totals
605
+ const updated = await client.setCheckoutCustomFields(checkoutId, customFieldValues);
606
+ setCheckout(updated); // updated.surchargeAmount and updated.appliedSurcharges are now fresh
607
+ setStep('payment');
608
+ } catch (err) {
609
+ setError(err instanceof Error ? err.message : 'Failed to save selections');
610
+ } finally {
611
+ setLoading(false);
612
+ }
613
+ }
614
+ \`\`\`
615
+
616
+ ### Order Summary \u2014 Show the Surcharges
617
+
618
+ **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:
619
+
620
+ \`\`\`typescript
621
+ {checkout.appliedSurcharges?.length > 0 && (
622
+ <>
623
+ {checkout.appliedSurcharges.map((s) => (
624
+ <div key={s.key} className="flex justify-between text-sm text-gray-600">
625
+ <span>{s.name}</span>
626
+ <span>{formatPrice(s.amount, { currency })}</span>
627
+ </div>
628
+ ))}
629
+ </>
630
+ )}
631
+ \`\`\`
632
+
633
+ \`checkout.surchargeAmount\` is a string (use \`parseFloat()\`); \`checkout.totalAmount\` already includes it.
634
+
635
+ ### Notes
636
+
637
+ - The server filters fields by \`visibility\` (\`always\` / \`shipping\` / \`pickup\` / \`products\`) \u2014 your client just renders what \`getCheckoutCustomFields()\` returns.
638
+ - 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.
639
+ - If \`getCheckoutCustomFields()\` returns 0 fields, skip the step entirely. Don't render an empty UI.
640
+ `;
641
+ }
414
642
  function getPaymentProvidersSection() {
415
643
  return `## Payment Provider Detection (DO THIS FIRST on checkout page!)
416
644
 
@@ -1260,6 +1488,10 @@ ${getCheckoutFlowSection(currency)}
1260
1488
 
1261
1489
  ---
1262
1490
 
1491
+ ${getCheckoutCustomFieldsSection()}
1492
+
1493
+ ---
1494
+
1263
1495
  ${getPaymentProvidersSection()}
1264
1496
 
1265
1497
  ---
@@ -1318,6 +1550,8 @@ function getSectionByTopic(topic, connectionId, currency) {
1318
1550
  return getCartSection(cur);
1319
1551
  case "checkout":
1320
1552
  return getCheckoutFlowSection(cur);
1553
+ case "checkout-custom-fields":
1554
+ return getCheckoutCustomFieldsSection();
1321
1555
  case "payment":
1322
1556
  return getPaymentProvidersSection();
1323
1557
  case "auth":
@@ -1346,7 +1580,7 @@ function getSectionByTopic(topic, connectionId, currency) {
1346
1580
  storeType: "E-commerce store"
1347
1581
  });
1348
1582
  default:
1349
- return `Unknown topic: "${topic}". Available topics: setup, products, cart, checkout, payment, auth, order-confirmation, inventory, discounts, recommendations, tax, critical-rules, type-reference, admin, all`;
1583
+ 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`;
1350
1584
  }
1351
1585
  }
1352
1586
 
@@ -1359,6 +1593,7 @@ var GET_SDK_DOCS_SCHEMA = {
1359
1593
  "products",
1360
1594
  "cart",
1361
1595
  "checkout",
1596
+ "checkout-custom-fields",
1362
1597
  "payment",
1363
1598
  "auth",
1364
1599
  "order-confirmation",
@@ -3380,6 +3615,7 @@ import type {
3380
3615
  ShippingDestinations,
3381
3616
  PickupLocation,
3382
3617
  CheckoutBumpsResponse,
3618
+ CheckoutCustomFieldDefinition,
3383
3619
  } from 'brainerce';
3384
3620
  import { formatPrice } from 'brainerce';
3385
3621
  import { getClient } from '@/lib/brainerce';
@@ -3389,6 +3625,7 @@ import { ShippingStep } from '@/components/checkout/shipping-step';
3389
3625
  import { PaymentStep } from '@/components/checkout/payment-step';
3390
3626
  import { DeliveryMethodStep } from '@/components/checkout/delivery-method-step';
3391
3627
  import { PickupStep } from '@/components/checkout/pickup-step';
3628
+ import { CustomFieldsStep } from '@/components/checkout/custom-fields-step';
3392
3629
  import { TaxDisplay } from '@/components/checkout/tax-display';
3393
3630
  import { OrderBumpCard } from '@/components/checkout/order-bump-card';
3394
3631
  import { CouponInput } from '@/components/cart/coupon-input';
@@ -3397,7 +3634,7 @@ import { LoadingSpinner } from '@/components/shared/loading-spinner';
3397
3634
  import { useTranslations } from '@/lib/translations';
3398
3635
  import { cn } from '@/lib/utils';
3399
3636
 
3400
- type CheckoutStep = 'method' | 'address' | 'shipping' | 'pickup' | 'payment';
3637
+ type CheckoutStep = 'method' | 'address' | 'shipping' | 'pickup' | 'custom-fields' | 'payment';
3401
3638
 
3402
3639
  function CheckoutContent() {
3403
3640
  const searchParams = useSearchParams();
@@ -3430,6 +3667,9 @@ function CheckoutContent() {
3430
3667
  const [orderBumps, setOrderBumps] = useState<CheckoutBumpsResponse | null>(null);
3431
3668
  const [addedBumpIds, setAddedBumpIds] = useState<Set<string>>(new Set());
3432
3669
  const [bumpLoading, setBumpLoading] = useState<string | null>(null);
3670
+ const [customFields, setCustomFields] = useState<CheckoutCustomFieldDefinition[]>([]);
3671
+ const [customFieldValues, setCustomFieldValues] = useState<Record<string, unknown>>({});
3672
+ const [customFieldsLoading, setCustomFieldsLoading] = useState(false);
3433
3673
 
3434
3674
  // Check for returning from canceled payment
3435
3675
  const canceled = searchParams.get('canceled') === 'true';
@@ -3481,6 +3721,23 @@ function CheckoutContent() {
3481
3721
  const existing = await client.getCheckout(existingCheckoutId);
3482
3722
  setCheckout(existing);
3483
3723
 
3724
+ // Preload custom field definitions and any existing values so the
3725
+ // step indicator and "change options" affordance work on resume.
3726
+ client
3727
+ .getCheckoutCustomFields(existing.id)
3728
+ .then((fields) => {
3729
+ setCustomFields(fields);
3730
+ const existingValues = (
3731
+ existing as unknown as {
3732
+ customFieldValues?: Record<string, unknown> | null;
3733
+ }
3734
+ ).customFieldValues;
3735
+ if (existingValues) setCustomFieldValues(existingValues);
3736
+ })
3737
+ .catch(() => {
3738
+ setCustomFields([]);
3739
+ });
3740
+
3484
3741
  // Determine step based on checkout state
3485
3742
  const allDigital = existing.lineItems.every(
3486
3743
  (i) => (i.product as unknown as { isDownloadable?: boolean }).isDownloadable
@@ -3651,6 +3908,21 @@ function CheckoutContent() {
3651
3908
  }
3652
3909
  }
3653
3910
 
3911
+ // After shipping/pickup is set, decide whether to show the custom-fields step
3912
+ // or jump straight to payment. Returns the next step.
3913
+ async function loadCustomFieldsOrSkip(checkoutId: string): Promise<CheckoutStep> {
3914
+ try {
3915
+ const fields = await getClient().getCheckoutCustomFields(checkoutId);
3916
+ setCustomFields(fields);
3917
+ return fields.length > 0 ? 'custom-fields' : 'payment';
3918
+ } catch {
3919
+ // If the endpoint isn't available or fails, fall through to payment
3920
+ // rather than blocking the customer.
3921
+ setCustomFields([]);
3922
+ return 'payment';
3923
+ }
3924
+ }
3925
+
3654
3926
  // Handle shipping method selection
3655
3927
  async function handleShippingSelect(rateId: string) {
3656
3928
  if (!checkout) return;
@@ -3663,7 +3935,7 @@ function CheckoutContent() {
3663
3935
 
3664
3936
  const updated = await client.selectShippingMethod(checkout.id, rateId);
3665
3937
  setCheckout(updated);
3666
- setStep('payment');
3938
+ setStep(await loadCustomFieldsOrSkip(updated.id));
3667
3939
  } catch (err) {
3668
3940
  const message = err instanceof Error ? err.message : t('failedToSelectShipping');
3669
3941
  setError(message);
@@ -3672,6 +3944,23 @@ function CheckoutContent() {
3672
3944
  }
3673
3945
  }
3674
3946
 
3947
+ // Submit custom fields
3948
+ async function handleCustomFieldsApply() {
3949
+ if (!checkout) return;
3950
+ try {
3951
+ setCustomFieldsLoading(true);
3952
+ setError(null);
3953
+ const updated = await getClient().setCheckoutCustomFields(checkout.id, customFieldValues);
3954
+ setCheckout(updated);
3955
+ setStep('payment');
3956
+ } catch (err) {
3957
+ const message = err instanceof Error ? err.message : t('customFieldsFailed');
3958
+ setError(message);
3959
+ } finally {
3960
+ setCustomFieldsLoading(false);
3961
+ }
3962
+ }
3963
+
3675
3964
  // Handle delivery method selection
3676
3965
  async function handleDeliveryTypeSelect(method: 'shipping' | 'pickup') {
3677
3966
  if (!checkout) return;
@@ -3717,7 +4006,7 @@ function CheckoutContent() {
3717
4006
  phone: customerInfo.phone,
3718
4007
  });
3719
4008
  setCheckout(updated);
3720
- setStep('payment');
4009
+ setStep(await loadCustomFieldsOrSkip(updated.id));
3721
4010
  } catch (err) {
3722
4011
  const message = err instanceof Error ? err.message : t('failedToSelectPickup');
3723
4012
  setError(message);
@@ -3779,9 +4068,15 @@ function CheckoutContent() {
3779
4068
  );
3780
4069
  }
3781
4070
 
4071
+ const customFieldsStep =
4072
+ customFields.length > 0
4073
+ ? [{ key: 'custom-fields' as CheckoutStep, label: t('stepCustomFields') }]
4074
+ : [];
4075
+
3782
4076
  const steps: { key: CheckoutStep; label: string }[] = isAllDigital
3783
4077
  ? [
3784
4078
  { key: 'address', label: t('stepContactInfo') },
4079
+ ...customFieldsStep,
3785
4080
  { key: 'payment', label: t('stepPayment') },
3786
4081
  ]
3787
4082
  : pickupLocations.length > 0
@@ -3789,17 +4084,20 @@ function CheckoutContent() {
3789
4084
  ? [
3790
4085
  { key: 'method', label: t('stepMethod') },
3791
4086
  { key: 'pickup', label: t('stepPickup') },
4087
+ ...customFieldsStep,
3792
4088
  { key: 'payment', label: t('stepPayment') },
3793
4089
  ]
3794
4090
  : [
3795
4091
  { key: 'method', label: t('stepMethod') },
3796
4092
  { key: 'address', label: t('stepAddress') },
3797
4093
  { key: 'shipping', label: t('stepShipping') },
4094
+ ...customFieldsStep,
3798
4095
  { key: 'payment', label: t('stepPayment') },
3799
4096
  ]
3800
4097
  : [
3801
4098
  { key: 'address', label: t('stepAddress') },
3802
4099
  { key: 'shipping', label: t('stepShipping') },
4100
+ ...customFieldsStep,
3803
4101
  { key: 'payment', label: t('stepPayment') },
3804
4102
  ];
3805
4103
 
@@ -3998,19 +4296,54 @@ function CheckoutContent() {
3998
4296
  </div>
3999
4297
  )}
4000
4298
 
4299
+ {/* Custom Fields (optional, between shipping/pickup and payment) */}
4300
+ {step === 'custom-fields' && checkout && (
4301
+ <div>
4302
+ <div className="mb-4 flex items-center justify-between">
4303
+ <h2 className="text-foreground text-lg font-semibold">{t('customFieldsTitle')}</h2>
4304
+ <button
4305
+ type="button"
4306
+ onClick={() => setStep(deliveryType === 'pickup' ? 'pickup' : 'shipping')}
4307
+ className="text-primary text-sm hover:underline"
4308
+ >
4309
+ {deliveryType === 'pickup' ? t('changePickup') : t('changeShipping')}
4310
+ </button>
4311
+ </div>
4312
+ <CustomFieldsStep
4313
+ fields={customFields}
4314
+ values={customFieldValues}
4315
+ onChange={(key, value) =>
4316
+ setCustomFieldValues((prev) => ({ ...prev, [key]: value }))
4317
+ }
4318
+ onApply={handleCustomFieldsApply}
4319
+ loading={customFieldsLoading}
4320
+ />
4321
+ </div>
4322
+ )}
4323
+
4001
4324
  {/* Payment */}
4002
4325
  {step === 'payment' && checkout && (
4003
4326
  <div>
4004
4327
  <div className="mb-4 flex items-center justify-between">
4005
4328
  <h2 className="text-foreground text-lg font-semibold">{t('payment')}</h2>
4006
- {!isAllDigital && (
4329
+ {customFields.length > 0 ? (
4007
4330
  <button
4008
4331
  type="button"
4009
- onClick={() => setStep(deliveryType === 'pickup' ? 'pickup' : 'shipping')}
4332
+ onClick={() => setStep('custom-fields')}
4010
4333
  className="text-primary text-sm hover:underline"
4011
4334
  >
4012
- {deliveryType === 'pickup' ? t('changePickup') : t('changeShipping')}
4335
+ {t('changeOptions')}
4013
4336
  </button>
4337
+ ) : (
4338
+ !isAllDigital && (
4339
+ <button
4340
+ type="button"
4341
+ onClick={() => setStep(deliveryType === 'pickup' ? 'pickup' : 'shipping')}
4342
+ className="text-primary text-sm hover:underline"
4343
+ >
4344
+ {deliveryType === 'pickup' ? t('changePickup') : t('changeShipping')}
4345
+ </button>
4346
+ )
4014
4347
  )}
4015
4348
  </div>
4016
4349
 
@@ -4197,6 +4530,20 @@ function CheckoutContent() {
4197
4530
  taxBreakdown={checkout.taxBreakdown}
4198
4531
  />
4199
4532
 
4533
+ {/* Custom field surcharges (one line per applied surcharge) */}
4534
+ {checkout.appliedSurcharges && checkout.appliedSurcharges.length > 0 && (
4535
+ <>
4536
+ {checkout.appliedSurcharges.map((s) => (
4537
+ <div key={s.key} className="flex items-center justify-between">
4538
+ <span className="text-muted-foreground">{s.name}</span>
4539
+ <span className="text-foreground">
4540
+ {formatPrice(Number(s.amount), { currency }) as string}
4541
+ </span>
4542
+ </div>
4543
+ ))}
4544
+ </>
4545
+ )}
4546
+
4200
4547
  <div className="border-border mt-2 border-t pt-2">
4201
4548
  <div className="flex items-center justify-between">
4202
4549
  <span className="text-foreground font-semibold">{tc('total')}</span>
@@ -11252,8 +11599,11 @@ async function fetchStoreInfo(connectionId, baseUrl = KNOWN_API_URLS.production)
11252
11599
  } catch {
11253
11600
  throw new Error(`Invalid response from ${baseUrl}`);
11254
11601
  }
11602
+ const storeName = json.storeName || json.name || "My Store";
11603
+ const displayName = json.channelName || storeName;
11255
11604
  return {
11256
- name: json.name || json.storeName || "My Store",
11605
+ name: displayName,
11606
+ storeName,
11257
11607
  currency: json.currency || "USD",
11258
11608
  language: json.language || "en",
11259
11609
  ...json.i18n ? { i18n: json.i18n } : {}
@@ -11294,7 +11644,7 @@ function getCandidateApiUrls() {
11294
11644
 
11295
11645
  // src/tools/get-store-info.ts
11296
11646
  var GET_STORE_INFO_NAME = "get-store-info";
11297
- 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.";
11647
+ 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.";
11298
11648
  var GET_STORE_INFO_SCHEMA = {
11299
11649
  connectionId: import_zod5.z.string().describe("Vibe-coded connection ID (starts with vc_)")
11300
11650
  };
@@ -11307,7 +11657,10 @@ async function handleGetStoreInfo(args) {
11307
11657
  type: "text",
11308
11658
  text: JSON.stringify(
11309
11659
  {
11660
+ // `name` is the channel display name (from the dashboard sales-channel card);
11661
+ // falls back to storeName when a channel doesn't have its own name.
11310
11662
  name: resolved.info.name,
11663
+ storeName: resolved.info.storeName,
11311
11664
  currency: resolved.info.currency,
11312
11665
  language: resolved.info.language,
11313
11666
  connectionId: args.connectionId,
@@ -11401,7 +11754,8 @@ var GET_STORE_CAPABILITIES_SCHEMA = {
11401
11754
  };
11402
11755
  function formatCapabilities(caps) {
11403
11756
  const lines = [];
11404
- lines.push(`## Store: ${caps.store.name} (${caps.store.currency})`);
11757
+ const channelSuffix = caps.store.channelName && caps.store.channelName !== caps.store.name ? ` \u2014 Channel: "${caps.store.channelName}"` : "";
11758
+ lines.push(`## Store: ${caps.store.name} (${caps.store.currency})${channelSuffix}`);
11405
11759
  lines.push(`Language: ${caps.store.language}`);
11406
11760
  if (caps.store.i18n?.enabled) {
11407
11761
  lines.push(
@@ -11434,6 +11788,9 @@ function formatCapabilities(caps) {
11434
11788
  caps.features.hasDownloadableProducts ? "\u2705 Downloadable products available" : "\u274C No downloadable products"
11435
11789
  );
11436
11790
  lines.push(caps.features.hasCoupons ? "\u2705 Coupons available" : "\u274C No coupons");
11791
+ lines.push(
11792
+ caps.features.hasCheckoutCustomFields ? "\u2705 Checkout custom fields configured (surcharges may apply)" : "\u274C No checkout custom fields"
11793
+ );
11437
11794
  lines.push("");
11438
11795
  lines.push("## Connection Settings");
11439
11796
  lines.push(
@@ -11479,6 +11836,11 @@ function formatCapabilities(caps) {
11479
11836
  if (caps.features.hasCoupons) {
11480
11837
  suggestions.push('Add a coupon input to the cart. Use get-code-example("coupon-input").');
11481
11838
  }
11839
+ if (caps.features.hasCheckoutCustomFields) {
11840
+ suggestions.push(
11841
+ `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").`
11842
+ );
11843
+ }
11482
11844
  if (caps.connection.requireEmailVerification) {
11483
11845
  suggestions.push(
11484
11846
  'Email verification is required \u2014 ALWAYS build the verify-email page. Use get-code-example("verify-email").'