# `automate.ax`

Happy-path Automate.ax package for automation authors. It provides the `automate` CLI plus the public helpers used in `automate.config.ts` and automation files.

## Install

```bash
bun add automate.ax
```

## CLI

```bash
bunx automate.ax --help
bunx automate.ax login
bunx automate.ax init
bunx automate.ax deploy --dir ./my-app
bunx automate.ax proj list
bunx automate.ax org apikey create --org <organization>
bunx automate.ax org apikey list --org <organization>
```

`proj` is an alias for the `project` command group.

The published CLI defaults to `https://automate.ax`. From this repo, use `bun run cli:local -- <command>` to run against the current worktree's Treeline-managed development origin. Organization API key commands also support `rename` and `delete`; created secrets are shown once.

`automate init` requires a saved CLI login, selects or creates a project, writes `automate.config.ts`, and scaffolds an organization email example in `automations/example.automation.ts` when the `automations` directory does not exist. It creates a minimal `package.json` and strict `tsconfig.json` when needed and installs `automate.ax` with the detected package manager, falling back to Bun; pass `--no-install` to skip installation. Existing configs are rejected without making changes.

## Config

```ts
import { defineConfig } from "automate.ax"

export default defineConfig({
  projectId: "project_...",
})
```

`automate deploy` resolves the Automate.ax directory from `--dir`, the current directory, the nearest parent config, or a bounded downward config search. It typechecks the project with TypeScript 7 before building; pass `--skip-typecheck` to bypass this check. It uses `.automate/build` as temporary workspace, removes it after bundling, and uploads the in-memory JavaScript artifact to Automate.ax. Successful deployments retain `.automate/deployment.md`. Deployment fails when no `*.automation.ts` files are found unless `--allow-empty` is passed. The CLI shows a single `Deploying` spinner while polling the Graphile-backed deployment every two seconds. Compatible account bindings carry over from the active deployment; pass `--rebind-accounts` to select every account again. JSON output returns the authorization state instead of prompting. Ctrl-C detaches without cancelling the deployment.

To authenticate a deployment with an organization API key, expose it to the command as `AUTOMATE_AX_API_KEY`:

```bash
AUTOMATE_AX_API_KEY="$AUTOMATE_AX_API_KEY" bunx automate.ax deploy --dir ./my-app
```

An API key takes precedence over a saved CLI login session and is never written to disk by the CLI. Authentication mode does not control interactivity: an API-key-authenticated command can still prompt in a terminal, while CI, non-TTY, and `--json` runs do not prompt.

## Automation

```ts
import {
  automation,
  onHttpRequest,
  respondToHttpRequest,
  transform,
} from "automate.ax"

export default automation("When a request is received", () => {
  const request = onHttpRequest({
    scope: "automation",
  })
  const method = request.method
  const path = request.path
  const message = transform(
    [method, path],
    (resolvedMethod, resolvedPath) =>
      `${resolvedMethod} request to ${resolvedPath}`,
  )

  respondToHttpRequest({ body: message, requestId: request.requestId })
})
```

`automation()` attaches a `.description` metadata field and runs the same bundle during planning and each durable context advance.

Declare project-specific values with dashboard-form-compatible parameter fields. Automate.ax collects required values during project setup and passes their inferred primitive types to the automation:

