/** * The upsell wire contract — shared by every Misar product. * * A single shape describes "you are blocked, here is how to unblock" across * MisarBlog, MisarMail, MisarReach and MisarPost, so one renderer in an MCP * client works for all of them and one dashboard can measure all of them. * * DESIGN RULE: all user-visible copy (plan names, prices, benefit bullets, * URLs) travels INSIDE this payload. MCP servers are published to npm and * external marketplaces, so text baked into a client can only change by * republishing and re-submitting everywhere. Keeping copy server-side means * marketing can retune the entire upsell and every already-installed client * renders the new content on its next blocked call. */ /** Products that can emit an upsell. */ export type UpsellProduct = "blog" | "mail" | "reach" | "post" | "io"; export type UpgradeReason = /** A metered allowance is spent (retryable when the period rolls over). */ "quota_exhausted" /** The feature isn't part of the current plan at all. */ | "feature_locked" /** Pay-as-you-go is on, but the spend cap is reached. */ | "overage_capped" /** Wallet balance is too low for this action (1 credit = $1 USD). */ | "credits_exhausted"; export interface UpgradePlanOption { slug: string; name: string; /** Display price, e.g. "$4.99/month" or "Custom pricing". */ price: string; /** Secondary price line, e.g. "or $47.90/year — 2 months free". */ price_note: string | null; recommended: boolean; current: boolean; /** What this plan does about the SPECIFIC limit that was hit. */ unlocks: string | null; /** Broader reasons to move up. */ benefits: string[]; url: string; } export interface UsageSnapshot { used: number | null; limit: number | null; remaining: number | null; period: "day" | "month" | "total" | null; /** ISO timestamp when a metered period rolls over; null if not periodic. */ resets_at: string | null; } /** A no-card trial the user can start to unblock themselves immediately. */ export interface TrialOffer { available: boolean; plan_slug: string; plan_name: string; days: number; /** Endpoint an MCP client can POST to in order to start the trial. */ start_url: string; requires_card: boolean; label: string; } /** Another Misar product that solves the need behind this limit. */ export interface CrossSellOffer { product: UpsellProduct; product_name: string; headline: string; url: string; } export interface TopupOffer { balance_credits: number; required_credits: number; options: { amount_dollars: number; label: string; url: string; }[]; } export interface UpgradeOffer { product: UpsellProduct; product_name: string; reason: UpgradeReason; /** Stable machine key, e.g. "mcp_write_actions". */ feature: string; /** Human label, e.g. "Automation write actions". */ feature_label: string; /** One-line explanation of exactly what was hit. */ headline: string; usage: UsageSnapshot | null; current_plan: { slug: string; name: string; }; recommended_plan: string | null; plans: UpgradePlanOption[]; /** Present only for `credits_exhausted`. */ topup: TopupOffer | null; /** Pay-as-you-go alternative, when the feature supports it. */ overage: { available: boolean; enabled: boolean; hint: string; url: string; } | null; /** Immediate self-serve unblock, shown above the paid plans. */ trial: TrialOffer | null; /** Suggestions for other Misar products, at most one or two. */ cross_sell: CrossSellOffer[]; urls: { pricing: string; billing: string; docs: string; compare: string; }; cta: string; /** Correlates the block with a later checkout — see the funnel dashboard. */ event_id?: string | null; } /** The full JSON body a blocked API call returns. */ export interface PlanLimitBody { error: string; code: "plan_limit_exceeded"; upgrade: UpgradeOffer; /** Products may add legacy top-level fields for back-compat. */ [key: string]: unknown; } /** Response headers carrying live usage on SUCCESSFUL metered calls. */ export declare const USAGE_HEADERS: { readonly feature: "x-misar-usage-feature"; readonly used: "x-misar-usage-used"; readonly limit: "x-misar-usage-limit"; readonly remaining: "x-misar-usage-remaining"; readonly resets: "x-misar-usage-resets"; readonly plan: "x-misar-plan"; readonly upgradeUrl: "x-misar-upgrade-url"; }; /** * Headers describing a PREPAID balance on a successful metered call. * * Deliberately separate from USAGE_HEADERS. Those describe a quota with a * denominator — `used` of `limit` — and `usageFooter` fires at 80% of it. * A prepaid wallet has no denominator: coercing balance into used/limit makes * `ratio` arithmetic meaningless and fires the warning at the wrong moment. * So prepaid gets its own signal, thresholded on a number the USER chose * (their auto-reload threshold), not on a fraction we invented. */ export declare const BALANCE_HEADERS: { readonly credits: "x-misar-balance-credits"; readonly threshold: "x-misar-balance-threshold"; }; /** * Client-side helpers: recognising a plan-limit rejection, and turning the * usage headers on a SUCCESSFUL response into a pre-emptive warning. */ /** Extract the offer from an arbitrary API error body, if present. */ export declare function extractUpgradeOffer(body: unknown): UpgradeOffer | null; export interface UsageWarning { feature: string; used: number; limit: number; remaining: number; ratio: number; upgradeUrl: string | null; resetsAt: string | null; } /** * Read the usage headers off a successful response. * Returns null when the route isn't metered or the headers are absent. */ export declare function parseUsageHeaders(headers: { get(name: string): string | null; }): UsageWarning | null; /** Warn from this fraction of the allowance onward. */ export declare const WARN_THRESHOLD = 0.8; /** * A one-line footer appended to SUCCESSFUL tool output once usage crosses the * threshold. * * Catching someone at 80% — while the task is still working — converts far * better than an error at 100%, and it reads as helpful rather than pushy. * Returns null below the threshold so the vast majority of calls stay clean. */ export declare function usageFooter(warning: UsageWarning | null, featureLabel?: string): string | null; export interface BalanceWarning { credits: number; threshold: number; upgradeUrl: string | null; } /** * Read the prepaid balance headers off a successful response. * Returns null when the route is not prepaid-metered, or the balance is healthy. */ export declare function parseBalanceWarning(headers: { get(name: string): string | null; }): BalanceWarning | null; /** * A one-line footer for a running-low wallet, appended to SUCCESSFUL output. * * Catching someone while their task still works converts far better than an * error after it stopped, and it reads as helpful rather than pushy. */ export declare function balanceFooter(warning: BalanceWarning | null): string | null; /** A 20-cell usage bar: ████████████░░░░░░░░ 80% */ export declare function usageBar(used: number, limit: number, cells?: number): string; export declare function humanDate(iso: string): string; /** Render the full upgrade card shown when a call is blocked. */ export declare function renderUpgradeOffer(offer: UpgradeOffer): string; export interface UsageRow { feature: string; feature_label: string; used: number; limit: number | null; remaining: number | null; period: "day" | "month" | "total"; resets_at: string | null; } /** Render the at-a-glance quota table used by the `upgrade` tool. */ export declare function renderUsageTable(productName: string, planName: string, usage: UsageRow[]): string; /** Compact one-liner for logs / nested contexts. */ export declare function summarizeOffer(offer: UpgradeOffer): string; //# sourceMappingURL=upgrade.d.ts.map