# @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. ## What it does - 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`. | Source | Default | What it reads | Auth | | --- | --- | --- | --- | | `calendar` | yes | Today's events via `@kody/google/calendar` | Google OAuth (`account`, default `personal` → integration `google`) | | `weather` | yes | Daily forecast + current conditions via Open-Meteo | None. Pass `location` (city or place name) | | `inbox` | no | Unread Gmail via `@kody/google/gmail` | Google OAuth with Gmail read scopes (`gmail.readonly`) | | `health` | no | US AQI / PM2.5 via Open-Meteo Air Quality | None. Uses `location` (or `health.location`) | | `home` | no | Caller-supplied snapshot items | None 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. ## Prerequisites / setup **Calendar (Google OAuth).** Connect the saved `google` integration: ```text 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](https://open-meteo.com/) (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 (Google OAuth with Gmail read).** Gmail read scopes are restricted — 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: ```text 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. Agents: see [`AGENTS.md`](./AGENTS.md) for import paths, smoke checks, and delivery dry-runs. ## Output: email or notify `output` is `email` (default) or `notify`. Both use `kody.emailSend` 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. ## Done when - Weather-only `build-report` returns sections without OAuth - Delivery dry-run returns `preview` and does not send mail - Optional Google sources warn or skip when auth is missing instead of aborting the whole briefing - `daily-morning-briefing` stays disabled until config is saved and a dry-run looks right ## Jobs `daily-morning-briefing` runs `./run-morning-briefing` daily at 07:00 UTC. It is **`enabled: false`**. Enabling it is an operator choice after you save config and confirm a dry-run.