# @agent-native/scheduling — Full Documentation Bundle

Generated by scripts/build-llms-full.mjs. Includes every markdown doc
and skill. For a curated index, see llms.txt.


---
## UI_UNIFICATION.md

# UI Unification — booking-link / event-type editor

As of 0.1.x the `@agent-native/scheduling` package ships a small set of
shared React components for calendar and scheduling surfaces so apps can
keep visual parity without duplicating logic.

Exported from `@agent-native/scheduling/react/components`:

| Component                 | Purpose                                                                                                                       |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `ConferencingSelector`    | 2x2 grid of "No conferencing / Google Meet / Zoom / Custom link"; includes a "Connect Zoom" affordance that starts real OAuth |
| `SlugEditor`              | Inline-editable URL preview (`host/prefix/username/slug`) with click-to-edit segments                                         |
| `CustomFieldsEditor`      | Add / edit / reorder / remove custom booking-form fields, with presets (LinkedIn, Company, Phone, Website)                    |
| `DurationPicker`          | Multi-select pills (15 / 30 / 45 / 60 + custom) for per-event-type durations                                                  |
| `BookingLinkCreateDialog` | Modal prompting for Title / URL / Duration / Description when creating a new event type                                       |
| `AvailabilityEditor`      | Weekly schedule grid with toggles + start/end time pickers; `summarizeAvailability(ws)` helper for list subtitles             |

The existing `SlotPicker` and `TimezoneSelect` continue to live alongside
these.

## Shadcn primitives required in the consumer

