From 16f37daa295d280663f9eb9b30a0683f9a3756ce Mon Sep 17 00:00:00 2001 From: Ben McAdams Date: Thu, 9 Jul 2026 08:59:19 -0700 Subject: [PATCH] Add shell-discovery guidance and example skeletons to the zapier-sdk skill Agents doing SDK discovery from a shell were reinventing basic flows because the skill front-loaded the SDK library and pushed CLI content into `references/cli.md`. `zapier-sdk --help` doesn't advertise `--json` (it's a per-command flag), so agents kept parsing the default table output. The "App keys have a canonical form" gotcha implied the CLIAPI suffix was universal, but the CLI's `run-action` accepts the short slug too, and only the SDK library's `sdk.runAction({ appKey })` requires the suffixed form. Changes to `SKILL.md`: - New "Shell discovery" section with the four essential commands and a canonical `find-first-connection --json | jq -r '.data.id'` -> `run-action` chain. Recommends the default output for reading and reserves `--json` for piping. - Rewrites the app-key gotcha to make clear it applies to `sdk.runAction` only; the CLI, `findFirstConnection`, `listActions`, and so on all accept either form. - New gotcha: multiple connections per app is normal (personal + work Gmail, multiple Slack workspaces). Default to `findFirstConnection` and take the first result rather than stopping to disambiguate. - New "Examples" pointer to the skeletons file. New `references/examples.md`: - Fill-in-the-blank skeletons for a plain single-action script, a Zapier Tables script, and a durable notify-on-event workflow. Every placeholder is called out (``, ``, ``, ...). - Opens and closes with pointers back to the verified corpus at https://github.com/zapier/sdk/tree/main/examples so agents don't ship the unverified skeletons themselves. --- skills/zapier-sdk/SKILL.md | 38 +++++- skills/zapier-sdk/references/examples.md | 149 +++++++++++++++++++++++ 2 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 skills/zapier-sdk/references/examples.md diff --git a/skills/zapier-sdk/SKILL.md b/skills/zapier-sdk/SKILL.md index a72bf30..0cee649 100644 --- a/skills/zapier-sdk/SKILL.md +++ b/skills/zapier-sdk/SKILL.md @@ -64,6 +64,37 @@ The Zapier SDK (`@zapier/zapier-sdk`) is new. Your training data does not contai 4. Never invent action keys. Run `zapier-sdk list-actions ` or `zapier.listActions({ app })` first. 5. Never assume input field shapes for dynamic actions. Run `zapier-sdk list-action-input-fields ` against the live connection. +## Shell discovery (agents working in the terminal) + +Prefer the default output for reading. It already includes slug, key, title, action type, and description in a compact form. Pass `--json` only when you need to pipe into `jq` or another tool. `--json` is a per-command flag (it appears on every subcommand's `--help`, not on `zapier-sdk --help`). + +```bash +# Find apps. --search is a substring match, so `notion` returns Motion, +# Potion, Tree-Nation too. Read the default output to confirm you picked +# the right app before moving on. +npx zapier-sdk list-apps --search notion + +# List actions on an app. Short slugs like `notion` work everywhere in +# the CLI. Filter by type to trim the list. +npx zapier-sdk list-actions notion --action-type search + +# Inspect an action's inputs. Many are dynamic, so verify against a +# live connection. +npx zapier-sdk list-action-input-fields notion search page_by_title + +# Run an action end-to-end. Use --json when you want to parse the result. +CONN=$(npx zapier-sdk find-first-connection notion --json | jq -r '.data.id') + +npx zapier-sdk run-action notion search page_by_title \ + --connection "$CONN" \ + --inputs '{"title":"Meeting Notes","exact_match":"no"}' \ + --json +``` + +The CLI's `run-action` accepts the short slug (`notion`, `slack`, `github`), the CLIAPI-suffixed form (`NotionCLIAPI`), or a versioned ID. The CLIAPI-suffix requirement in the "Gotchas" section applies to the typed SDK library method (`sdk.runAction`), not the CLI. + +Full CLI walkthrough: [`references/cli.md`](references/cli.md). Complete command inventory: [`references/cli-commands.md`](references/cli-commands.md). + ## Authentication The SDK supports two auth modes. Browser login is the default for local development: @@ -143,7 +174,8 @@ const { data: schema } = await zapier.getActionInputFieldsSchema({ - **Two connection reference shapes.** At runtime, pass `connection.id` (UUID) to `runAction`. Inside a durable workflow (`@zapier/zapier-durable`), pass a string alias like `"notion_primary"` that the runtime resolves at deploy time. Don't mix them up. - **`runAction` returns `{ data: T[] }`, always an array.** Search-style actions typically return one row; downstream code destructures `data: [result]`. Write actions also return an array (usually one element). - **Dynamic input fields.** Notion database properties, HubSpot custom fields, Jira per-project schemas: none of these are knowable ahead of time. Always run `list-action-input-fields` against the live connection before authoring. -- **App keys have a canonical form.** `list-actions` returns `app_key: "NotionCLIAPI"` for Notion. Pass that exact string to `runAction`'s `appKey` field. The short slug `"notion"` works for `list-actions` and `findFirstConnection`, but `runAction` wants the CLIAPI-suffixed form. +- **App keys: the SDK's `runAction` wants the CLIAPI-suffixed form.** In TypeScript, `sdk.runAction({ appKey })` requires `"NotionCLIAPI"`. Get it from `list-apps` or `list-actions`. Everywhere else (the CLI's `run-action`, `sdk.findFirstConnection`, `sdk.listActions`, and so on) accepts either the short slug (`"notion"`) or the CLIAPI form. +- **Multiple connections per app is normal.** Users often have several (personal + work Gmail, multiple Slack workspaces). Default to `findFirstConnection` / `find-first-connection` and take the first result. Don't stop to disambiguate unless the user explicitly asks; filter by `title` or `owner` if you need a specific one. ## SDK method reference @@ -169,6 +201,10 @@ const response = await zapier.fetch("https://api.example.com/data", { Same auth and audit trail as `runAction`. Use this when the app's Zapier action catalog doesn't cover what you need (bulk reads, custom endpoints, partner-specific APIs). +## Examples + +For fill-in-the-blank skeletons (plain script, Zapier Table, durable workflow), see [`references/examples.md`](references/examples.md). These are shape-only; the real, action-key-verified corpus is at https://github.com/zapier/sdk/tree/main/examples. Grep the corpus by app or pattern when you need a working reference. + ## Full documentation - Quickstart: https://docs.zapier.com/sdk/quickstart.md diff --git a/skills/zapier-sdk/references/examples.md b/skills/zapier-sdk/references/examples.md new file mode 100644 index 0000000..49bbba7 --- /dev/null +++ b/skills/zapier-sdk/references/examples.md @@ -0,0 +1,149 @@ +# Example skeletons + +Fill-in-the-blank shells for orientation, not runnable examples. They answer "what does a plain script / Table script / durable workflow *look like*" without pretending to be verified against a real action catalog. + +**The real, verified corpus lives at https://github.com/zapier/sdk/tree/main/examples** (or the `examples/` directory of a local clone). Grep it for the app or pattern you actually need. + +Every example in that corpus has had its action key verified against the live catalog by CI. The skeletons below have not. Copy the shape, then look up real action keys and inputs before shipping. + +## 1. Plain single-action script + +The simplest thing you can write with the SDK. One authenticated call. No durable wrapper, no `ctx.step`. Runs wherever your code runs (Node script, Next.js route, Lambda). + +Matches the style of `examples/by-app//*.ts`. + +```typescript +// examples/by-app//.ts +import { createZapierSdk } from "@zapier/zapier-sdk"; + +const zapier = createZapierSdk(); + +async function main() { + const { data: connection } = await zapier.findFirstConnection({ + app: "", // short slug from `zapier-sdk list-apps`, e.g. "notion" + owner: "me", + }); + + const result = await zapier.runAction({ + appKey: "", // CLIAPI-suffixed form, e.g. "NotionCLIAPI" + actionType: "", + actionKey: "", // from `zapier-sdk list-actions ` + connection: connection.id, + inputs: { + // Verify shape with `zapier-sdk list-action-input-fields `. + // Mark dynamic inputs with `// dynamic` so future readers know to re-verify. + }, + }); + + console.log(result.data); +} + +main().catch(console.error); +``` + +## 2. Zapier Tables + +Tables uses first-class SDK methods (`createTable`, `createTableFields`, `createTableRecords`, `listTables`, `listTableRecords`). No connection lookup, no action discovery. The method names in the SDK reference are the surface. + +Matches `examples/by-app/zapier-tables/log-event.ts`. + +```typescript +import { createZapierSdk } from "@zapier/zapier-sdk"; + +const zapier = createZapierSdk(); + +async function main() { + // Find-or-create the table by name. + const { data: tables } = await zapier.listTables({ search: "" }); + let table = tables[0]; + + if (!table) { + const created = await zapier.createTable({ + name: "", + description: "", + }); + await zapier.createTableFields({ + table: created.data.id, + fields: [ + { name: "", type: "string" }, // or number | boolean | datetime | json + ], + }); + table = created.data; + } + + // Write records. keyMode: "names" addresses fields by declared name. + await zapier.createTableRecords({ + table: table.id, + keyMode: "names", + records: [ + { data: { "": "" } }, + ], + }); +} + +main().catch(console.error); +``` + +## 3. Durable workflow (notify-on-event shape) + +Deployable to Zapier's infrastructure. Runs on a trigger (webhook, poll, schedule). Every side-effect goes through `ctx.step` with the trigger's primary id in the step name so retries are idempotent. + +Matches `examples/by-pattern/notify-on-event//workflow.ts`. + +```typescript +// workflow.ts +import { defineDurable } from "@zapier/zapier-durable"; +import { createZapierSdk } from "@zapier/zapier-sdk"; +import { z } from "zod"; + +const sdk = createZapierSdk(); + +// Constants get pulled out and documented in the leaf directory's README. +const DESTINATION_CONNECTION = ""; // e.g. "gmail_primary" +const DESTINATION_APP_KEY = ""; // e.g. "GoogleMailV2CLIAPI" + +const InputSchema = z.object({ + primaryId: z.string(), // whatever id the trigger emits (charge id, response id, ...) + // ...other fields the trigger provides +}); +type Input = z.infer; + +export default defineDurable( + "", + async (ctx, rawInput) => { + const input = InputSchema.parse(rawInput); + + // Step name MUST include `${input.primaryId}`. Without it, a retry + // would double-write. This is the whole reason `defineDurable` exists. + await ctx.step(`-${input.primaryId}`, async () => + sdk.runAction({ + appKey: DESTINATION_APP_KEY, + actionType: "", + actionKey: "", + connection: DESTINATION_CONNECTION, + inputs: { + // ... + }, + }), + ); + + return { done: true }; + }, +); +``` + +Deploy loop: + +```bash +npm install +npx tsc --noEmit workflow.ts +npx zapier-sdk publish-workflow-version --file workflow.ts +``` + +## What these skeletons deliberately leave out + +- **Real action keys and input shapes.** Both are per-app and change over time. Look them up. +- **Sensitive-partner apps.** The corpus excludes some partners (Salesforce, Slack, others). Don't copy skeletons for those without checking. +- **Error handling, retries, structured logging.** The corpus examples don't add these either; the `defineDurable` runtime handles retries. + +For anything past the shape, go to the corpus.