npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

automate.ax

v0.122.1

Published

TypeScript SDK and CLI for Automate.ax automations.

Readme

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

bun add automate.ax

CLI

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

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

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

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

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

  respondToHttpRequest({ body: message })
})

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

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, { ttl? }) adds immutable key metadata, while signal.globally() explicitly selects one shared cross-context partition. correlate(streams, { ordered? }) eagerly joins keyed signals one-to-one and oldest-first. 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. 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. fallback(signals) selects by declaration order; race(signals) persists the earliest emitted or failed input by outcome order. 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.

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:

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:

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.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 configured with waitForResponse: true; responding automations use the platform-provided respondToHttpRequest action.

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:

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:

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:

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

gmail.sendEmail({
  from: {
    address: "[email protected]",
    name: "Status Bot",
  },
  html: "<strong>The automation completed.</strong>",
  replyTo: "[email protected]",
  subject: "Status update",
  to: {
    address: "[email protected]",
    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.

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:

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: "[email protected]",
    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:

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

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

googleCalendar.createEvent({
  attendees: [{ email: "[email protected]" }],
  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:

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.