diff --git a/.claude/skills/workflows-create/SKILL.md b/.claude/skills/workflows-create/SKILL.md new file mode 100644 index 0000000..993a6ac --- /dev/null +++ b/.claude/skills/workflows-create/SKILL.md @@ -0,0 +1,565 @@ +--- +name: workflows-create +description: Create a durable Zapier workflow from natural language using @zapier/zapier-durable and the Zapier SDK CLI. Use when the user wants to build a Zapier workflow, create an automation, write a durable workflow, build me a Zap that, create a durable that, or automate a multi-step process involving Zapier-connected apps. +license: MIT +metadata: + author: zapier + version: "1.3.6" + sdk_cli_min: "0.54.3" + sdk_cli_validated: "0.59.3" + refresh_source: "zapier/agent-skills" +--- + +# Zapier Workflows Create + +Create a complete durable workflow from natural language, test it when appropriate, and deploy it through the Zapier SDK experimental Code Workflows commands. + +Use the public SDK CLI path. Do not use `zapier-sdk-code-substrate`. + +## Compatibility Gate + +Before using this skill, run the `workflows-doctor` bundle compatibility check. If `workflows-doctor` is not installed or cannot be loaded, run `workflows-install` or install `workflows-doctor` from `zapier/agent-skills` before continuing. If `workflows-doctor` reports SDK/skill drift, follow its refresh instructions, stop this skill invocation, reload the agent workspace if needed, and ask the user to rerun the original request. + +## Prerequisites + +Verify these at the start: + +```bash +zapier-sdk --version +zapier-sdk get-profile --json +zapier-sdk --experimental --help +zapier-sdk --experimental create-workflow --help +zapier-sdk --experimental publish-workflow-version --help +zapier-sdk --experimental run-durable --help +zapier-sdk --experimental list-triggers --help +zapier-sdk --experimental trigger-workflow --help +``` + +Pin **aged** versions, not npm-latest. The Vercel sandbox installs dependencies with `pnpm install --config.minimumReleaseAge=1440`, so any direct dependency published less than 24h ago is rejected. `@zapier/zapier-sdk` publishes often (several times a day), so its npm-latest is regularly younger than 24h. `@zapier/zapier-sdk`, `@zapier/zapier-durable`, and `zod` (imported by the generated `workflow.ts`) are all direct dependencies of the sandbox install, so select the latest version of each **published at least 24h ago**. This needs only Node (already required) — no `jq` or other tooling: + +```bash +SELECT_AGED_VERSION=' +const cp = require("child_process"); +const pkg = process.argv[1]; +const times = JSON.parse(cp.execSync("npm view " + pkg + " time --json", { encoding: "utf8" })); +const cutoff = Date.now() - 24 * 60 * 60 * 1000; +const eligible = Object.keys(times) + .filter((v) => /^[0-9]+\.[0-9]+\.[0-9]+$/.test(v)) + .map((v) => ({ v, t: new Date(times[v]).getTime() })) + .filter((x) => x.t <= cutoff) + .sort((a, b) => a.t - b.t); +if (!eligible.length) { + console.error("No " + pkg + " stable version published >=24h ago"); + process.exit(1); +} +console.log(eligible[eligible.length - 1].v); +' +SDK_VERSION="$(node -e "$SELECT_AGED_VERSION" @zapier/zapier-sdk)" +DURABLE_VERSION="$(node -e "$SELECT_AGED_VERSION" @zapier/zapier-durable)" +ZOD_VERSION="$(node -e "$SELECT_AGED_VERSION" zod)" +echo "SDK_VERSION=$SDK_VERSION DURABLE_VERSION=$DURABLE_VERSION ZOD_VERSION=$ZOD_VERSION" +``` + +Capture: + +- `SDK_VERSION` — the latest `@zapier/zapier-sdk` published at least 24h ago. Use it as the pinned SDK dependency. +- `DURABLE_VERSION` — the latest `@zapier/zapier-durable` published at least 24h ago. Use it for the local `package.json` pin and for `--zapier-durable-version`. +- `ZOD_VERSION` — the latest `zod` published at least 24h ago. Use it for the local `package.json` pin and in `--dependencies`, because the generated `workflow.ts` imports `zod`. + +Use exact versions in commands. Do not pass `latest`. Pass the aged `SDK_VERSION` and `ZOD_VERSION` to `--dependencies` and the aged `DURABLE_VERSION` to `--zapier-durable-version` (see Phases 5 and 6) — all are subject to the 24h guard. **Every package the generated `workflow.ts` imports must appear in `--dependencies`**, aged-pinned: the sandbox installs from `--dependencies`, not your local `package.json`, so a missing import (such as `zod`) fails the run with `Cannot find package`. + +The user must also have app connections configured at https://zapier.com/app/assets/connections for any app actions the workflow will run. + +## Phase 1: Understand The Intent + +Read the user's natural language request and extract: + +1. Steps and ordering. +2. Apps involved. +3. Data passed between steps. +4. Manual input fields or trigger input fields. +5. Conditional logic. +6. Waits, callbacks, or human approval gates. + +Summarize the proposed workflow back to the user before discovery. Ask focused clarifying questions for missing details like target channels, folders, recipients, or whether to stop when a search returns no results. + +Do not generate code until the user agrees on the workflow shape. + +## Phase 2: Discover Apps, Connections, Actions, Triggers, And Fields + +Use the standard Zapier SDK CLI for app/action discovery: + +```bash +zapier-sdk list-apps --search "" --json +zapier-sdk list-connections --owner me --json +zapier-sdk list-actions --action-type --json +zapier-sdk list-action-input-fields --connection --json +zapier-sdk list-action-input-field-choices --connection --json +``` + +For workflows that should subscribe to a Zapier app trigger, use the experimental trigger discovery commands: + +```bash +zapier-sdk --experimental list-triggers --json +zapier-sdk --experimental list-trigger-input-fields --connection --json +zapier-sdk --experimental list-trigger-input-field-choices --connection --json +``` + +If several apps, connections, actions, triggers, or field choices are plausible, show the candidates and ask the user to choose. + +### Use "AI by Zapier" For AI Steps + +For any AI / "call an LLM" step — summarize, extract, classify, generate, or analyze text — **always use "AI by Zapier"** (app key `AICLIAPI`) as the step and select the model *inside* it: if the user names a provider or model, set that as the `model_id` (see below); otherwise use its default model. It runs on Zapier's built-in AI credentials (no third-party account required) and bills as normal Zapier tasks, so an agent-built workflow does not silently route to a separate raw-provider app the user must connect and pay for. Discover it with `list-apps --search "AI by Zapier"`; its generic completion action is `get_completion` ("Analyze and Return Data"), alongside `extract_content` (from a URL) and `search_content` (confirm the current set with `list-actions AICLIAPI --action-type write --json`). + +**Configuring the `get_completion` step.** Inspect its fields with `list-action-input-fields AICLIAPI write get_completion --json`. The ones that matter for a generated step: + +- `instructions` (**required**) — the prompt describing what the AI should do. +- `provider_id` (optional) — the AI provider, needed only when the user names one. Choices are `openai`, `anthropic`, `google`, `azure-openai`, `amazon-bedrock` (`list-action-input-field-choices AICLIAPI write get_completion provider_id --json`). Setting it is what makes `model_id`'s choices resolve. +- `model_id` (**required**, default `"advanced/auto"`) — the model. **For a generic step, pass the default `"advanced/auto"`** — auto-pick a model in the Advanced tier (tiers: `standard`/`advanced`/`premium`) on built-in credentials. **When the user names a provider or model,** set `provider_id` first, then resolve the valid model for it with `list-action-input-field-choices AICLIAPI write get_completion model_id --inputs '{"provider_id":""}' --json` (the list is empty until `provider_id` is set) and pass the matching `/` value (for example `anthropic/claude-sonnet-5`, `openai/gpt-4o`). Do not hardcode a model list — resolve it at build time. +- `authentication_id` (**required**, default `"0"`) — `"0"` is Zapier's built-in AI credentials (the models shown with a Zap icon). Keep `"0"` for the default and any built-in model. A model the user names may not be available on built-in credentials — those require the user's own AI provider account (a custom `authentication_id`); if so, tell the user and use their authentication. `model_id` depends on this field. +- `inputFields` (optional, OBJECT) — extra context fields mapped from earlier steps, merged into the prompt. + +So a default AI step needs only a prompt. `model_id` and `authentication_id` are required but have working defaults; pass them explicitly with those defaults (`"advanced/auto"` and `"0"`) so the `runAction` inputs are complete, and no connection alias is needed for the built-in path: + +```typescript +const summary = await ctx.step("summarize-with-ai", async () => + sdk.runAction({ + appKey: "AICLIAPI", + actionType: "write", + actionKey: "get_completion", + inputs: { + instructions: `Summarize this in one sentence: ${input.text}`, + model_id: "advanced/auto", + authentication_id: "0", + }, + }), +); +``` + +Naming a provider or model is **not** a reason to leave "AI by Zapier" — set it as the `model_id` above. Reach for a raw-provider AI app (Anthropic, OpenAI, Google AI, and so on) only when the user explicitly asks for that standalone app, or needs a capability "AI by Zapier" does not offer. When you do, tell the user the step uses their own provider connection and billing, not "AI by Zapier." + +Assign a short snake_case connection alias for each chosen connection, such as `slack_work` or `gmail_primary`. Track alias to connection ID. The alias goes in workflow code; the connection ID is passed to test/deploy commands through the `--connections` JSON. + +For output mapping between steps, run a safe action test only after user confirmation. Use the current SDK command shape: + +```bash +zapier-sdk run-action \ + --connection \ + --inputs '<{"key":"value"}>' \ + --json +``` + +For trigger-backed workflows, capture the trigger configuration for publish: + +```json +{ + "selected_api": "GoogleSheetsAPI@2.3.0", + "action": "new_row", + "authentication_id": "connection-id-or-null", + "params": {} +} +``` + +Use the version-pinned app/API identifier for `selected_api`, the trigger action key for `action`, the trigger source connection ID for `authentication_id` when the trigger requires auth, and trigger input values for `params`. Omit optional fields only when the trigger does not need them. + +For `selected_api`, use the **version-pinned implementation identifier** — the `implementation_id` returned by SDK discovery (`list-apps`/`get-app`), such as `GoogleSheetsAPI@2.3.0`. Do not use the bare app key (`GoogleSheetsAPI`) and do not substitute a display name. A bare, unversioned `selected_api` makes the trigger claim **fail silently at publish**: the publish call returns success with no errors, but the workflow stays disabled and nothing surfaces the cause. If discovery only exposes a bare app slug and not a versioned `implementation_id`, treat that as a blocker and record it in the build plan before publishing — do not publish a trigger with an unversioned identifier. + +For `params`, match each field's `value_type` from `list-trigger-input-fields `. ARRAY fields must be JSON arrays (for example `"dow": ["1"]`); STRING fields must be plain strings (for example `"hod": "9:00 AM"`). Passing a scalar where an array is expected (or vice versa) fails the trigger claim the same silent way. + +Capture app implementation/version information from SDK discovery output when available, such as `list-apps`, `get-app`, `list-actions`, or trigger/action result metadata. Do not invent app versions. If no implementation/version binding is exposed, omit `--app_versions` rather than guessing. + +"Webhooks by Zapier" and other apps with a catch-hook trigger (PayPal, Salesforce, Twilio, WordPress, Wufoo, Zillow, and others) are discovered and configured exactly like any other trigger app — nothing about them is special-cased. Search `list-apps --search "webhook"` (or the specific app name) for its `implementation_id` (for example `WebHookCLIAPI@1.1.0` — an illustrative example, not a version to hardcode; confirm the current version via discovery), then `list-triggers ` for its catch-hook trigger action. "Webhooks by Zapier" itself is no-auth (`authentication_id: null`) with empty `params`, but confirm its action key via `list-triggers WebHookCLIAPI` rather than hardcoding one — as of this writing it exposes both `hook_v2` (parsed payload; the common default) and `hook_raw` (unparsed body and headers, max 2MB), and that pair of action keys is specific to `WebHookCLIAPI`, not a pattern the other apps share. Other catch-hook apps (PayPal, Salesforce, Twilio, ...) commonly require a connection, because claiming their trigger means calling the provider's API to register a subscription. Do not assume no-auth or empty `params` for those — confirm each app's actual action key, auth, and param requirements via `list-triggers`/`list-trigger-input-fields` (see above) rather than generalizing from "Webhooks by Zapier." Configure them through `--trigger` at publish time (Phase 6) like any other trigger — do not treat them as "no trigger" / manual-only workflows. + +## Phase 3: Confirm The Build Plan + +Before writing code, present: + +```text +Workflow: +Input: { field1, field2 } +Connections: + alias = connectionId (connection title) +Trigger: + selected_api.action with params (including "Webhooks by Zapier" or other catch-hook apps), or none for a workflow fired only manually via `trigger-workflow` +Steps: + 1. - .. + 2. - .. +Return: +``` + +Ask the user to confirm before generating files. + +## Phase 4: Generate The Workflow Project + +Create a workflow directory: + +```text +/ + / + package.json + workflow.ts +``` + +`package.json` should include exact dependencies: + +```json +{ + "type": "module", + "dependencies": { + "@zapier/zapier-sdk": "", + "@zapier/zapier-durable": "", + "zod": "" + }, + "devDependencies": { + "typescript": "latest" + } +} +``` + +If you add a build script, use `--skipLibCheck` for now to avoid type-check failures from SDK/durable transitive type declarations: + +```json +{ + "scripts": { + "build": "tsc --target es2022 --module nodenext --moduleResolution nodenext --skipLibCheck --outDir dist workflow.ts" + } +} +``` + +`workflow.ts` should: + +- Import `defineDurable` from `@zapier/zapier-durable`. +- Import `createZapierSdk` from `@zapier/zapier-sdk`. +- Create the SDK client once at module level: `const sdk = createZapierSdk()` above `defineDurable` +- Use Zod for input validation when the workflow has input. +- Keep external side effects (app actions, fetches) inside `ctx.step` calls. +- Make each app action exactly **one** `ctx.step` whose body is a single `return sdk.runAction({...})` call — one `runAction` per step. +- Group validation, input normalization, simple guards, data shaping into steps as needed. +- Use connection aliases, not raw connection IDs, inside workflow code. +- Reference a prior step's output with `stepVar.data[0].field` for the first result, or `stepVar.data` for the whole array. +- Normalize manual input before Zod validation. In the current `run-durable` path, input may arrive as a JSON string rather than an already-parsed object. + +Use this helper pattern for workflows with input: + +```typescript +function normalizeInput(rawInput: unknown): unknown { + if (typeof rawInput === "string") { + return JSON.parse(rawInput); + } + return rawInput; +} +``` + +Then parse the normalized value: + +```typescript +const input = InputSchema.parse(normalizeInput(rawInput)); +``` + +### Visualizer-Friendly Structure + +Generate durable source that can be turned into a meaningful step graph. Avoid overly dynamic construction. + +**`defineDurable` call shape — every call must resolve `run` to a function.** Use either the bare form `defineDurable("workflow-name", async (ctx, input) => { ... })` or the object form `defineDurable({ name: "workflow-name", inputSchema, outputSchema, description, run: async (ctx, input) => { ... } })`. `ctx` is always the first parameter of `run`; `input` is the optional second parameter, so `async (ctx) => { ... }` is also valid. These shapes are invalid and make the workflow fail on its first run with `durable.run is not a function`: + +- `defineDurable(async (ctx, input) => { ... })` — a bare function with no name. The function is treated as an options object, so `run` is never set. This is the most common mistake. +- `defineDurable({ name: "workflow-name" })` — object missing `run`. +- `defineDurable({ name: "workflow-name", run: someNonFunction })` — `run` is not a function. + +`durable.run is not a function` is a code-shape defect in your `defineDurable` call, not a version mismatch. Do not change the pinned `@zapier/zapier-durable` or `@zapier/zapier-sdk` versions to fix it — correct the call so it passes a `name` and a `run` function. + +Default to this parser-friendly shape — module-level `sdk`, hoisted app-key/connection constants, and a bare `runAction` body for each app action: + +```typescript +import { defineDurable } from "@zapier/zapier-durable"; +import { createZapierSdk } from "@zapier/zapier-sdk"; +import { z } from "zod"; + +const sdk = createZapierSdk(); + +const InputSchema = z.object({ reaction: z.string() }); +type Input = z.infer; + +const TODOIST_APP_KEY = "TodoistV2CLIAPI"; +const TODOIST_CONNECTION = "todoist_primary"; + +const workflow = defineDurable( + "example-workflow", + async (ctx, input) => { + // Plain code: guard outside any step. + if (input.reaction !== "todo") { + return { skipped: true }; + } + + // Plain code: shape the action input outside the step. + const taskInput = buildTaskInput(input); + + // App action: one runAction, object literal, module-level sdk. + const createdTask = await ctx.step("create-todoist-task", async () => + sdk.runAction({ + appKey: TODOIST_APP_KEY, + actionType: "write", + actionKey: "new_task", + connection: TODOIST_CONNECTION, + inputs: taskInput, + }), + ); + + return { createdTask }; + }, +); +``` + +### App-Action Step Shape (Editor Recognition) + +The editor renders a `ctx.step` as an **app-action step** (with the app icon) when its body is a single `sdk.runAction({...})` call with `appKey`, `actionType`, and `actionKey` (object literal, or a `const` that resolves to one; the `app` / `action` spellings also work). A string-literal step id (`ctx.step("create-todoist-task", ...)`) and an inline `async () => ...` callback are the recognized form; object form `ctx.step({ name, run })` works too. + +Other steps render as plain **code steps** — for example a step with no `runAction`, or with more than one, or one created in a loop with a dynamic id (`` `process-item-${index}` ``). That is expected, not a regression; loops and fan-out legitimately need dynamic ids. + +## Phase 5: Test The Workflow + +Build `source_files` from `workflow.ts`: + +```bash +SOURCE_FILES="$(jq -n --rawfile workflow workflow.ts '{"workflow.ts": $workflow}')" +``` + +Build the `connections` JSON from the selected aliases. It's a nested object — each alias maps to an object holding a `connectionId` (never a bare string). The same shape is used for `publish-workflow-version` in Phase 6: + +```json +{ + "slack_work": { "connectionId": "12345678" }, + "gmail_primary": { "connectionId": "87654321" } +} +``` + +Before running, tell the user what actions may happen in connected apps and wait for confirmation if there are side effects. + +Run the durable: + +```bash +zapier-sdk --experimental run-durable "$SOURCE_FILES" \ + --dependencies '{"@zapier/zapier-sdk":"","zod":""}' \ + --zapier-durable-version '' \ + --connections '' \ + --input '' \ + --private +``` + +`run-durable` returns a run immediately, often before the workflow is complete. Capture the returned run ID, then poll until terminal status. Do not assume the first response contains final output. + +```bash +zapier-sdk --experimental get-durable-run --json +``` + +Terminal success means the run has `status: "finished"`, an expected `output`, `error: null`, and top-level `errors: []`. Terminal failure means `status: "failed"` or a non-null `error`. Continue polling while the run is initialized or started. + +Fix code and retest until the behavior matches the confirmed plan. + +## Phase 6: Deploy The Workflow + +Decide whether the workflow should be private before creating it. For EA users, default to private unless the user explicitly wants an account-visible workflow. + +Create a private workflow container: + +```bash +zapier-sdk --experimental create-workflow "" \ + --description "" \ + --private \ + --json +``` + +Omit `--private` only if the user explicitly wants the workflow visible to the broader account. + +Capture the returned workflow ID. Then publish the version. The current SDK CLI expects `source_files` as a JSON object, not a path to `workflow.ts`. + +For publish, use the same nested `connections` shape as `run-durable` — each alias maps to an object holding a `connectionId`: + +```json +{ + "slack_work": { "connectionId": "123-or-uuid" }, + "gmail_primary": { "connectionId": "456-or-uuid" } +} +``` + +If app implementation/version information is known, build `app_versions`: + +```json +{ + "slack": { "implementation_name": "SlackCLIAPI", "version": "optional" } +} +``` + +Omit the entire `--app_versions` flag when no app implementation/version binding is needed. Likewise, omit `--connections` when the workflow has no connection bindings. Do not pass placeholder text like "if needed" to the CLI. + +For trigger-backed workflows, build the `trigger` JSON from Phase 2. Keep `selected_api` version-pinned to the `implementation_id` (for example `GoogleSheetsAPI@2.3.0`) and keep each `params` field shaped to its `value_type` (see Phase 2) — a bare app key or a wrong param shape makes the trigger claim fail silently at publish: + +```json +{ + "selected_api": "GoogleSheetsAPI@2.3.0", + "action": "new_row", + "authentication_id": "connection-id-or-null", + "params": {} +} +``` + +A "Webhooks by Zapier" or other catch-hook trigger is a real trigger — publish it with `--trigger` using the config captured in Phase 2, the same as any other app trigger. + +Publish a workflow with no trigger at all — invoked only manually via `trigger-workflow` — by omitting `--trigger`: + +```bash +SOURCE_FILES="$(jq -n --rawfile workflow workflow.ts '{"workflow.ts": $workflow}')" + +zapier-sdk --experimental publish-workflow-version "$SOURCE_FILES" \ + --dependencies '{"@zapier/zapier-sdk":"","zod":""}' \ + --zapier-durable-version '' \ + --connections '' \ + --app_versions '' \ + --enabled \ + --json +``` + +Publish a trigger-backed workflow by adding `--trigger`: + +```bash +zapier-sdk --experimental publish-workflow-version "$SOURCE_FILES" \ + --dependencies '{"@zapier/zapier-sdk":"","zod":""}' \ + --zapier-durable-version '' \ + --connections '' \ + --app_versions '' \ + --trigger '' \ + --enabled \ + --json +``` + +Do not use the old `--trigger-app`, `--trigger-action`, `--trigger-auth`, or `--trigger-params` flags. The current trigger publish path is the single JSON `--trigger` object. + +## Phase 7: Verify Deployment + +Read back the workflow and versions: + +```bash +zapier-sdk --experimental get-workflow --json +zapier-sdk --experimental list-workflow-versions --json +zapier-sdk --experimental get-workflow-version --json +``` + +For trigger-backed workflows, verify the trigger actually claimed. The claim is asynchronous and can fail silently, so re-read the workflow (allow a few seconds; poll if needed) and confirm it is enabled: + +```bash +zapier-sdk --experimental get-workflow --json +``` + +If `enabled` is `false` even though you published with `--enabled`, the trigger claim failed. The most common cause is a `selected_api` that is not version-pinned to the `implementation_id`, or a `params` field with the wrong shape (see Phase 2). Re-publish with a corrected `--trigger` and re-check. Do not report the workflow as deployed until `get-workflow` shows `enabled: true`. + +Regardless of trigger type, check the matching entry in `triggers[]` from the `get-workflow --json` read-back above for `details.webhook_url` (re-run the same command if enough time has passed since that read that the claim state could have changed). If present, it is the catch URL external services call — show it to the user plainly; unlike the workflow-level `trigger_url`, it is meant to be shared. Most triggers have no `webhook_url`, and that is normal — do not flag its absence. + +If you configured a catch-hook trigger in Phase 2 (a "Webhooks by Zapier" or similar catch-hook app/action) and `details.webhook_url` is still absent once the trigger is active, the installed `@zapier/zapier-sdk` may predate this field — run `workflows-doctor` to check for an update, and in the meantime tell the user to copy the URL from the trigger step in the Zapier editor (`https://zapier.com/durables-editor/`). + +If manual triggering is supported for the workflow, test it only after confirming side effects with the user: + +```bash +zapier-sdk --experimental trigger-workflow --input '' --json +``` + +If `trigger-workflow` returns a trigger ID before a workflow run ID is available, bridge from trigger to run: + +```bash +zapier-sdk --experimental get-trigger-run --json +``` + +Then inspect run history and, if needed, a deployed workflow run: + +```bash +zapier-sdk --experimental list-workflow-runs --json +zapier-sdk --experimental get-workflow-run --json +``` + +Finish by reporting: + +- Workflow name and ID. +- Where `workflow.ts` lives locally. +- Whether testing passed. +- Whether the deployed workflow is enabled. +- Whether the workflow is private or account-visible. +- Whether the workflow uses a Zapier app trigger, a catch-hook trigger (report its `webhook_url` if available), or no trigger (manual-only via `trigger-workflow`). +- The Zapier editor link: `https://zapier.com/durables-editor/`. + +## Durable Patterns + +### Waits + +```typescript +await ctx.wait("wait-before-followup", 3600); +``` + +Place waits at top-level workflow scope, not inside `ctx.step`. + +### Callbacks + +```typescript +const [approvalPromise, callbackUrl] = await ctx.createCallback({ + name: "wait-for-approval", + payloadSchema: z.object({ approved: z.boolean() }), + timeoutSeconds: 86400, +}); + +await ctx.step("send-approval-request", async () => + sdk.runAction({ + appKey: "ExampleCLIAPI", + actionType: "write", + actionKey: "send_message", + connection: "example_connection", + inputs: { callbackUrl }, + }), +); + +const approval = await approvalPromise; +if (!approval.approved) { + throw new Error("Approval denied"); +} +``` + +### Parallel Or Repeated Work + +Use `Promise.all()` outside `ctx.step`; each iteration creates its own step: + +```typescript +const results = await Promise.all( + items.map((item, index) => + ctx.step(`process-item-${index}`, async () => + sdk.runAction({ + appKey: "ExampleCLIAPI", + actionType: "write", + actionKey: "do_something", + connection: "example_connection", + inputs: { item }, + }), + ), + ), +); +``` + +Loop/fan-out steps use a dynamic id (`` `process-item-${index}` ``), so the editor renders them as code steps — expected for this pattern (see **App-Action Step Shape (Editor Recognition)**). + +### Error Handling + +Use step-level retries for flaky external calls: + +```typescript +const result = await ctx.step({ + name: "flaky-api-call", + maxAttempts: 3, + retryDelaySeconds: 5, + run: async () => + sdk.runAction({ + appKey: "ExampleCLIAPI", + actionType: "write", + actionKey: "do_something", + connection: "example_connection", + inputs: {}, + }), +}); +``` + +Prefer `sdk.runAction` when a Zapier action exists. Use `sdk.fetch` only when the app action cannot provide the needed behavior or data. diff --git a/.claude/skills/workflows-doctor/SKILL.md b/.claude/skills/workflows-doctor/SKILL.md new file mode 100644 index 0000000..62e1a35 --- /dev/null +++ b/.claude/skills/workflows-doctor/SKILL.md @@ -0,0 +1,157 @@ +--- +name: workflows-doctor +description: Diagnose Zapier Workflows skill and SDK CLI compatibility. Use when a workflow skill asks for a compatibility check, when SDK commands or flags are missing, when a workflow skill may be stale, or when updating workflow skills after an SDK CLI change. +license: MIT +metadata: + author: zapier + version: "1.2.1" + sdk_cli_min: "0.54.3" + sdk_cli_validated: "0.54.3" + refresh_source: "zapier/agent-skills" +--- + +# Zapier Workflows Doctor + +Diagnose whether the installed Zapier SDK CLI can support the Zapier Workflows skill bundle. Be diagnostic first. Do not refresh skills unless SDK/skill drift is detected or compatibility cannot be confirmed. + +## Compatibility Metadata + +Workflow skills use these metadata fields: + +- `sdk_cli_min`: oldest SDK CLI version the skill is allowed to run against. Set it to the first SDK CLI version that supports the newest command or flag the skill depends on. If that exact first-supported version is uncertain, use the SDK CLI version used when introducing the skill instruction change. +- `sdk_cli_validated`: SDK CLI version used during the latest validation pass. Update it whenever workflow skills are intentionally tested and republished against a newer SDK CLI, even if `sdk_cli_min` does not change. +- `refresh_source`: canonical skill source. For these skills, keep this as `zapier/agent-skills`. + +Command-surface checks verify required bundle capabilities only. They do not prove full workflow correctness or that JSON payload semantics are unchanged. + +## Step 0: Daily Skill-Freshness Check + +Run this before the SDK compatibility steps below. It keeps the workflow skills current with `zapier/agent-skills` even when the SDK CLI has not changed, by occasionally running `npx skills update` for the bundle. It is **soft and non-blocking**: it self-throttles to roughly once per day per project, never stops the calling skill, and prints nothing unless it actually applied an update. + +Run it exactly once, then continue to Step 1 regardless of its output. Do **not** parse or branch on the result: + +```bash +bash scripts/skill-freshness-check.sh +``` + +Resolve `scripts/skill-freshness-check.sh` relative to this skill's own directory. The script locates the installed skill bundle from its own path and runs the bundle update from the scope root that contains it (the directory holding `.agents`/`.claude`), so it does not matter which directory you invoke it from. + +- If it prints a note that skills were refreshed, pass that note along to the user and keep going; the update takes full effect on the next workspace reload. +- If it prints nothing, say nothing and continue. + +This freshness check is independent of the SDK command-surface compatibility check in Steps 1–4 below, which is unchanged and remains a hard gate. For troubleshooting, set `ZAPIER_WORKFLOWS_DEBUG=1` to see the freshness check's decision on stderr. + +## Step 1: Check Bundle Compatibility + +Check the workflow skill bundle as one unit. Do not maintain separate compatibility checks for `workflows-install`, `workflows-create`, `workflows-list`, `workflows-history`, and `workflows-modify`; users will normally use these skills together, and drift in any core workflow SDK surface should refresh the whole bundle. + +Current workflow skills use `sdk_cli_min: "0.54.3"` and `sdk_cli_validated: "0.54.3"` unless the installed skills' metadata says otherwise. + +## Step 2: Check SDK CLI Versions + +Run: + +```bash +which zapier-sdk +zapier-sdk --version +npm view @zapier/zapier-sdk-cli version +``` + +If `zapier-sdk` is missing or `zapier-sdk --version` is below the bundle's `sdk_cli_min`, update the SDK CLI before continuing: + +```bash +npm install -g @zapier/zapier-sdk-cli@latest +zapier-sdk --version +``` + +If global npm installs fail because of permissions, tell the user to fix their Node/npm setup before retrying. Prefer a user-owned Node install through nvm or Homebrew over `sudo npm install -g`. + +If the installed SDK CLI version is newer than the bundle's `sdk_cli_validated`, continue to command-surface discovery. Do not refresh skills solely because the SDK CLI is newer. + +## Step 3: Discover Current Command Surface + +Start from the SDK help output: + +```bash +zapier-sdk --experimental --help +``` + +Use the help output to discover the current command names and flags for the required bundle capabilities below. Current command names in this skill are examples from the SDK CLI version the workflow skill bundle was validated against; they are not the compatibility contract. If the current help output exposes an equivalent way to perform a required capability, use the current help output. + +For each discovered candidate command, inspect command-specific help: + +```bash +zapier-sdk --experimental --help +``` + +## Required Bundle Capabilities + +Confirm that the SDK CLI exposes a clear way to perform these operations for the workflow skill bundle: + +- Create a workflow container. +- Publish a workflow version. +- Run a durable workflow locally or synthetically. +- List workflows. +- List workflow runs. +- Inspect a workflow run. +- Discover or list app triggers. +- Trigger a workflow. +- Control workflow visibility, including private workflow creation or the current equivalent. +- Bind app connections for test runs and published workflow versions. +- Bind app implementation/version metadata when required. +- Provide trigger configuration for published workflow versions. +- Pass workflow input when running or triggering workflows. +- Control enabled state when publishing workflow versions. +- Run synthetic durable tests privately or with the current equivalent behavior. + +When the current SDK help output is clear, prefer it over the example commands below. If discovery is ambiguous or a required capability appears absent, treat compatibility as unconfirmed and refresh the workflow skill bundle. + +Example commands from the validated SDK CLI surface: + +```bash +zapier-sdk --experimental create-workflow --help +zapier-sdk --experimental publish-workflow-version --help +zapier-sdk --experimental run-durable --help +zapier-sdk --experimental list-workflows --help +zapier-sdk --experimental list-workflow-runs --help +zapier-sdk --experimental get-workflow-run --help +zapier-sdk --experimental list-triggers --help +zapier-sdk --experimental trigger-workflow --help +zapier-sdk --experimental get-trigger-run --help +zapier-sdk --experimental get-workflow --help +zapier-sdk --experimental get-workflow-version --help +``` + +Example flags from the validated SDK CLI surface: + +- `create-workflow`: `--private` +- `publish-workflow-version`: `--connections`, `--app_versions`, `--trigger`, `--enabled` +- `run-durable`: `--connections`, `--input`, `--private` +- `trigger-workflow`: `--input` + +Equivalent current flags or command shapes are acceptable if the help text clearly supports the same required bundle capability. + +## Step 4: Decide Whether To Refresh Skills + +If all required bundle capabilities are confirmed, tell the calling skill to continue without refreshing. + +If any required capability is missing, or compatibility cannot be confirmed, update the entire workflow skill bundle so the skills stay in sync. + +Prefer the standard day-2 update path first: + +```bash +npx skills update workflows-install workflows-doctor workflows-create workflows-list workflows-history workflows-modify -y +``` + +If `skills update` cannot find the installed skills, updates the wrong scope, or otherwise fails, fall back to explicit installs from canonical GitHub: + +```bash +npx skills add zapier/agent-skills --skill workflows-install --yes +npx skills add zapier/agent-skills --skill workflows-doctor --yes +npx skills add zapier/agent-skills --skill workflows-create --yes +npx skills add zapier/agent-skills --skill workflows-list --yes +npx skills add zapier/agent-skills --skill workflows-history --yes +npx skills add zapier/agent-skills --skill workflows-modify --yes +``` + +After updating skills, stop the current skill invocation. Tell the user to reload the agent workspace and rerun their original request. Do not promise that the current invocation has changed its already-loaded instructions. diff --git a/.claude/skills/workflows-doctor/scripts/skill-freshness-check.sh b/.claude/skills/workflows-doctor/scripts/skill-freshness-check.sh new file mode 100644 index 0000000..bc3f9ae --- /dev/null +++ b/.claude/skills/workflows-doctor/scripts/skill-freshness-check.sh @@ -0,0 +1,174 @@ +#!/bin/bash +# ABOUTME: Skill-freshness check for the Zapier Workflows skill bundle. +# ABOUTME: Throttled (~daily) best-effort `npx skills update`; ALWAYS exits 0, never blocks the caller. +# +# Invoked by workflows-doctor "Step 0". Soft and non-blocking by design. +# DELIBERATELY no `set -e` (the repo's usual convention): a failure here must +# never abort the worker skill that ran the doctor. Every path ends with exit 0. +# +# Env hooks: +# ZAPIER_WORKFLOWS_DEBUG=1 verbose decision log -> stderr (bundle-wide flag) +# ZAPIER_WORKFLOWS_DOCTOR_NOW= override the clock (tests) +# ZAPIER_WORKFLOWS_DOCTOR_UPDATE_CMD= override the update command (tests) +# ZAPIER_WORKFLOWS_DOCTOR_BUNDLE_ROOT= override the fingerprint root (tests) +# XDG_CACHE_HOME= override cache root (tests / XDG) + +DAILY=86400 +BURST_COOLDOWN=900 +MAX_FAILURES=3 +UPDATE_NOTE="Refreshed the Zapier Workflows skills; the updates take full effect the next time you reload this workspace." +DEFAULT_UPDATE_CMD="npx --yes skills update workflows-install workflows-doctor workflows-create workflows-list workflows-history workflows-modify -y" + +debug() { + if [ "${ZAPIER_WORKFLOWS_DEBUG:-}" = "1" ]; then + printf '[workflows-doctor freshness] %s\n' "$*" >&2 + fi + return 0 +} + +now_epoch() { + if [ -n "${ZAPIER_WORKFLOWS_DOCTOR_NOW:-}" ]; then + printf '%s' "$ZAPIER_WORKFLOWS_DOCTOR_NOW" + else + date +%s + fi +} + +as_int() { + case "$1" in + ''|*[!0-9]*) printf '0' ;; + *) printf '%s' "$1" ;; + esac +} + +# The installed bundle root. Resolved with `pwd -P` so it works through the +# symlinks the skills CLI creates (~/.claude/skills/ -> ~/.agents/skills/); +# the skill is installed at //scripts/skill-freshness-check.sh. +bundle_root() { + if [ -n "${ZAPIER_WORKFLOWS_DOCTOR_BUNDLE_ROOT:-}" ]; then + printf '%s' "$ZAPIER_WORKFLOWS_DOCTOR_BUNDLE_ROOT" + return + fi + ( cd "$(dirname "$0")/../.." 2>/dev/null && pwd -P ) +} + +# Working directory for the update command. The `skills` CLI resolves *project* +# skills relative to its CWD -- it only finds skills under a `.agents`/`.claude` +# directory that is a direct child of the working directory. This script is invoked +# from inside a skill subdirectory, so running `npx skills update` there discovers +# no project skills and silently refreshes nothing (exit 0, no on-disk change). +# The install root is `/.agents/skills` (or the `.claude` equivalent), so the +# scope root the CLI needs is two levels up. Fall back to the current directory when +# that can't be resolved or doesn't look like a scope root (e.g. test fixtures), +# which preserves prior behavior. +scope_root() { + local install_root="$1" candidate + candidate="$( cd "$install_root/../.." 2>/dev/null && pwd -P )" || candidate="" + if [ -n "$candidate" ] && { [ -d "$candidate/.agents" ] || [ -d "$candidate/.claude" ]; }; then + printf '%s' "$candidate" + else + printf '%s' "$PWD" + fi +} + +# Aggregate checksum of every installed workflow skill's SKILL.md. Used to detect +# "did an update actually change anything on disk" -- a signal that cannot misfire +# on CLI wording, unlike output parsing. cksum/find/sort only (bash 3.2-safe; no +# jq/stat/shasum). No matches -> a stable constant, so before==after when nothing changed. +bundle_fingerprint() { + local root="$1" + [ -d "$root" ] || { printf '0'; return; } + find "$root" -maxdepth 3 -path '*workflows*/SKILL.md' -type f -exec cksum {} + 2>/dev/null \ + | sort | cksum | awk '{print $1}' +} + +# Primary signal is the fingerprint diff; prose is only a fallback for the rare +# case fingerprinting can't see the change. +decide_outcome() { + local before="$1" after="$2" rc="$3" out="$4" lc + if [ "$before" != "$after" ]; then printf 'updated'; return; fi + if [ "$rc" -ne 0 ]; then printf 'failed'; return; fi + lc="$(printf '%s' "$out" | tr '[:upper:]' '[:lower:]')" + # `skills update` exits 0 even on its own errors (e.g. "No installed skills + # found"), so scan output too. The substrings can overlap benign text like + # "0 errors"; because the fingerprint check ran first, that only flips a silent + # noop to a (silent) failed -> retry sooner, never suppresses a real update. + case "$lc" in + *error*|*"not found"*|*enotfound*|*etimedout*|*network*|*failed*) printf 'failed'; return ;; + esac + case "$lc" in + *updated*|*upgraded*|*added*|*"->"*|*"→"*) printf 'updated'; return ;; + esac + printf 'noop' +} + +write_marker() { + printf '%s\n%s\n%s\n' "$2" "$3" "$4" > "$1" 2>/dev/null || true +} + +main() { + local now scope_root key cache_dir marker + now="$(now_epoch)" + scope_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" + key="$(printf '%s' "$scope_root" | cksum | awk '{print $1}')" + cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}/zapier-workflows-doctor" + marker="$cache_dir/$key" + debug "scope_root=$scope_root key=$key marker=$marker now=$now" + + local last_success last_attempt failures l1 l2 l3 + last_success=0; last_attempt=0; failures=0 + if [ -f "$marker" ]; then + l1=""; l2=""; l3="" + { IFS= read -r l1; IFS= read -r l2; IFS= read -r l3; } < "$marker" + last_success="$(as_int "$l1")" + last_attempt="$(as_int "$l2")" + failures="$(as_int "$l3")" + else + debug "marker missing -> treat as due" + fi + + local since_success since_attempt due + since_success=$(( now - last_success )) + since_attempt=$(( now - last_attempt )) + due=0 + if [ "$since_success" -lt "$DAILY" ]; then + due=0 + elif [ "$failures" -ge "$MAX_FAILURES" ]; then + if [ "$since_attempt" -ge "$DAILY" ]; then due=1; fi + else + if [ "$since_attempt" -ge "$BURST_COOLDOWN" ]; then due=1; fi + fi + + if [ "$due" -ne 1 ]; then + debug "state=skipped since_success=$since_success since_attempt=$since_attempt failures=$failures" + exit 0 + fi + + local root run_dir before_fp after_fp update_cmd out rc outcome + root="$(bundle_root)" + run_dir="$(scope_root "$root")" + before_fp="$(bundle_fingerprint "$root")" + update_cmd="${ZAPIER_WORKFLOWS_DOCTOR_UPDATE_CMD:-$DEFAULT_UPDATE_CMD}" + debug "due -> root=$root run_dir=$run_dir before_fp=$before_fp running update: $update_cmd" + out="$( cd "$run_dir" 2>/dev/null && eval "$update_cmd" 2>&1 )" + rc=$? + after_fp="$(bundle_fingerprint "$root")" + outcome="$(decide_outcome "$before_fp" "$after_fp" "$rc" "$out")" + debug "state=$outcome rc=$rc after_fp=$after_fp" + + mkdir -p "$cache_dir" 2>/dev/null + if [ "$outcome" = "failed" ]; then + failures=$(( failures + 1 )) + write_marker "$marker" "$last_success" "$now" "$failures" + else + write_marker "$marker" "$now" "$now" "0" + fi + + if [ "$outcome" = "updated" ]; then + printf '%s\n' "$UPDATE_NOTE" + fi + exit 0 +} + +main "$@" +exit 0 diff --git a/.claude/skills/workflows-doctor/scripts/skill-freshness-check.test.sh b/.claude/skills/workflows-doctor/scripts/skill-freshness-check.test.sh new file mode 100644 index 0000000..d4814e0 --- /dev/null +++ b/.claude/skills/workflows-doctor/scripts/skill-freshness-check.test.sh @@ -0,0 +1,117 @@ +#!/bin/bash +# ABOUTME: Framework-free tests for skill-freshness-check.sh (throttle + outcome logic). +# ABOUTME: Uses env hooks (fake clock, stub update cmd, temp bundle root + XDG_CACHE_HOME) — no network, no real time. +# +# Run: bash skills/workflows/doctor/scripts/skill-freshness-check.test.sh +set -u + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +SCRIPT="$SCRIPT_DIR/skill-freshness-check.sh" +NOW=2000000000 +NOTE_SUBSTR="Refreshed the Zapier Workflows skills" + +pass=0 +fail=0 + +setup() { + XDG_CACHE_HOME="$(mktemp -d)" + export XDG_CACHE_HOME + EMPTY_ROOT="$(mktemp -d)" # an empty bundle root -> fingerprint never changes + CACHE_DIR="$XDG_CACHE_HOME/zapier-workflows-doctor" + KEY="$(printf '%s' "$(git rev-parse --show-toplevel 2>/dev/null || pwd)" | cksum | awk '{print $1}')" + MARKER="$CACHE_DIR/$KEY" +} +teardown() { rm -rf "$XDG_CACHE_HOME" "$EMPTY_ROOT"; } + +put_marker() { # last_success last_attempt failures + mkdir -p "$CACHE_DIR" + printf '%s\n%s\n%s\n' "$1" "$2" "$3" > "$MARKER" +} + +# run STUB -> sets RUN_OUT (stdout), RUN_ERR (stderr), RUN_RC (exit code). +# Pins the bundle root to an empty dir so fingerprint stays constant and these +# cases exercise the prose/exit fallback path. The on-disk-change path is Case 12. +run() { + local stub="$1" errfile + errfile="$(mktemp)" + RUN_OUT="$(ZAPIER_WORKFLOWS_DEBUG=1 ZAPIER_WORKFLOWS_DOCTOR_NOW="$NOW" \ + ZAPIER_WORKFLOWS_DOCTOR_BUNDLE_ROOT="$EMPTY_ROOT" \ + ZAPIER_WORKFLOWS_DOCTOR_UPDATE_CMD="$stub" bash "$SCRIPT" 2>"$errfile")" + RUN_RC=$? + RUN_ERR="$(cat "$errfile")" + rm -f "$errfile" +} + +ok() { pass=$((pass+1)); printf ' PASS: %s\n' "$1"; } +bad() { fail=$((fail+1)); printf ' FAIL: %s\n' "$1"; } + +want_state() { case "$RUN_ERR" in *"state=$1"*) ok "state=$1";; *) bad "expected state=$1; stderr=[$RUN_ERR]";; esac; } +want_stdout_has(){ case "$RUN_OUT" in *"$1"*) ok "stdout has [$1]";; *) bad "expected stdout to contain [$1]; got [$RUN_OUT]";; esac; } +want_stdout_empty(){ [ -z "$RUN_OUT" ] && ok "stdout empty" || bad "expected empty stdout; got [$RUN_OUT]"; } +want_rc0() { [ "$RUN_RC" -eq 0 ] && ok "exit 0" || bad "expected exit 0; got $RUN_RC"; } +want_marker_line(){ local n="$1" exp="$2" got; got="$(sed -n "${n}p" "$MARKER")"; [ "$got" = "$exp" ] && ok "marker L$n=$exp" || bad "marker L$n expected [$exp] got [$got]"; } + +STUB_CHANGED='printf "Updated workflows-doctor 1.2.0 -> 1.3.0\n"' +STUB_UNCHANGED='printf "Checking skills from source: zapier/agent-skills\nAll global skills are up to date\n"' +STUB_FAIL='exit 1' +STUB_FAIL_EXIT0='printf "error: network unreachable\n"' + +echo "Case 1: missing marker -> prose shows update -> updated + note" +setup; run "$STUB_CHANGED"; want_state updated; want_stdout_has "$NOTE_SUBSTR"; want_rc0; want_marker_line 1 "$NOW"; want_marker_line 3 "0"; teardown + +echo "Case 2: missing marker -> update unchanged -> noop, silent" +setup; run "$STUB_UNCHANGED"; want_state noop; want_stdout_empty; want_rc0; want_marker_line 1 "$NOW"; teardown + +echo "Case 3: fresh (success 1h ago) -> skipped, no update" +setup; put_marker $((NOW-3600)) $((NOW-3600)) 0; run "$STUB_CHANGED"; want_state skipped; want_stdout_empty; want_rc0; teardown + +echo "Case 4: bursting, attempt 10m ago -> skipped (cooldown)" +setup; put_marker $((NOW-90000)) $((NOW-600)) 1; run "$STUB_CHANGED"; want_state skipped; want_stdout_empty; teardown + +echo "Case 5: bursting, attempt 20m ago -> due -> failure, count=2, silent" +setup; put_marker $((NOW-90000)) $((NOW-1200)) 1; run "$STUB_FAIL"; want_state failed; want_stdout_empty; want_rc0; want_marker_line 2 "$NOW"; want_marker_line 3 "2"; teardown + +echo "Case 6: exhausted, attempt 2h ago -> skipped (daily fallback)" +setup; put_marker $((NOW-200000)) $((NOW-7200)) 3; run "$STUB_CHANGED"; want_state skipped; want_stdout_empty; teardown + +echo "Case 7: exhausted, attempt 25h ago -> due -> success resets count" +setup; put_marker $((NOW-200000)) $((NOW-90000)) 3; run "$STUB_UNCHANGED"; want_state noop; want_rc0; want_marker_line 1 "$NOW"; want_marker_line 3 "0"; teardown + +echo "Case 8: corrupt marker -> treated as due" +setup; mkdir -p "$CACHE_DIR"; printf 'garbage\n\nxyz\n' > "$MARKER"; run "$STUB_UNCHANGED"; want_state noop; want_rc0; teardown + +echo "Case 9: not in a git repo -> pwd fallback, exits 0" +setup; nonrepo="$(mktemp -d)"; ( cd "$nonrepo" && ZAPIER_WORKFLOWS_DEBUG=1 ZAPIER_WORKFLOWS_DOCTOR_NOW="$NOW" ZAPIER_WORKFLOWS_DOCTOR_BUNDLE_ROOT="$EMPTY_ROOT" ZAPIER_WORKFLOWS_DOCTOR_UPDATE_CMD="$STUB_UNCHANGED" bash "$SCRIPT" >/dev/null 2>&1 ); rc=$?; [ "$rc" -eq 0 ] && ok "non-repo exit 0" || bad "non-repo exit $rc"; rm -rf "$nonrepo"; teardown + +echo "Case 10: update fails -> still exit 0" +setup; run "$STUB_FAIL"; want_rc0; teardown + +echo "Case 11: exit 0 but output shows an error -> failed (output-based detection)" +setup; run "$STUB_FAIL_EXIT0"; want_state failed; want_stdout_empty; want_rc0; want_marker_line 3 "1"; teardown + +echo "Case 12: bundle files change on disk -> updated, even with '0 errors' in output (fingerprint beats prose)" +setup; fproot="$(mktemp -d)"; mkdir -p "$fproot/workflows-doctor"; printf 'version: 1\n' > "$fproot/workflows-doctor/SKILL.md" +stub="printf 'syncing skills, 0 errors\n'; printf 'version: 2\n' > '$fproot/workflows-doctor/SKILL.md'" +errfile="$(mktemp)" +RUN_OUT="$(ZAPIER_WORKFLOWS_DEBUG=1 ZAPIER_WORKFLOWS_DOCTOR_NOW="$NOW" ZAPIER_WORKFLOWS_DOCTOR_BUNDLE_ROOT="$fproot" ZAPIER_WORKFLOWS_DOCTOR_UPDATE_CMD="$stub" bash "$SCRIPT" 2>"$errfile")" +RUN_RC=$?; RUN_ERR="$(cat "$errfile")"; rm -f "$errfile" +want_state updated; want_stdout_has "$NOTE_SUBSTR"; want_rc0; want_marker_line 1 "$NOW"; want_marker_line 3 "0" +rm -rf "$fproot"; teardown + +echo "Case 13: update runs from the scope root containing the install dir (not the invocation dir)" +setup +scope="$(mktemp -d)" +mkdir -p "$scope/.agents/skills/workflows-doctor" # install root = $scope/.agents/skills +cwdfile="$(mktemp)" +stub="pwd -P > '$cwdfile'" +( cd / && ZAPIER_WORKFLOWS_DEBUG=1 ZAPIER_WORKFLOWS_DOCTOR_NOW="$NOW" \ + ZAPIER_WORKFLOWS_DOCTOR_BUNDLE_ROOT="$scope/.agents/skills" \ + ZAPIER_WORKFLOWS_DOCTOR_UPDATE_CMD="$stub" bash "$SCRIPT" >/dev/null 2>&1 ) +got="$(cat "$cwdfile")" +want="$(cd "$scope" && pwd -P)" +[ "$got" = "$want" ] && ok "update cwd = scope root ($want)" || bad "expected update cwd [$want]; got [$got]" +rm -rf "$scope" "$cwdfile"; teardown + +echo "" +echo "TOTAL: $pass passed, $fail failed" +[ "$fail" -eq 0 ] diff --git a/.claude/skills/workflows-history/SKILL.md b/.claude/skills/workflows-history/SKILL.md new file mode 100644 index 0000000..f095544 --- /dev/null +++ b/.claude/skills/workflows-history/SKILL.md @@ -0,0 +1,63 @@ +--- +name: workflows-history +description: Show run history for a specific durable workflow using the Zapier SDK experimental Code Workflows commands. Use when the user asks for run history, execution history, what happened with this Zap, or how a workflow fired. +license: MIT +metadata: + author: zapier + version: "1.1.0" + sdk_cli_min: "0.54.3" + sdk_cli_validated: "0.54.3" + refresh_source: "zapier/agent-skills" +--- + +# Zapier Workflows History + +Use the public SDK CLI experimental command surface. Do not use `zapier-sdk-code-substrate`. + +## Compatibility Gate + +Before using this skill, run the `workflows-doctor` bundle compatibility check. If `workflows-doctor` is not installed or cannot be loaded, run `workflows-install` or install `workflows-doctor` from `zapier/agent-skills` before continuing. If `workflows-doctor` reports SDK/skill drift, follow its refresh instructions, stop this skill invocation, reload the agent workspace if needed, and ask the user to rerun the original request. + +## Identify The Workflow + +If the user provides a workflow ID, use it directly. + +If the user refers to the workflow by name or description, list workflows first and find the matching ID: + +```bash +zapier-sdk --experimental list-workflows --json +``` + +If multiple workflows match, show the candidates and ask the user which one they mean. + +## Fetch Run History + +```bash +zapier-sdk --experimental list-workflow-runs --json +``` + +Parse the JSON output. Useful fields may include `id`, `status`, `started_at`, `finished_at`, `input`, and `output`. + +When the workflow ID is known, include the Zapier editor link: + +```text +https://zapier.com/durables-editor/ +``` + +## Drill Into A Run + +If a single deployed workflow run failed or the user wants step-level detail: + +```bash +zapier-sdk --experimental get-workflow-run --json +``` + +Use `get-durable-run ` only for one-off synthetic runs created by `zapier-sdk --experimental run-durable`, not for deployed workflow runs returned by `list-workflow-runs`. + +If a manual trigger response returns a trigger ID before a workflow run ID is available, bridge from trigger to run: + +```bash +zapier-sdk --experimental get-trigger-run --json +``` + +Summarize the failure, status, timing, input, output, and any step error details that appear in the response. Avoid dumping raw JSON unless the user asks for it. diff --git a/.claude/skills/workflows-install/SKILL.md b/.claude/skills/workflows-install/SKILL.md new file mode 100644 index 0000000..04c3548 --- /dev/null +++ b/.claude/skills/workflows-install/SKILL.md @@ -0,0 +1,302 @@ +--- +name: workflows-install +description: Install the Zapier SDK CLI for Zapier Workflows Early Access and bootstrap the workflows companion skills. Use when the user wants to set up Zapier Workflows, get started building durable workflows, install workflow skills, or configure the Zapier SDK CLI. +license: MIT +metadata: + author: zapier + version: "1.1.0" + sdk_cli_min: "0.54.3" + sdk_cli_validated: "0.54.3" + refresh_source: "zapier/agent-skills" +--- + +# Zapier Workflows Early Access Install + +Imperative recipe. Each step gates the next. Do not skip a step that failed. + +This is the public-first EA path. It uses the Zapier SDK CLI and does not install the legacy `@zapier/zapier-sdk-code-substrate` package. + +## Flow + +```mermaid +flowchart TD + probe["1. Probe environment"] --> envOk{"Node 18+, npm, git OK?"} + envOk -->|no| stopEnv["STOP: tell user what to install"] + envOk -->|yes| installCli["2. Install or update SDK CLI"] + installCli --> verifyExperimental["3. Verify experimental commands"] + verifyExperimental --> commandsOk{"Code Workflows commands visible?"} + commandsOk -->|no| stopCli["STOP: diagnose SDK CLI install"] + commandsOk -->|yes| checkAuth["4. Check Zapier auth"] + checkAuth --> authOk{"JSON has data and no errors?"} + authOk -->|no| login["4b. Ask user to run interactive login"] + login --> checkAuth + authOk -->|yes| checkAccess["5. Check Zapier Workflows EA access"] + checkAccess --> accessOk{"Read-only workflow list succeeds?"} + accessOk -->|no| stopAccess["STOP: closed beta access required"] + accessOk -->|yes| installSkills["6. Bootstrap companion skills"] + installSkills --> report["7. Report success and next steps"] +``` + +## What This Installs + +- `@zapier/zapier-sdk-cli@latest` — public npm package that provides `zapier-sdk`, `zapier-sdk-cli`, and `zapier-sdk-experimental`. Installed globally. +- Five companion skills installed through the `skills` CLI: `workflows/doctor`, `workflows/create`, `workflows/list`, `workflows/history`, and `workflows/modify`. + +What this does not install: + +- `@zapier/zapier-sdk-code-substrate` — old private CLI path. Do not install it for EA. +- `@zapier/zapier-durable` globally. The create skill installs or pins it inside workflow projects when needed. + +## Step 1: Probe Environment + +Run each check. If any fails, stop and tell the user how to fix it. + +```bash +node --version +npm --version +git --version +``` + +Expected output: + +```bash +v18.0.0 # or higher +10.x.x # npm version; any current version is fine +git version 2.x.x +``` + +Requirements: + +| Tool | Minimum version | Install if missing | +|---|---|---| +| Node | 18 | `brew install node` or use nvm | +| npm | any current version | bundled with Node | +| git | any | usually preinstalled on macOS; otherwise `brew install git` | + +If Node or npm is missing, explain that Node includes npm and the user needs a normal Node install before continuing. For macOS users, suggest either the Node LTS installer from `nodejs.org`, Homebrew (`brew install node`), or nvm if they already use it. Do not continue until `node --version` and `npm --version` work. + +If git is missing, explain that git is needed only to download the companion skills from GitHub. For macOS users, suggest installing Apple Command Line Tools or Homebrew git. Do not continue until `git --version` works. + +## Step 2: Install Or Update The Zapier SDK CLI + +Check for an existing binary and the latest published CLI version: + +```bash +which zapier-sdk +zapier-sdk --version +npm view @zapier/zapier-sdk-cli version +``` + +If `zapier-sdk` is missing, install the CLI globally: + +```bash +npm install -g @zapier/zapier-sdk-cli@latest +``` + +If `zapier-sdk` already exists, compare the installed version from `zapier-sdk --version` with the latest version from `npm view @zapier/zapier-sdk-cli version`. If they differ, update the CLI: + +```bash +npm install -g @zapier/zapier-sdk-cli@latest +``` + +After updating, rerun: + +```bash +zapier-sdk --version +npm view @zapier/zapier-sdk-cli version +``` + +Continue only when the installed CLI version matches the latest published `@zapier/zapier-sdk-cli` version. + +Verify the binary is on PATH: + +```bash +which zapier-sdk +zapier-sdk --version +``` + +If global npm installs fail because of permissions, tell the user to fix their Node/npm setup before retrying. Prefer a user-owned Node install through nvm or Homebrew over `sudo npm install -g`. + +## Step 3: Verify Code Workflows Experimental Commands + +```bash +zapier-sdk --experimental --help +zapier-sdk --experimental create-workflow --help +zapier-sdk --experimental publish-workflow-version --help +zapier-sdk --experimental run-durable --help +zapier-sdk --experimental list-triggers --help +zapier-sdk --experimental get-workflow-run --help +zapier-sdk --experimental trigger-workflow --help +``` + +Expected output includes the Code Workflows command group, including commands such as: + +```text +create-workflow +list-workflows +run-durable +publish-workflow-version +list-workflow-runs +get-workflow-run +``` + +The command-specific help must expose the flags the companion skills depend on: + +- `create-workflow --help` includes `--private`. +- `publish-workflow-version --help` includes `--connections`, `--app_versions`, and `--trigger`. +- `run-durable --help` includes `--connections` and `--private`. +- `list-triggers --help` succeeds. +- `get-workflow-run --help` succeeds. +- `trigger-workflow --help` includes `--input`. + +The equivalent binary may also work: + +```bash +zapier-sdk-experimental --help +``` + +If neither form exposes Code Workflows commands, stop and diagnose the SDK CLI install. Do not fall back to `@zapier/zapier-sdk-code-substrate`. + +If `zapier-sdk` exists but the Code Workflows command group or required command-specific flags are missing, the user likely has an older SDK CLI. Run: + +```bash +npm install -g @zapier/zapier-sdk-cli@latest +zapier-sdk --experimental --help +zapier-sdk --experimental publish-workflow-version --help +``` + +Retry the command-specific help checks once after updating. Proceed only after the Code Workflows command group and required flags are visible. If the required flags are still missing after updating, stop and report the installed CLI version and latest npm version; do not install companion skills into a workspace that cannot run their documented command shapes. + +## Step 4: Authenticate To Zapier + +Check auth state first: + +```bash +zapier-sdk get-profile --json +``` + +Treat auth as successful only if the JSON has a non-null `data` object with an email and the `errors` array is empty. Do not rely on exit code alone; some SDK CLI auth failures return exit code 0 with errors in the JSON body. + +Expected successful output includes the user's email: + +```json +{ + "data": { + "email": "user@example.com" + }, + "errors": [] +} +``` + +If `data` is null, `errors` is non-empty, or the error message says authentication is required, stop and ask the user to run the interactive login command in a real terminal: + +```bash +zapier-sdk login +``` + +This opens a browser. The CLI error text may suggest `npx zapier-sdk login`, but after the global install above the preferred command is `zapier-sdk login`. Do not run browser login inside a non-interactive shell or background process unless the user explicitly asks you to manage the interactive login. After the user finishes login, rerun `zapier-sdk get-profile --json` and inspect the JSON again. + +For Zapier employees, the normal path is to log in with their Zapier work account. For external-user testing, use the account that has been allowlisted for Zapier Workflows EA. + +Do not ask the user for a Zapier password, API key, npm token, or copied auth token. Authentication should happen through the browser-based `zapier-sdk login` flow unless the user explicitly says they are using client credentials for automation. + +If the user wants non-interactive auth for automation, note that the CLI error message may mention `ZAPIER_CREDENTIALS` or client credential environment variables. For this EA install path, prefer browser login unless the user already has client credentials. + +## Step 5: Check Zapier Workflows EA Access + +After SDK profile auth succeeds, confirm the authenticated account has Zapier Workflows EA access with a read-only Code Workflows call: + +```bash +zapier-sdk --experimental list-workflows --json +``` + +Expected output is JSON containing workflow data or an empty list, with no errors. This command should not create or modify cloud state. + +Treat the access check as successful only if the JSON has workflow data or an empty workflow list and `errors` is empty. Do not rely on exit code alone; this command may return exit code 0 while the JSON body contains errors. + +If the response says authentication is required, return to Step 4 and diagnose SDK auth. + +If the response includes any of the following, treat it as a Zapier Workflows EA access failure and stop before installing companion skills: + +- `None of the security schemes (userJwt) successfully authenticated this request` +- `allowlist`, `not allowlisted`, or `not whitelisted` +- `forbidden`, `permission`, `unauthorized`, or `access denied` + +When EA access fails, tell the user: + +```text +You're logged in to Zapier as , and the Zapier SDK CLI is installed, but this account does not currently have Zapier Workflows EA access. + +Zapier Workflows is currently only available to members of our closed beta. + +To request access, fill out the beta sign-up form: + +https://next-gen-zaps.zapier.app/ + +Submitting the form does not grant access immediately. The Zapier team will review your request and let you know once access has been granted. + +After your account is allowlisted, rerun the workflows-install skill in this workspace. Reinstalling Node, npm, git, or the SDK CLI will not fix this access check. +``` + +Use the email from `zapier-sdk get-profile --json` in place of ``. + +## Step 6: Bootstrap The Workflows Companion Skills + +Install the companion skills into the current workspace only after SDK auth and Zapier Workflows EA access are confirmed. + +Use the public `skills.sh` install path. The `npx` command runs the `skills` CLI; the skill content comes from the public `zapier/agent-skills` GitHub repo after that repo is published. + +```bash +npx skills add zapier/agent-skills --skill workflows-doctor --yes +npx skills add zapier/agent-skills --skill workflows-create --yes +npx skills add zapier/agent-skills --skill workflows-list --yes +npx skills add zapier/agent-skills --skill workflows-history --yes +npx skills add zapier/agent-skills --skill workflows-modify --yes +``` + +Verify: + +```bash +npx skills list --json +``` + +Expected output should include the installed workflows companion skills: `workflows-doctor`, `workflows-create`, `workflows-list`, `workflows-history`, and `workflows-modify`. + +If any companion skill is missing, rerun the specific `npx skills add ...` command and diagnose before proceeding. + +Updates later use the standard `skills` CLI update path. If a companion skill detects SDK/skill drift, rerun `workflows-install` or run `workflows-doctor`; those are the canonical repair paths. + +```bash +npx skills update --project +``` + +## Step 7: Report Success + +Tell the user: + +- Zapier SDK CLI is installed and on PATH, confirmed via `which zapier-sdk`. +- Code Workflows experimental commands are available. +- The authenticated Zapier account email from `zapier-sdk get-profile --json`. +- Zapier Workflows EA access was confirmed with a read-only workflow listing. +- Five companion workflow skills are installed: `workflows/doctor`, `workflows/create`, `workflows/list`, `workflows/history`, and `workflows/modify`. +- This confirms SDK CLI install, login, Zapier Workflows EA access, and skill bootstrap. It does not yet prove that building, publishing, triggering, or running a full workflow works. + +Next steps for the user: + +- Configure app connections at https://zapier.com/app/assets/connections before attempting to build workflows. +- Reload your agent workspace so the new skills are picked up. This is required before the agent can reliably auto-discover the installed workflow skills. +- Ask your agent to create a workflow, for example: "Create a Zapier workflow that takes a manual input and sends a Slack message." + +## Troubleshooting + +| Symptom | Likely cause | Fix | +|---|---|---| +| `node --version` prints less than `v18` | Old Node | `brew upgrade node` or use nvm to install a current LTS | +| `npm install -g` fails with permissions errors | Global npm prefix is not user-writable | Use nvm or Homebrew Node; avoid `sudo npm install -g` unless the user explicitly accepts that system-level change | +| `zapier-sdk --experimental --help` lacks Code Workflows commands | Old CLI or wrong package installed | Install `@zapier/zapier-sdk-cli@latest`, then rerun `zapier-sdk --version` and the help command | +| `zapier-sdk get-profile` says not logged in | User has not authenticated the CLI | Run `zapier-sdk login` in an interactive terminal, then retry | +| `get-profile` succeeds but `list-workflows` returns an access, permission, allowlist, or JWT/security-scheme error | The Zapier account is authenticated but does not have Zapier Workflows EA access | Stop before installing companion skills. Tell the user Zapier Workflows is currently only available to members of our closed beta, include the authenticated email, and ask them to rerun `workflows-install` after allowlisting. | +| `zapier-sdk login` does not open a browser | No default browser configured, or remote/SSH session | Try `zapier-sdk login --no-browser` if supported by the installed CLI, or run from a local terminal | +| `zapier-sdk login` hangs in a non-interactive shell | `login` is browser-interactive; cannot run unattended | Ask the user to run it manually in an actual terminal | +| `npx skills add zapier/agent-skills --skill workflows-...` fails | Public skill source is unavailable, the skill has not been published yet, or network access failed | Confirm the `zapier/agent-skills` public repo and workflow skill path are available, then rerun the specific install command | +| Skills do not auto-invoke after install | Agent workspace has not reloaded the skills directory | Reload workspace or restart your agent | diff --git a/.claude/skills/workflows-list/SKILL.md b/.claude/skills/workflows-list/SKILL.md new file mode 100644 index 0000000..ca00641 --- /dev/null +++ b/.claude/skills/workflows-list/SKILL.md @@ -0,0 +1,63 @@ +--- +name: workflows-list +description: List durable workflows in the authenticated Zapier account using the Zapier SDK experimental Code Workflows commands. Use when the user asks to list my Zaps, show my durable workflows, what workflows do I have, or see what Zapier workflows are deployed. +license: MIT +metadata: + author: zapier + version: "1.1.1" + sdk_cli_min: "0.54.3" + sdk_cli_validated: "0.54.3" + refresh_source: "zapier/agent-skills" +--- + +# Zapier Workflows List + +Use the public SDK CLI experimental command surface. Do not use `zapier-sdk-code-substrate`. + +## Compatibility Gate + +Before using this skill, run the `workflows-doctor` bundle compatibility check. If `workflows-doctor` is not installed or cannot be loaded, run `workflows-install` or install `workflows-doctor` from `zapier/agent-skills` before continuing. If `workflows-doctor` reports SDK/skill drift, follow its refresh instructions, stop this skill invocation, reload the agent workspace if needed, and ask the user to rerun the original request. + +## Check Prerequisites + +```bash +zapier-sdk --version +zapier-sdk get-profile --json +zapier-sdk --experimental --help +``` + +If auth fails, ask the user to run `zapier-sdk login` in an interactive terminal and retry. + +## List Workflows + +```bash +zapier-sdk --experimental list-workflows --json +``` + +Parse the JSON output and format what the user asked for. Common useful fields may include `id`, `name`, `enabled`, `is_private`, `created_by_user_id`, `created_at`, `updated_at`, `description`, `current_version`, and trigger-related metadata if present. + +For each workflow with an `id`, include the Zapier editor link: + +```text +https://zapier.com/durables-editor/ +``` + +Treat `trigger_url` as account-sensitive: firing it invokes the workflow as the authenticated account, so while the token in the URL is no longer a standalone credential, it is still not something to print gratuitously. Do not print `trigger_url` unless the user explicitly asks for it. + +Check each entry in `triggers[]` for `details.webhook_url`, regardless of trigger type — its presence alone tells you there's a catch URL. Unlike `trigger_url`, `webhook_url` is meant to be shared — it's the URL the user pastes into the external service — so surface it plainly when present. Most triggers have no `webhook_url`, and that is normal; do not flag its absence unless the user specifically expects one (for example they mention "Webhooks by Zapier"), in which case the installed SDK may predate this field. + +## Ownership Scoping + +`list-workflows` may return every workflow the authenticated user can see, including team workflows. If the user asks for "my workflows," first show the likely matches and explain any uncertainty rather than silently filtering by the wrong ID. + +Known quirk: `zapier-sdk get-profile` may return a UUID that does not match `list-workflows[].created_by_user_id`, which may be a separate numeric user ID. If you cannot confidently map those IDs, say so and present the unfiltered list with enough context for the user to choose. + +## Last Run Time + +If the user asks for recent activity, fetch runs for each relevant workflow: + +```bash +zapier-sdk --experimental list-workflow-runs --json +``` + +Use the most recent run. Be mindful of API volume for large accounts. diff --git a/.claude/skills/workflows-modify/SKILL.md b/.claude/skills/workflows-modify/SKILL.md new file mode 100644 index 0000000..c3551e4 --- /dev/null +++ b/.claude/skills/workflows-modify/SKILL.md @@ -0,0 +1,152 @@ +--- +name: workflows-modify +description: Modify and republish an existing durable workflow using the Zapier SDK experimental Code Workflows commands. Use when the user asks to fix my Zap, update my Zap, modify my workflow, repair this Zap, or edit a deployed Zapier workflow. +license: MIT +metadata: + author: zapier + version: "1.1.3" + sdk_cli_min: "0.54.3" + sdk_cli_validated: "0.54.3" + refresh_source: "zapier/agent-skills" +--- + +# Zapier Workflows Modify + +Modifying a deployed workflow follows a discovery, fetch, edit, republish, verify pattern. Publishing a workflow version writes to the user's Zapier account, so get explicit confirmation before publishing. + +Use the public SDK CLI experimental command surface. Do not use `zapier-sdk-code-substrate`. + +## Compatibility Gate + +Before using this skill, run the `workflows-doctor` bundle compatibility check. If `workflows-doctor` is not installed or cannot be loaded, run `workflows-install` or install `workflows-doctor` from `zapier/agent-skills` before continuing. If `workflows-doctor` reports SDK/skill drift, follow its refresh instructions, stop this skill invocation, reload the agent workspace if needed, and ask the user to rerun the original request. + +## Step 1: Identify The Workflow + +If the user provides a workflow ID, use it directly. Otherwise list workflows and find the matching one by name or description: + +```bash +zapier-sdk --experimental list-workflows --json +``` + +If multiple workflows match, show candidates and ask the user which one to modify. + +## Step 2: Fetch Current Metadata And Version + +Run these reads, then preserve the current metadata before changing anything: + +```bash +zapier-sdk --experimental get-workflow --json +zapier-sdk --experimental list-workflow-versions --json +``` + +From the versions list, pick the current or newest version ID, then fetch it: + +```bash +zapier-sdk --experimental get-workflow-version --json +``` + +Capture: + +- `source_files`, especially `source_files["workflow.ts"]`. +- `dependencies`. +- `zapier_durable_version`. +- `enabled`. +- Any `connections`, `app_versions`, `trigger`, or workflow metadata present in the workflow or version response. + +The current SDK publish command takes `source_files` as a JSON object. Do not pass a raw `workflow.ts` path to `publish-workflow-version`. + +## Step 3: Make The Edit + +Prefer editing an existing local workflow file if one exists. Otherwise, write `source_files["workflow.ts"]` into a local `workflow.ts` in a workflow-specific directory and edit that copy. + +Apply the requested change narrowly. Preserve existing Zod schemas, `ctx.step` boundaries, connection aliases, dependency pins, durable runtime version, publish connection bindings, app-version bindings, trigger configuration, and visibility/enabled state unless there is a reason to change them. + +When the edit adds a new AI/LLM step, follow `workflows-create` Phase 2: always use "AI by Zapier" (`AICLIAPI`, action `get_completion`) and select the model with `model_id` — the user's named provider/model if they gave one, otherwise the default `"advanced/auto"` with built-in credentials (`authentication_id: "0"`). Only use a raw-provider AI app if the user explicitly asks for that standalone app or needs a capability AI by Zapier lacks. + +## Step 4: Optional Synthetic Test + +For non-trivial changes, propose a test run before publishing. This may run real downstream actions, so summarize side effects and wait for confirmation. + +Build `source_files` from the local file: + +```bash +SOURCE_FILES="$(jq -n --rawfile workflow workflow.ts '{"workflow.ts": $workflow}')" +``` + +Run the workflow: + +```bash +zapier-sdk --experimental run-durable "$SOURCE_FILES" \ + --dependencies '' \ + --zapier_durable_version '' \ + --connections '' \ + --input '' \ + --private +``` + +For synthetic `run-durable` tests, reuse the fetched version's connection bindings as-is — they're already the nested object shape `{ "alias": { "connectionId": "..." } }` that both `run-durable` and `publish-workflow-version` accept. Do not flatten to a bare string like `{ "alias": "id" }`; that fails with `expected object, received string`. + +If the run returns a run ID, inspect it when needed: + +```bash +zapier-sdk --experimental get-durable-run --json +``` + +## Step 5: Confirm, Then Republish + +Before publishing, summarize for the user: + +1. The diagnosis. +2. The code or config change. +3. The workflow ID being updated. +4. The publish command shape and values that will be preserved from the old version, including dependencies, durable version, enabled state, connections, app versions, and trigger configuration. + +Wait for explicit confirmation before publishing. + +Build `source_files`: + +```bash +SOURCE_FILES="$(jq -n --rawfile workflow workflow.ts '{"workflow.ts": $workflow}')" +``` + +Publish: + +```bash +zapier-sdk --experimental publish-workflow-version "$SOURCE_FILES" \ + --dependencies '' \ + --zapier_durable_version '' \ + --connections '' \ + --app_versions '' \ + --trigger '' \ + --json +``` + +Use the fetched workflow's enabled state when publishing. If the workflow was enabled before the edit, either omit `--enabled` or pass bare `--enabled` because publish defaults to enabled. If the workflow was disabled before the edit, add `--enabled false`; do not use `--enabled=false` or `--no-enabled`. Do not accidentally re-enable a disabled workflow. + +Omit `--connections`, `--app_versions`, or `--trigger` only when the fetched metadata confirms the workflow version does not use that field. If the fetched metadata includes trigger, connection, or app-version configuration but the shape cannot be mapped to the current publish flags, stop before publishing and tell the user the workflow needs SDK confirmation rather than silently dropping metadata. + +Do not use the old trigger republish flags (`--trigger-app`, `--trigger-action`, `--trigger-auth`, `--trigger-params`). The current trigger publish path is the single JSON `--trigger` object. + +## Step 6: Verify + +Read back the workflow and versions: + +```bash +zapier-sdk --experimental get-workflow --json +zapier-sdk --experimental list-workflow-versions --json +``` + +Confirm the newest version reflects the publish, the workflow is still enabled if it should be, and trigger/connection/app-version metadata was preserved. Check the matching entry in `triggers[]` for `details.webhook_url`, regardless of trigger type — if present, it's the catch URL external services call and is meant to be shared, unlike the workflow-level `trigger_url`; most triggers have none, and that is normal. If the change is hard to validate without a live trigger fire, tell the user exactly what test event to send and what result to expect. + +Finish by reporting: + +- Workflow name and ID. +- Whether the requested change was published. +- Whether trigger, connection, and app-version metadata were preserved. +- Whether the workflow is enabled. +- The trigger's `webhook_url`, if present. +- The Zapier editor link: `https://zapier.com/durables-editor/`. + +## Reverting + +Previous versions remain available. To revert, fetch the prior version's source and republish it with the same `publish-workflow-version` pattern above, preserving dependency, durable version, connection, app-version, trigger, and enabled-state metadata. diff --git a/biome.json b/biome.json index c702a0f..719ac8a 100644 --- a/biome.json +++ b/biome.json @@ -19,7 +19,12 @@ "!**/*.min.js", "!packages/web/components.json", "!**/package-lock.json", - "!**/.fallowrc.json" + "!**/.fallowrc.json", + "!**/*.db", + "!**/*.sqlite", + "!**/*.sqlite3", + "!**/.dolt", + "!**/.git" ] }, "formatter": { diff --git a/packages/agents/scripts/README.md b/packages/agents/scripts/README.md index 654411b..467f913 100644 --- a/packages/agents/scripts/README.md +++ b/packages/agents/scripts/README.md @@ -47,6 +47,23 @@ The Mastra Datasets pipeline that scores the agent against a labeled set (see th `show-logs.mjs` reads `DATABASE_URL` (falls back to the local Supabase default). +## Dev seed SQL + +Hand-run against the local Supabase Postgres to populate UI surfaces that need data +before they render anything worth looking at. Pipe them in with: + +```bash +docker exec -i supabase_db_foreman psql -U postgres -d postgres < scripts/.sql +``` + +| File | Purpose | +|---|---| +| `seed-automations-verify.sql` | One automation + finished/failed/started runs for verifying `/automations` polling and drill-down. Self-contained (creates its own workspace + user). | +| `seed-automations-verify-down.sql` | Teardown for the above | +| `seed-dashboard-snapshot.sql` | One `app_data_snapshot` so `/dashboards` renders; attaches to the oldest `user` row | + +These are dev-only fixtures — never run them against a shared or deployed database. + ## Zapier durable / trigger-inbox probes (standalone) Manual harness + probes for the Zapier experimental durable-workflow and diff --git a/packages/agents/scripts/seed-automations-verify-down.sql b/packages/agents/scripts/seed-automations-verify-down.sql new file mode 100644 index 0000000..73a7006 --- /dev/null +++ b/packages/agents/scripts/seed-automations-verify-down.sql @@ -0,0 +1,18 @@ +-- Teardown for seed-automations-verify.sql. Idempotent: safe to re-run. +-- Deletes in FK order (runs → automation → user → workspace). +-- +-- Does NOT touch the auth.users row — that was created via the GoTrue admin API, +-- not by the seed, so removing it here would be deleting something we didn't make. +BEGIN; + +DELETE FROM automation_run WHERE automation_id = 'auto-verify-1'; +DELETE FROM automation WHERE id = 'auto-verify-1'; +DELETE FROM "user" WHERE id = '6637a72b-ba57-4afb-b1fa-7e38e64430ea'; +DELETE FROM workspaces WHERE id = '11111111-2222-3333-4444-555555555555'; + +COMMIT; + +SELECT 'automation_run' t, count(*) n FROM automation_run WHERE automation_id = 'auto-verify-1' +UNION ALL SELECT 'automation', count(*) FROM automation WHERE id = 'auto-verify-1' +UNION ALL SELECT 'user', count(*) FROM "user" WHERE id = '6637a72b-ba57-4afb-b1fa-7e38e64430ea' +UNION ALL SELECT 'workspaces', count(*) FROM workspaces WHERE id = '11111111-2222-3333-4444-555555555555'; diff --git a/packages/agents/scripts/seed-automations-verify.sql b/packages/agents/scripts/seed-automations-verify.sql new file mode 100644 index 0000000..d4117e4 --- /dev/null +++ b/packages/agents/scripts/seed-automations-verify.sql @@ -0,0 +1,52 @@ +-- Seed for the j3um /automations live verify (polling + drill-down). +-- Auth user 6637a72b-... was created via the GoTrue admin API. +-- Idempotent: safe to re-run. Remove with scripts/seed-automations-verify-down.sql. +BEGIN; + +INSERT INTO workspaces (id, slug, name, membership_type, created_at) +VALUES ('11111111-2222-3333-4444-555555555555', 'verify-ws', 'Verify Workspace', 'solo', now()) +ON CONFLICT (id) DO NOTHING; + +INSERT INTO "user" (id, name, email, "emailVerified", "createdAt", "updatedAt", default_workspace_id) +VALUES ( + '6637a72b-ba57-4afb-b1fa-7e38e64430ea', 'Verify User', 'foreman-verify@local.test', + true, now(), now(), '11111111-2222-3333-4444-555555555555' +) +ON CONFLICT (id) DO UPDATE SET default_workspace_id = EXCLUDED.default_workspace_id; + +INSERT INTO automation ( + id, user_id, workspace_id, name, description, zapier_workflow_id, zapier_version_id, + source, connections, trigger, trigger_inbox_id, enabled, status, editor_url, trigger_url, + created_at, updated_at +) VALUES ( + 'auto-verify-1', '6637a72b-ba57-4afb-b1fa-7e38e64430ea', '11111111-2222-3333-4444-555555555555', + 'Verify: GitHub issue → Slack', 'Posts new GitHub issues to Slack (seeded for UI verify).', + 'wf_verify_123', 'ver_1', '// durable source (seed)', '{}'::jsonb, + '{"app":"github","action":"new_issue"}'::jsonb, null, true, 'active', + 'https://zapier.com/editor/wf_verify_123', null, + now() - interval '10 minutes', now() +) +ON CONFLICT (id) DO NOTHING; + +INSERT INTO automation_run ( + id, automation_id, workspace_id, inbox_message_id, trigger_id, durable_run_id, + workflow_version_id, status, input, output, error, created_at, updated_at +) VALUES + ('run-finished-1', 'auto-verify-1', '11111111-2222-3333-4444-555555555555', 'msg-1', 'trig-f1', 'dr-f1', null, + 'finished', '{"id":"msg-1"}'::jsonb, '{"posted":true,"channel":"#eng","ts":"1719300000.123"}'::jsonb, null, + now() - interval '8 minutes', now() - interval '8 minutes'), + ('run-failed-1', 'auto-verify-1', '11111111-2222-3333-4444-555555555555', 'msg-2', 'trig-x1', 'dr-x1', null, + 'failed', '{"id":"msg-2"}'::jsonb, null, + '{"code":"execution_failed","message":"Step \"post_slack\" exhausted all retry attempts.","details":{"name":"StepExhaustedError"}}'::jsonb, + now() - interval '5 minutes', now() - interval '5 minutes'), + ('run-started-1', 'auto-verify-1', '11111111-2222-3333-4444-555555555555', 'msg-3', 'trig-s1', 'dr-s1', null, + 'started', '{"id":"msg-3"}'::jsonb, null, null, + now() - interval '20 seconds', now() - interval '20 seconds') +ON CONFLICT (id) DO NOTHING; + +COMMIT; + +SELECT 'workspaces' t, count(*) n FROM workspaces +UNION ALL SELECT 'user', count(*) FROM "user" +UNION ALL SELECT 'automation', count(*) FROM automation +UNION ALL SELECT 'automation_run', count(*) FROM automation_run; diff --git a/packages/agents/scripts/seed-dashboard-snapshot.sql b/packages/agents/scripts/seed-dashboard-snapshot.sql new file mode 100644 index 0000000..148b9ac --- /dev/null +++ b/packages/agents/scripts/seed-dashboard-snapshot.sql @@ -0,0 +1,34 @@ +-- Dev-only: seed one app_data_snapshot for the local user so /dashboards renders. +-- Run: docker exec -i supabase_db_foreman psql -U postgres -d postgres < this file +-- Safe to re-run (append-only; each run adds a fresh snapshot row). +-- +-- Targets the OLDEST user row rather than a hard-coded UUID, so this works on any +-- dev machine. Seed a user first (e.g. seed-automations-verify.sql) if the table +-- is empty — the insert is a no-op when the subselect finds nothing. +INSERT INTO public.app_data_snapshot + (id, user_id, workspace_id, app_key, source_config, records, row_count, trigger_id, refreshed_at, created_at) +SELECT + gen_random_uuid()::text, + u.id, + NULL, + 'hubspot', + '{"app":"hubspot","action":"new_contact","inputs":{}}', + '[ + {"company":"Acme","stage":"lead","contact":"Ada Lovelace","deal_value":1200}, + {"company":"Globex","stage":"customer","contact":"Alan Turing","deal_value":8400}, + {"company":"Initech","stage":"opportunity","contact":"Grace Hopper","deal_value":5300}, + {"company":"Acme","stage":"customer","contact":"Linus Torvalds","deal_value":9100}, + {"company":"Umbrella","stage":"lead","contact":"Margaret Hamilton","deal_value":2750}, + {"company":"Globex","stage":"opportunity","contact":"Katherine Johnson","deal_value":6200}, + {"company":"Initech","stage":"customer","contact":"Dennis Ritchie","deal_value":4400}, + {"company":"Acme","stage":"opportunity","contact":"Barbara Liskov","deal_value":3100}, + {"company":"Umbrella","stage":"customer","contact":"Donald Knuth","deal_value":7700}, + {"company":"Globex","stage":"lead","contact":"Edsger Dijkstra","deal_value":1850} + ]', + 10, + NULL, + now(), + now() +FROM "user" u +ORDER BY u."createdAt" +LIMIT 1; diff --git a/skills-lock.json b/skills-lock.json index 1469ab6..14aacfb 100644 --- a/skills-lock.json +++ b/skills-lock.json @@ -12,6 +12,42 @@ "sourceType": "github", "skillPath": "skills/supabase-postgres-best-practices/SKILL.md", "computedHash": "3639bed1f40b3fbadae79fee631c42c89b2d1f5c30b05d5aa3cca06422a6bbbc" + }, + "workflows-create": { + "source": "zapier/agent-skills", + "sourceType": "github", + "skillPath": "skills/workflows/create/SKILL.md", + "computedHash": "559af2c3be4826cce84b1b0870ba6380968e31f7ad70ccbfc61336d08191961a" + }, + "workflows-doctor": { + "source": "zapier/agent-skills", + "sourceType": "github", + "skillPath": "skills/workflows/doctor/SKILL.md", + "computedHash": "e1d7b22ad8d4f61f2f2e215a1137cdf00216686540d85ed09124bbda153f50d4" + }, + "workflows-history": { + "source": "zapier/agent-skills", + "sourceType": "github", + "skillPath": "skills/workflows/history/SKILL.md", + "computedHash": "4ffdf306b2535063633287120f71855ac2be9d724c2378a6c0acdc70811a5057" + }, + "workflows-install": { + "source": "zapier/agent-skills", + "sourceType": "github", + "skillPath": "skills/workflows/install/SKILL.md", + "computedHash": "fed59f9a0623069a0b1b2b8007ca170e0fbf2eed35b7b343fecab9e9a4d2a61d" + }, + "workflows-list": { + "source": "zapier/agent-skills", + "sourceType": "github", + "skillPath": "skills/workflows/list/SKILL.md", + "computedHash": "623ba78117b9160554883c02372951aaf977cc81b0861d1606021726bb25b7e1" + }, + "workflows-modify": { + "source": "zapier/agent-skills", + "sourceType": "github", + "skillPath": "skills/workflows/modify/SKILL.md", + "computedHash": "e9651d2a7879696ae6762ab5b5b3abf16a1317fad48557d66a74281e9ebcd1a8" } } }