Every component in this package imports shadcn primitives via the
standard `@/components/ui/*` alias (resolved by the consumer's bundler).
The package ships type shims for them in
`react/components/_shadcn-shims.d.ts` so it compiles cleanly on its own.

Add these to your template's `app/components/ui/` before importing any
of the shared components:

| Component required | Used by                                                                                               |
| ------------------ | ----------------------------------------------------------------------------------------------------- |
| `button`           | ConferencingSelector, BookingLinkCreateDialog, DurationPicker                                         |
| `input`            | ConferencingSelector, CustomFieldsEditor, DurationPicker, BookingLinkCreateDialog, AvailabilityEditor |
| `label`            | ConferencingSelector, CustomFieldsEditor, SlugEditor, BookingLinkCreateDialog, AvailabilityEditor     |
| `textarea`         | CustomFieldsEditor, BookingLinkCreateDialog                                                           |
| `switch`           | CustomFieldsEditor, AvailabilityEditor                                                                |
| `badge`            | ConferencingSelector                                                                                  |
| `dialog`           | BookingLinkCreateDialog                                                                               |

Icons come from `@tabler/icons-react` (not bundled; each template already
depends on it).

## What moved where

| Old home (calendar template)             | New home                                                              |
| ---------------------------------------- | --------------------------------------------------------------------- |
| `ConferencingEditor` in BookingLinksPage | `ConferencingSelector` in `@agent-native/scheduling/react/components` |
| `EditableBookingUrl` in BookingLinksPage | `SlugEditor` in the package                                           |
| `CustomFieldsEditor` in BookingLinksPage | `CustomFieldsEditor` in the package                                   |

| Old home (removed scheduling app)          | New home                                                                    |
| ------------------------------------------ | --------------------------------------------------------------------------- |
| `DurationsEditor` in event-type editor     | `DurationPicker` in the package                                             |
| `LocationEditor` + `AppsGrid` (two places) | `ConferencingSelector` in the package (Apps tab removed — it was redundant) |
| Inline create dialog in `_index`           | `BookingLinkCreateDialog` in the package                                    |

## Zoom became real OAuth

Previously the calendar template's Zoom option asked the user to paste a
personal meeting URL. The removed scheduling app had no OAuth-based Zoom at
all (only the `zoom_video` "app install" placeholder).

Both now use real Zoom OAuth via the provider's new optional
`startOAuth` / `completeOAuth` methods on `VideoProvider`. Consumers:

- **Custom scheduling surfaces** — can call the package's `connect-video`
  action for `zoom_video`, route callbacks to their app, and store tokens via
  `completeVideoOAuth()`.
- **Calendar template** — ships a lightweight `server/lib/zoom.ts` that
  uses the package's `createZoomProvider` but stores tokens directly in
  core's `oauth_tokens` keyed by Zoom user id + owner email. The
  booking-create handler calls `createZoomMeeting()` when the booking
  link's conferencing type is `zoom`.

Both flows auto-refresh the Zoom access token when it's within 60s of
expiry and mark credentials invalid if Zoom returns 401/403.


---
## actions.md

# Actions

Every action is a `defineAction` module you can re-export into your
template's `actions/` folder:

```ts
// actions/create-booking.ts
export { default } from "@agent-native/scheduling/actions/create-booking";
```

The action scaffolder can do this for you. Full list:

## Event types

- list-event-types, get-event-type, create-event-type, update-event-type,
  delete-event-type, duplicate-event-type, toggle-event-type-hidden,
  reorder-event-types, set-event-type-location, add-private-link,
  revoke-private-link

## Availability

- list-schedules, create-schedule, update-schedule, delete-schedule,
  set-default-schedule, add-date-override, remove-date-override,
  get-availability, check-availability, find-available-slot

## Bookings

- list-bookings, get-booking, create-booking, reschedule-booking,
  cancel-booking, confirm-booking, mark-no-show, add-booking-attendee,
  remove-booking-attendee, send-reschedule-link, add-booking-note,
  export-bookings-csv

## Integrations

- list-calendar-integrations, connect-calendar, disconnect-calendar,
  list-selected-calendars, toggle-selected-calendar, set-destination-calendar,
  refresh-busy-times, install-conferencing-app

## Team

- create-team, invite-team-member, accept-team-invite, remove-team-member,
  update-member-role, set-team-branding

## Round-robin & hosts

- assign-round-robin-host, set-event-type-hosts,
  set-host-availability-override, create-host-group

## Settings

- update-profile, set-appearance, set-default-conferencing-app

## Workflows

- list-workflows, create-workflow, update-workflow, delete-workflow,
  toggle-workflow

## Routing forms

- list-routing-forms, create-routing-form, update-routing-form,
  delete-routing-form, list-routing-form-responses

All actions respect the framework sharing system — if a resource is not
owned by or shared with the current user, read/write actions throw.


---
## eject.md

# Ejecting `@agent-native/scheduling`

For full customization, you can move the package source into your own repo.

Preview the complete change report, then apply it explicitly:

```bash
agent-native package eject @agent-native/scheduling
agent-native package eject @agent-native/scheduling --apply
```

The command copies the published source into `packages/scheduling`, updates the
consumer dependency to `workspace:*`, preserves canonical
`@agent-native/scheduling` imports, and runs the detected package manager. It
refuses an existing target, unsupported workspace layout, or other collision.
All writes and lockfiles roll back if installation fails.

Now you own the code and can modify it freely. Upstream updates are available via
`npm view @agent-native/scheduling versions` — you can selectively port changes.


---
## llms.txt

# @agent-native/scheduling

Scheduling primitives for agent-native apps — event types, availability,
bookings, team scheduling, workflows, routing forms. A self-hosted,
locally-owned scheduling stack, not an API wrapper.

## What this package provides

- **Drizzle schemas** (`/schema`) — event_types, schedules, availability,
  date_overrides, bookings, booking_attendees, booking_references, teams,
  team_members, credentials, calendar_cache, selected_calendars,
  destination_calendars, hashed_links, workflows, workflow_steps,
  scheduled_reminders, webhooks, routing_forms, routing_form_responses,
  api_keys, out_of_office_entries, travel_schedules.
- **Pure helpers** (`/core`) — DST-safe slot computation, timezone math,
  availability rule evaluation, round-robin host selection, buffer
  application, booking-limit bucketing, recurring-event expansion.
- **Server layer** (`/server`) — DB repos, availability engine, booking
  service, pluggable calendar + video providers (Google Calendar, Outlook,
  Zoom, Microsoft Teams, Google Meet, built-in video), iCal generator, lifecycle hooks →
  workflows.
- **Actions** (`/actions/*`) — ~55 `defineAction` modules covering every
  operation in the product (event type CRUD, availability edit, booking
  lifecycle, calendar connect, team ops, round-robin, workflows, routing
  forms). Re-export from a consumer template's `actions/` folder for
  zero-boilerplate adoption.
- **React primitives** (`/react`, `/react/components`) — headless hooks for
  timezone, slots, booking flow; minimal unstyled primitives (SlotPicker,
  TimezoneSelect).
- **Skill docs** (`/skills/*`) — drop-in agent skills for scheduling
  concepts: `scheduling-basics`, `event-types`, `availability`, `bookings`,
  `booker`, `slot-engine`, `team-scheduling`, `integrations`, `embeds`,
  `workflows`, `routing-forms`.

## Quick start

```ts
// server/db/schema.ts
export * from "@agent-native/scheduling/schema";

// server/bootstrap.ts
import { setSchedulingContext } from "@agent-native/scheduling/server";
import {
  registerCalendarProvider,
  registerVideoProvider,
  createGoogleCalendarProvider,
  createDailyVideoProvider,
  createTeamsProvider,
} from "@agent-native/scheduling/server/providers";
import { getDb, schema } from "./db/index.js";

setSchedulingContext({
  getDb,
  schema,
  getCurrentUserEmail: () => getRequestUserEmail(),
  getCurrentOrgId: () => getRequestOrgId(),
  publicBaseUrl: process.env.PUBLIC_URL,
});

registerCalendarProvider(createGoogleCalendarProvider({
  clientId: process.env.GOOGLE_CLIENT_ID!,
  clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
  getAccessToken: fetchGoogleToken, // your implementation
}));

registerVideoProvider(createDailyVideoProvider({
  apiKey: process.env.DAILY_API_KEY!,
}));

registerVideoProvider(createTeamsProvider({
  clientId: process.env.MICROSOFT_CLIENT_ID!,
  clientSecret: process.env.MICROSOFT_CLIENT_SECRET!,
  getAccessToken: fetchMicrosoftToken, // your refresh-aware implementation
}));
```

## Full documentation

For the full bundled docs (every .md file concatenated), see
`llms-full.txt` in this folder.

## Package lifecycle

The published static `agent-native.package.json` drives safe package lifecycle
commands. Use `agent-native package inspect @agent-native/scheduling --json`
for contribution metadata, and preview `package add` or `package eject` before
re-running with `--apply`. Inspection never executes package code.

## Individual skills

- skills/scheduling-basics/SKILL.md — Core concepts, terminology.
- skills/event-types/SKILL.md — Event type model, tabs, scheduling types.
- skills/availability/SKILL.md — Schedules, date overrides, timezone rules.
- skills/bookings/SKILL.md — Lifecycle, reschedule, cancel, no-show.
- skills/booker/SKILL.md — Public booking flow, state machine.
- skills/slot-engine/SKILL.md — The pure slot-computation function and
  invariants.
- skills/team-scheduling/SKILL.md — Team event types, round-robin,
  collective.
- skills/integrations/SKILL.md — Calendar + video provider integrations.
- skills/embeds/SKILL.md — Inline/popup/floating-button embeds.
- skills/workflows/SKILL.md — Trigger-based automations.
- skills/routing-forms/SKILL.md — Pre-booking routing forms.


---
## providers.md

# Writing a custom provider

Scheduling needs two kinds of providers:

- **CalendarProvider** — reads busy intervals + writes events.
- **VideoProvider** — creates meeting rooms when a booking is confirmed.

Built-ins: Google Calendar, Office 365, Zoom, Microsoft Teams, built-in video
(Daily.co), and Google Meet (piggy-backs on Google Calendar).

## CalendarProvider

```ts
import { registerCalendarProvider } from "@agent-native/scheduling/server/providers";

registerCalendarProvider({
  kind: "my_calendar",
  label: "My Calendar",
  async startOAuth({ redirectUri, state }) {
    /* return { authUrl } */
  },
  async completeOAuth({ code, credentialId, userEmail, redirectUri }) {
    /* exchange code, persist tokens, return externalEmail + calendars */
  },
  async listCalendars({ credentialId }) {
    /* ... */
  },
  async getBusy({ credentialId, calendarExternalIds, start, end }) {
    /* ... */
  },
  async createEvent({
    credentialId,
    calendarExternalId,
    booking,
    includeConference,
  }) {
    /* ... */
  },
  async updateEvent({ credentialId, externalId, booking }) {
    /* ... */
  },
  async deleteEvent({ credentialId, externalId }) {
    /* ... */
  },
});
```

All methods receive a `credentialId` — use it to look up the OAuth token via
your token store. The package's `setSchedulingContext()` doesn't touch
tokens; consumers typically use core's `oauth_tokens` and pass a
`getAccessToken(credentialId)` callback.

## VideoProvider

```ts
registerVideoProvider({
  kind: "my_video",
  label: "My Video",
  async createMeeting({ credentialId, booking }) {
    return { meetingUrl, meetingId, meetingPassword? };
  },
  async deleteMeeting?({ credentialId, meetingId }) { /* ... */ },
});
```

Video providers are invoked by the booking service when a booking's location
is `builtin-video`, `zoom`, `google-meet`, or `teams`. Meeting URLs land in
`booking_references`.

### Microsoft Teams

Teams uses delegated Microsoft OAuth for work or school accounts. Register the
provider with consumer-owned token storage and refresh callbacks:

```ts
import {
  createTeamsProvider,
  registerVideoProvider,
} from "@agent-native/scheduling/server/providers";

registerVideoProvider(
  createTeamsProvider({
    clientId: process.env.MICROSOFT_CLIENT_ID!,
    clientSecret: process.env.MICROSOFT_CLIENT_SECRET!,
    tenant: process.env.MICROSOFT_TENANT_ID,
    getAccessToken: resolveMicrosoftAccessToken,
    updateTokens: saveMicrosoftTokens,
    markInvalid: markSchedulingCredentialInvalid,
  }),
);
```

The provider requests `offline_access`, `OnlineMeetings.ReadWrite`, and
`User.Read`. Start the grant with `connect-video --kind teams_video` and finish
the callback with `completeVideoOAuth()`. `getAccessToken` must refresh expired
tokens; the package never reads a token store or environment directly.


---
## schema.md

# Schema reference

All tables use dialect-agnostic helpers from `@agent-native/core/db/schema`
and compose with `ownableColumns()` + `createSharesTable()` for framework-
wide sharing.

## event_types

The primary bookable resource. Duration, location options, custom fields,
buffers, limits, period (rolling/range/unlimited), scheduling type
(personal/collective/round-robin/managed), optional team assignment,
hashed-link support.

## event_type_hosts

Join between event types and users for collective / round-robin. Each host
has `isFixed`, `weight`, `priority`, optional per-host `scheduleId` override.

## hashed_links

Private booking links: `/d/:hash/:slug`. Optional `expiresAt` and
`isSingleUse`.

## event_type_slug_redirects

Rename history so old URLs keep working.

## schedules / schedule_availability / date_overrides

Named availability presets. `schedules` is the header (name, timezone,
isDefault). `schedule_availability` rows are one weekly interval per day.
`date_overrides` replaces the weekly rule for a specific date (empty =
fully blocked).

## travel_schedules / out_of_office_entries

Per-user timezone overrides for trips; OOO windows that block bookings and
optionally redirect.

## bookings / booking_attendees / booking_references / booking_seats /

## booking_notes

Bookings are the materialized appointments. Attendees are N per booking.
References are external IDs (Google event id, Zoom meeting id). Seats are
reservation tokens for seated events. Notes are host-only.

## teams / team_members

Teams group users. `team_members.role` ∈ owner|admin|member.

## scheduling_credentials / selected_calendars / destination_calendars

`scheduling_credentials` is a view over OAuth tokens (with display metadata
and `invalid` flag). Selected calendars are read for busy-time. Destination
calendar is where new events get written.

## calendar_cache

Short-TTL cache of busy intervals. Busted on booking write.

## workflows / workflow_steps / scheduled_reminders

Trigger-based automations. `workflows` is the rule; `workflow_steps` are
ordered actions; `scheduled_reminders` are materialized sends waiting to
fire (drained by a recurring job).

## webhooks / webhook_deliveries / api_keys

Developer-facing surface: outgoing webhooks with HMAC-signed payloads, and
API keys for programmatic access.

## routing_forms / routing_form_responses

ChiliPiper-style routing forms. Fields + rules → either an event type,
external URL, or custom message.

## verified_emails / verified_numbers

Verified sender addresses / numbers used by workflows.

## Shares tables

For every ownable resource: `event_type_shares`, `schedule_shares`,
`team_shares`, `workflow_shares`, `routing_form_shares`, `booking_shares`.
Use `share-resource` / `set-resource-visibility` framework actions.


---
## skills/availability/SKILL.md

---
name: availability
description: How schedules, weekly rules, date overrides, travel schedules, and out-of-office entries combine to determine when someone is bookable.
---

# Availability

## Schedule

A named set of rules (e.g. "Working Hours"). Each user has ≥1 schedule and
marks one as `isDefault`. Event types either use the user's default or
reference a specific schedule.

Timezone is set on the schedule, not the user — lets you have a "Europe
hours" schedule and a "US hours" schedule for the same person.

## Weekly availability

Rows in `schedule_availability`: (day 0-6, startTime "HH:MM", endTime
"HH:MM"). You can have multiple intervals per day — e.g. Mon 9-12 and 1-5.

## Date overrides

Rows in `date_overrides`: (date "YYYY-MM-DD", intervals JSON).
- `intervals: []` → day fully blocked.
- `intervals: [{start, end}]` → only those times available on that date.

## Travel schedules

`travel_schedules` overrides the user's default timezone for a date range.

## Out of office

`out_of_office_entries` blocks bookings across a range, optionally with a
redirect to another team member.

## Common tasks

| User request | Action |
|---|---|
| "I'm unavailable next Friday" | `add-date-override --scheduleId <id> --date 2026-04-10 --intervals []` |
| "I take lunch 12-1 weekdays" | `update-schedule --weeklyAvailability [...9-12, 13-17...]` |
| "Create evenings schedule" | `create-schedule --name Evenings --weeklyAvailability [...]` |
| "Going to Tokyo next week" | Insert a `travel_schedules` row (no dedicated action yet) |


---
## skills/booker/SKILL.md

---
name: booker
description: Public booking flow — the state machine, animations, and URL/app-state sync.
---

# Booker

## Stages

1. **pick-date** — month grid shows days with availability
2. **pick-slot** — available slots on the selected day
3. **fill-form** — attendee form (name, email, custom fields)
4. **success** — confirmation with add-to-calendar buttons

Plus **reschedule** mode — prefilled from existing booking uid.

## Animations

Use motion (`motion/react`) `AnimatePresence` with `fadeInLeft` variants. The outer
container animates its width across stages: narrow (calendar only) → wider
(calendar + slots) → widest (form).

## Timezone + 12/24h

- Detect browser timezone at mount; let user override via dropdown.
- Persist choice to localStorage key `scheduling.timezone`.
- 12/24h toggle near slot column; persist to user settings.

## URL + application-state sync

Selected date, slot, timezone, and duration are mirrored to URL query
params AND `application_state.booker-state` so:
- Page refresh preserves selection.
- Agent can read the user's current pick.

## Copy-link affordance

Every event-type row has a "copy link" button. On click, write to clipboard
and toast "Copied!" with Sonner. Works on mobile Safari too.

## Accessibility

- Month grid is ARIA `grid` + `gridcell` with arrow-key nav.
- Slot buttons are keyboard-focusable, with `aria-pressed`.
- Focus returns to trigger on modal close.
- Toast content announced via `aria-live=polite`.

## Mobile

`< 768px`: stack vertically. Calendar full-width, slot list below, form
slides in as full-screen sheet.


---
## skills/bookings/SKILL.md

---
name: bookings
description: Booking lifecycle — pending, confirmed, rescheduled, cancelled — plus attendees, references, no-shows, and reminders.
---

# Bookings

## Lifecycle

- **pending** — requires-confirmation event types start here
- **confirmed** — normal state
- **rescheduled** — the old booking after a reschedule (the new one is
  confirmed; `from_reschedule` links them)
- **cancelled** — either party cancelled
- **rejected** — pending was declined

## Reschedule vs cancel+rebook

The `reschedule-booking` action creates a new booking with a link back to
the old via `fromReschedule`. iCalUID is preserved across the reschedule
chain (RFC 5545), and `iCalSequence` is bumped.

## No-show

`mark-no-show` sets `noShow: true` on an attendee. Round-robin calibration
uses this to penalize hosts whose attendees no-show frequently.

## Cancel / reschedule tokens

Every booking has a `cancelToken` and `rescheduleToken` used in public
magic links sent to attendees. These let them manage the booking without
logging in.

## References

External system IDs: Google Calendar event id, Zoom meeting id, Daily.co
room name. Stored in `booking_references`, used during cancel/reschedule to
propagate changes back to the source system.

## ICS

`/booking/:uid.ics` returns the RFC 5545 calendar file. Used for
confirmation-email attachments and "Add to calendar" buttons.


---
## skills/embeds/SKILL.md

---
name: embeds
description: Inline, popup, and floating-button embeds — snippet generation and theming.
---

# Embeds

## Modes

- **inline** — iframe embedded in the page
- **popup** — trigger opens modal overlay with iframe
- **element-click / floating-button** — fixed-position trigger anywhere on
  the page

## URL

Embed page: `/:user/:slug/embed` — same Booker, no chrome, theme honored
via query params.

## Snippet

```html
<script src="https://<host>/embed.js" async></script>
<div id="cal-inline-embed"></div>
<script>
  Cal("init", { origin: "https://<host>" });
  Cal("inline", {
    elementOrSelector: "#cal-inline-embed",
    calLink: "my-user/intro",
    config: { theme: "light" }
  });
</script>
```

## Theming

Query params on the embed URL:
- `theme=light|dark`
- `primaryColor=<hex>`
- `locale=en|es|...`
- `timeZone=America/Los_Angeles`

These override the event type's defaults for the embedded session only.

## Message passing

The iframe posts messages to the parent on lifecycle events:
`__cal.init`, `__cal.booking-successful`, `__cal.booking-cancelled`,
`__cal.booking-rescheduled`.


---
## skills/event-types/SKILL.md

---
name: event-types
description: How event types work — fields, scheduling types, tabs in the editor, and the full set of configurable options.
---

# Event types

## Editor tabs
- **Setup** — title, slug, duration(s), description, default location
- **Availability** — pick a schedule or override per-event-type
- **Limits** — buffers, min notice, booking window (rolling/range), caps
  (perDay/perWeek/perMonth/perYear), slot interval
- **Advanced** — event name template, lock timezone, require confirmation,
  disable guests, redirect URL, private hashed links, seats
- **Apps** — connect per-event location types (Zoom, Meet, etc.)
- **Workflows** — attach workflows to run on booking lifecycle events

## Scheduling types

| Type | Meaning |
|---|---|
| `personal` | Owned by a user, only they host |
| `collective` | Team event; all selected hosts must be free |
| `round-robin` | Team event; assign to one host by rotation |
| `managed` | Parent event pushed to child event types across members |

## Location kinds

`builtin-video`, `zoom`, `google-meet`, `teams`, `phone`, `in-person`,
`custom-link`, `attendee-phone`, `organizer-phone`, `attendee-choice`.

## Custom fields

Text, textarea, number, email, phone, select, multiselect, boolean, radio.
Stored on the event type; responses stored on the booking.

## Hashed links

Private booking URLs at `/d/:hash/:slug`. Create via `add-private-link`,
optionally with `expiresAt` and `isSingleUse`.


---
## skills/integrations/SKILL.md

---
name: integrations
description: Calendar + video provider integrations — Google Calendar, Office 365, Zoom, built-in video, Google Meet — and how to write new ones.
---

# Integrations

## Calendar providers

- **google_calendar** — OAuth; read freeBusy + write events; optionally
  create Google Meet conference via `includeConference=true`.
- **office365_calendar** — Microsoft Graph; read freeBusy + write events.
- **caldav** (planned) — generic CalDAV for Apple iCloud, Fastmail, etc.

## Video providers

- **builtin_video** — Daily.co-backed; zero OAuth; server-to-server API key.
- **zoom_video** — OAuth; create meetings via Zoom REST API.
- **google_meet** — piggy-backs on Google Calendar credential.
- **teams_video** — delegated Microsoft OAuth for work or school accounts;
  creates and cancels standalone meetings through Microsoft Graph.

## Credential lifecycle

1. User clicks "Connect" → `connect-calendar` returns `authUrl` + `state`
2. Redirect to provider → consent → provider redirects to our callback
3. Server exchanges code → writes `scheduling_credentials` row +
   core `oauth_tokens` entry
4. We fetch calendar list, let user pick "checked" + "destination"
5. Token expires → refresh flow runs silently; on failure, set
   `invalid: true` and show re-connect banner

## Busy-time aggregation

`aggregateBusy({userEmail, rangeStart, rangeEnd})` merges:
- Confirmed bookings hosted by the user
- External busy from each `selected_calendars` entry via the provider

Cached in `calendar_cache` (short TTL, default 5 min). Busted on any
booking write for that host.

## Writing a new provider

See `docs/providers.md` for the full interface.

## Common tasks

| User | Action |
|---|---|
| "Connect Google Calendar" | `connect-calendar --kind google_calendar --redirectUri ...` → redirect to returned `authUrl` |
| "Stop checking my vacation calendar" | `toggle-selected-calendar --include false` for that externalId |
| "Default to Zoom for new bookings" | `set-default-conferencing-app --credentialId <zoom-cred>` |
| "Connect Microsoft Teams" | `connect-video --kind teams_video --redirectUri ...` → complete the callback with `completeVideoOAuth()` |
| "Default to Teams for new bookings" | `set-default-conferencing-app --credentialId <teams-cred>` |
| "Refresh calendar cache" | `refresh-busy-times` |

## Microsoft Teams setup

Register `createTeamsProvider()` as a video provider with the deployment's
Microsoft client id and secret plus consumer-owned `getAccessToken`,
`updateTokens`, and `markInvalid` callbacks. The provider defaults to the
`organizations` tenant because Graph's delegated online-meeting API supports
work or school accounts, not personal Microsoft accounts. It requests only
`offline_access`, `OnlineMeetings.ReadWrite`, and `User.Read`.

Use `connect-video` to start OAuth and `completeVideoOAuth()` in the callback;
inserting a `teams_video` row with `install-conferencing-app` alone does not
grant Microsoft access. Token refresh remains the consumer's responsibility in
`getAccessToken`. The booking service stores the returned meeting id and join
URL in `booking_references` and calls the provider again on cancellation.


---
## skills/routing-forms/SKILL.md

---
name: routing-forms
description: ChiliPiper-style pre-booking forms that route prospects to the right event type based on their answers.
---

# Routing forms

## Shape

- **Fields** — text, email, phone, number, select, multi
- **Rules** — ordered list of `{conditions, action}`
- **Fallback** — action taken when no rule matches

## Rule conditions

`conditions: [{fieldId, op, value}, …]` — all ANDed together. Ops:
- `equals`, `not-equals`, `contains`, `starts-with`, `in` (value is array)

## Rule actions

- `{kind: "event-type", eventTypeId, teamId?}` → redirect to Booker
- `{kind: "external-url", url}` → redirect off-site
- `{kind: "custom-message", message}` → render message, no booking

## Public URL

`/forms/:formId` renders the form. On submit:
1. Walk rules in order; first match wins.
2. If none match, use `fallback`.
3. Record the response in `routing_form_responses` with `matchedRuleId`.
4. If the action is `event-type`, redirect to Booker with prefilled
   `name` / `email` / custom-field values from the form answers.

## Common tasks

| User | Action |
|---|---|
| "Route enterprise prospects to Bob" | `create-routing-form` with a rule matching company size → Bob's event type |
| "See submissions" | `list-routing-form-responses --formId <id>` |


---
## skills/scheduling-basics/SKILL.md

---
name: scheduling-basics
description: Core concepts of the scheduling package — event types, schedules, bookings, hosts, teams, and how they compose.
---

# Scheduling basics

The mental model:

- **Event Type** — a definition of "a bookable thing" (30-minute intro call,
  45-minute demo). Lives at `/:user/:slug` or `/team/:teamSlug/:slug`.
- **Schedule** — a named set of availability rules ("Working Hours",
  "Evenings"). Weekly intervals plus date-specific overrides. Each user
  has a default schedule; event types can pick any.
- **Booking** — the materialized appointment. Has attendees, references to
  external systems (Google Calendar event, Zoom meeting), and a stable
  iCalUID across reschedules.
- **Host** — a user assigned to an event type. Hosts have weights and
  priorities for round-robin.
- **Team** — a group of users who can co-host event types.
- **Location** — where the meeting happens. Video (built-in, Zoom, Meet,
  Teams), phone, in-person, or custom link.

## Workflows and routing forms

- **Workflow** — trigger + ordered steps. "Email attendee 1h before event."
- **Routing Form** — pre-booking form with rules that route to the right
  event type based on answers. Like ChiliPiper.

## Public URLs

- `/:user` — user profile with event type list
- `/:user/:slug` — Booker for a personal event type
- `/:user/:slug/embed` — chromeless for iframe embedding
- `/team/:teamSlug` — team profile
- `/team/:teamSlug/:slug` — Booker for team event type (round-robin or collective)
- `/d/:hash/:slug` — private hashed-link Booker
- `/booking/:uid` — booking detail / manage
- `/reschedule/:uid` — reschedule Booker
- `/forms/:formId` — public routing form

## Common tasks

| User request | Action(s) |
|---|---|
| "Create a 30-minute intro meeting" | `create-event-type --title "Intro" --slug intro --length 30` |
| "What's my availability tomorrow?" | `check-availability --slug <slug> --from ... --to ...` |
| "Cancel my 3pm with Alex" | `list-bookings --status upcoming`, then `cancel-booking --uid ...` |
| "Block next Friday" | `add-date-override --scheduleId <id> --date 2026-04-10 --intervals []` |
| "Connect Google Calendar" | `connect-calendar --kind google_calendar --redirectUri ...` |
| "Make a team event rotating between Alice and Bob" | `create-event-type --schedulingType round-robin --teamId ...`, then `set-event-type-hosts` |


---
## skills/slot-engine/SKILL.md

---
name: slot-engine
description: The pure `computeAvailableSlots` function — inputs, outputs, invariants, and debugging guide.
---

# Slot engine

`import { computeAvailableSlots } from "@agent-native/scheduling/core"`

## Inputs
- Event type config (duration, buffers, minimum notice, period, limits,
  slot interval)
- Schedule (timezone + weekly availability + date overrides)
- Busy intervals (UTC, aggregated from external calendars + existing
  bookings)
- Booking counts by bucket (day/week/month/year) for limit enforcement
- Time range to compute over
- Optional seats-per-slot + seats-taken map
- Viewer timezone (for limit bucketing, if it differs from the schedule)

## Output
- Array of `Slot { start: ISO, end: ISO, available: boolean, seatsRemaining?, hostEmail? }`

## Invariants

The function guarantees, in order:

1. No slot falls in the past (`now + minimumBookingNotice` is the floor).
2. No slot overlaps a busy interval (with buffers applied).
3. No slot falls outside the schedule's available intervals for that day.
4. No slot exceeds a booking limit.
5. No slot falls outside the event's period (rolling/range).
6. DST-safe: we convert to UTC using the schedule's timezone before doing
   any interval arithmetic.

## Debugging

If a slot is unexpectedly missing:
1. Check the schedule's weekly availability for that day-of-week in the
   schedule's timezone (not UTC — weekends can shift across the date line).
2. Check date overrides for that local date (empty intervals = fully
   blocked).
3. Check merged busy intervals for overlap, *including* the before/after
   buffer expansion.
4. Check booking limits: a single existing booking can close a day.
5. Check period caps: rolling periods restrict how far in the future slots
   appear.

If a slot is unexpectedly present:
1. Check that `now` is being passed correctly (defaults to `new Date()`).
2. Check that busy intervals from providers are in UTC.
3. Confirm slot interval matches what you expect (default = duration).


---
## skills/team-scheduling/SKILL.md

---
name: team-scheduling
description: Team event types, round-robin assignment, collective bookings, host weights, and no-show calibration.
---

# Team scheduling

## Scheduling types

- **Collective** — all selected team members must be free; booking lists
  all as organizers.
- **Round-robin** — one team member is chosen per booking by a rotation
  strategy.
- **Managed** — parent event type pushed to member-level children (advanced;
  stub in v1).

## Round-robin strategies

| Strategy | How |
|---|---|
| `lowest-recent-bookings` | (Default) host with fewest bookings in past 30d wins; tiebreak by priority, then weight, then email |
| `weighted` | Weighted random pick; deterministic given the same seed |
| `calibrated` | Weighted with no-show penalty (hosts with high no-show rates get fewer) |

## Hosts

Rows in `event_type_hosts`: `{userEmail, isFixed, weight, priority,
scheduleId?}`. Fixed hosts always attend (like collective within a
round-robin set). Weight scales the relative share. Priority (lower =
higher) breaks ties.

## Host schedule override

Normally each host's default schedule is used for their slots. A
per-event-type-per-host override is possible via
`set-host-availability-override`.

## Out-of-office

OOO hosts are auto-excluded from round-robin for the duration of the OOO
window. Bookings can redirect to the OOO's `redirectUserEmail`.

## Host groups

`event_type_host_groups` lets you split hosts into groups — useful for
"collective within each group, round-robin across groups".

## Common tasks

| User | Action |
|---|---|
| "Make a sales demo that rotates Alice / Bob / Carol" | `create-event-type --schedulingType round-robin --teamId ...`, then `set-event-type-hosts` |
| "Add Dave as a fixed host" | `set-event-type-hosts` with Dave as `isFixed: true` |
| "Stop routing to Alice while she's on PTO" | Insert an `out_of_office_entries` row for Alice's range |


---
## skills/workflows/SKILL.md

---
name: workflows
description: Trigger-based automations — reminders, follow-ups, webhooks — across the booking lifecycle.
---

# Workflows

## Triggers

- `new-booking` — fires when a booking is created
- `before-event` — offset minutes BEFORE `startTime`
- `after-event` — offset minutes AFTER `endTime`
- `reschedule` — booking rescheduled
- `cancellation` — booking cancelled
- `no-show` — a host marked an attendee as no-show

## Steps

| Action | Sends |
|---|---|
| `email-host` | Email to the organizer |
| `email-attendee` | Email to the attendee |
| `email-address` | Email to a fixed address (e.g. ops@example.com) |
| `sms-attendee` | SMS to the attendee's phone (requires attendee `phone` custom field) |
| `sms-host` | SMS to the host |
| `sms-number` | SMS to a fixed number |
| `webhook` | HTTP POST to a URL |

Step `offsetMinutes` is relative to the trigger time. For `before-event`
use positive values (we apply them with a minus sign internally).

## Template variables

In email subjects / bodies and SMS bodies:
- `{eventName}` — event type title
- `{attendeeName}`, `{attendeeEmail}` — first attendee
- `{hostName}`, `{hostEmail}` — organizer
- `{startTime}`, `{endTime}` — formatted in host's timezone
- `{location}` — meeting URL or address
- `{cancelUrl}`, `{rescheduleUrl}` — public magic links

## Firing

When a booking fires a trigger, the hook dispatcher materializes rows in
`scheduled_reminders`. A recurring job processes due rows and fires the
actual emails/SMS/webhooks. Framework-side recurring jobs handle the
polling.

## Common tasks

| User | Action |
|---|---|
| "Email attendees 24h before the meeting" | `create-workflow --trigger before-event --steps '[{action: email-attendee, offsetMinutes: 1440}]'` |
| "Text me when someone books" | `create-workflow --trigger new-booking --steps '[{action: sms-host, ...}]'` |
| "Stop all reminders on an event type" | `toggle-workflow` to disable |