```ts
import { automation, fetchHttp } from "automate.ax"

export default automation(
  "Sync an onboarding segment",
  {
    parameters: [
      { label: "Segment ID", name: "segmentId", type: "text" },
      { defaultValue: 100, name: "batchSize", type: "number" },
    ],
  },
  ({ parameters }) => {
    fetchHttp({
      method: "POST",
      url: `https://api.example.com/segments/${parameters.segmentId}/sync?batchSize=${parameters.batchSize}`,
    })
  },
)
```

Parameters are non-secret configuration. Values are inherited by later deployments while the automation identity and parameter name remain unchanged.

`transform(signal, fn)` and `transform(signals, fn)` record their signal inputs as dependencies. Symbolic traversal discovers durable event and action values without calling `fn`; targeted hydration materializes the inputs and then calls it. Transform results stay in memory and may be any value. A thrown transform error immediately fails the derived signal, skips dependent actions, and doesn't consume action retries. `signal.transform(fn)`, `signal.transform(other, fn)`, and `signal.transform(others, fn)` prepend the receiver to the same operation. Non-function property access is shorthand for a property transform and composes across objects and primitives, so `request.path.length` is a `Signal<number>`. Use an explicit transform for function-valued properties, such as `names.transform((values) => values.join(", "))`.

`timestamp(anchor)` returns the stable platform `Date` for one signal occurrence; `anchor.timestamp()` is the fluent form. Trigger timestamps record event acceptance, action timestamps record completion, persisted signal timestamps record the decision, and pure composites use their latest contributing boundary. `correlationId(anchor, perDeclaration?)` returns a stable opaque identity for one signal occurrence, keyed by that identity for direct use in cross-context operators. Each declaration is distinct by default; pass `false` when equivalent signals with the same durable dependency boundary should share identity. Prefer provider domain identities when they carry the needed semantics. `signal.keyBy(getKey)` adds immutable key metadata, while `signal.globally()` explicitly selects one shared cross-context partition. `correlate(streams, { occurrenceTtl?, ordered? })` eagerly joins keyed signals. Inputs default to oldest-and-consume; `{ signal, selection: "latest", consumption: "retain" }` creates a reusable current-value register. A match creates a child context with every parent history, so downstream work consumes the original signals; the returned `Signal<null>` is available when a composition needs the explicit match boundary.

`collect(signal, count)` gathers a fixed number of occurrences. `once(signal, { ttl? })` and `take(signal, count, { ttl? })` bound admission per partition. `rateLimit(signal, { limit, interval, overflow? })` applies a rolling quota and waits FIFO by default. `funnel(signal, options)` durably reshapes cross-context bursts with quiet periods, relative maximum durations or an absolute `until` deadline, minimum gaps, and leading or trailing emission. `debounce(signal, duration)` selects the latest occurrence after a quiet period. `window(signal, duration)` buffers a fixed-duration burst, while `window(signal, { until })` buffers through an absolute `Date`. These operations require a keyed or explicitly global input and preserve that partition choice. Collected and buffered results are typed as nonempty arrays.

Use `dependentOn(signal, prerequisites)` or `signal.dependentOn(prerequisites)` to preserve one signal value while attaching dependencies. Use `withPrerequisites(prerequisites, fn)` to add dependencies to every durable operation traversed synchronously by `fn`. `scope(fn)` creates only an isolated hook namespace, while `group(options, fn)` adds a named presentation group. `branch(condition, whenTrue, whenFalse?)` composes scoped prerequisite sections under complementary gates. `filter(signal, predicate)` and `partition(signal, predicate)` preserve and route a signal value directly. `merge(signals)` emits every occurrence from independent streams, while `race(signals)` selects the earliest emitted or failed alternative within one causal occurrence. `serialize(stream, fn)` runs one structured work region at a time per keyed or global partition; `concurrent(stream, { limit }, fn)` runs up to `limit`. Branch callbacks are always traversed so hook identities remain deterministic; actions under unopened gates are skipped without running.

Every action's complete dependency chain must resolve to exactly one closest context boundary. One-root automations infer that root for actions without signal dependencies. Automations with multiple visible or hidden roots must attach literal actions through a signal input or `withPrerequisites`. `correlate`, `collect`, `window`, and `funnel` create boundaries from selected occurrences, `each` creates one per item, and `race` creates one for its selected result. Planning rejects an action when no unique closest boundary exists. Durable `delay` and `timeout`, `firecrawl.crawlWebsite`, `codex.runCloudTask`, and `codex.waitForCloudTask` declare hidden triggers; scope their literal starter actions to the initiating signal.

Declare triggers outside prerequisite sections. Planning rejects triggers inside `withPrerequisites(prerequisites, fn)` or `branch`; use keyed signals and `correlate` to join independent trigger contexts.

```ts
import { automation, branch } from "automate.ax"
import { gmail } from "automate.ax/gmail"

