const COMPANY_BRAIN_PRODUCT_ID = "company_brain" // Add-on resolved by product presence, not tier. // better-auth returns org.metadata as a JSON string, so accept string or object. export function hasCompanyBrain( metadataRaw: Record | string | null | undefined, ): boolean { if (!metadataRaw) return false let metadata: Record if (typeof metadataRaw === "string") { try { metadata = JSON.parse(metadataRaw) as Record } catch { return false } } else { metadata = metadataRaw } const overrides = metadata.featureOverrides as | Record | undefined const override = overrides?.[COMPANY_BRAIN_PRODUCT_ID] if (override) return Boolean(override.allow) const activeProducts = Array.isArray(metadata.activeProducts) ? (metadata.activeProducts as string[]) : [] return activeProducts.includes(COMPANY_BRAIN_PRODUCT_ID) } // Explicit concierge override for company_brain, or undefined when none is set. export function getCompanyBrainOverride( metadataRaw: Record | string | null | undefined, ): boolean | undefined { if (!metadataRaw) return undefined let metadata: Record if (typeof metadataRaw === "string") { try { metadata = JSON.parse(metadataRaw) as Record } catch { return undefined } } else { metadata = metadataRaw } const overrides = metadata.featureOverrides as | Record | undefined const override = overrides?.[COMPANY_BRAIN_PRODUCT_ID] return override ? Boolean(override.allow) : undefined } // Origin of the org. Consumer (app.supermemory) orgs get company_brain attached, // but the add-on lands async — signupSource is set at creation, so it's the // reliable "this org uses brain spaces" signal in the UI. export function getSignupSource( metadataRaw: Record | string | null | undefined, ): string | null { if (!metadataRaw) return null let metadata: Record if (typeof metadataRaw === "string") { try { metadata = JSON.parse(metadataRaw) as Record } catch { return null } } else { metadata = metadataRaw } return typeof metadata.signupSource === "string" ? (metadata.signupSource as string) : null } // Company domain captured during team onboarding. export function getBrainWorkspaceDomain( metadataRaw: Record | string | null | undefined, ): string | null { if (!metadataRaw) return null let metadata: Record if (typeof metadataRaw === "string") { try { metadata = JSON.parse(metadataRaw) as Record } catch { return null } } else { metadata = metadataRaw } return typeof metadata.brainWorkspaceDomain === "string" ? (metadata.brainWorkspaceDomain as string) : null } // Brain mode chosen during onboarding ("personal" | "team"). Set synchronously // at org creation, so it's the reliable pre-webhook signal for company brain. export function getBrainMode( metadataRaw: Record | string | null | undefined, ): string | null { if (!metadataRaw) return null let metadata: Record if (typeof metadataRaw === "string") { try { metadata = JSON.parse(metadataRaw) as Record } catch { return null } } else { metadata = metadataRaw } return typeof metadata.brainMode === "string" ? (metadata.brainMode as string) : null } export type BrainTrialStatus = | "active" | "exhausted" | "expired" | "converted" | "skipped" export type BrainTrialInfo = { status: BrainTrialStatus | null startedAtMs: number | null endsAtMs: number | null credits: number | null daysRemaining: number | null } function parseOrgMetadata( metadataRaw: Record | string | null | undefined, ): Record | null { if (!metadataRaw) return null if (typeof metadataRaw === "string") { try { return JSON.parse(metadataRaw) as Record } catch { return null } } return metadataRaw } /** Company Brain Slack trial fields written by mono after OAuth attach. */ export function getBrainTrialInfo( metadataRaw: Record | string | null | undefined, ): BrainTrialInfo { const metadata = parseOrgMetadata(metadataRaw) const rawStatus = metadata?.brainTrialStatus const status = rawStatus === "active" || rawStatus === "exhausted" || rawStatus === "expired" || rawStatus === "converted" || rawStatus === "skipped" ? rawStatus : null const startedAtMs = typeof metadata?.brainTrialStartedAt === "string" ? Date.parse(metadata.brainTrialStartedAt) : Number.NaN const endsAtMs = typeof metadata?.brainTrialEndsAt === "string" ? Date.parse(metadata.brainTrialEndsAt) : Number.NaN const credits = typeof metadata?.brainTrialCredits === "number" ? metadata.brainTrialCredits : null const safeEnds = Number.isFinite(endsAtMs) ? endsAtMs : null const daysRemaining = safeEnds != null ? Math.max(0, Math.ceil((safeEnds - Date.now()) / (1000 * 60 * 60 * 24))) : null return { status, startedAtMs: Number.isFinite(startedAtMs) ? startedAtMs : null, endsAtMs: safeEnds, credits, daysRemaining, } } /** * Format a number with K/M/B suffix for display * @example formatUsageNumber(1500000) => "1.5M" * @example formatUsageNumber(50000) => "50K" * @example formatUsageNumber(999950) => "1.0M" */ export function formatUsageNumber(value: number): string { const withSuffix = (n: number, suffix: string) => n % 1 === 0 ? `${n}${suffix}` : `${n.toFixed(1)}${suffix}` if (value >= 1_000_000) { const millions = value / 1_000_000 return millions >= 999.95 ? withSuffix(value / 1_000_000_000, "B") : withSuffix(millions, "M") } if (value >= 1_000) { const thousands = value / 1_000 return thousands >= 999.95 ? withSuffix(value / 1_000_000, "M") : withSuffix(thousands, "K") } return value.toString() } /** * Calculate usage percentage, clamped between 0 and 100 */ export function calculateUsagePercent(used: number, limit: number): number { if (limit <= 0) return 0 return Math.min(Math.max((used / limit) * 100, 0), 100) } /** * Get the number of days remaining until a reset date * @param resetAt - Unix timestamp in milliseconds (from Autumn billing) * @returns number of days, or null if no date provided */ export function getDaysRemaining( resetAt: number | null | undefined, ): number | null { if (!resetAt) return null const end = new Date(resetAt) const now = new Date() const diffMs = end.getTime() - now.getTime() const diffDays = Math.ceil(diffMs / (1000 * 60 * 60 * 24)) return Math.max(0, diffDays) }