@aglyn/aglyn 1.0.0-beta.269 → 1.0.0-beta.271

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.
Files changed (121) hide show
  1. package/package.json +11 -11
  2. package/src/lib/app-utils/account-preferences.d.ts +248 -0
  3. package/src/lib/app-utils/account-preferences.js +434 -0
  4. package/src/lib/app-utils/account-preferences.js.map +1 -0
  5. package/src/lib/app-utils/activity-labels.js +2 -0
  6. package/src/lib/app-utils/activity-labels.js.map +1 -1
  7. package/src/lib/app-utils/analytics-events.d.ts +13 -0
  8. package/src/lib/app-utils/analytics-events.js +2 -0
  9. package/src/lib/app-utils/analytics-events.js.map +1 -1
  10. package/src/lib/app-utils/append-only-history.d.ts +94 -0
  11. package/src/lib/app-utils/append-only-history.js +73 -0
  12. package/src/lib/app-utils/append-only-history.js.map +1 -0
  13. package/src/lib/app-utils/bundle-intent.d.ts +186 -0
  14. package/src/lib/app-utils/bundle-intent.js +386 -0
  15. package/src/lib/app-utils/bundle-intent.js.map +1 -0
  16. package/src/lib/app-utils/client-build.d.ts +204 -0
  17. package/src/lib/app-utils/client-build.js +237 -0
  18. package/src/lib/app-utils/client-build.js.map +1 -0
  19. package/src/lib/app-utils/console-routes.d.ts +4 -0
  20. package/src/lib/app-utils/console-routes.js +2 -0
  21. package/src/lib/app-utils/console-routes.js.map +1 -1
  22. package/src/lib/app-utils/country-options.d.ts +27 -0
  23. package/src/lib/app-utils/country-options.js +96 -0
  24. package/src/lib/app-utils/country-options.js.map +1 -0
  25. package/src/lib/app-utils/docs-help-sections.generated.js +7 -5
  26. package/src/lib/app-utils/docs-help-sections.generated.js.map +1 -1
  27. package/src/lib/app-utils/docs-help.generated.d.ts +11 -5
  28. package/src/lib/app-utils/docs-help.generated.js +22 -2
  29. package/src/lib/app-utils/docs-help.generated.js.map +1 -1
  30. package/src/lib/app-utils/docs-index.generated.js +116 -39
  31. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  32. package/src/lib/app-utils/margin-utilization.js +6 -4
  33. package/src/lib/app-utils/margin-utilization.js.map +1 -1
  34. package/src/lib/app-utils/media-filter.d.ts +26 -0
  35. package/src/lib/app-utils/media-filter.js +32 -0
  36. package/src/lib/app-utils/media-filter.js.map +1 -1
  37. package/src/lib/app-utils/mobile-push.d.ts +8 -0
  38. package/src/lib/app-utils/mobile-push.js +4 -1
  39. package/src/lib/app-utils/mobile-push.js.map +1 -1
  40. package/src/lib/app-utils/native-app-association.d.ts +3 -2
  41. package/src/lib/app-utils/native-app-association.js +4 -2
  42. package/src/lib/app-utils/native-app-association.js.map +1 -1
  43. package/src/lib/app-utils/native-nav.d.ts +265 -0
  44. package/src/lib/app-utils/native-nav.js +741 -0
  45. package/src/lib/app-utils/native-nav.js.map +1 -0
  46. package/src/lib/app-utils/native-themes.d.ts +249 -0
  47. package/src/lib/app-utils/native-themes.js +732 -0
  48. package/src/lib/app-utils/native-themes.js.map +1 -0
  49. package/src/lib/app-utils/notifications.d.ts +1 -1
  50. package/src/lib/app-utils/notifications.js +10 -0
  51. package/src/lib/app-utils/notifications.js.map +1 -1
  52. package/src/lib/app-utils/onboarding-deep-link.d.ts +36 -2
  53. package/src/lib/app-utils/onboarding-deep-link.js +48 -5
  54. package/src/lib/app-utils/onboarding-deep-link.js.map +1 -1
  55. package/src/lib/app-utils/one-box-search.d.ts +108 -0
  56. package/src/lib/app-utils/one-box-search.js +167 -0
  57. package/src/lib/app-utils/one-box-search.js.map +1 -0
  58. package/src/lib/app-utils/operator-alerts.js +21 -0
  59. package/src/lib/app-utils/operator-alerts.js.map +1 -1
  60. package/src/lib/app-utils/org-permissions.js +10 -61
  61. package/src/lib/app-utils/org-permissions.js.map +1 -1
  62. package/src/lib/app-utils/org-role-grants.d.ts +95 -0
  63. package/src/lib/app-utils/org-role-grants.js +193 -0
  64. package/src/lib/app-utils/org-role-grants.js.map +1 -0
  65. package/src/lib/app-utils/owner-approval-record.d.ts +102 -0
  66. package/src/lib/app-utils/owner-approval-record.js +148 -0
  67. package/src/lib/app-utils/owner-approval-record.js.map +1 -0
  68. package/src/lib/app-utils/plan-entitlements.d.ts +224 -68
  69. package/src/lib/app-utils/plan-entitlements.js +278 -61
  70. package/src/lib/app-utils/plan-entitlements.js.map +1 -1
  71. package/src/lib/app-utils/release-flags.d.ts +1 -1
  72. package/src/lib/app-utils/release-flags.js +10 -0
  73. package/src/lib/app-utils/release-flags.js.map +1 -1
  74. package/src/lib/app-utils/server.d.ts +4 -0
  75. package/src/lib/app-utils/server.js +7 -0
  76. package/src/lib/app-utils/server.js.map +1 -1
  77. package/src/lib/app-utils/site-care-project.d.ts +227 -0
  78. package/src/lib/app-utils/site-care-project.js +423 -0
  79. package/src/lib/app-utils/site-care-project.js.map +1 -0
  80. package/src/lib/app-utils/site-care.d.ts +268 -0
  81. package/src/lib/app-utils/site-care.js +283 -0
  82. package/src/lib/app-utils/site-care.js.map +1 -0
  83. package/src/lib/app-utils/tenant-dns-targets.d.ts +62 -0
  84. package/src/lib/app-utils/tenant-dns-targets.js +80 -0
  85. package/src/lib/app-utils/tenant-dns-targets.js.map +1 -0
  86. package/src/lib/app-utils/upgrade-proposal.d.ts +29 -4
  87. package/src/lib/app-utils/upgrade-proposal.js +60 -9
  88. package/src/lib/app-utils/upgrade-proposal.js.map +1 -1
  89. package/src/lib/foundation/definitions/org-billing.types.d.ts +54 -10
  90. package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
  91. package/src/lib/foundation/definitions/organization.types.d.ts +3 -1
  92. package/src/lib/foundation/definitions/organization.types.js.map +1 -1
  93. package/src/lib/foundation/definitions/platform.types.d.ts +9 -0
  94. package/src/lib/foundation/definitions/platform.types.js.map +1 -1
  95. package/src/lib/foundation/index.d.ts +1 -1
  96. package/src/lib/foundation/index.js.map +1 -1
  97. package/src/lib/plugin-manager/feature-plugins.d.ts +86 -0
  98. package/src/lib/plugin-manager/feature-plugins.js +20 -0
  99. package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
  100. package/src/lib/plugin-manager/first-party-plugins.generated.js +22 -1
  101. package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
  102. package/src/lib/plugin-manager/plugin-ai-capabilities.d.ts +19 -0
  103. package/src/lib/plugin-manager/plugin-ai-capabilities.js.map +1 -1
  104. package/src/lib/plugin-manager/plugin-domain-events.d.ts +4 -3
  105. package/src/lib/plugin-manager/plugin-domain-events.js.map +1 -1
  106. package/src/lib/plugin-manager/plugin-domain-search.d.ts +81 -0
  107. package/src/lib/plugin-manager/plugin-domain-search.js +63 -0
  108. package/src/lib/plugin-manager/plugin-domain-search.js.map +1 -0
  109. package/src/lib/plugin-manager/plugin-entitlements.d.ts +23 -5
  110. package/src/lib/plugin-manager/plugin-entitlements.js +10 -0
  111. package/src/lib/plugin-manager/plugin-entitlements.js.map +1 -1
  112. package/src/lib/plugin-manager/plugin-media-ingest.d.ts +25 -0
  113. package/src/lib/plugin-manager/plugin-media-ingest.js.map +1 -1
  114. package/src/lib/plugin-manager/plugin-search-sources.d.ts +85 -0
  115. package/src/lib/plugin-manager/plugin-search-sources.js +91 -0
  116. package/src/lib/plugin-manager/plugin-search-sources.js.map +1 -0
  117. package/src/lib/plugin-manager/plugin-services.js +6 -0
  118. package/src/lib/plugin-manager/plugin-services.js.map +1 -1
  119. package/src/lib/plugin-manager/purchase-gate-preclearance.d.ts +61 -0
  120. package/src/lib/plugin-manager/purchase-gate-preclearance.js +102 -0
  121. package/src/lib/plugin-manager/purchase-gate-preclearance.js.map +1 -0