export default automation("Star requested emails", () => {
  const email = gmail.onNewEmail()

  branch(
    email.subject.transform((subject) => subject.includes("/starme")),
    () => {
      gmail.starMessages({ messageIds: email.messageId })
    },
  )
})
```

When both callbacks return signals, `branch` returns the selected result as a deferred union signal:

```ts
const contact = branch(
  shouldCreate,
  () => createContact({ email }),
  () => findContact({ email }),
)
// Signal<CreateContactOutput | FindContactOutput>
```

Signals finish by emitting a value, closing without one, or failing. Signal dependencies propagate closure and failure; dependent actions are recorded as skipped. `outcome(signal)` turns every terminal state into a tagged value, `succeeded(signal)`, `failed(signal)`, and `closed(signal)` emit boolean predicates, and `onSuccess(signal)`, `onFailure(signal)`, and `onClose(signal)` selectively emit one outcome. Each `onX` API also accepts a synchronous callback as either `onX(signal, callback)` or `signal.onX(callback)`. The callback receives `(selectedSignal, originalSource)`, its declarations depend on the selection, and the API returns its result.

Declare custom actions at the top level, then call them inside an automation:

```ts
import { defineAction } from "automate.ax"
import { z } from "zod"

const notify = defineAction("Notify")
  .describe("Sends a notification")
  .input(
    z.object({
      message: z.string(),
    }),
  )
  .output(z.void())
  .retry({ replaySafety: "unsafe" })
  .handler(({ input }) => {
    console.log(input.message)
  })
```

Omitting `input` uses an empty-object schema, and omitting `output` uses a void schema. `describe`, `input`, and `output` can be called in any order or repeated before `handler`; the latest value for each replaces the previous one. The resulting action exposes its display metadata through `.meta`. The runtime snapshots that metadata onto each action invocation for display and debugging. Handlers receive a `runtime` object containing the platform capabilities available to action code; context and transport methods remain internal to the SDK. The current capabilities are `runtime.createCallbackUrl(endpoint)`, `runtime.evaluate(input)`, `runtime.generate(input)`, `runtime.startImageGeneration(input)`, `runtime.sendOutput({ type, data })`, and `runtime.sendEmail(input)`.

Every action must call `.retry()` after its final input schema. `maxAttempts` includes the first attempt, defaults to `3`, and accepts `1` through `10`. `replaySafety` accepts `"safe"`, `"unsafe"`, or a function of validated input. Ordinary handler errors retry only when replay is safe; unsafe ambiguous failures settle as indeterminate. Throw `RetryableActionError`, `TerminalActionError`, or `IndeterminateActionError` for an explicit disposition. A retryable error can include an absolute `retryAt`; `parseRetryAfter` converts provider seconds or HTTP dates, and the runtime treats that time as a lower bound over platform backoff. Provider clients can classify an existing error without replacing it through `retryableActionError`, `terminalActionError`, or `indeterminateActionError`.

`runtime.createCallbackUrl(endpoint)` signs an `onHttpRequest({ protection: "callback" })` endpoint for the exact trigger and receiving automation. Treat the result as an expiring bearer secret. The URL remains reusable for provider retries, and ingress strips its protection query parameter from emitted request data. A verified callback still creates an independent root context; correlation by provider job identity connects it to the action that registered the callback.

`runtime.sendOutput({ type, data })` requires a string discriminator and encodable data. It durably emits the output without ending the automation. A handler may emit multiple ordered outputs; call positions make retries idempotent. Outputs do not enter the trigger event bus.

Pass `{ sensitive: true }` or a structural sensitivity mask as the second `.output(schema, options)` argument to keep all or part of an action result out of author-visible execution data. Arrays use a one-element mask for every item. Sensitivity follows matching property signals and makes arbitrary transforms wholly sensitive, but an action's inputs never mark its result implicitly. `runtime.sendOutput` accepts the same options as a second argument, and `runtime.log` accepts a `sensitive` property covering its stored values. The runtime uses only these declarations and does not guess from property names or scan strings.

## Package Role

Use `automate.ax` for app projects, automation and integration authoring, deploy-time subscriptions, core actions and triggers, and the `automate` CLI. HTTP triggers return `202` immediately unless a targeted `respondToHttpRequest` action enables waiting or `waitForResponse: true` requires it explicitly.

`generate` mirrors the AI SDK's common text-generation settings and returns text, structured output, usage, finish details, warnings, and response metadata. Omitting `account` uses the platform AI Gateway and defaults to `openai/gpt-4.1-nano`; explicit accounts select a provider-native default. Model suggestions follow the selected account's AI SDK provider enum while allowing newly released provider IDs. A Standard Schema constrains the provider response and types the output signal:

```ts
import { generate } from "automate.ax"
import { z } from "zod"

