# @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 the `calendar` and `scheduling` templates both
consume so the two apps 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 (scheduling template)             | 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 scheduling template 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:

- **Scheduling template** — calls the package's `connect-video` action
  for `zoom_video`, callback lands at
  `/_agent-native/oauth/zoom/callback.get.ts`, tokens stored 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.

**Planned for v0.2:** `agent-native eject @agent-native/scheduling`.

**Today (v0.1)** — do it manually:

1. `cp -r node_modules/@agent-native/scheduling/src packages/scheduling-local/src`
2. Add `packages/scheduling-local/` to your workspaces.
3. Replace `"@agent-native/scheduling": "^0.1"` in dependencies with
   `"@local/scheduling": "workspace:*"` (or similar).
4. Run a find/replace across your repo from `@agent-native/scheduling` to
   `@local/scheduling`.
5. Install: `pnpm install`.

Now you own the code and can modify 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, 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,
} 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!,
}));
```

## Full documentation

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

## 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, built-in video (Daily.co), 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`.


---
## 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** (planned) — Microsoft Teams.

## 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>` |
| "Refresh calendar cache" | `refresh-busy-times` |


---
## 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 |