@@ -259,6 +259,10 @@ export const PLAN_ENTITLEMENTS = {
259
259
  siteExport: false,
260
260
  multilingual: false,
261
261
  eventCalendar: false,
262
+ // Site Care (AGL-3954) is never granted by a tier, on this plan or any
263
+ // other: it is the managed-website add-on, switched on only by a live
264
+ // `seatAddons.siteCare` purchase (or a staff override), never by a row.
265
+ siteCare: false,
262
266
  redirects: false,
263
267
  screenAnalytics: false,
264
268
  /**
@@ -358,7 +362,10 @@ export const PLAN_ENTITLEMENTS = {
358
362
  removeBranding: true,
359
363
  scheduledPublishing: false,
360
364
  aiAssist: true,
361
- aiGenerative: false,
365
+ // One pool (AGL-3965): the plan's included credits pay for generation
366
+ // as well as assist on every paid plan, and an Aglyn AI tier adds
367
+ // credits on top rather than opening this door.
368
+ aiGenerative: true,
362
369
  workflows: true,
363
370
  dataStore: true,
364
371
  videoMedia: false,
@@ -377,6 +384,7 @@ export const PLAN_ENTITLEMENTS = {
377
384
  siteExport: false,
378
385
  multilingual: false,
379
386
  eventCalendar: false,
387
+ siteCare: false,
380
388
  redirects: true,
381
389
  screenAnalytics: false,
382
390
  mediaCdn: true,
@@ -476,7 +484,7 @@ export const PLAN_ENTITLEMENTS = {
476
484
  removeBranding: true,
477
485
  scheduledPublishing: false,
478
486
  aiAssist: true,
479
- aiGenerative: false,
487
+ aiGenerative: true,
480
488
  workflows: true,
481
489
  dataStore: true,
482
490
  videoMedia: true,
@@ -490,6 +498,7 @@ export const PLAN_ENTITLEMENTS = {
490
498
  siteExport: true,
491
499
  multilingual: false,
492
500
  eventCalendar: false,
501
+ siteCare: false,
493
502
  redirects: true,
494
503
  screenAnalytics: true,
495
504
  mediaCdn: true,
@@ -546,7 +555,7 @@ export const PLAN_ENTITLEMENTS = {
546
555
  removeBranding: true,
547
556
  scheduledPublishing: true,
548
557
  aiAssist: true,
549
- aiGenerative: false,
558
+ aiGenerative: true,
550
559
  workflows: true,
551
560
  dataStore: true,
552
561
  videoMedia: true,
@@ -560,6 +569,7 @@ export const PLAN_ENTITLEMENTS = {
560
569
  siteExport: true,
561
570
  multilingual: true,
562
571
  eventCalendar: false,
572
+ siteCare: false,
563
573
  redirects: true,
564
574
  screenAnalytics: true,
565
575
  mediaCdn: true,
@@ -619,7 +629,7 @@ export const PLAN_ENTITLEMENTS = {
619
629
  removeBranding: true,
620
630
  scheduledPublishing: true,
621
631
  aiAssist: true,
622
- aiGenerative: false,
632
+ aiGenerative: true,
623
633
  workflows: true,
624
634
  dataStore: true,
625
635
  videoMedia: true,
@@ -633,6 +643,7 @@ export const PLAN_ENTITLEMENTS = {
633
643
  siteExport: true,
634
644
  multilingual: true,
635
645
  eventCalendar: false,
646
+ siteCare: false,
636
647
  redirects: true,
637
648
  screenAnalytics: true,
638
649
  mediaCdn: true,
@@ -689,7 +700,7 @@ export const PLAN_ENTITLEMENTS = {
689
700
  removeBranding: true,
690
701
  scheduledPublishing: true,
691
702
  aiAssist: true,
692
- aiGenerative: false,
703
+ aiGenerative: true,
693
704
  workflows: true,
694
705
  dataStore: true,
695
706
  videoMedia: true,
@@ -703,6 +714,7 @@ export const PLAN_ENTITLEMENTS = {
703
714
  siteExport: true,
704
715
  multilingual: true,
705
716
  eventCalendar: false,
717
+ siteCare: false,
706
718
  redirects: true,
707
719
  screenAnalytics: true,
708
720
  mediaCdn: true,
@@ -763,7 +775,7 @@ export const PLAN_ENTITLEMENTS = {
763
775
  removeBranding: true,
764
776
  scheduledPublishing: true,
765
777
  aiAssist: true,
766
- aiGenerative: false,
778
+ aiGenerative: true,
767
779
  workflows: true,
768
780
  dataStore: true,
769
781
  videoMedia: true,
@@ -777,6 +789,7 @@ export const PLAN_ENTITLEMENTS = {
777
789
  siteExport: true,
778
790
  multilingual: true,
779
791
  eventCalendar: false,
792
+ siteCare: false,
780
793
  redirects: true,
781
794
  screenAnalytics: true,
782
795
  mediaCdn: true,
@@ -888,6 +901,7 @@ export const PLAN_ENTITLEMENTS = {
888
901
  multilingual: true,
889
902
  // Event Calendar stays an add-on purchase, as on every other tier.
890
903
  eventCalendar: false,
904
+ siteCare: false,
891
905
  redirects: true,
892
906
  screenAnalytics: true,
893
907
  mediaCdn: true,
@@ -955,6 +969,17 @@ fillPluginPlanEntitlements(PLAN_ENTITLEMENTS);
955
969
  */ export function isCustomPricedPlan(plan) {
956
970
  return plan === 'enterprise';
957
971
  }
972
+ /**
973
+ * Whether a plan sells the Site Care add-on (AGL-3954): every paid self-serve
974
+ * plan, never `free` (no subscription to bill it on) and never a
975
+ * custom-priced plan (Enterprise names it in the agreement instead). An
976
+ * unknown plan string sells nothing. The ONE reading of that rule — the
977
+ * add-on ceiling, the checkout refusal and the Billing card all ask it.
978
+ */ export function planSellsSiteCare(plan) {
979
+ if (!plan || plan === 'free') return false;
980
+ if (isCustomPricedPlan(plan)) return false;
981
+ return SELF_SERVE_PLANS.includes(plan);
982
+ }
958
983
  /** Human plan names — the ONE source every plan badge/label reads. */ export const PLAN_LABELS = {
959
984
  free: 'Free',
960
985
  starter: 'Starter',
@@ -977,52 +1002,166 @@ fillPluginPlanEntitlements(PLAN_ENTITLEMENTS);
977
1002
  * boolean from any quantity >= 1. One purchase covers every host in the org.
978
1003
  * The marketing copy on /pricing is written against that behaviour.
979
1004
  */ export const EVENT_CALENDAR_ADDON_MONTHLY_USD = 9;
1005
+ /**
1006
+ * Site Care add-on (AGL-3954): $49/mo per organization, the managed-website
1007
+ * service. It buys staff time, not capacity: small changes to the owner's
1008
+ * existing site (hours, prices, photos, a staff member, a seasonal banner, a
1009
+ * section on an existing page), up to about one hour of work a month, done
1010
+ * within two business days, minutes logged and no rollover. New pages, new
1011
+ * forms, redesigns, brand work, photography, whole-site copy and ads are
1012
+ * quoted separately, and it never raises a plan limit — a 26th page or a
1013
+ * second site is still an upgrade.
1014
+ *
1015
+ * Flat across plans, like the Event Calendar, because the work it pays for
1016
+ * is the same hour whichever tier the site is on; the yearly price is ×12
1017
+ * with no discount, as every add-on's is. Sold on every paid self-serve plan
1018
+ * and never on Free (no subscription to carry it) or Enterprise (quoted per
1019
+ * deal) — `planSellsSiteCare` is that rule. `resolveOrgEntitlements` flips
1020
+ * the org-wide `features.siteCare` from any purchased quantity >= 1.
1021
+ */ export const SITE_CARE_ADDON_MONTHLY_USD = 49;
1022
+ /**
1023
+ * Site Care's included time (AGL-3959): about an hour of staff work a month,
1024
+ * no rollover. `SITE_CARE_INCLUDED_BLOCK_MINUTES` is the step each request is
1025
+ * counted against it in, always rounded up — a 10-minute change uses one
1026
+ * 15-minute block. The timer's actual minutes are still logged beside it.
1027
+ */ export const SITE_CARE_INCLUDED_MINUTES = 60;
1028
+ /** The block a request counts against the included hour in (AGL-3959). */ export const SITE_CARE_INCLUDED_BLOCK_MINUTES = 15;
1029
+ /**
1030
+ * Site Care extra time (AGL-3959): $79 per STARTED hour past the included
1031
+ * hour in a month. Billed in whole hours, rounded up — any part of an hour is
1032
+ * the full rate — and never less than the hours the owner approved. Only with
1033
+ * the owner's recorded yes, as an invoice item on the next subscription
1034
+ * invoice; never recurring. `site-care.ts` holds the arithmetic.
1035
+ */ export const SITE_CARE_EXTRA_HOUR_USD = 79;
1036
+ /**
1037
+ * Site Care projects (AGL-3970): work that adds a page, a form, a design or
1038
+ * a campaign is a flat quote, never hourly. The quote is the estimated hours
1039
+ * × `SITE_CARE_EXTRA_HOUR_USD`, rounded up to the whole hour, and never below
1040
+ * the published starting price of the kind of project it is. These three are
1041
+ * those starting prices, as `/pricing` publishes them.
1042
+ */ export const SITE_CARE_PROJECT_LANDING_PAGE_FROM_USD = 249;
1043
+ /** A redesign's published starting price (AGL-3970). */ export const SITE_CARE_PROJECT_REDESIGN_FROM_USD = 749;
1044
+ /** A logo or brand kit's published starting price (AGL-3970). */ export const SITE_CARE_PROJECT_BRAND_KIT_FROM_USD = 349;
1045
+ /**
1046
+ * A project priced ABOVE this is billed half up front and half at launch, on
1047
+ * two invoices from the same record; at or below it, in full up front
1048
+ * (AGL-3970). `site-care-project.ts` holds the arithmetic.
1049
+ */ export const SITE_CARE_PROJECT_SPLIT_ABOVE_USD = 500;
980
1050
  /**
981
1051
  * POS Pro register add-on (AGL-329): $89/mo per extra register/location
982
1052
  * (Shopify POS Pro parity). Purchased add-ons land as a per-org
983
1053
  * `posRegisters` entitlement override, which
984
1054
  * `resolveOrgEntitlements` already applies over the plan default.
985
1055
  */ export const POS_REGISTER_ADDON_MONTHLY_USD = 89;
1056
+ /** The tiers, smallest first — the order every picker lists them in. */ export const AI_TIER_IDS = [
1057
+ 'spark',
1058
+ 'boost',
1059
+ 'plus',
1060
+ 'max',
1061
+ 'ultra',
1062
+ 'studio'
1063
+ ];
1064
+ export const AI_TIERS = {
1065
+ spark: {
1066
+ label: 'Spark',
1067
+ monthlyUsd: 9,
1068
+ yearlyUsd: 108,
1069
+ credits: 4000
1070
+ },
1071
+ boost: {
1072
+ label: 'Boost',
1073
+ monthlyUsd: 19,
1074
+ yearlyUsd: 228,
1075
+ credits: 9000
1076
+ },
1077
+ plus: {
1078
+ label: 'Plus',
1079
+ monthlyUsd: 39,
1080
+ yearlyUsd: 468,
1081
+ credits: 19000
1082
+ },
1083
+ max: {
1084
+ label: 'Max',
1085
+ monthlyUsd: 69,
1086
+ yearlyUsd: 828,
1087
+ credits: 34000
1088
+ },
1089
+ ultra: {
1090
+ label: 'Ultra',
1091
+ monthlyUsd: 99,
1092
+ yearlyUsd: 1188,
1093
+ credits: 49000
1094
+ },
1095
+ studio: {
1096
+ label: 'Studio',
1097
+ monthlyUsd: 299,
1098
+ yearlyUsd: 3588,
1099
+ credits: 149000
1100
+ }
1101
+ };
986
1102
  /**
987
- * The Aglyn AI add-on's credit band (AGL-2896): the credits a month the
988
- * add-on ADDS to the plan's `assistCreditsPerMonth` when
989
- * `seatAddons.aiAddon` is set. One pool and one meter — the add-on widens
990
- * the band the assist meter already draws on rather than opening a second
991
- * one, so `resolveAssistCreditBudget`, the overage price and the hard-cap
992
- * switch all see one number.
993
- *
994
- * ## Sized against the add-on's own price
995
- *
996
- * A credit is `ASSIST_CREDIT_COST_USD` of provider spend, so each band is a
997
- * cost figure: 1,000 credits are $1.00. Every band here costs at most 50% of
998
- * `PlanPricing.aiAddonMonthlyUsd` at full consumption — Starter $4 of $9,
999
- * Pro $9 of $19, Business $19 of $39, Scale $34 of $69, Advanced $49 of
1000
- * $99, Agency $149 of $299 — which is the same line-margin floor
1001
- * (`ASSIST_CREDIT_MIN_MARGIN_PCT`) every retail assist rate clears.
1002
- * `tier-margin-floor.spec.ts` holds the whole tier non-negative with the
1003
- * add-on band at 100% and the add-on's revenue counted, and pins each band's
1004
- * share of its price.
1005
- *
1006
- * ## Free and Enterprise
1007
- *
1008
- * Free is 0: the plan sells no add-on (`aiAddonMonthlyUsd` is null), so a
1009
- * quantity written onto a Free org adds nothing. Enterprise is finite at
1010
- * twice Agency's, the rule every Enterprise fallback follows since the
1011
- * 2026-09-07 decision (AGL-2654) — never `UNLIMITED`, for the reason
1012
- * `ENTERPRISE_ASSIST_CREDITS_PER_MONTH` gives: an unbounded band reads back
1013
- * as zero off the wire. Enterprise carries `aiGenerative` in the agreement
1014
- * and its price is null too, so this figure is what a contract that names
1015
- * the add-on starts from before its own number is written.
1016
- */ export const AI_ADDON_CREDITS_PER_MONTH = {
1017
- free: 0,
1018
- starter: 4000,
1019
- pro: 9000,
1020
- business: 19000,
1021
- scale: 34000,
1022
- advanced: 49000,
1023
- agency: 149000,
1024
- enterprise: 298000
1103
+ * The tier an add-on bought before AGL-3965 is (AGL-3965): the one with the
1104
+ * same price and the same band the plan sold it at, so a workspace that
1105
+ * held "the Pro add-on" holds Boost — $19, 9,000 credits — and neither its
1106
+ * bill nor its credits move. Read when `aiTier` is absent and to classify a
1107
+ * per-plan `STRIPE_PRICE_{PLAN}_AI_ADDON` item still on a subscription.
1108
+ */ export const LEGACY_AI_ADDON_TIER_BY_PLAN = {
1109
+ starter: 'spark',
1110
+ pro: 'boost',
1111
+ business: 'plus',
1112
+ scale: 'max',
1113
+ advanced: 'ultra',
1114
+ agency: 'studio'
1025
1115
  };
1116
+ /**
1117
+ * The tier a purchase that names a plan and no tier means (AGL-3965): a
1118
+ * `?plan=pro&ai=1` deep link, a checkout posting `aiAddon: true` alone. The
1119
+ * tier with that plan's old price and band, so a link published before tiers
1120
+ * existed still sells what it quoted. Null where the plan sells no tier.
1121
+ */ export function defaultAiTierForPlan(plan) {
1122
+ var _LEGACY_AI_ADDON_TIER_BY_PLAN_plan;
1123
+ if (!planSellsAiTiers(plan)) return null;
1124
+ return (_LEGACY_AI_ADDON_TIER_BY_PLAN_plan = LEGACY_AI_ADDON_TIER_BY_PLAN[plan]) != null ? _LEGACY_AI_ADDON_TIER_BY_PLAN_plan : null;
1125
+ }
1126
+ /** Whether a value is one of the tier ids. */ export function isAiTierId(value) {
1127
+ return typeof value === 'string' && AI_TIER_IDS.includes(value);
1128
+ }
1129
+ /** `ai:<tier>` for a tier. */ export function aiTierAddonKey(tier) {
1130
+ return `ai:${tier}`;
1131
+ }
1132
+ /**
1133
+ * The tier an `ai:<tier>` key names, or null for anything else — another
1134
+ * add-on's key, a bare `ai`, an unknown tier, a non-string. Exact and
1135
+ * case-sensitive: a key is read off a link, and guessing is the expensive
1136
+ * direction to be wrong in (`parseOnboardingPlanIntent` reads `?ai=` the
1137
+ * same way).
1138
+ */ export function parseAiTierAddonKey(key) {
1139
+ if (typeof key !== 'string' || !key.startsWith('ai:')) return null;
1140
+ const tier = key.slice(3);
1141
+ return isAiTierId(tier) ? tier : null;
1142
+ }
1143
+ /**
1144
+ * Every tier as an `ai:<tier>` add-on, smallest first — what a picker that
1145
+ * offers "Aglyn AI" beside the other add-ons lists. Empty for a plan that
1146
+ * sells no tier (Free, Enterprise) when a plan is given.
1147
+ */ export function listAiTierAddons(plan) {
1148
+ if (plan !== undefined && !planSellsAiTiers(plan)) return [];
1149
+ return AI_TIER_IDS.map((tier)=>({
1150
+ key: aiTierAddonKey(tier),
1151
+ tier,
1152
+ label: AI_TIERS[tier].label,
1153
+ monthlyUsd: AI_TIERS[tier].monthlyUsd,
1154
+ yearlyUsd: AI_TIERS[tier].yearlyUsd,
1155
+ credits: AI_TIERS[tier].credits
1156
+ }));
1157
+ }
1158
+ /**
1159
+ * Whether a plan may buy an Aglyn AI tier (AGL-3965): every paid self-serve
1160
+ * plan. Not Free, which has no subscription to carry it, and not Enterprise,
1161
+ * whose AI is in the agreement.
1162
+ */ export function planSellsAiTiers(plan) {
1163
+ return planSellsSiteCare(plan);
1164
+ }
1026
1165
  /**
1027
1166
  * The generative add-on's name as a person sees it (AGL-2896): the brand,
1028
1167
  * then "AI". Read from the platform brand by default and from the org's
@@ -1069,7 +1208,6 @@ fillPluginPlanEntitlements(PLAN_ENTITLEMENTS);
1069
1208
  extraDataGbMonthlyUsd: null,
1070
1209
  extraApiRequestsUsdPer1k: null,
1071
1210
  extraAssistCreditsUsdPer1k: null,
1072
- aiAddonMonthlyUsd: null,
1073
1211
  extraContactsUsdPer1k: null,
1074
1212
  extraEmailSendsUsdPer1k: null,
1075
1213
  meteredInfraPassThrough: false
@@ -1090,7 +1228,6 @@ fillPluginPlanEntitlements(PLAN_ENTITLEMENTS);
1090
1228
  // the silent free overage, and this rate without the band is a fee
1091
1229
  // advertised on a quantity the plan never sold.
1092
1230
  extraAssistCreditsUsdPer1k: 3,
1093
- aiAddonMonthlyUsd: 9,
1094
1231
  extraContactsUsdPer1k: 1,
1095
1232
  // No email band to be "over" — see `emailSendsPerMonth` on the Starter
1096
1233
  // entitlements. `emailSendsOverage` returns 0 for any non-positive band,
@@ -1109,7 +1246,6 @@ fillPluginPlanEntitlements(PLAN_ENTITLEMENTS);
1109
1246
  extraDataGbMonthlyUsd: 0.36,
1110
1247
  extraApiRequestsUsdPer1k: null,
1111
1248
  extraAssistCreditsUsdPer1k: 3,
1112
- aiAddonMonthlyUsd: 19,
1113
1249
  extraContactsUsdPer1k: 0.75,
1114
1250
  extraEmailSendsUsdPer1k: 2.25,
1115
1251
  meteredInfraPassThrough: true
@@ -1124,7 +1260,6 @@ fillPluginPlanEntitlements(PLAN_ENTITLEMENTS);
1124
1260
  extraDataGbMonthlyUsd: 0.36,
1125
1261
  extraApiRequestsUsdPer1k: 0.5,
1126
1262
  extraAssistCreditsUsdPer1k: 2.75,
1127
- aiAddonMonthlyUsd: 39,
1128
1263
  extraContactsUsdPer1k: 0.5,
1129
1264
  extraEmailSendsUsdPer1k: 2,
1130
1265
  meteredInfraPassThrough: true
@@ -1139,7 +1274,6 @@ fillPluginPlanEntitlements(PLAN_ENTITLEMENTS);
1139
1274
  extraDataGbMonthlyUsd: 0.36,
1140
1275
  extraApiRequestsUsdPer1k: 0.35,
1141
1276
  extraAssistCreditsUsdPer1k: 2.5,
1142
- aiAddonMonthlyUsd: 69,
1143
1277
  extraContactsUsdPer1k: 0.4,
1144
1278
  extraEmailSendsUsdPer1k: 1.9,
1145
1279
  meteredInfraPassThrough: true
@@ -1157,7 +1291,6 @@ fillPluginPlanEntitlements(PLAN_ENTITLEMENTS);
1157
1291
  // or above $0.234 (AGL-3444).
1158
1292
  extraApiRequestsUsdPer1k: 0.25,
1159
1293
  extraAssistCreditsUsdPer1k: 2.25,
1160
- aiAddonMonthlyUsd: 99,
1161
1294
  // FLOORED at the 50% retail margin, not stepped down again. The ladder
1162
1295
  // above it descends $1.00 → $0.75 → $0.50 → $0.40, and one more step
1163
1296
  // would have reached $0.25 against a `perContactMonth` cost of $0.20 per
@@ -1191,7 +1324,6 @@ fillPluginPlanEntitlements(PLAN_ENTITLEMENTS);
1191
1324
  // FLOORED at the 50% retail margin — see Advanced's.
1192
1325
  extraApiRequestsUsdPer1k: 0.25,
1193
1326
  extraAssistCreditsUsdPer1k: 2,
1194
- aiAddonMonthlyUsd: 299,
1195
1327
  // $0.40, because the band it meters is now FINITE.
1196
1328
  //
1197
1329
  // This row has been wrong in both directions. It shipped $0.20 against an
@@ -1234,7 +1366,6 @@ fillPluginPlanEntitlements(PLAN_ENTITLEMENTS);
1234
1366
  extraDataGbMonthlyUsd: null,
1235
1367
  extraApiRequestsUsdPer1k: null,
1236
1368
  extraAssistCreditsUsdPer1k: null,
1237
- aiAddonMonthlyUsd: null,
1238
1369
  extraContactsUsdPer1k: null,
1239
1370
  extraEmailSendsUsdPer1k: null,
1240
1371
  meteredInfraPassThrough: false
@@ -1582,9 +1713,12 @@ function trimmedOrNull(value) {
1582
1713
  const annual = (org == null ? void 0 : (_org_subscription1 = org.subscription) == null ? void 0 : _org_subscription1.interval) === 'year';
1583
1714
  const addons = (_ref = org == null ? void 0 : org.seatAddons) != null ? _ref : {};
1584
1715
  const seats = (quantity, price)=>Math.max(0, quantity != null ? quantity : 0) * (price != null ? price : 0);
1585
- return (annual ? pricing.basePriceAnnualMonthlyUsd : pricing.basePriceMonthlyUsd) + seats(addons.managers, pricing.extraSeatMonthlyUsd) + seats(addons.members, pricing.extraCollaboratorMonthlyUsd) + seats(addons.datasets, pricing.extraDatasetMonthlyUsd) + seats(addons.hosts, pricing.extraHostMonthlyUsd) + seats(addons.posRegisters, POS_REGISTER_ADDON_MONTHLY_USD) + seats(addons.eventCalendar, EVENT_CALENDAR_ADDON_MONTHLY_USD) + // A toggle, not a quantity: one purchase covers the org, so a stored
1586
- // quantity above one bills once.
1587
- seats(aiAddonUnits(addons), pricing.aiAddonMonthlyUsd);
1716
+ return (annual ? pricing.basePriceAnnualMonthlyUsd : pricing.basePriceMonthlyUsd) + seats(addons.managers, pricing.extraSeatMonthlyUsd) + seats(addons.members, pricing.extraCollaboratorMonthlyUsd) + seats(addons.datasets, pricing.extraDatasetMonthlyUsd) + seats(addons.hosts, pricing.extraHostMonthlyUsd) + seats(addons.posRegisters, POS_REGISTER_ADDON_MONTHLY_USD) + seats(addons.eventCalendar, EVENT_CALENDAR_ADDON_MONTHLY_USD) + seats(siteCareUnits(addons), SITE_CARE_ADDON_MONTHLY_USD) + // A toggle, not a quantity: one purchase covers the org, so a stored
1717
+ // quantity above one bills once, at its tier's price (AGL-3965).
1718
+ aiTierMonthlyUsd(aiTierOfPurchase(org));
1719
+ }
1720
+ /** A tier's monthly price, or 0 for no tier. */ function aiTierMonthlyUsd(tier) {
1721
+ return tier ? AI_TIERS[tier].monthlyUsd : 0;
1588
1722
  }
1589
1723
  /**
1590
1724
  * How many Aglyn AI add-ons a purchase map counts as: 0 or 1 (AGL-2896).
@@ -1596,6 +1730,15 @@ function trimmedOrNull(value) {
1596
1730
  const quantity = Number((_ref = addons == null ? void 0 : addons.aiAddon) != null ? _ref : 0);
1597
1731
  return Number.isFinite(quantity) && quantity >= 1 ? 1 : 0;
1598
1732
  }
1733
+ /**
1734
+ * How many Site Care add-ons a purchase map counts as: 0 or 1 (AGL-3954).
1735
+ * Org-wide like the Aglyn AI add-on, so a quantity above one is one
1736
+ * purchase, and a non-number, `NaN` or a negative is none.
1737
+ */ export function siteCareUnits(addons) {
1738
+ var _ref;
1739
+ const quantity = Number((_ref = addons == null ? void 0 : addons.siteCare) != null ? _ref : 0);
1740
+ return Number.isFinite(quantity) && quantity >= 1 ? 1 : 0;
1741
+ }
1599
1742
  /**
1600
1743
  * A list price net of a discount (AGL-1105). Percentage discounts scale the
1601
1744
  * whole bill; fixed discounts subtract a flat USD amount. Never below 0, and
@@ -2401,6 +2544,74 @@ function resolvePlan(org) {
2401
2544
  */ export function hasAiAddon(org) {
2402
2545
  return aiAddonUnits(resolvePurchasedAddons(org)) === 1;
2403
2546
  }
2547
+ /**
2548
+ * Which Aglyn AI tier the org's purchase map holds (AGL-3965), whether or
2549
+ * not the subscription is still paying for it — `resolveAiTier` is the
2550
+ * reading that drops a dead one. `org.aiTier` when it names a tier;
2551
+ * otherwise, for an add-on bought before tiers existed, the tier with that
2552
+ * plan's old price and band (`LEGACY_AI_ADDON_TIER_BY_PLAN`). Null when no
2553
+ * add-on is held or the plan sells no tier (Free, Enterprise).
2554
+ */ export function aiTierOfPurchase(org) {
2555
+ var _LEGACY_AI_ADDON_TIER_BY_PLAN_;
2556
+ if (aiAddonUnits(org == null ? void 0 : org.seatAddons) !== 1) return null;
2557
+ if (!planSellsAiTiers(org == null ? void 0 : org.plan)) return null;
2558
+ if (isAiTierId(org == null ? void 0 : org.aiTier)) return org.aiTier;
2559
+ return (_LEGACY_AI_ADDON_TIER_BY_PLAN_ = LEGACY_AI_ADDON_TIER_BY_PLAN[org == null ? void 0 : org.plan]) != null ? _LEGACY_AI_ADDON_TIER_BY_PLAN_ : null;
2560
+ }
2561
+ /**
2562
+ * The Aglyn AI tier that currently applies (AGL-3965): bought, on a
2563
+ * subscription still paying for it, on a plan that sells tiers. The ONE
2564
+ * reading every surface that shows or counts a tier goes through.
2565
+ */ export function resolveAiTier(org) {
2566
+ return hasAiAddon(org) ? aiTierOfPurchase(org) : null;
2567
+ }
2568
+ /**
2569
+ * What `seatAddons.aiAddon` adds on an Enterprise org (AGL-2896, kept by
2570
+ * AGL-3965): twice Studio's band, the figure the per-plan table carried as
2571
+ * twice Agency's (the rule every Enterprise fallback follows since AGL-2654),
2572
+ * and still what a contract that names the add-on starts from before its own
2573
+ * number is written. Enterprise buys no tier — its AI is in the agreement —
2574
+ * so this is the contract figure kept, not a price; finite, never
2575
+ * `UNLIMITED`, for the reason `ENTERPRISE_ASSIST_CREDITS_PER_MONTH` gives.
2576
+ */ export const ENTERPRISE_AI_ADDON_CREDITS_PER_MONTH = AI_TIERS.studio.credits * 2;
2577
+ /**
2578
+ * The Aglyn AI add-on's per-unit quota as `resolveOrgEntitlements` folds it
2579
+ * (AGL-3965): the purchased tier's credits, on a plan that sells tiers;
2580
+ * Enterprise's contract figure (`ENTERPRISE_AI_ADDON_CREDITS_PER_MONTH`);
2581
+ * else 0 (Free). The AI plugin declares its seat add-on with this as
2582
+ * `quota.perUnit`.
2583
+ */ export function aiTierQuotaPerUnit(org, plan) {
2584
+ if (plan === 'enterprise') return ENTERPRISE_AI_ADDON_CREDITS_PER_MONTH;
2585
+ if (!planSellsAiTiers(plan)) return 0;
2586
+ const tier = aiTierOfPurchase(org);
2587
+ return tier ? AI_TIERS[tier].credits : 0;
2588
+ }
2589
+ /**
2590
+ * The credits a month the org's Aglyn AI add-on adds to its plan's included
2591
+ * band (AGL-3965) — 0 with none held. The SAME figure `resolveOrgEntitlements`
2592
+ * folds into `assistCreditsPerMonth` (`aiTierQuotaPerUnit`), so a surface
2593
+ * quoting "your tier adds" and the meter agree.
2594
+ */ export function aiTierCreditsPerMonth(org) {
2595
+ if (!hasAiAddon(org)) return 0;
2596
+ return aiTierQuotaPerUnit(org, resolveEffectivePlan(org));
2597
+ }
2598
+ /**
2599
+ * Whether this org can buy or change an Aglyn AI tier itself (AGL-3965): a
2600
+ * plan that sells tiers, and not one a staff comp grants — a comp has no
2601
+ * subscription to put the tier on (`compPricing`).
2602
+ */ export function orgCanBuyAiTier(org) {
2603
+ return !resolvePlanComp(org) && planSellsAiTiers(resolveEffectivePlan(org));
2604
+ }
2605
+ /** "Aglyn AI Boost": the product name, then the tier's. */ export const aiTierName = (tier, brand = PLATFORM_BRAND_NAME)=>`${aiAddonName(brand)} ${AI_TIERS[tier].label}`;
2606
+ /**
2607
+ * Whether the org's Site Care add-on currently applies (AGL-3954): bought,
2608
+ * and on a subscription that is still paying for it, so a dead subscription
2609
+ * reads false here exactly as it drops `features.siteCare`. The reader for
2610
+ * surfaces that ask about the purchase rather than the resolved feature —
2611
+ * the staff org page's chip, the request intake.
2612
+ */ export function hasSiteCare(org) {
2613
+ return siteCareUnits(resolvePurchasedAddons(org)) === 1;
2614
+ }
2404
2615
  /**
2405
2616
  * Numeric entitlement keys that were REMOVED from `PLAN_ENTITLEMENTS` and
2406
2617
  * must never be resolved again from a stored override (AGL-2133).
@@ -2484,7 +2695,7 @@ function resolvePlan(org) {
2484
2695
  * per-key overrides applied (features merge key-by-key too), then
2485
2696
  * purchased add-ons stacked on top (AGL-524): `seatAddons.hosts` raises
2486
2697
  * `hostLimit`, `seatAddons.eventCalendar` switches the `eventCalendar`
2487
- * feature on, and every seat add-on a plugin declared through
2698
+ * feature on, `seatAddons.siteCare` switches `siteCare` on, and every seat add-on a plugin declared through
2488
2699
  * `registerPluginEntitlements` (AGL-2940) widens the quota it names by its
2489
2700
  * per-plan figure per unit and switches its features on — the Aglyn AI
2490
2701
  * add-on (AGL-2896) is the first such declaration. (Seat/dataset add-ons
@@ -2589,17 +2800,22 @@ function resolvePlan(org) {
2589
2800
  const purchased = resolvePurchasedAddons(org);
2590
2801
  const extraHosts = Math.max(0, (_purchased_hosts = purchased.hosts) != null ? _purchased_hosts : 0);
2591
2802
  const eventCalendar = ((_purchased_eventCalendar = purchased.eventCalendar) != null ? _purchased_eventCalendar : 0) >= 1;
2803
+ const siteCare = siteCareUnits(purchased) === 1;
2592
2804
  // A declared add-on's quota is added AFTER the per-org override, so a
2593
2805
  // contracted figure and a purchased add-on stack the way a raised
2594
2806
  // `hostLimit` and purchased sites do. The per-unit figure is the PLAN's,
2595
2807
  // read off the effective plan rather than the stored one, so a dead
2596
2808
  // subscription that resolved to free adds free's figure.
2597
- const folded = foldPluginSeatAddons(resolved, purchased, resolvePlan(org));
2598
- if (!extraHosts && !eventCalendar && folded === resolved) return resolved;
2809
+ const folded = foldPluginSeatAddons(resolved, purchased, resolvePlan(org), org != null ? org : {});
2810
+ if (!extraHosts && !eventCalendar && !siteCare && folded === resolved) {
2811
+ return resolved;
2812
+ }
2599
2813
  return _extends({}, folded, {
2600
2814
  hostLimit: folded.hostLimit + extraHosts,
2601
2815
  features: _extends({}, folded.features, eventCalendar ? {
2602
2816
  eventCalendar: true
2817
+ } : {}, siteCare ? {
2818
+ siteCare: true
2603
2819
  } : {})
2604
2820
  });
2605
2821
  }
@@ -2631,7 +2847,7 @@ function resolvePlan(org) {
2631
2847
  * quota each names gains its per-plan figure per unit, and its features
2632
2848
  * switch on. Returns the input object untouched when nothing applies, so a
2633
2849
  * caller can tell "nothing folded" from "folded to the same numbers".
2634
- */ function foldPluginSeatAddons(resolved, purchased, plan) {
2850
+ */ function foldPluginSeatAddons(resolved, purchased, plan, org) {
2635
2851
  let out = resolved;
2636
2852
  for (const addon of listPluginSeatAddons()){
2637
2853
  var _addon_features;
@@ -2641,8 +2857,9 @@ function resolvePlan(org) {
2641
2857
  features: _extends({}, out.features)
2642
2858
  });
2643
2859
  if (addon.quota) {
2644
- var _addon_quota_perUnitByPlan_plan, _bag_addon_quota_key;
2645
- const perUnit = (_addon_quota_perUnitByPlan_plan = addon.quota.perUnitByPlan[plan]) != null ? _addon_quota_perUnitByPlan_plan : 0;
2860
+ var _ref, _bag_addon_quota_key;
2861
+ var _addon_quota_perUnitByPlan;
2862
+ const perUnit = addon.quota.perUnit ? addon.quota.perUnit(org, plan) : (_ref = (_addon_quota_perUnitByPlan = addon.quota.perUnitByPlan) == null ? void 0 : _addon_quota_perUnitByPlan[plan]) != null ? _ref : 0;
2646
2863
  const bag = next;
2647
2864
  const current = Number((_bag_addon_quota_key = bag[addon.quota.key]) != null ? _bag_addon_quota_key : 0);
2648
2865
  bag[addon.quota.key] = (Number.isFinite(current) ? current : 0) + perUnit * units;