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)}`)
}