Skip to content

Built for people who want to own their automations. Join the waitlist for an invite.

Package listing

@kody/morning-briefing

src/types.ts

123 lines · 2.9 KB · TypeScript
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)}`)
}