const recipe = generate({
  prompt: "Create a weeknight pasta recipe",
  schema: z.object({
    ingredients: z.string().array(),
    name: z.string(),
    steps: z.string().array(),
  }),
})

// recipe.output is Signal<{ name: string; ingredients: string[]; steps: string[] }>
```

Pass a provider account helper to use connected customer-managed credentials and a provider-native model ID. API-key helpers are available for Anthropic, Cerebras, Cohere, DeepInfra, DeepSeek, Fireworks AI, Google Generative AI, Groq, Hugging Face, Mistral AI, OpenAI, OpenRouter, Perplexity, Together AI, Vercel AI Gateway, and xAI. For example, `generate({ account: openaiAccount("work"), prompt: "Summarize this" })` uses the connected `work` binding and defaults to `gpt-4.1-nano`; the API key never enters automation code.

`generateImage` mirrors AI SDK image generation for text prompts, source-image edits, masks, multiple images, dimensions or aspect ratios, seeds, provider options, retries, headers, and result metadata. Omit `account` to use asynchronous Platform AI with `bfl/flux-2-flex`, or pass a DeepInfra, Fireworks AI, Google Generative AI, OpenAI, Together AI, Vercel AI Gateway, or xAI account. Results expose each image as a `Blob`.

Cron triggers use five-field expressions and default to UTC. Set an IANA time zone when the schedule should follow local wall time:

```ts
import { automation, onSchedule } from "automate.ax"

export default automation("Weekday report", () => {
  const tick$ = onSchedule({
    schedule: "30 9 * * 1-5",
    timeZone: "America/Los_Angeles",
  })

  // Connect actions to tick$.scheduledAt.
})
```

Gmail actions are available directly from the SDK:

```ts
import { gmail } from "automate.ax/gmail"

gmail.sendEmail({
  from: {
    address: "updates@example.com",
    name: "Status Bot",
  },
  html: "<strong>The automation completed.</strong>",
  replyTo: "support@example.com",
  subject: "Status update",
  to: {
    address: "person@example.com",
    name: "Alex",
  },
})
```

They use the default connected Google account unless the action receives an `account` binding or account reference in its second argument. The Gmail subpath includes rich message composition, send-as aliases, named recipients, custom headers, priority, inline attachments, drafts, replies, forwards, complete MIME parsing, threads, raw messages, paginated search, profiles, labels, message state, archive, Spam, and Trash actions.

`gmail.onNewEmail` subscribes to newly received inbox messages, and `gmail.onEmailSent` subscribes to messages sent from the watched mailbox. Both emit the same fully parsed message shape. Authenticated Automate.ax messages are withheld only from the automation that sent them, preventing self-recursion without hiding them from other automations. `gmail.onLabelAdded` and `gmail.onLabelRemoved` emit the parsed message plus the affected label IDs and any names Gmail can still resolve.

```ts
import { automation } from "automate.ax"
import { gmail } from "automate.ax/gmail"
import { googleAccount } from "automate.ax/google"

