Skip to content
← Community packages

Composable daily briefing from calendar, weather, and optional inbox or home signals.

Browse files

  • Integrations
  • morning
  • briefing
  • calendar
  • weather
  • inbox
  • health
  • home
  • email
  • notify
  • daily
  • composable
License
MIT
Published
August 22, 2026
Pinned commit
3f4b7c6
Rating
No ratings yet
Forks
0
Stars
0
Adaptation effort

README

@kody/morning-briefing

Composable daily briefing from calendar, weather, and optional inbox, health, or home signals. Delivered by email or a short notify-self message. No Discord.

Intent

Give any signed-in account one reliable morning update they can compose themselves. Calendar and weather are the default sources. Inbox, health (air quality), and home are optional and degrade to a skipped or warning section when auth or data is missing. Success is a structured report plus a dryRun preview that never sends mail until you choose an output channel and turn dry-run off.

This package is the generic official briefing. It does not post to Discord, and it does not ship Kent-specific Kit, YouTube, GitHub, Tesla, or apartment wiring.

When to use

  • Build today's briefing from the sources you have connected
  • Preview the email or notify payload with dryRun: true
  • Read a stored report for a date without rebuilding every source
  • Schedule a disabled-by-default 07:00 UTC job after you save config

Compose sources

Pass sources to choose what runs. Default is calendar and weather.

import runMorningBriefing from 'kody:@kody/morning-briefing'

export default async function main() {
	return await runMorningBriefing({
		dryRun: true,
		location: 'Denver, CO',
		timezone: 'America/Denver',
		sources: ['calendar', 'weather', 'inbox', 'health', 'home'],
		output: 'email',
		calendar: { accounts: ['personal'] },
		inbox: { account: 'personal', query: 'is:unread newer_than:1d' },
		home: { items: [{ title: 'Thermostat 70°', detail: 'Heat, occupied' }] },
	})
}
SourceDefaultWhat it readsAuth
calendaryesToday's events via @kody/google/calendarBuilt-in Google OAuth (account, default personal → integration google)
weatheryesDaily forecast + current conditions via Open-MeteoNone. Pass location (city or place name)
inboxnoUnread Gmail via @kody/google/gmailBring-your-own Google OAuth with Gmail read scopes. Built-in Google is Calendar/send, not inbox read
healthnoUS AQI / PM2.5 via Open-Meteo Air QualityNone. Uses location (or health.location)
homenoCaller-supplied snapshot itemsNone in this package. Pass home.items, or skip until you have a home connector

Sources run independently. A missing integration becomes a warning or skipped section; the rest of the briefing still builds.

Auth each source

Calendar (built-in Google). Connect the saved google integration:

https://kody.codes/connect/oauth?provider=google

Pass calendar.accounts using @kody/google aliases (personal, business, …). Every Calendar call needs an explicit account. Widen Calendar scopes from the connect UI when a mailbox has more than the primary calendar.

Weather and health (zero auth). Public Open-Meteo geocoding, forecast, and air-quality APIs. Attribute Open-Meteo (CC BY 4.0) when you reuse the data. Set location on the run or persist it in package config for the scheduled job.

Inbox (bring-your-own Google). Gmail read scopes are restricted and are not on the built-in Google menu — this package still exports the inbox source; do not hide it. Create a Google Cloud OAuth Web application client, register redirect https://kody.codes/connect/oauth, enable the Gmail API and Calendar API, publish the app to Production (Testing refresh tokens expire after seven days), then connect:

https://kody.codes/connect/oauth?provider=google&authorizeUrl=https%3A%2F%2Faccounts.google.com%2Fo%2Foauth2%2Fv2%2Fauth&tokenUrl=https%3A%2F%2Foauth2.googleapis.com%2Ftoken&flow=confidential&scopes=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcalendar.readonly%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fgmail.readonly&allowedHosts=www.googleapis.com%2Cgmail.googleapis.com&extraAuthorizeParams=%7B%22access_type%22%3A%22offline%22%2C%22prompt%22%3A%22consent%22%7D

Use the personal (or other) @kody/google account alias that matches that connection. A 403 / insufficient-scope error names gmail.readonly and this URL. See coding_guide_get({ guide: "provider_google" }).

Home. This official package does not call a personal home MCP or Discord. Pass home.items from your own home package, or omit home from sources.

This composer does not need an API-key secret. There is no /account/secrets/new step for calendar, weather, health, or email output.

Hosts

  • www.googleapis.com — Calendar
  • gmail.googleapis.com — inbox (BYO only)
  • geocoding-api.open-meteo.com, api.open-meteo.com, air-quality-api.open-meteo.com — weather and health (public)

Scopes

  • Calendar: https://www.googleapis.com/auth/calendar.readonly (or calendar.events.readonly)
  • Inbox: https://www.googleapis.com/auth/gmail.readonly (restricted; BYO client)

If Google returns 403 / insufficient-scope, the package throws (or records a warning section) naming the missing scope and the next connect URL. It does not fail silently.

Output: email or notify

output is email (default) or notify. Both use kody.email_send to your own account address.

  • email — full markdown briefing
  • notify — short subject + callout-only body

dryRun: true builds and stores the report, returns preview.subject / preview.text, and does not send. The scheduled wrapper uses the same implementation; keep the job disabled until a dry-run looks right.

Persist config for the job

The no-argument job daily-morning-briefing loads JSON from package storage table config key briefing (same fields as the run input, minus dryRun). Save it once, invoke ./run-morning-briefing with no params to prove the scheduler contract, then enable the job.

Exports

  • kody:@kody/morning-briefing — build, optionally deliver (dryRun supported)
  • kody:@kody/morning-briefing/run-morning-briefing — same function (job + invoke)
  • kody:@kody/morning-briefing/build-report — build and store without sending
  • kody:@kody/morning-briefing/get-report — read a stored report, rebuild if missing

Smoke test

Weather-only (no OAuth):

import buildReport from 'kody:@kody/morning-briefing/build-report'

export default async function main() {
	const report = await buildReport({
		location: 'Austin',
		sources: ['weather'],
	})
	return { date: report.date, sections: report.sections.map((s) => ({ id: s.id, status: s.status })) }
}

Delivery dry run (does not send):

import runMorningBriefing from 'kody:@kody/morning-briefing/run-morning-briefing'

export default async function main() {
	return await runMorningBriefing({
		dryRun: true,
		location: 'Austin',
		sources: ['weather'],
		output: 'email',
	})
}

Keyless invoke from the same account:

import { packages } from 'kody:runtime'

export default async function main() {
	return await packages.invoke({
		kodyId: 'morning-briefing',
		exportName: './build-report',
		params: { location: 'Austin', sources: ['weather'] },
	})
}

Example

import buildReport from 'kody:@kody/morning-briefing/build-report'

export default async function main() {
	const report = await buildReport({
		location: 'Denver, CO',
		sources: ['weather'],
	})
	return { date: report.date, sections: report.sections.map((s) => s.id) }
}
Report this listing

Log in to report this listing.