@aglyn/aglyn 1.0.0-beta.270 → 1.0.0-beta.272
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/package.json +11 -11
- package/src/lib/app-utils/account-preferences.d.ts +50 -0
- package/src/lib/app-utils/account-preferences.js +96 -0
- package/src/lib/app-utils/account-preferences.js.map +1 -1
- package/src/lib/app-utils/activity-labels.js +2 -0
- package/src/lib/app-utils/activity-labels.js.map +1 -1
- package/src/lib/app-utils/analytics-events.d.ts +16 -2
- package/src/lib/app-utils/analytics-events.js +2 -0
- package/src/lib/app-utils/analytics-events.js.map +1 -1
- package/src/lib/app-utils/append-only-history.d.ts +94 -0
- package/src/lib/app-utils/append-only-history.js +73 -0
- package/src/lib/app-utils/append-only-history.js.map +1 -0
- package/src/lib/app-utils/bundle-intent.d.ts +186 -0
- package/src/lib/app-utils/bundle-intent.js +386 -0
- package/src/lib/app-utils/bundle-intent.js.map +1 -0
- package/src/lib/app-utils/client-build.d.ts +207 -0
- package/src/lib/app-utils/client-build.js +237 -0
- package/src/lib/app-utils/client-build.js.map +1 -0
- package/src/lib/app-utils/console-routes.d.ts +4 -0
- package/src/lib/app-utils/console-routes.js +2 -0
- package/src/lib/app-utils/console-routes.js.map +1 -1
- package/src/lib/app-utils/docs-help-sections.generated.js +13 -6
- package/src/lib/app-utils/docs-help-sections.generated.js.map +1 -1
- package/src/lib/app-utils/docs-help.generated.d.ts +11 -5
- package/src/lib/app-utils/docs-help.generated.js +31 -2
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.js +105 -35
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/margin-utilization.js +6 -4
- package/src/lib/app-utils/margin-utilization.js.map +1 -1
- package/src/lib/app-utils/media-filter.d.ts +26 -0
- package/src/lib/app-utils/media-filter.js +32 -0
- package/src/lib/app-utils/media-filter.js.map +1 -1
- package/src/lib/app-utils/onboarding-deep-link.d.ts +36 -2
- package/src/lib/app-utils/onboarding-deep-link.js +48 -5
- package/src/lib/app-utils/onboarding-deep-link.js.map +1 -1
- package/src/lib/app-utils/operator-alerts.js +21 -0
- package/src/lib/app-utils/operator-alerts.js.map +1 -1
- package/src/lib/app-utils/owner-approval-record.d.ts +102 -0
- package/src/lib/app-utils/owner-approval-record.js +148 -0
- package/src/lib/app-utils/owner-approval-record.js.map +1 -0
- package/src/lib/app-utils/plan-card-meters.d.ts +96 -0
- package/src/lib/app-utils/plan-card-meters.js +197 -0
- package/src/lib/app-utils/plan-card-meters.js.map +1 -0
- package/src/lib/app-utils/plan-entitlements.d.ts +183 -67
- package/src/lib/app-utils/plan-entitlements.js +213 -58
- package/src/lib/app-utils/plan-entitlements.js.map +1 -1
- package/src/lib/app-utils/release-flags.d.ts +1 -1
- package/src/lib/app-utils/release-flags.js +22 -0
- package/src/lib/app-utils/release-flags.js.map +1 -1
- package/src/lib/app-utils/server.d.ts +4 -0
- package/src/lib/app-utils/server.js +7 -0
- package/src/lib/app-utils/server.js.map +1 -1
- package/src/lib/app-utils/site-care-project.d.ts +227 -0
- package/src/lib/app-utils/site-care-project.js +423 -0
- package/src/lib/app-utils/site-care-project.js.map +1 -0
- package/src/lib/app-utils/site-care.d.ts +291 -0
- package/src/lib/app-utils/site-care.js +310 -0
- package/src/lib/app-utils/site-care.js.map +1 -0
- package/src/lib/app-utils/upgrade-proposal.d.ts +29 -4
- package/src/lib/app-utils/upgrade-proposal.js +60 -9
- package/src/lib/app-utils/upgrade-proposal.js.map +1 -1
- package/src/lib/foundation/definitions/org-billing.types.d.ts +40 -10
- package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
- package/src/lib/foundation/definitions/organization.types.d.ts +3 -1
- package/src/lib/foundation/definitions/organization.types.js.map +1 -1
- package/src/lib/foundation/definitions/platform.types.d.ts +20 -0
- package/src/lib/foundation/definitions/platform.types.js.map +1 -1
- package/src/lib/foundation/index.d.ts +1 -1
- package/src/lib/foundation/index.js.map +1 -1
- package/src/lib/plugin-manager/feature-plugins.d.ts +48 -0
- package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
- package/src/lib/plugin-manager/first-party-plugins.generated.js +16 -0
- package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
- package/src/lib/plugin-manager/plugin-ai-capabilities.d.ts +19 -0
- package/src/lib/plugin-manager/plugin-ai-capabilities.js.map +1 -1
- package/src/lib/plugin-manager/plugin-domain-search.d.ts +81 -0
- package/src/lib/plugin-manager/plugin-domain-search.js +63 -0
- package/src/lib/plugin-manager/plugin-domain-search.js.map +1 -0
- package/src/lib/plugin-manager/plugin-entitlements.d.ts +23 -5
- package/src/lib/plugin-manager/plugin-entitlements.js +10 -0
- package/src/lib/plugin-manager/plugin-entitlements.js.map +1 -1
- package/src/lib/plugin-manager/plugin-media-ingest.d.ts +18 -1
- package/src/lib/plugin-manager/plugin-media-ingest.js.map +1 -1
- package/src/lib/plugin-manager/plugin-site-resources.d.ts +74 -0
- package/src/lib/plugin-manager/plugin-site-resources.js +135 -0
- package/src/lib/plugin-manager/plugin-site-resources.js.map +1 -0
- package/src/lib/plugin-manager/purchase-gate-preclearance.d.ts +61 -0
- package/src/lib/plugin-manager/purchase-gate-preclearance.js +102 -0
- package/src/lib/plugin-manager/purchase-gate-preclearance.js.map +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/owner-approval-record.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * THE OWNER'S RECORDED YES (AGL-3959).\n *\n * Paid work beyond what a subscription includes is billed only after the\n * owner agreed to it, and the owner never clicks anything in the console to\n * agree: they say yes to a person — at the booth, on the phone, by text or by\n * email — and STAFF record how and when. Saving the record emails the owner a\n * confirmation, and that email is the written record.\n *\n * This module is the record's shape and its rules, with no Firestore and no\n * product in it, so any staff surface that bills by an approval reads it the\n * same way: Site Care's extra time first, and the flat-quoted project invoice\n * (AGL-3970) after it. A correction never edits a record in place: it is a\n * new record with `version` one higher, `corrected: true` and the reason, and\n * the old one stays in the request's history.\n */\n\n/** How the owner said yes. */\nexport const OWNER_APPROVAL_CHANNELS = ['in-person', 'phone', 'text', 'email'] as const\n\nexport type OwnerApprovalChannel = (typeof OWNER_APPROVAL_CHANNELS)[number]\n\n/** The channel as a sentence reads it: \"by phone\", \"in person\". */\nexport const OWNER_APPROVAL_CHANNEL_WORDS: Readonly<Record<OwnerApprovalChannel, string>> = {\n 'in-person': 'in person',\n phone: 'by phone',\n text: 'by text message',\n email: 'by email',\n}\n\n/** The channel as a picker labels it. */\nexport const OWNER_APPROVAL_CHANNEL_LABELS: Readonly<Record<OwnerApprovalChannel, string>> = {\n 'in-person': 'In person',\n phone: 'Phone call',\n text: 'Text message',\n email: 'Email',\n}\n\n/** The longest approval an hourly record may carry, in hours. */\nexport const OWNER_APPROVAL_MAX_HOURS = 40\n\n/** The longest note or reason a record keeps. */\nexport const OWNER_APPROVAL_NOTE_MAX = 500\n\n/** One recorded yes. Written by staff, read by the close and the emails. */\nexport interface OwnerApprovalRecord {\n /** Whole hours the owner agreed to, at most. */\n hours: number\n /** The published hourly rate the owner agreed to, in whole dollars. */\n rateUsd: number\n channel: OwnerApprovalChannel\n /** When the owner said yes, epoch ms. */\n approvedAtMs: number\n /** When staff recorded it, epoch ms. */\n recordedAtMs: number\n recordedByUid: string\n recordedByEmail: string | null\n /** What the owner said yes to, in staff's words; optional. */\n note: string | null\n /** 1 for the first record; each correction is one higher. */\n version: number\n /** True on every record after the first. */\n corrected: boolean\n /** Why it was corrected; required when `corrected`. */\n correctionReason: string | null\n}\n\nexport type OwnerApprovalInputResult =\n | { ok: true; record: OwnerApprovalRecord }\n | { ok: false; error: string }\n\n/** A trimmed, bounded string, or null when blank. */\nfunction cleanText(value: unknown, max: number): string | null {\n const text = typeof value === 'string' ? value.trim().slice(0, max) : ''\n return text ? text : null\n}\n\n/**\n * Reads what staff typed into a record, refusing anything the record cannot\n * honestly say. `previous` is the record it corrects, when there is one: a\n * correction needs a reason, and the reason is what the history and the\n * re-sent confirmation say changed.\n */\nexport function readOwnerApprovalInput(\n raw: Readonly<Record<string, unknown>> | null | undefined,\n context: {\n rateUsd: number\n nowMs: number\n by: { uid: string; email: string | null }\n previous?: OwnerApprovalRecord | null\n /**\n * The most hours the record may carry; `OWNER_APPROVAL_MAX_HOURS` unless\n * the surface says otherwise. A flat-quoted project (AGL-3970) records\n * the quote's own whole hours, which a redesign can take past a week.\n */\n maxHours?: number\n },\n): OwnerApprovalInputResult {\n const hours = Number(raw?.['hours'])\n const maxHours = context.maxHours ?? OWNER_APPROVAL_MAX_HOURS\n if (!Number.isInteger(hours) || hours < 1 || hours > maxHours) {\n return {\n ok: false,\n error: `Enter the hours the owner approved, a whole number from 1 to ${maxHours}.`,\n }\n }\n const channel = String(raw?.['channel'] ?? '') as OwnerApprovalChannel\n if (!OWNER_APPROVAL_CHANNELS.includes(channel)) {\n return { ok: false, error: 'Choose how the owner said yes.' }\n }\n const approvedAtMs = Number(raw?.['approvedAtMs'])\n if (!Number.isFinite(approvedAtMs) || approvedAtMs <= 0) {\n return { ok: false, error: 'Enter when the owner said yes.' }\n }\n // A minute of clock skew is allowed; a yes recorded for tomorrow is not one\n // anybody has heard yet.\n if (approvedAtMs > context.nowMs + 60_000) {\n return { ok: false, error: 'The owner’s yes cannot be in the future.' }\n }\n const previous = context.previous ?? null\n const correctionReason = cleanText(raw?.['reason'], OWNER_APPROVAL_NOTE_MAX)\n if (previous && !correctionReason) {\n return { ok: false, error: 'Say why the approval is being corrected.' }\n }\n return {\n ok: true,\n record: {\n hours,\n rateUsd: context.rateUsd,\n channel,\n approvedAtMs: Math.floor(approvedAtMs),\n recordedAtMs: context.nowMs,\n recordedByUid: context.by.uid,\n recordedByEmail: context.by.email,\n note: cleanText(raw?.['note'], OWNER_APPROVAL_NOTE_MAX),\n version: previous ? Math.max(1, Number(previous.version) || 1) + 1 : 1,\n corrected: Boolean(previous),\n correctionReason: previous ? correctionReason : null,\n },\n }\n}\n\n/** A stored record read back defensively, or null when it is not one. */\nexport function readOwnerApprovalRecord(value: unknown): OwnerApprovalRecord | null {\n if (!value || typeof value !== 'object') return null\n const record = value as Partial<OwnerApprovalRecord>\n const hours = Number(record.hours)\n if (!Number.isInteger(hours) || hours < 1) return null\n if (!OWNER_APPROVAL_CHANNELS.includes(record.channel as OwnerApprovalChannel)) return null\n return {\n hours,\n rateUsd: Number(record.rateUsd) || 0,\n channel: record.channel as OwnerApprovalChannel,\n approvedAtMs: Number(record.approvedAtMs) || 0,\n recordedAtMs: Number(record.recordedAtMs) || 0,\n recordedByUid: String(record.recordedByUid ?? ''),\n recordedByEmail: record.recordedByEmail ?? null,\n note: record.note ?? null,\n version: Number(record.version) || 1,\n corrected: record.corrected === true,\n correctionReason: record.correctionReason ?? null,\n }\n}\n\n/** \"2 hours\", \"1 hour\". */\nexport function describeHours(hours: number): string {\n return `${hours} ${hours === 1 ? 'hour' : 'hours'}`\n}\n\n/**\n * The confirmation's one sentence, in the owner's words: what they agreed\n * to, how and when.\n */\nexport function ownerApprovalSentence(\n record: Pick<OwnerApprovalRecord, 'hours' | 'rateUsd' | 'channel' | 'approvedAtMs'>,\n what: string,\n formatDate: (ms: number) => string,\n): string {\n return (\n `You approved up to ${describeHours(record.hours)} of extra time at ` +\n `$${record.rateUsd} an hour for “${what}”, ` +\n `${OWNER_APPROVAL_CHANNEL_WORDS[record.channel]} on ${formatDate(record.approvedAtMs)}.`\n )\n}\n"],"names":["OWNER_APPROVAL_CHANNELS","OWNER_APPROVAL_CHANNEL_WORDS","phone","text","email","OWNER_APPROVAL_CHANNEL_LABELS","OWNER_APPROVAL_MAX_HOURS","OWNER_APPROVAL_NOTE_MAX","cleanText","value","max","trim","slice","readOwnerApprovalInput","raw","context","hours","Number","maxHours","isInteger","ok","error","channel","String","includes","approvedAtMs","isFinite","nowMs","previous","correctionReason","record","rateUsd","Math","floor","recordedAtMs","recordedByUid","by","uid","recordedByEmail","note","version","corrected","Boolean","readOwnerApprovalRecord","describeHours","ownerApprovalSentence","what","formatDate"],"mappings":"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"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* The native apps' plan card (AGL-3992): the usage meters it rotates through,
|
|
19
|
+
* which one leads for what is on screen, and in what order the rest follow.
|
|
20
|
+
*
|
|
21
|
+
* ONE mapping for both apps, generated into the native contracts, so the
|
|
22
|
+
* Apple and Kotlin cards cannot drift. Every meter here is one the console's
|
|
23
|
+
* Billing › Usage page already meters, read from the same source it reads,
|
|
24
|
+
* against the same entitlement; none is invented for the card:
|
|
25
|
+
*
|
|
26
|
+
* | meter | Usage page label | used | limit |
|
|
27
|
+
* | ---------------- | --------------------------------------------- | -------------------------------------- | --------------------------- |
|
|
28
|
+
* | `storage` | Storage (organization) | `/api/media/storage` usedBytes | its allowanceMb |
|
|
29
|
+
* | `bandwidth` | Bandwidth (this month, organization) | `/api/billing/host-usage`, every site + the org | `bandwidthGb` |
|
|
30
|
+
* | `emailSends` | Campaign emails (this month, organization) | `orgs/{org}/counters/campaignEmailSends` | `emailSendsPerMonth` |
|
|
31
|
+
* | `aiCredits` | AI credits (this month) | `/api/ai/billing/credits` | `assistCreditsPerMonth` |
|
|
32
|
+
* | `dataStorage` | Data storage (organization) | `orgs/{org}/usage/{month}` dataStorageMb | `dataStorageMbPerOrg` |
|
|
33
|
+
* | `seats` | Team seats (incl. you) | `/api/orgs/members?counts=1` | the managers seat quota |
|
|
34
|
+
* | `pages` | Pages (a site's) | `/api/hosts/usage` screens | `screensPerHost` |
|
|
35
|
+
* | `forms` | Saved forms (a site's) | `hosts/{host}/forms` count | `formsPerHost` |
|
|
36
|
+
* | `sites` | Sites | the workspace's sites | `hostLimit` |
|
|
37
|
+
*
|
|
38
|
+
* A label is the page's own, without its parenthesis ("(this month,
|
|
39
|
+
* organization)", "(incl. you)"): the card is one line wide and already headed
|
|
40
|
+
* by the workspace and plan, and a tap opens the Usage page that says the rest.
|
|
41
|
+
*/
|
|
42
|
+
declare const METERS: readonly ["storage", "bandwidth", "emailSends", "aiCredits", "dataStorage", "seats", "pages", "forms", "sites"];
|
|
43
|
+
export type PlanCardMeter = (typeof METERS)[number];
|
|
44
|
+
/**
|
|
45
|
+
* The meters the card can show, in the order a tie between two equally full
|
|
46
|
+
* meters is broken. Plain `string[]` and records below, as the native
|
|
47
|
+
* contracts generator reads values; `satisfies` keeps them total.
|
|
48
|
+
*/
|
|
49
|
+
export declare const PLAN_CARD_METERS: string[];
|
|
50
|
+
/** Each meter's label: the Usage page's own words, without their parenthesis. */
|
|
51
|
+
export declare const PLAN_CARD_METER_LABELS: Record<string, string>;
|
|
52
|
+
/** The unit a meter's figures are written in, as the Usage page writes them; empty for a count. */
|
|
53
|
+
export declare const PLAN_CARD_METER_UNITS: Record<string, string>;
|
|
54
|
+
declare const CONTEXTS: readonly ["media", "website", "email", "ai", "forms", "data", "team", "sites"];
|
|
55
|
+
export type PlanCardContext = (typeof CONTEXTS)[number];
|
|
56
|
+
/** What the screen in front is about, for the meter that leads. */
|
|
57
|
+
export declare const PLAN_CARD_CONTEXTS: string[];
|
|
58
|
+
/** The meters that lead for each context, in order; the rest follow by how full they are. */
|
|
59
|
+
export declare const PLAN_CARD_CONTEXT_METERS: Record<string, string[]>;
|
|
60
|
+
/**
|
|
61
|
+
* Each screen's context, by the screen id the native nav manifest opens (its
|
|
62
|
+
* rows' `screen`). A screen not named here has none, and the fullest meter
|
|
63
|
+
* leads. A site's Home is the website's front door; the workspace's Home has
|
|
64
|
+
* no context (`planCardContext`).
|
|
65
|
+
*/
|
|
66
|
+
export declare const PLAN_CARD_SCREEN_CONTEXTS: Record<string, string>;
|
|
67
|
+
/** The screen id of Home, which is a site's website when the window stands in a site. */
|
|
68
|
+
export declare const PLAN_CARD_HOME_SCREEN = "shell.home";
|
|
69
|
+
/** How long one meter stays before the card moves on, slowly (Zach: "auto rotates slowly"). */
|
|
70
|
+
export declare const PLAN_CARD_ADVANCE_MS = 8000;
|
|
71
|
+
/** The share from which a meter warns, as the console's meter does (`USAGE_METER_WARNING_PCT`, 80). */
|
|
72
|
+
export declare const PLAN_CARD_WARNING_SHARE = 0.8;
|
|
73
|
+
/** One meter's reading: `null` where the read failed or the plan does not cap it. */
|
|
74
|
+
export interface PlanCardReading {
|
|
75
|
+
meter: string;
|
|
76
|
+
used: number | null;
|
|
77
|
+
limit: number | null;
|
|
78
|
+
}
|
|
79
|
+
/** The context of the screen in front: a site's Home is its website; a screen not mapped has none. */
|
|
80
|
+
export declare function planCardContext(screen: string | null, inSite: boolean): string | null;
|
|
81
|
+
/**
|
|
82
|
+
* The order the card shows its meters in: the context's own meters first, in
|
|
83
|
+
* the mapping's order, then every other meter fullest first (a tie keeps
|
|
84
|
+
* `PLAN_CARD_METERS`' order). A meter that failed to read or that the plan
|
|
85
|
+
* does not cap is left out. Sites leads only on the Sites list: elsewhere it
|
|
86
|
+
* follows every other meter, however full, because a missing site is the
|
|
87
|
+
* limit a workspace is least likely to upgrade for.
|
|
88
|
+
*/
|
|
89
|
+
export declare function planCardMeterOrder(readings: readonly PlanCardReading[], context: string | null): string[];
|
|
90
|
+
/**
|
|
91
|
+
* Whether the card shows at all: a meter to draw, and either a plan the viewer
|
|
92
|
+
* can upgrade (the workspace menu's "Upgrade plan" rule) or a meter at its
|
|
93
|
+
* warning share, which every plan is told about.
|
|
94
|
+
*/
|
|
95
|
+
export declare function planCardShows(readings: readonly PlanCardReading[], offersUpgrade: boolean): boolean;
|
|
96
|
+
export {};
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ /**
|
|
17
|
+
* The native apps' plan card (AGL-3992): the usage meters it rotates through,
|
|
18
|
+
* which one leads for what is on screen, and in what order the rest follow.
|
|
19
|
+
*
|
|
20
|
+
* ONE mapping for both apps, generated into the native contracts, so the
|
|
21
|
+
* Apple and Kotlin cards cannot drift. Every meter here is one the console's
|
|
22
|
+
* Billing › Usage page already meters, read from the same source it reads,
|
|
23
|
+
* against the same entitlement; none is invented for the card:
|
|
24
|
+
*
|
|
25
|
+
* | meter | Usage page label | used | limit |
|
|
26
|
+
* | ---------------- | --------------------------------------------- | -------------------------------------- | --------------------------- |
|
|
27
|
+
* | `storage` | Storage (organization) | `/api/media/storage` usedBytes | its allowanceMb |
|
|
28
|
+
* | `bandwidth` | Bandwidth (this month, organization) | `/api/billing/host-usage`, every site + the org | `bandwidthGb` |
|
|
29
|
+
* | `emailSends` | Campaign emails (this month, organization) | `orgs/{org}/counters/campaignEmailSends` | `emailSendsPerMonth` |
|
|
30
|
+
* | `aiCredits` | AI credits (this month) | `/api/ai/billing/credits` | `assistCreditsPerMonth` |
|
|
31
|
+
* | `dataStorage` | Data storage (organization) | `orgs/{org}/usage/{month}` dataStorageMb | `dataStorageMbPerOrg` |
|
|
32
|
+
* | `seats` | Team seats (incl. you) | `/api/orgs/members?counts=1` | the managers seat quota |
|
|
33
|
+
* | `pages` | Pages (a site's) | `/api/hosts/usage` screens | `screensPerHost` |
|
|
34
|
+
* | `forms` | Saved forms (a site's) | `hosts/{host}/forms` count | `formsPerHost` |
|
|
35
|
+
* | `sites` | Sites | the workspace's sites | `hostLimit` |
|
|
36
|
+
*
|
|
37
|
+
* A label is the page's own, without its parenthesis ("(this month,
|
|
38
|
+
* organization)", "(incl. you)"): the card is one line wide and already headed
|
|
39
|
+
* by the workspace and plan, and a tap opens the Usage page that says the rest.
|
|
40
|
+
*/ const METERS = [
|
|
41
|
+
'storage',
|
|
42
|
+
'bandwidth',
|
|
43
|
+
'emailSends',
|
|
44
|
+
'aiCredits',
|
|
45
|
+
'dataStorage',
|
|
46
|
+
'seats',
|
|
47
|
+
'pages',
|
|
48
|
+
'forms',
|
|
49
|
+
'sites'
|
|
50
|
+
];
|
|
51
|
+
/**
|
|
52
|
+
* The meters the card can show, in the order a tie between two equally full
|
|
53
|
+
* meters is broken. Plain `string[]` and records below, as the native
|
|
54
|
+
* contracts generator reads values; `satisfies` keeps them total.
|
|
55
|
+
*/ export const PLAN_CARD_METERS = [
|
|
56
|
+
...METERS
|
|
57
|
+
];
|
|
58
|
+
/** Each meter's label: the Usage page's own words, without their parenthesis. */ export const PLAN_CARD_METER_LABELS = {
|
|
59
|
+
storage: 'Storage',
|
|
60
|
+
bandwidth: 'Bandwidth',
|
|
61
|
+
emailSends: 'Campaign emails',
|
|
62
|
+
aiCredits: 'AI credits',
|
|
63
|
+
dataStorage: 'Data storage',
|
|
64
|
+
seats: 'Team seats',
|
|
65
|
+
pages: 'Pages',
|
|
66
|
+
forms: 'Saved forms',
|
|
67
|
+
sites: 'Sites'
|
|
68
|
+
};
|
|
69
|
+
/** The unit a meter's figures are written in, as the Usage page writes them; empty for a count. */ export const PLAN_CARD_METER_UNITS = {
|
|
70
|
+
storage: 'MB',
|
|
71
|
+
bandwidth: 'GB',
|
|
72
|
+
emailSends: '',
|
|
73
|
+
aiCredits: '',
|
|
74
|
+
dataStorage: 'MB',
|
|
75
|
+
seats: '',
|
|
76
|
+
pages: '',
|
|
77
|
+
forms: '',
|
|
78
|
+
sites: ''
|
|
79
|
+
};
|
|
80
|
+
const CONTEXTS = [
|
|
81
|
+
'media',
|
|
82
|
+
'website',
|
|
83
|
+
'email',
|
|
84
|
+
'ai',
|
|
85
|
+
'forms',
|
|
86
|
+
'data',
|
|
87
|
+
'team',
|
|
88
|
+
'sites'
|
|
89
|
+
];
|
|
90
|
+
/** What the screen in front is about, for the meter that leads. */ export const PLAN_CARD_CONTEXTS = [
|
|
91
|
+
...CONTEXTS
|
|
92
|
+
];
|
|
93
|
+
/** The meters that lead for each context, in order; the rest follow by how full they are. */ export const PLAN_CARD_CONTEXT_METERS = {
|
|
94
|
+
media: [
|
|
95
|
+
'storage'
|
|
96
|
+
],
|
|
97
|
+
website: [
|
|
98
|
+
'bandwidth'
|
|
99
|
+
],
|
|
100
|
+
email: [
|
|
101
|
+
'emailSends'
|
|
102
|
+
],
|
|
103
|
+
ai: [
|
|
104
|
+
'aiCredits'
|
|
105
|
+
],
|
|
106
|
+
forms: [
|
|
107
|
+
'forms'
|
|
108
|
+
],
|
|
109
|
+
data: [
|
|
110
|
+
'dataStorage'
|
|
111
|
+
],
|
|
112
|
+
team: [
|
|
113
|
+
'seats'
|
|
114
|
+
],
|
|
115
|
+
sites: [
|
|
116
|
+
'sites'
|
|
117
|
+
]
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* Each screen's context, by the screen id the native nav manifest opens (its
|
|
121
|
+
* rows' `screen`). A screen not named here has none, and the fullest meter
|
|
122
|
+
* leads. A site's Home is the website's front door; the workspace's Home has
|
|
123
|
+
* no context (`planCardContext`).
|
|
124
|
+
*/ export const PLAN_CARD_SCREEN_CONTEXTS = {
|
|
125
|
+
'site.media': 'media',
|
|
126
|
+
'site.pages': 'website',
|
|
127
|
+
'logic.page': 'website',
|
|
128
|
+
'shell.analytics': 'website',
|
|
129
|
+
'email.messages': 'email',
|
|
130
|
+
'marketing.overview': 'email',
|
|
131
|
+
'outreach.sequences': 'email',
|
|
132
|
+
'ai.jobs': 'ai',
|
|
133
|
+
'ai.credits': 'ai',
|
|
134
|
+
'forms.list': 'forms',
|
|
135
|
+
'data.datasets': 'data',
|
|
136
|
+
'core.team': 'team',
|
|
137
|
+
'core.team.roles': 'team',
|
|
138
|
+
'core.team.activity': 'team',
|
|
139
|
+
'core.site.users': 'team',
|
|
140
|
+
'site.sites': 'sites'
|
|
141
|
+
};
|
|
142
|
+
/** The screen id of Home, which is a site's website when the window stands in a site. */ export const PLAN_CARD_HOME_SCREEN = 'shell.home';
|
|
143
|
+
/** How long one meter stays before the card moves on, slowly (Zach: "auto rotates slowly"). */ export const PLAN_CARD_ADVANCE_MS = 8000;
|
|
144
|
+
/** The share from which a meter warns, as the console's meter does (`USAGE_METER_WARNING_PCT`, 80). */ export const PLAN_CARD_WARNING_SHARE = 0.8;
|
|
145
|
+
/** The context of the screen in front: a site's Home is its website; a screen not mapped has none. */ export function planCardContext(screen, inSite) {
|
|
146
|
+
var _PLAN_CARD_SCREEN_CONTEXTS_screen;
|
|
147
|
+
if (!screen) return null;
|
|
148
|
+
if (screen === PLAN_CARD_HOME_SCREEN) return inSite ? 'website' : null;
|
|
149
|
+
return (_PLAN_CARD_SCREEN_CONTEXTS_screen = PLAN_CARD_SCREEN_CONTEXTS[screen]) != null ? _PLAN_CARD_SCREEN_CONTEXTS_screen : null;
|
|
150
|
+
}
|
|
151
|
+
/** A meter the card can draw: read, capped (an unlimited quota arrives as `null`), and with something to cap. */ function drawable(reading) {
|
|
152
|
+
return typeof reading.used === 'number' && Number.isFinite(reading.used) && typeof reading.limit === 'number' && Number.isFinite(reading.limit) && reading.limit > 0;
|
|
153
|
+
}
|
|
154
|
+
function rank(meter) {
|
|
155
|
+
const index = PLAN_CARD_METERS.indexOf(meter);
|
|
156
|
+
return index < 0 ? PLAN_CARD_METERS.length : index;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* The order the card shows its meters in: the context's own meters first, in
|
|
160
|
+
* the mapping's order, then every other meter fullest first (a tie keeps
|
|
161
|
+
* `PLAN_CARD_METERS`' order). A meter that failed to read or that the plan
|
|
162
|
+
* does not cap is left out. Sites leads only on the Sites list: elsewhere it
|
|
163
|
+
* follows every other meter, however full, because a missing site is the
|
|
164
|
+
* limit a workspace is least likely to upgrade for.
|
|
165
|
+
*/ export function planCardMeterOrder(readings, context) {
|
|
166
|
+
const shown = readings.filter(drawable);
|
|
167
|
+
const seen = new Set();
|
|
168
|
+
const unique = shown.filter((reading)=>{
|
|
169
|
+
if (seen.has(reading.meter)) return false;
|
|
170
|
+
seen.add(reading.meter);
|
|
171
|
+
return true;
|
|
172
|
+
});
|
|
173
|
+
const leading = context && PLAN_CARD_CONTEXT_METERS[context] || [];
|
|
174
|
+
const lead = leading.filter((meter)=>unique.some((reading)=>reading.meter === meter));
|
|
175
|
+
const rest = unique.filter((reading)=>!lead.includes(reading.meter)).sort((a, b)=>{
|
|
176
|
+
const aSites = a.meter === 'sites';
|
|
177
|
+
const bSites = b.meter === 'sites';
|
|
178
|
+
if (aSites !== bSites) return aSites ? 1 : -1;
|
|
179
|
+
const byShare = b.used / b.limit - a.used / a.limit;
|
|
180
|
+
return byShare !== 0 ? byShare : rank(a.meter) - rank(b.meter);
|
|
181
|
+
}).map((reading)=>reading.meter);
|
|
182
|
+
return [
|
|
183
|
+
...lead,
|
|
184
|
+
...rest
|
|
185
|
+
];
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Whether the card shows at all: a meter to draw, and either a plan the viewer
|
|
189
|
+
* can upgrade (the workspace menu's "Upgrade plan" rule) or a meter at its
|
|
190
|
+
* warning share, which every plan is told about.
|
|
191
|
+
*/ export function planCardShows(readings, offersUpgrade) {
|
|
192
|
+
const shown = readings.filter(drawable);
|
|
193
|
+
if (!shown.length) return false;
|
|
194
|
+
return offersUpgrade || shown.some((reading)=>reading.used / reading.limit >= PLAN_CARD_WARNING_SHARE);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
//# sourceMappingURL=plan-card-meters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/plan-card-meters.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The native apps' plan card (AGL-3992): the usage meters it rotates through,\n * which one leads for what is on screen, and in what order the rest follow.\n *\n * ONE mapping for both apps, generated into the native contracts, so the\n * Apple and Kotlin cards cannot drift. Every meter here is one the console's\n * Billing › Usage page already meters, read from the same source it reads,\n * against the same entitlement; none is invented for the card:\n *\n * | meter | Usage page label | used | limit |\n * | ---------------- | --------------------------------------------- | -------------------------------------- | --------------------------- |\n * | `storage` | Storage (organization) | `/api/media/storage` usedBytes | its allowanceMb |\n * | `bandwidth` | Bandwidth (this month, organization) | `/api/billing/host-usage`, every site + the org | `bandwidthGb` |\n * | `emailSends` | Campaign emails (this month, organization) | `orgs/{org}/counters/campaignEmailSends` | `emailSendsPerMonth` |\n * | `aiCredits` | AI credits (this month) | `/api/ai/billing/credits` | `assistCreditsPerMonth` |\n * | `dataStorage` | Data storage (organization) | `orgs/{org}/usage/{month}` dataStorageMb | `dataStorageMbPerOrg` |\n * | `seats` | Team seats (incl. you) | `/api/orgs/members?counts=1` | the managers seat quota |\n * | `pages` | Pages (a site's) | `/api/hosts/usage` screens | `screensPerHost` |\n * | `forms` | Saved forms (a site's) | `hosts/{host}/forms` count | `formsPerHost` |\n * | `sites` | Sites | the workspace's sites | `hostLimit` |\n *\n * A label is the page's own, without its parenthesis (\"(this month,\n * organization)\", \"(incl. you)\"): the card is one line wide and already headed\n * by the workspace and plan, and a tap opens the Usage page that says the rest.\n */\n\nconst METERS = [\n 'storage',\n 'bandwidth',\n 'emailSends',\n 'aiCredits',\n 'dataStorage',\n 'seats',\n 'pages',\n 'forms',\n 'sites',\n] as const\n\nexport type PlanCardMeter = (typeof METERS)[number]\n\n/**\n * The meters the card can show, in the order a tie between two equally full\n * meters is broken. Plain `string[]` and records below, as the native\n * contracts generator reads values; `satisfies` keeps them total.\n */\nexport const PLAN_CARD_METERS: string[] = [...METERS]\n\n/** Each meter's label: the Usage page's own words, without their parenthesis. */\nexport const PLAN_CARD_METER_LABELS: Record<string, string> = {\n storage: 'Storage',\n bandwidth: 'Bandwidth',\n emailSends: 'Campaign emails',\n aiCredits: 'AI credits',\n dataStorage: 'Data storage',\n seats: 'Team seats',\n pages: 'Pages',\n forms: 'Saved forms',\n sites: 'Sites',\n} satisfies Record<PlanCardMeter, string>\n\n/** The unit a meter's figures are written in, as the Usage page writes them; empty for a count. */\nexport const PLAN_CARD_METER_UNITS: Record<string, string> = {\n storage: 'MB',\n bandwidth: 'GB',\n emailSends: '',\n aiCredits: '',\n dataStorage: 'MB',\n seats: '',\n pages: '',\n forms: '',\n sites: '',\n} satisfies Record<PlanCardMeter, string>\n\nconst CONTEXTS = ['media', 'website', 'email', 'ai', 'forms', 'data', 'team', 'sites'] as const\n\nexport type PlanCardContext = (typeof CONTEXTS)[number]\n\n/** What the screen in front is about, for the meter that leads. */\nexport const PLAN_CARD_CONTEXTS: string[] = [...CONTEXTS]\n\n/** The meters that lead for each context, in order; the rest follow by how full they are. */\nexport const PLAN_CARD_CONTEXT_METERS: Record<string, string[]> = {\n media: ['storage'],\n website: ['bandwidth'],\n email: ['emailSends'],\n ai: ['aiCredits'],\n forms: ['forms'],\n data: ['dataStorage'],\n team: ['seats'],\n sites: ['sites'],\n} satisfies Record<PlanCardContext, PlanCardMeter[]>\n\n/**\n * Each screen's context, by the screen id the native nav manifest opens (its\n * rows' `screen`). A screen not named here has none, and the fullest meter\n * leads. A site's Home is the website's front door; the workspace's Home has\n * no context (`planCardContext`).\n */\nexport const PLAN_CARD_SCREEN_CONTEXTS: Record<string, string> = {\n 'site.media': 'media',\n 'site.pages': 'website',\n 'logic.page': 'website',\n 'shell.analytics': 'website',\n 'email.messages': 'email',\n 'marketing.overview': 'email',\n 'outreach.sequences': 'email',\n 'ai.jobs': 'ai',\n 'ai.credits': 'ai',\n 'forms.list': 'forms',\n 'data.datasets': 'data',\n 'core.team': 'team',\n 'core.team.roles': 'team',\n 'core.team.activity': 'team',\n 'core.site.users': 'team',\n 'site.sites': 'sites',\n} satisfies Record<string, PlanCardContext>\n\n/** The screen id of Home, which is a site's website when the window stands in a site. */\nexport const PLAN_CARD_HOME_SCREEN = 'shell.home'\n\n/** How long one meter stays before the card moves on, slowly (Zach: \"auto rotates slowly\"). */\nexport const PLAN_CARD_ADVANCE_MS = 8000\n\n/** The share from which a meter warns, as the console's meter does (`USAGE_METER_WARNING_PCT`, 80). */\nexport const PLAN_CARD_WARNING_SHARE = 0.8\n\n/** One meter's reading: `null` where the read failed or the plan does not cap it. */\nexport interface PlanCardReading {\n meter: string\n used: number | null\n limit: number | null\n}\n\n/** The context of the screen in front: a site's Home is its website; a screen not mapped has none. */\nexport function planCardContext(screen: string | null, inSite: boolean): string | null {\n if (!screen) return null\n if (screen === PLAN_CARD_HOME_SCREEN) return inSite ? 'website' : null\n return PLAN_CARD_SCREEN_CONTEXTS[screen] ?? null\n}\n\n/** A meter the card can draw: read, capped (an unlimited quota arrives as `null`), and with something to cap. */\nfunction drawable(reading: PlanCardReading): reading is PlanCardReading & { used: number; limit: number } {\n return (\n typeof reading.used === 'number' &&\n Number.isFinite(reading.used) &&\n typeof reading.limit === 'number' &&\n Number.isFinite(reading.limit) &&\n reading.limit > 0\n )\n}\n\nfunction rank(meter: string): number {\n const index = PLAN_CARD_METERS.indexOf(meter)\n return index < 0 ? PLAN_CARD_METERS.length : index\n}\n\n/**\n * The order the card shows its meters in: the context's own meters first, in\n * the mapping's order, then every other meter fullest first (a tie keeps\n * `PLAN_CARD_METERS`' order). A meter that failed to read or that the plan\n * does not cap is left out. Sites leads only on the Sites list: elsewhere it\n * follows every other meter, however full, because a missing site is the\n * limit a workspace is least likely to upgrade for.\n */\nexport function planCardMeterOrder(\n readings: readonly PlanCardReading[],\n context: string | null,\n): string[] {\n const shown = readings.filter(drawable)\n const seen = new Set<string>()\n const unique = shown.filter((reading) => {\n if (seen.has(reading.meter)) return false\n seen.add(reading.meter)\n return true\n })\n const leading = (context && PLAN_CARD_CONTEXT_METERS[context]) || []\n const lead = leading.filter((meter) => unique.some((reading) => reading.meter === meter))\n const rest = unique\n .filter((reading) => !lead.includes(reading.meter))\n .sort((a, b) => {\n const aSites = a.meter === 'sites'\n const bSites = b.meter === 'sites'\n if (aSites !== bSites) return aSites ? 1 : -1\n const byShare = b.used / b.limit - a.used / a.limit\n return byShare !== 0 ? byShare : rank(a.meter) - rank(b.meter)\n })\n .map((reading) => reading.meter)\n return [...lead, ...rest]\n}\n\n/**\n * Whether the card shows at all: a meter to draw, and either a plan the viewer\n * can upgrade (the workspace menu's \"Upgrade plan\" rule) or a meter at its\n * warning share, which every plan is told about.\n */\nexport function planCardShows(readings: readonly PlanCardReading[], offersUpgrade: boolean): boolean {\n const shown = readings.filter(drawable)\n if (!shown.length) return false\n return offersUpgrade || shown.some((reading) => reading.used / reading.limit >= PLAN_CARD_WARNING_SHARE)\n}\n"],"names":["METERS","PLAN_CARD_METERS","PLAN_CARD_METER_LABELS","storage","bandwidth","emailSends","aiCredits","dataStorage","seats","pages","forms","sites","PLAN_CARD_METER_UNITS","CONTEXTS","PLAN_CARD_CONTEXTS","PLAN_CARD_CONTEXT_METERS","media","website","email","ai","data","team","PLAN_CARD_SCREEN_CONTEXTS","PLAN_CARD_HOME_SCREEN","PLAN_CARD_ADVANCE_MS","PLAN_CARD_WARNING_SHARE","planCardContext","screen","inSite","drawable","reading","used","Number","isFinite","limit","rank","meter","index","indexOf","length","planCardMeterOrder","readings","context","shown","filter","seen","Set","unique","has","add","leading","lead","some","rest","includes","sort","a","b","aSites","bSites","byShare","map","planCardShows","offersUpgrade"],"mappings":"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"}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
|
-
import type { AglynOrgBilling, CoreOrgEntitlements, CoreOrgFeatureFlags, OrgDiscount, OrgFeatureFlags, OrgPlan, OrgSeatAddons } from '../foundation';
|
|
17
|
+
import type { AglynOrgBilling, AiTierId, CoreOrgEntitlements, CoreOrgFeatureFlags, OrgDiscount, OrgFeatureFlags, OrgPlan, OrgSeatAddons } from '../foundation';
|
|
18
18
|
import type { PluginOrgFeatures, PluginOrgQuotas } from '../plugin-manager/plugin-entitlement-keys';
|
|
19
19
|
import { type OrgOverrideReasonCode } from './org-override-reason';
|
|
20
20
|
import type { ResolvedBrandingProfile } from './platform-brand';
|
|
@@ -261,6 +261,41 @@ export declare const EVENT_CALENDAR_ADDON_MONTHLY_USD = 9;
|
|
|
261
261
|
* the org-wide `features.siteCare` from any purchased quantity >= 1.
|
|
262
262
|
*/
|
|
263
263
|
export declare const SITE_CARE_ADDON_MONTHLY_USD = 49;
|
|
264
|
+
/**
|
|
265
|
+
* Site Care's included time (AGL-3959): about an hour of staff work a month,
|
|
266
|
+
* no rollover. `SITE_CARE_INCLUDED_BLOCK_MINUTES` is the step each request is
|
|
267
|
+
* counted against it in, always rounded up — a 10-minute change uses one
|
|
268
|
+
* 15-minute block. The timer's actual minutes are still logged beside it.
|
|
269
|
+
*/
|
|
270
|
+
export declare const SITE_CARE_INCLUDED_MINUTES = 60;
|
|
271
|
+
/** The block a request counts against the included hour in (AGL-3959). */
|
|
272
|
+
export declare const SITE_CARE_INCLUDED_BLOCK_MINUTES = 15;
|
|
273
|
+
/**
|
|
274
|
+
* Site Care extra time (AGL-3959): $79 per STARTED hour past the included
|
|
275
|
+
* hour in a month. Billed in whole hours, rounded up — any part of an hour is
|
|
276
|
+
* the full rate — and never less than the hours the owner approved. Only with
|
|
277
|
+
* the owner's recorded yes, as an invoice item on the next subscription
|
|
278
|
+
* invoice; never recurring. `site-care.ts` holds the arithmetic.
|
|
279
|
+
*/
|
|
280
|
+
export declare const SITE_CARE_EXTRA_HOUR_USD = 79;
|
|
281
|
+
/**
|
|
282
|
+
* Site Care projects (AGL-3970): work that adds a page, a form, a design or
|
|
283
|
+
* a campaign is a flat quote, never hourly. The quote is the estimated hours
|
|
284
|
+
* × `SITE_CARE_EXTRA_HOUR_USD`, rounded up to the whole hour, and never below
|
|
285
|
+
* the published starting price of the kind of project it is. These three are
|
|
286
|
+
* those starting prices, as `/pricing` publishes them.
|
|
287
|
+
*/
|
|
288
|
+
export declare const SITE_CARE_PROJECT_LANDING_PAGE_FROM_USD = 249;
|
|
289
|
+
/** A redesign's published starting price (AGL-3970). */
|
|
290
|
+
export declare const SITE_CARE_PROJECT_REDESIGN_FROM_USD = 749;
|
|
291
|
+
/** A logo or brand kit's published starting price (AGL-3970). */
|
|
292
|
+
export declare const SITE_CARE_PROJECT_BRAND_KIT_FROM_USD = 349;
|
|
293
|
+
/**
|
|
294
|
+
* A project priced ABOVE this is billed half up front and half at launch, on
|
|
295
|
+
* two invoices from the same record; at or below it, in full up front
|
|
296
|
+
* (AGL-3970). `site-care-project.ts` holds the arithmetic.
|
|
297
|
+
*/
|
|
298
|
+
export declare const SITE_CARE_PROJECT_SPLIT_ABOVE_USD = 500;
|
|
264
299
|
/**
|
|
265
300
|
* POS Pro register add-on (AGL-329): $89/mo per extra register/location
|
|
266
301
|
* (Shopify POS Pro parity). Purchased add-ons land as a per-org
|
|
@@ -269,37 +304,105 @@ export declare const SITE_CARE_ADDON_MONTHLY_USD = 49;
|
|
|
269
304
|
*/
|
|
270
305
|
export declare const POS_REGISTER_ADDON_MONTHLY_USD = 89;
|
|
271
306
|
/**
|
|
272
|
-
* The Aglyn AI
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
* `
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
|
|
302
|
-
|
|
307
|
+
* The Aglyn AI tiers (AGL-3965): credits sold on their own, to ANY paid
|
|
308
|
+
* plan, on top of what the plan includes.
|
|
309
|
+
*
|
|
310
|
+
* ## One pool
|
|
311
|
+
*
|
|
312
|
+
* Every plan's `assistCreditsPerMonth` pays for ALL of Aglyn AI — the
|
|
313
|
+
* assistant and generation alike — and `features.aiGenerative` is on for
|
|
314
|
+
* every plan. A tier ADDS `credits` to that same band in
|
|
315
|
+
* `resolveOrgEntitlements`, so `resolveAssistCreditBudget`, the overage
|
|
316
|
+
* price and the hard-cap switch all still see one number. Past the pool the
|
|
317
|
+
* plan's `extraAssistCreditsUsdPer1k` bills per 1,000 credits unless the
|
|
318
|
+
* owner turned on `assistOverage.hardCap`; a tier changes neither.
|
|
319
|
+
*
|
|
320
|
+
* ## Sized against the tier's own price
|
|
321
|
+
*
|
|
322
|
+
* A credit is `ASSIST_CREDIT_COST_USD` of provider spend, so `credits` is a
|
|
323
|
+
* cost figure: 1,000 credits are $1.00. Every tier costs at most 50% of its
|
|
324
|
+
* price at full consumption — Spark $4 of $9, Boost $9 of $19, Plus $19 of
|
|
325
|
+
* $39, Max $34 of $69, Ultra $49 of $99, Studio $149 of $299 — the same
|
|
326
|
+
* line-margin floor (`ASSIST_CREDIT_MIN_MARGIN_PCT`) every retail assist
|
|
327
|
+
* rate clears. The plan's included band is priced at 100% burn on its own
|
|
328
|
+
* tier, so a tier's margin never leans on the plan's; that is what
|
|
329
|
+
* `tier-margin-floor.spec.ts` holds, per tier.
|
|
330
|
+
*
|
|
331
|
+
* These are the six price/credit pairs the add-on was sold at per plan
|
|
332
|
+
* before AGL-3965 (Starter's $9 / 4,000 through Agency's $299 / 149,000),
|
|
333
|
+
* now chosen independently of the plan. `LEGACY_AI_ADDON_TIER_BY_PLAN` maps
|
|
334
|
+
* each old per-plan add-on to the tier with its price and band.
|
|
335
|
+
*
|
|
336
|
+
* Yearly is ×12 with no discount, as every add-on is. Studio is the top;
|
|
337
|
+
* more is an Enterprise contract. Free sells none (`planSellsAiTiers`).
|
|
338
|
+
*/
|
|
339
|
+
export interface AiTier {
|
|
340
|
+
/** The tier's name as a person sees it, after the product name. */
|
|
341
|
+
label: string;
|
|
342
|
+
monthlyUsd: number;
|
|
343
|
+
yearlyUsd: number;
|
|
344
|
+
/** Credits a month the tier adds to the plan's included band. */
|
|
345
|
+
credits: number;
|
|
346
|
+
}
|
|
347
|
+
/** The tiers, smallest first — the order every picker lists them in. */
|
|
348
|
+
export declare const AI_TIER_IDS: readonly AiTierId[];
|
|
349
|
+
export declare const AI_TIERS: Readonly<Record<AiTierId, AiTier>>;
|
|
350
|
+
/**
|
|
351
|
+
* The tier an add-on bought before AGL-3965 is (AGL-3965): the one with the
|
|
352
|
+
* same price and the same band the plan sold it at, so a workspace that
|
|
353
|
+
* held "the Pro add-on" holds Boost — $19, 9,000 credits — and neither its
|
|
354
|
+
* bill nor its credits move. Read when `aiTier` is absent and to classify a
|
|
355
|
+
* per-plan `STRIPE_PRICE_{PLAN}_AI_ADDON` item still on a subscription.
|
|
356
|
+
*/
|
|
357
|
+
export declare const LEGACY_AI_ADDON_TIER_BY_PLAN: Readonly<Partial<Record<OrgPlan, AiTierId>>>;
|
|
358
|
+
/**
|
|
359
|
+
* The tier a purchase that names a plan and no tier means (AGL-3965): a
|
|
360
|
+
* `?plan=pro&ai=1` deep link, a checkout posting `aiAddon: true` alone. The
|
|
361
|
+
* tier with that plan's old price and band, so a link published before tiers
|
|
362
|
+
* existed still sells what it quoted. Null where the plan sells no tier.
|
|
363
|
+
*/
|
|
364
|
+
export declare function defaultAiTierForPlan(plan: OrgPlan | string | null | undefined): AiTierId | null;
|
|
365
|
+
/** Whether a value is one of the tier ids. */
|
|
366
|
+
export declare function isAiTierId(value: unknown): value is AiTierId;
|
|
367
|
+
/**
|
|
368
|
+
* The add-on key an Aglyn AI tier travels under outside the billing routes
|
|
369
|
+
* (AGL-3965): `ai:<tier>`, e.g. `ai:boost`. The ONE spelling a bundle
|
|
370
|
+
* checkout link (AGL-3955) and a hand-off proposal's add-on list (AGL-3956)
|
|
371
|
+
* name a tier by, parsed and listed here, beside the tiers, so neither
|
|
372
|
+
* restates the ids.
|
|
373
|
+
*/
|
|
374
|
+
export type AiTierAddonKey = `ai:${AiTierId}`;
|
|
375
|
+
/** `ai:<tier>` for a tier. */
|
|
376
|
+
export declare function aiTierAddonKey(tier: AiTierId): AiTierAddonKey;
|
|
377
|
+
/**
|
|
378
|
+
* The tier an `ai:<tier>` key names, or null for anything else — another
|
|
379
|
+
* add-on's key, a bare `ai`, an unknown tier, a non-string. Exact and
|
|
380
|
+
* case-sensitive: a key is read off a link, and guessing is the expensive
|
|
381
|
+
* direction to be wrong in (`parseOnboardingPlanIntent` reads `?ai=` the
|
|
382
|
+
* same way).
|
|
383
|
+
*/
|
|
384
|
+
export declare function parseAiTierAddonKey(key: unknown): AiTierId | null;
|
|
385
|
+
/** One tier as an add-on a bundle or a proposal can carry. */
|
|
386
|
+
export interface AiTierAddonOption {
|
|
387
|
+
key: AiTierAddonKey;
|
|
388
|
+
tier: AiTierId;
|
|
389
|
+
label: string;
|
|
390
|
+
monthlyUsd: number;
|
|
391
|
+
yearlyUsd: number;
|
|
392
|
+
credits: number;
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Every tier as an `ai:<tier>` add-on, smallest first — what a picker that
|
|
396
|
+
* offers "Aglyn AI" beside the other add-ons lists. Empty for a plan that
|
|
397
|
+
* sells no tier (Free, Enterprise) when a plan is given.
|
|
398
|
+
*/
|
|
399
|
+
export declare function listAiTierAddons(plan?: OrgPlan | string | null): AiTierAddonOption[];
|
|
400
|
+
/**
|
|
401
|
+
* Whether a plan may buy an Aglyn AI tier (AGL-3965): every paid self-serve
|
|
402
|
+
* plan. Not Free, which has no subscription to carry it, and not Enterprise,
|
|
403
|
+
* whose AI is in the agreement.
|
|
404
|
+
*/
|
|
405
|
+
export declare function planSellsAiTiers(plan: OrgPlan | string | undefined | null): boolean;
|
|
303
406
|
/**
|
|
304
407
|
* The generative add-on's name as a person sees it (AGL-2896): the brand,
|
|
305
408
|
* then "AI". Read from the platform brand by default and from the org's
|
|
@@ -463,41 +566,6 @@ export interface PlanPricing {
|
|
|
463
566
|
* no unrefusable assist traffic to absorb.
|
|
464
567
|
*/
|
|
465
568
|
extraAssistCreditsUsdPer1k: number | null;
|
|
466
|
-
/**
|
|
467
|
-
* The Aglyn AI add-on, flat per organization per month (AGL-2896); null
|
|
468
|
-
* when the plan does not sell it.
|
|
469
|
-
*
|
|
470
|
-
* ## What it buys
|
|
471
|
-
*
|
|
472
|
-
* `features.aiGenerative` — generative building and automation, the rung
|
|
473
|
-
* no self-serve tier includes — plus `AI_ADDON_CREDITS_PER_MONTH[plan]`
|
|
474
|
-
* added to the plan's assist band. It also switches `aiAssist` on, so on
|
|
475
|
-
* Starter it brings the two rungs the tier does not carry — the copy
|
|
476
|
-
* assist and the generative one — on top of the 750 credits the plan
|
|
477
|
-
* already includes (AGL-3203). It ADDS to that band; it does not replace
|
|
478
|
-
* it, on Starter or on any tier.
|
|
479
|
-
*
|
|
480
|
-
* ## Where the ladder sits
|
|
481
|
-
*
|
|
482
|
-
* Priced as an uplift on the plan rather than on its own cost: the field
|
|
483
|
-
* charges roughly 30% of the base price for an AI tier (Zylo's 2025
|
|
484
|
-
* survey puts the AI uplift at 20–37% across SaaS; aissist.io's benchmark
|
|
485
|
-
* and the Framer, Webflow and HubSpot credit add-ons sit in the same
|
|
486
|
-
* band), and the share DESCENDS with the tier — 36% of Starter's $25, 34%
|
|
487
|
-
* of Pro's, 28% of Business's, 28% of Scale's, 25% of Advanced's, 23% of
|
|
488
|
-
* Agency's — because the larger the plan the more of its price is already
|
|
489
|
-
* carrying the bands the assistant builds against.
|
|
490
|
-
*
|
|
491
|
-
* Annual is x12 of this with no discount, as every other add-on is
|
|
492
|
-
* (`orgListPriceMonthlyUsd`). The band it funds costs at most 50% of it in
|
|
493
|
-
* provider spend, the `ASSIST_CREDIT_MIN_MARGIN_PCT` floor every retail
|
|
494
|
-
* assist figure holds.
|
|
495
|
-
*
|
|
496
|
-
* Null on Free, which sells no add-on of any kind, and on Enterprise,
|
|
497
|
-
* where every figure is the "not for sale" sentinel and generative
|
|
498
|
-
* building is in the agreement.
|
|
499
|
-
*/
|
|
500
|
-
aiAddonMonthlyUsd: number | null;
|
|
501
569
|
/**
|
|
502
570
|
* Metered overage per 1,000 CRM records beyond `contactsPerHost` (AGL-890,
|
|
503
571
|
* widened to contacts + companies + deals in AGL-2611): audience bands,
|
|
@@ -1490,6 +1558,54 @@ export declare function resolveEffectivePlan(org: Partial<AglynOrgBilling> | nul
|
|
|
1490
1558
|
* together rather than leaving a rate on a band that has gone.
|
|
1491
1559
|
*/
|
|
1492
1560
|
export declare function hasAiAddon(org: Partial<AglynOrgBilling> | null | undefined): boolean;
|
|
1561
|
+
/**
|
|
1562
|
+
* Which Aglyn AI tier the org's purchase map holds (AGL-3965), whether or
|
|
1563
|
+
* not the subscription is still paying for it — `resolveAiTier` is the
|
|
1564
|
+
* reading that drops a dead one. `org.aiTier` when it names a tier;
|
|
1565
|
+
* otherwise, for an add-on bought before tiers existed, the tier with that
|
|
1566
|
+
* plan's old price and band (`LEGACY_AI_ADDON_TIER_BY_PLAN`). Null when no
|
|
1567
|
+
* add-on is held or the plan sells no tier (Free, Enterprise).
|
|
1568
|
+
*/
|
|
1569
|
+
export declare function aiTierOfPurchase(org: Partial<AglynOrgBilling> | null | undefined): AiTierId | null;
|
|
1570
|
+
/**
|
|
1571
|
+
* The Aglyn AI tier that currently applies (AGL-3965): bought, on a
|
|
1572
|
+
* subscription still paying for it, on a plan that sells tiers. The ONE
|
|
1573
|
+
* reading every surface that shows or counts a tier goes through.
|
|
1574
|
+
*/
|
|
1575
|
+
export declare function resolveAiTier(org: Partial<AglynOrgBilling> | null | undefined): AiTierId | null;
|
|
1576
|
+
/**
|
|
1577
|
+
* What `seatAddons.aiAddon` adds on an Enterprise org (AGL-2896, kept by
|
|
1578
|
+
* AGL-3965): twice Studio's band, the figure the per-plan table carried as
|
|
1579
|
+
* twice Agency's (the rule every Enterprise fallback follows since AGL-2654),
|
|
1580
|
+
* and still what a contract that names the add-on starts from before its own
|
|
1581
|
+
* number is written. Enterprise buys no tier — its AI is in the agreement —
|
|
1582
|
+
* so this is the contract figure kept, not a price; finite, never
|
|
1583
|
+
* `UNLIMITED`, for the reason `ENTERPRISE_ASSIST_CREDITS_PER_MONTH` gives.
|
|
1584
|
+
*/
|
|
1585
|
+
export declare const ENTERPRISE_AI_ADDON_CREDITS_PER_MONTH: number;
|
|
1586
|
+
/**
|
|
1587
|
+
* The Aglyn AI add-on's per-unit quota as `resolveOrgEntitlements` folds it
|
|
1588
|
+
* (AGL-3965): the purchased tier's credits, on a plan that sells tiers;
|
|
1589
|
+
* Enterprise's contract figure (`ENTERPRISE_AI_ADDON_CREDITS_PER_MONTH`);
|
|
1590
|
+
* else 0 (Free). The AI plugin declares its seat add-on with this as
|
|
1591
|
+
* `quota.perUnit`.
|
|
1592
|
+
*/
|
|
1593
|
+
export declare function aiTierQuotaPerUnit(org: Partial<AglynOrgBilling> | null | undefined, plan: OrgPlan): number;
|
|
1594
|
+
/**
|
|
1595
|
+
* The credits a month the org's Aglyn AI add-on adds to its plan's included
|
|
1596
|
+
* band (AGL-3965) — 0 with none held. The SAME figure `resolveOrgEntitlements`
|
|
1597
|
+
* folds into `assistCreditsPerMonth` (`aiTierQuotaPerUnit`), so a surface
|
|
1598
|
+
* quoting "your tier adds" and the meter agree.
|
|
1599
|
+
*/
|
|
1600
|
+
export declare function aiTierCreditsPerMonth(org: Partial<AglynOrgBilling> | null | undefined): number;
|
|
1601
|
+
/**
|
|
1602
|
+
* Whether this org can buy or change an Aglyn AI tier itself (AGL-3965): a
|
|
1603
|
+
* plan that sells tiers, and not one a staff comp grants — a comp has no
|
|
1604
|
+
* subscription to put the tier on (`compPricing`).
|
|
1605
|
+
*/
|
|
1606
|
+
export declare function orgCanBuyAiTier(org: Partial<AglynOrgBilling> | null | undefined): boolean;
|
|
1607
|
+
/** "Aglyn AI Boost": the product name, then the tier's. */
|
|
1608
|
+
export declare const aiTierName: (tier: AiTierId, brand?: string) => string;
|
|
1493
1609
|
/**
|
|
1494
1610
|
* Whether the org's Site Care add-on currently applies (AGL-3954): bought,
|
|
1495
1611
|
* and on a subscription that is still paying for it, so a dead subscription
|