const workGoogle = googleAccount("work")

export default automation("Read incoming mail", () => {
  const email = gmail.onNewEmail({ account: workGoogle })
  gmail.markMessagesAsRead(
    { messageIds: email.messageId },
    { account: workGoogle },
  )
})
```

Every integration service subpath exports an account helper named for that service. Import the helper instead of calling `defineAccount` with a service ID. For example, `automate.ax/google` exports `googleAccount`, `automate.ax/microsoft` exports `microsoftAccount`, and `automate.ax/slack` exports `slackAccount`. Product subpaths backed by Google or Microsoft also re-export the shared helper. Call the helper without an argument for the `"default"` binding or pass a name for another binding, then pass the resulting reference as the action's second-argument `account`. Gmail watches are shared by stable Google account identity and renewed by the platform.

Search pagination is handled internally and returns stable message/thread or draft identities. Batch-friendly mutations accept one message ID or up to 1,000 IDs and return the modified IDs for action chaining. Trash, untrash, and multi-message reads bound request concurrency automatically.

Email-sending actions accept `text`, `markdown`, `html`, or any combination. Automate.ax preserves supplied plain-text and HTML bodies and derives missing alternatives. Parsed messages include structured addresses, duplicate-preserving headers, Gmail and RFC 5322 identities, labels, timestamps, snippets, bodies, and File-backed attachments. Action inputs and outputs are inferred directly from documented schemas.

Additional communication integrations use dedicated SDK subpaths:

- `automate.ax/outlook` covers profiles, folders, message reads and search, sending, drafts, replies, forwarding, state, categories, movement, and attachments.
- `automate.ax/teams` covers teams, channels, members, channel threads, chats, chat creation, and messages. These delegated Graph APIs require a Microsoft work or school account.
- `automate.ax/slack` covers messages, channels, direct messages, members, files, reactions, pins, bookmarks, user groups, Canvases, Lists, and typed Events API triggers across message, channel, member, file, reaction, and pin activity.
- `automate.ax/resend` covers transactional and batch email, scheduling, delivery records, contacts, and segments.
- `automate.ax/brevo` covers transactional email and SMS, delivery events, templates, contacts, lists, and list membership.

Provider actions use the default connected account unless passed an `account` binding or account reference in their second argument. Import the named service namespace from its subpath, then call provider-free members such as `gmail.sendEmail` or `linear.createIssue`. Fully qualified callable exports such as `sendGmailEmail` remain available for direct per-action imports. Outlook and Teams share one Microsoft OAuth account, Slack installs a bot with only the scopes required by the selected actions, Resend prefers OAuth with an API-key fallback, and Brevo uses API keys. Most subpaths also export an authenticated raw API helper for custom actions outside the packaged surface. Slack intentionally exposes only packaged callables because Slack prohibits automation platforms from offering direct API access through the platform app's managed authorization.

Slack message creation uses `chat:write` and requires the bot to belong to the target conversation. Use `slack.sendMessageToPublicChannel` only when posting to a public channel that has not added the bot; that explicit action also requests `chat:write.public`. Slack requires automation-platform apps to connect only to paid workspaces, so free-workspace grants are rejected and revoked during OAuth.

Slack triggers use one passive source per installed workspace. `slack.onEvent` emits the supported typed callback union, while semantic triggers cover app mentions, ordinary human messages, thread replies, message changes and deletion, reactions, conversation membership, files, workspace members, channel lifecycle, and pins. `slack.onMessagePosted` includes only new human-authored messages visible to the bot; edits, deletions, thread replies, bot messages, and system subtypes have their own classifications or are excluded.

Linear triggers cover issues, comments, projects, attachments, cycles, documents, initiatives, labels, project updates, reactions, releases, customers, users, and issue SLA changes. Broad `Event` triggers emit the provider action and direct webhook data; action-specific variants narrow the lifecycle action. Triggers require Linear OAuth because personal API keys cannot own workspace webhooks.

Resend's `resend.onEmailEvent` emits send, delivery, engagement, failure, and inbound email events. Its semantic triggers are `resend.onEmailSent`, `resend.onEmailDelivered`, `resend.onEmailBounced`, `resend.onEmailFailed`, `resend.onEmailOpened`, `resend.onEmailClicked`, `resend.onEmailComplained`, `resend.onEmailReceived`, `resend.onEmailDeliveryDelayed`, `resend.onEmailScheduled`, and `resend.onEmailSuppressed`. `brevo.onEvent` covers Brevo transactional and marketing email, transactional and marketing SMS, and contact callbacks. Family and semantic Brevo triggers narrow those events. The platform creates and authenticates only the provider webhooks required by each selected account.

Outlook's `outlook.onNewEmail` emits newly created Inbox messages. Teams provides `teams.onChannelMessagePosted` and `teams.onChatMessagePosted` for a selected channel or chat. Microsoft Graph subscriptions are renewed by the platform before they expire.

Project-management integrations use dedicated SDK subpaths:

- `automate.ax/asana` covers workspaces, users, teams, projects, sections, tasks, dependencies, attachments, tags, custom fields, advanced search, and comments.
- `automate.ax/linear` covers workspace metadata, teams, users, workflow states, labels, issues, projects, comments, attachments, relations, cycles, documents, project updates, milestones, and reactions. Actions accept either Linear OAuth or a personal API key and request the least OAuth scope supported by Linear.
- `automate.ax/trello` covers boards, lists, cards, members, labels, comments, URL attachments, checklists, and checklist items.

Asana uses OAuth. Linear supports OAuth and personal API keys, while Trello uses its OAuth-backed API token flow. Each subpath returns normalized code-first objects, accepts a named `account` binding, and exports an authenticated raw API helper for provider capabilities outside the packaged actions.

Asana's `asana.onResourceEvent` emits every compact change propagated through a selected resource. Semantic triggers cover task lifecycle, completion, assignment, dates, followers, projects, and tags; comment, section, project, attachment, and tag lifecycle; and enriched comment creation. Trello's `trello.onBoardEvent` emits provider actions affecting a selected board. `trello.onCardCreated`, `trello.onCardMoved`, `trello.onCardUpdated`, `trello.onCardArchived`, and `trello.onCardCommented` expose normalized card events. The platform creates, verifies, and removes these resource webhooks with their deployments.

Sales intelligence and engagement actions are available from `automate.ax/apollo`. The integration covers people and organization search and enrichment, contacts, accounts, lists, sequences, tasks, workspace metadata, and API usage. Apollo uses an API key, and the subpath also exports an authenticated raw API helper for endpoints outside the packaged surface.

Web data actions are available from `automate.ax/firecrawl`. `firecrawl.scrapeUrl` extracts one page, `firecrawl.searchWeb` searches web, news, image, and developer sources, and `firecrawl.mapWebsite` discovers site URLs. `firecrawl.startWebsiteCrawl` starts a crawl without waiting, while `firecrawl.crawlWebsite` uses a protected callback trigger and correlates Firecrawl's independent delivery root with the crawl job. Scope literal `crawlWebsite` inputs to the initiating signal with `withPrerequisites`; a signal input supplies that boundary directly. Outputs preserve Firecrawl's document, source-specific search, and crawl-job shapes, and the subpath exports the official authenticated client for endpoints outside the packaged surface.

1Password actions are available from `automate.ax/onepassword`. A service-account connection resolves secret references and manages items, files, shares, vaults, group permissions, and Environment variables. An Events API connection reads cursor pages of audit events, item usages, and sign-in attempts; 1Password does not offer push delivery for these feeds, so use them from scheduled automations. `onePassword.generatePassword` runs locally without an account. Secret-bearing and identity-bearing results are declared sensitive, and the build includes the official SDK's runtime asset only in projects that import this subpath.

Vercel infrastructure actions and triggers are available from `automate.ax/vercel`. The integration covers account and team identity, projects, deployments, project environment variables, and project domains. Broad webhook utilities such as `vercel.onDeploymentEvent` and `vercel.onProjectDomainEvent` are accompanied by typed semantic triggers such as `vercel.onDeploymentFailed`, `vercel.onProjectRenamed`, and `vercel.onProjectDomainMoved`; automations do not need to inspect the provider discriminator for common lifecycle events. Triggers require an OAuth installation; OAuth actions default to the installed team, while personal access-token actions can supply `teamId` or use their personal scope. The subpath exports the official authenticated Vercel SDK client for APIs outside the packaged surface.

Airtable's `airtable.onRecordCreated`, `airtable.onRecordUpdated`, and `airtable.onRecordDeleted` triggers watch a selected table. `airtable.onRecordMovedIntoView` and `airtable.onRecordMovedOutOfView` watch membership in a selected view. They emit field-ID-keyed record data when Airtable supplies it, preserve the complete provider transaction, and use cursor-backed webhooks that the platform renews before expiration.

Google Sheets actions are available from `automate.ax/google-sheets` and share the same connected Google account bindings:

```ts
import { googleSheets } from "automate.ax/google-sheets"

