export const SOURCE_IDS = ['calendar', 'weather', 'inbox', 'health', 'home'] as const export type SourceId = (typeof SOURCE_IDS)[number] export const OUTPUT_CHANNELS = ['email', 'notify'] as const export type OutputChannel = (typeof OUTPUT_CHANNELS)[number] export type Severity = 'good' | 'info' | 'notice' | 'warning' | 'critical' export type BriefingItem = { title: string detail?: string severity?: Severity href?: string label?: string } export type SectionStatus = 'ok' | 'empty' | 'skipped' | 'warning' | 'error' export type BriefingSection = { id: SourceId title: string summary?: string status: SectionStatus items: BriefingItem[] } export type SourceHealth = { source: SourceId ok: boolean detail?: string ms?: number } export type BriefingMetric = { label: string value: string detail?: string } export type HomeItemInput = { title: string detail?: string severity?: Severity href?: string label?: string } export type BriefingInput = { /** Calendar date `YYYY-MM-DD` in `timezone`. Defaults to today. */ date?: string /** IANA timezone used for "today" and calendar day bounds. Defaults to UTC. */ timezone?: string /** Place name for weather and health (Open-Meteo geocode). */ location?: string /** Sources to run. Defaults to `calendar` and `weather`. */ sources?: SourceId[] /** Delivery channel after the report is built. Defaults to `email`. */ output?: OutputChannel /** Build and preview without calling `email_send`. */ dryRun?: boolean calendar?: { /** `@kody/google` account aliases. Defaults to `['personal']`. */ accounts?: string[] } inbox?: { /** `@kody/google` account alias. Defaults to `personal`. */ account?: string /** Gmail search query. Defaults to `is:unread newer_than:1d`. */ query?: string /** Max messages to summarize. Defaults to 8. */ max?: number } health?: { /** Override place for air quality. Defaults to `location`. */ location?: string } home?: { /** Caller-composed home snapshot. Required for a useful home section. */ items?: HomeItemInput[] } } export type BriefingReport = { date: string timezone: string title: string generatedAt: string location?: string sources: SourceId[] metrics: BriefingMetric[] callouts: BriefingItem[] sections: BriefingSection[] sourceHealth: SourceHealth[] markdown: string notifyText: string } export type DeliveryPreview = { subject: string text: string } export type BriefingResult = BriefingReport & { dryRun: boolean output: OutputChannel delivered: boolean delivery?: { channel: OutputChannel; subject: string } preview: DeliveryPreview } export function isSourceId(value: string): value is SourceId { return (SOURCE_IDS as readonly string[]).includes(value) } export function isOutputChannel(value: string): value is OutputChannel { return (OUTPUT_CHANNELS as readonly string[]).includes(value) } export function assertNever(value: never, label: string): never { throw new Error(`Unhandled ${label}: ${String(value)}`) }