const openLeads = googleSheets.findRows({
  filters: [{ column: "Status", operator: "equals", value: "Open" }],
  source: { sheet: "Leads" },
  spreadsheet: "https://docs.google.com/spreadsheets/d/...",
})

googleSheets.appendRows({
  rows: {
    Email: "ada@example.com",
    Name: "Ada",
    Status: "Open",
  },
  source: { table: "LeadsTable" },
  spreadsheet: "spreadsheet-id",
})
```

The subpath includes spreadsheet and sheet lifecycle actions, batched A1 value operations, exact-header row reads and mutations, key-based upserts, native Google Sheets tables, range formatting and sorting, and find-and-replace. Row actions accept either a native table or a sheet with an optional one-based header row. Inputs use spreadsheet IDs or standard Google Sheets URLs.

Google Calendar actions provide normalized timed and all-day events, recurrence, attendees, reminders, attachments, Meet creation, calendar and ACL management, free/busy queries, and common-availability search:

```ts
import { googleCalendar } from "automate.ax/google-calendar"

const slots = googleCalendar.findAvailability({
  calendars: ["primary", "team@example.com"],
  durationMinutes: 30,
  timeMax: "2026-08-08T21:00:00Z",
  timeMin: "2026-08-08T13:00:00Z",
})

googleCalendar.createEvent({
  attendees: [{ email: "ada@example.com" }],
  createMeet: true,
  schedule: {
    end: "2026-08-08T15:30:00Z",
    start: "2026-08-08T15:00:00Z",
  },
  summary: "Project review",
})
```

`googleCalendar.onEventCreated`, `googleCalendar.onEventUpdated`, and `googleCalendar.onEventDeleted` use provider watches plus durable incremental sync. The initial calendar snapshot does not emit historical events.

Google Forms actions create and modify forms through typed, high-level item and question definitions while retaining a typed batch-update escape hatch:

```ts
import { googleForms } from "automate.ax/google-forms"

googleForms.createForm({
  items: [
    {
      question: {
        options: ["Product", "Billing", "Other"],
        required: true,
        type: "multipleChoice",
      },
      title: "What can we help with?",
      type: "question",
    },
  ],
  title: "Support request",
})
```

The Forms subpath also covers settings, publishing, item positioning, quiz grading, and normalized response reads. `googleForms.onResponseSubmitted` emits new or edited submissions, while `googleForms.onFormChanged` emits the refreshed form after content or settings changes. Provider watches are shared and renewed by the platform.
