Compose-ready workspace: packages/zappier (rate card, portal, Stripe), packages/verae-zapier-middleware (timestamp + NATS), packages/verae-zapier (CLI app), vendor/zapier-platform, and research/zapier vendor corpus. Gate 0 structure checks pass. Product code and research are not yet wired.
1152 lines
54 KiB
Python
1152 lines
54 KiB
Python
#!/usr/bin/env python3
|
|
"""Document every Zapier CLI / core / SDK function in MCP-reference format.
|
|
|
|
Writes kind=cli_function, core_function, sdk_function (and upgrades the
|
|
legacy cli_command / sdk_command rows) plus FUNCTIONS-REFERENCE.md and a
|
|
guide:zapier-functions playbook.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import re
|
|
import sys
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(os.environ.get("ZAPIER_RESEARCH_ROOT") or Path(__file__).resolve().parents[1])
|
|
REPOS = ROOT / "repos"
|
|
NOW = datetime.now(timezone.utc).isoformat()
|
|
|
|
PLATFORM_CLI_MD = (
|
|
REPOS / "zapier-platform" / "packages" / "cli" / "docs" / "cli.md"
|
|
)
|
|
if not PLATFORM_CLI_MD.exists():
|
|
PLATFORM_CLI_MD = Path("/Users/marchon/research/zapier-platform/packages/cli/docs/cli.md")
|
|
|
|
SDK_CLI_MD = REPOS / "sdk" / "skills" / "zapier-sdk" / "references" / "cli-commands.md"
|
|
|
|
|
|
def kebab_to_camel(name: str) -> str:
|
|
parts = name.replace(":", "-").split("-")
|
|
if not parts:
|
|
return name
|
|
return parts[0] + "".join(p[:1].upper() + p[1:] if p else "" for p in parts[1:])
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Core runtime (z.*) — hand-authored, MCP-quality
|
|
# ---------------------------------------------------------------------------
|
|
|
|
CORE = [
|
|
{
|
|
"key": "z.request",
|
|
"category": "http",
|
|
"title": "Authenticated HTTP to the partner API",
|
|
"signature": "z.request(url, options?) → Promise<HttpResponse>\nz.request(optionsWithUrl) → Promise<HttpResponse>",
|
|
"high_level": "The only HTTP client you should use inside a perform. Adds auth middleware, logging, and status checks.",
|
|
"internals": (
|
|
"Runs the beforeRequest chain (basic/digest/oauth1 headers, query merge, prepare-request), "
|
|
"then Node fetch, then afterResponse (throw-for-status, stale-auth → RefreshAuthError, "
|
|
"throttling, log-response). `json` serializes the body; `form` sends urlencoded; `params` "
|
|
"are query string; `raw:true` returns a stream (RawHttpResponse). Non-2xx throws "
|
|
"ResponseError unless skipThrowForStatus. Same path Zapier uses in production."
|
|
),
|
|
"inputs": """```ts
|
|
type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS" | "HEAD";
|
|
type Input =
|
|
| [url: string, options?: HttpRequestOptions]
|
|
| [options: HttpRequestOptions & { url: string }];
|
|
interface HttpRequestOptions {
|
|
method?: HttpMethod;
|
|
headers?: Record<string, string>;
|
|
body?: string | Buffer | NodeJS.ReadableStream | Record<string, unknown>;
|
|
json?: unknown; // JSON body
|
|
form?: unknown; // application/x-www-form-urlencoded
|
|
params?: Record<string, unknown>;
|
|
raw?: boolean;
|
|
skipThrowForStatus?: boolean;
|
|
removeMissingValuesFrom?: { params?: boolean; body?: boolean };
|
|
skipEncodingChars?: string;
|
|
middlewareData?: Record<string, unknown>;
|
|
timeout?: number;
|
|
redirect?: "manual" | "error" | "follow";
|
|
}
|
|
```""",
|
|
"outputs": """```ts
|
|
interface HttpResponse<T = any> {
|
|
status: number;
|
|
headers: Headers;
|
|
content: string;
|
|
data: T; // parsed JSON when content-type is JSON
|
|
throwForStatus(): void;
|
|
getHeader(key: string): string | undefined;
|
|
request: HttpRequestOptions;
|
|
}
|
|
// raw:true → RawHttpResponse with .body stream, .buffer(), .json(), .text()
|
|
```""",
|
|
"related": ["BeforeRequestMiddleware", "AfterResponseMiddleware", "z.errors.RefreshAuthError", "z.errors.ResponseError", "z.console"],
|
|
"examples": [
|
|
{"title": "GET with auth middleware", "code": "const resp = await z.request('https://api.example.com/v1/contacts');\nreturn resp.data;"},
|
|
{"title": "POST JSON", "code": "const resp = await z.request({\n url: 'https://api.example.com/v1/contacts',\n method: 'POST',\n json: { name: bundle.inputData.name },\n});\nreturn resp.data;"},
|
|
],
|
|
"mcp_twin": "execute_zapier_read_action / execute_zapier_write_action (hosted)",
|
|
"sdk_twin": "zapier.fetch(url, { connection, method }) / zapier-sdk curl",
|
|
},
|
|
{
|
|
"key": "z.console",
|
|
"category": "debug",
|
|
"title": "Integration logger",
|
|
"signature": "z.console.log|info|warn|error(...args) → void",
|
|
"high_level": "Logs that show up in zapier-platform logs and Zap history. Prefer this over console.log.",
|
|
"internals": "Forwards to Zapier's log pipeline (type=console). Local `zapier-platform test` prints to stdout; production is queried via `zapier-platform logs --type console`.",
|
|
"inputs": "```ts\n(...args: unknown[]) => void\n```",
|
|
"outputs": "```ts\nvoid\n```",
|
|
"related": ["zapier-platform logs"],
|
|
"examples": [{"title": "Debug a payload", "code": "z.console.log('input', bundle.inputData);"}],
|
|
"mcp_twin": None,
|
|
"sdk_twin": None,
|
|
},
|
|
{
|
|
"key": "z.dehydrate",
|
|
"category": "hydration",
|
|
"title": "Lazy pointer to an expensive object",
|
|
"signature": "z.dehydrate(func, inputData?, cacheExpiration?) → string",
|
|
"high_level": "Returns a pointer string. Zapier later calls func(z, bundle) when a downstream step needs the data.",
|
|
"internals": "Serializes {method path, inputData} into a hydrate token. At hydrate time the runtime resolves the function from App.hydrators / the original module and invokes it. Use for large payloads you do not want in every trigger item.",
|
|
"inputs": "```ts\n(func: (z: ZObject, bundle: Bundle<T>) => any, inputData?: T, cacheExpiration?: number) => string\n```",
|
|
"outputs": "```ts\nstring // hydrate pointer, not the payload\n```",
|
|
"related": ["z.dehydrateFile", "z.stashFile", "HydratorsSchema"],
|
|
"examples": [{"title": "Defer a contact fetch", "code": "return {\n id: contact.id,\n name: contact.name,\n extra: z.dehydrate(getFullContact, { id: contact.id }),\n};"}],
|
|
"mcp_twin": None,
|
|
"sdk_twin": None,
|
|
},
|
|
{
|
|
"key": "z.dehydrateFile",
|
|
"category": "hydration",
|
|
"title": "Lazy pointer to a file",
|
|
"signature": "z.dehydrateFile(func, inputData?, cacheExpiration?) → string",
|
|
"high_level": "File-specific dehydrator. Zapier fetches bytes only when a later step needs the file.",
|
|
"internals": "Same pointer mechanism as z.dehydrate but marked as a file so the editor treats it as a file field. See example-apps/files.",
|
|
"inputs": "```ts\n(func: (z: ZObject, bundle: Bundle<T>) => any, inputData?: T, cacheExpiration?: number) => string\n```",
|
|
"outputs": "```ts\nstring\n```",
|
|
"related": ["z.dehydrate", "z.stashFile"],
|
|
"examples": [{"title": "Trigger item with a file", "code": "return { id: file.id, file: z.dehydrateFile(downloadFile, { id: file.id }) };"}],
|
|
"mcp_twin": None,
|
|
"sdk_twin": None,
|
|
},
|
|
{
|
|
"key": "z.stashFile",
|
|
"category": "files",
|
|
"title": "Upload bytes and get a public Zapier URL",
|
|
"signature": "z.stashFile(input, knownLength?, filename?, contentType?) → string",
|
|
"high_level": "Turns a Buffer, stream, URL string, or Promise<RawHttpResponse> into a short-lived public URL.",
|
|
"internals": "Uploads to Zapier's file stash. Used by hydrators and file creates. Length/filename/contentType help the stash; streams should pass knownLength when possible.",
|
|
"inputs": "```ts\n(input: string | Buffer | NodeJS.ReadableStream | Promise<RawHttpResponse> | Promise<string>,\n knownLength?: number, filename?: string, contentType?: string) => string\n```",
|
|
"outputs": "```ts\nstring // https://zapier-dev-files.s3… style URL\n```",
|
|
"related": ["z.dehydrateFile", "z.request"],
|
|
"examples": [{"title": "Stash a download", "code": "const raw = await z.request({ url: fileUrl, raw: true });\nreturn { file: z.stashFile(raw, undefined, 'report.pdf', 'application/pdf') };"}],
|
|
"mcp_twin": None,
|
|
"sdk_twin": None,
|
|
},
|
|
{
|
|
"key": "z.cursor.get",
|
|
"category": "polling",
|
|
"title": "Read the polling cursor",
|
|
"signature": "z.cursor.get() → Promise<string>",
|
|
"high_level": "Per-subscription opaque cursor so a poll can resume from last-seen id/timestamp.",
|
|
"internals": "Zapier stores one cursor string per user/zap/trigger. Empty string if never set. Pair with z.cursor.set at the end of perform.",
|
|
"inputs": "```ts\n() => Promise<string>\n```",
|
|
"outputs": "```ts\nPromise<string>\n```",
|
|
"related": ["z.cursor.set", "PollingTriggerPerform"],
|
|
"examples": [{"title": "Incremental poll", "code": "const since = await z.cursor.get();\nconst rows = await fetchSince(since);\nawait z.cursor.set(rows[0]?.updated_at || since);\nreturn rows;"}],
|
|
"mcp_twin": None,
|
|
"sdk_twin": None,
|
|
},
|
|
{
|
|
"key": "z.cursor.set",
|
|
"category": "polling",
|
|
"title": "Write the polling cursor",
|
|
"signature": "z.cursor.set(cursor) → Promise<null>",
|
|
"high_level": "Persist an opaque cursor for the next poll.",
|
|
"internals": "Replaces the stored cursor. Keep it small (id or ISO timestamp).",
|
|
"inputs": "```ts\n(cursor: string) => Promise<null>\n```",
|
|
"outputs": "```ts\nPromise<null>\n```",
|
|
"related": ["z.cursor.get"],
|
|
"examples": [{"title": "Save newest id", "code": "await z.cursor.set(String(newest.id));"}],
|
|
"mcp_twin": None,
|
|
"sdk_twin": None,
|
|
},
|
|
{
|
|
"key": "z.generateCallbackUrl",
|
|
"category": "callback",
|
|
"title": "Resume URL for long-running creates",
|
|
"signature": "z.generateCallbackUrl() → string",
|
|
"high_level": "Partner API can POST here to resume a create via performResume.",
|
|
"internals": "Mints a one-shot Zapier URL. Original perform returns quickly; later performResume gets bundle.cleanedRequest + bundle.outputData. See example-apps/callback.",
|
|
"inputs": "```ts\n() => string\n```",
|
|
"outputs": "```ts\nstring // https://hooks.zapier.com/… callback\n```",
|
|
"related": ["CreatePerformResume"],
|
|
"examples": [{"title": "Kick off async job", "code": "const hook = z.generateCallbackUrl();\nawait z.request({ url: '.../jobs', method: 'POST', json: { callback: hook } });\nreturn { pending: true };"}],
|
|
"mcp_twin": None,
|
|
"sdk_twin": None,
|
|
},
|
|
{
|
|
"key": "z.hash",
|
|
"category": "crypto",
|
|
"title": "Hash helper (Node crypto)",
|
|
"signature": "z.hash(algorithm, data, encoding?, input_encoding?) → string",
|
|
"high_level": "Convenience around crypto.createHash. Default hex / binary.",
|
|
"internals": "algorithm typically 'sha256'. Used for cache keys and HMAC-style signatures you compute yourself (OAuth1 signing is automatic).",
|
|
"inputs": "```ts\n(algorithm: string, data: string, encoding?: string, input_encoding?: string) => string\n```",
|
|
"outputs": "```ts\nstring\n```",
|
|
"related": [],
|
|
"examples": [{"title": "SHA-256 hex", "code": "const digest = z.hash('sha256', bundle.inputData.raw);"}],
|
|
"mcp_twin": None,
|
|
"sdk_twin": None,
|
|
},
|
|
{
|
|
"key": "z.JSON.parse",
|
|
"category": "json",
|
|
"title": "JSON.parse with a clean Zapier error",
|
|
"signature": "z.JSON.parse(text) → any",
|
|
"high_level": "Prefer over JSON.parse so users see a friendly error on bad JSON.",
|
|
"internals": "Wraps JSON.parse and rethrows as a user-visible AppError.",
|
|
"inputs": "```ts\n(text: string) => any\n```",
|
|
"outputs": "```ts\nany\n```",
|
|
"related": ["z.JSON.stringify", "z.errors.Error"],
|
|
"examples": [{"title": "Parse partner body", "code": "const data = z.JSON.parse(resp.content);"}],
|
|
"mcp_twin": None,
|
|
"sdk_twin": None,
|
|
},
|
|
{
|
|
"key": "z.JSON.stringify",
|
|
"category": "json",
|
|
"title": "JSON.stringify on z",
|
|
"signature": "z.JSON.stringify(value) → string",
|
|
"high_level": "Same as built-in JSON.stringify; kept for symmetry with z.JSON.parse.",
|
|
"internals": "Delegates to JSON.stringify.",
|
|
"inputs": "```ts\n(value: unknown) => string\n```",
|
|
"outputs": "```ts\nstring\n```",
|
|
"related": ["z.JSON.parse"],
|
|
"examples": [{"title": "Serialize", "code": "const body = z.JSON.stringify({ ok: true });"}],
|
|
"mcp_twin": None,
|
|
"sdk_twin": None,
|
|
},
|
|
{
|
|
"key": "z.cache.get",
|
|
"category": "cache",
|
|
"title": "Read per-auth cache",
|
|
"signature": "z.cache.get(key) → Promise<any>",
|
|
"high_level": "JSON-encodable values scoped to the connected account.",
|
|
"internals": "Zapier-hosted key/value. Use for account-id lookups you would otherwise repeat every poll.",
|
|
"inputs": "```ts\n(key: string) => Promise<any>\n```",
|
|
"outputs": "```ts\nPromise<any> // undefined/null if missing\n```",
|
|
"related": ["z.cache.set", "z.cache.delete"],
|
|
"examples": [{"title": "Memoize org id", "code": "let org = await z.cache.get('orgId');\nif (!org) { org = (await fetchOrg()).id; await z.cache.set('orgId', org, 3600); }"}],
|
|
"mcp_twin": None,
|
|
"sdk_twin": None,
|
|
},
|
|
{
|
|
"key": "z.cache.set",
|
|
"category": "cache",
|
|
"title": "Write per-auth cache",
|
|
"signature": "z.cache.set(key, value, ttl?, scope?, nx?) → Promise<boolean|null>",
|
|
"high_level": "Store JSON-encodable data. ttl in seconds. nx=true sets only if missing.",
|
|
"internals": "scope further namespaces the key. Returns whether the write landed.",
|
|
"inputs": "```ts\n(key: string, value: any, ttl?: number, scope?: string[], nx?: boolean) => Promise<boolean | null>\n```",
|
|
"outputs": "```ts\nPromise<boolean | null>\n```",
|
|
"related": ["z.cache.get", "z.cache.delete"],
|
|
"examples": [{"title": "TTL 1h", "code": "await z.cache.set('orgId', org.id, 3600);"}],
|
|
"mcp_twin": None,
|
|
"sdk_twin": None,
|
|
},
|
|
{
|
|
"key": "z.cache.delete",
|
|
"category": "cache",
|
|
"title": "Delete a cache key",
|
|
"signature": "z.cache.delete(key) → Promise<boolean>",
|
|
"high_level": "Drop stale metadata (e.g. after auth refresh).",
|
|
"internals": "Removes one key from the per-auth cache.",
|
|
"inputs": "```ts\n(key: string) => Promise<boolean>\n```",
|
|
"outputs": "```ts\nPromise<boolean>\n```",
|
|
"related": ["z.cache.get", "z.cache.set"],
|
|
"examples": [{"title": "Invalidate", "code": "await z.cache.delete('orgId');"}],
|
|
"mcp_twin": None,
|
|
"sdk_twin": None,
|
|
},
|
|
{
|
|
"key": "z.errors.Error",
|
|
"category": "errors",
|
|
"title": "User-visible app error",
|
|
"signature": "throw new z.errors.Error(message, code?, status?)",
|
|
"high_level": "Primary error to throw from perform. Message is shown to the user.",
|
|
"internals": "Serializes {message, code, status} as AppError. code/status help Zapier classify retries vs user-fixable failures.",
|
|
"inputs": "```ts\n(message: string, code?: string, status?: number)\n```",
|
|
"outputs": "```ts\nnever // throws\n```",
|
|
"related": ["z.errors.HaltedError", "z.errors.RefreshAuthError", "z.errors.ThrottledError"],
|
|
"examples": [{"title": "Validation", "code": "throw new z.errors.Error('Name is required', 'InvalidInput', 400);"}],
|
|
"mcp_twin": None,
|
|
"sdk_twin": None,
|
|
},
|
|
{
|
|
"key": "z.errors.HaltedError",
|
|
"category": "errors",
|
|
"title": "Stop this run without failing the Zap",
|
|
"signature": "throw new z.errors.HaltedError(message?)",
|
|
"high_level": "Filter-out. Use when the payload should be ignored.",
|
|
"internals": "Marks the run halted, not errored. Zaps stay on. Typical for 'this webhook is not the event we care about'.",
|
|
"inputs": "```ts\n(message?: string)\n```",
|
|
"outputs": "```ts\nnever\n```",
|
|
"related": ["z.errors.Error"],
|
|
"examples": [{"title": "Ignore other event types", "code": "if (bundle.cleanedRequest.type !== 'invoice.paid') {\n throw new z.errors.HaltedError('not an invoice.paid event');\n}"}],
|
|
"mcp_twin": None,
|
|
"sdk_twin": None,
|
|
},
|
|
{
|
|
"key": "z.errors.ExpiredAuthError",
|
|
"category": "errors",
|
|
"title": "Dead connection — user must reconnect",
|
|
"signature": "throw new z.errors.ExpiredAuthError(message?)",
|
|
"high_level": "Revoked tokens that cannot be refreshed.",
|
|
"internals": "Pauses the Zap and emails the user to reconnect. Do not use for a refreshable 401 — that is RefreshAuthError.",
|
|
"inputs": "```ts\n(message?: string)\n```",
|
|
"outputs": "```ts\nnever\n```",
|
|
"related": ["z.errors.RefreshAuthError", "manage_zapier_connections"],
|
|
"examples": [{"title": "Revoked", "code": "if (resp.status === 403 && resp.data.error === 'revoked') {\n throw new z.errors.ExpiredAuthError('Reconnect this account');\n}"}],
|
|
"mcp_twin": "manage_zapier_connections",
|
|
"sdk_twin": "create-connection / wait-for-new-connection",
|
|
},
|
|
{
|
|
"key": "z.errors.RefreshAuthError",
|
|
"category": "errors",
|
|
"title": "Refresh OAuth/session and retry",
|
|
"signature": "throw new z.errors.RefreshAuthError(message?)",
|
|
"high_level": "Typical 401 handler. Zapier calls refreshAccessToken / sessionConfig.perform then retries.",
|
|
"internals": "Thrown automatically by afterResponse when it sees stale auth; you can also throw it yourself. Only works for oauth2/session with refresh configured.",
|
|
"inputs": "```ts\n(message?: string)\n```",
|
|
"outputs": "```ts\nnever\n```",
|
|
"related": ["z.errors.ExpiredAuthError", "AfterResponseMiddleware", "AuthenticationOAuth2ConfigSchema"],
|
|
"examples": [{"title": "401", "code": "if (resp.status === 401) throw new z.errors.RefreshAuthError();"}],
|
|
"mcp_twin": None,
|
|
"sdk_twin": None,
|
|
},
|
|
{
|
|
"key": "z.errors.ThrottledError",
|
|
"category": "errors",
|
|
"title": "Rate limit — retry after delay",
|
|
"signature": "throw new z.errors.ThrottledError(message, delaySeconds?)",
|
|
"high_level": "Signals 429. Pass delay from Retry-After.",
|
|
"internals": "Zapier backs off for delaySeconds then retries the perform. Also thrown by afterResponse throw-for-throttling middleware.",
|
|
"inputs": "```ts\n(message: string, delaySeconds?: number)\n```",
|
|
"outputs": "```ts\nnever\n```",
|
|
"related": ["z.request"],
|
|
"examples": [{"title": "Honor Retry-After", "code": "throw new z.errors.ThrottledError('slow down', Number(resp.getHeader('retry-after') || 60));"}],
|
|
"mcp_twin": None,
|
|
"sdk_twin": "ZAPIER_ACTION_ERROR … N) Retry-After on run-action",
|
|
},
|
|
{
|
|
"key": "z.errors.ResponseError",
|
|
"category": "errors",
|
|
"title": "Wrap an HTTP response as an error",
|
|
"signature": "throw new z.errors.ResponseError(response)",
|
|
"high_level": "Usually auto-thrown by z.request on non-2xx.",
|
|
"internals": "JSON-stringifies status, content-type, retry-after, content, request URL.",
|
|
"inputs": "```ts\n(response: HttpResponse)\n```",
|
|
"outputs": "```ts\nnever\n```",
|
|
"related": ["z.request"],
|
|
"examples": [{"title": "Manual throw", "code": "if (resp.status >= 500) throw new z.errors.ResponseError(resp);"}],
|
|
"mcp_twin": None,
|
|
"sdk_twin": None,
|
|
},
|
|
{
|
|
"key": "z.errors.CheckError",
|
|
"category": "errors",
|
|
"title": "Platform check failed",
|
|
"signature": "throw new z.errors.CheckError(message?)",
|
|
"high_level": "Raised by runtime checks (trigger not array, create not object, missing id).",
|
|
"internals": "See packages/core/src/checks/*. You rarely throw this yourself; fix the return shape instead.",
|
|
"inputs": "```ts\n(message?: string)\n```",
|
|
"outputs": "```ts\nnever\n```",
|
|
"related": ["PollingTriggerPerform", "CreatePerform"],
|
|
"examples": [],
|
|
"mcp_twin": None,
|
|
"sdk_twin": None,
|
|
},
|
|
{
|
|
"key": "z.errors.StopRequestError",
|
|
"category": "errors",
|
|
"title": "Abort an outbound HTTP request from middleware",
|
|
"signature": "throw new z.errors.StopRequestError(message?)",
|
|
"high_level": "Throw from beforeRequest to cancel z.request without treating it as an app failure.",
|
|
"internals": "Short-circuits the HTTP stack. Used when middleware decides the call should not go out.",
|
|
"inputs": "```ts\n(message?: string)\n```",
|
|
"outputs": "```ts\nnever\n```",
|
|
"related": ["BeforeRequestMiddleware", "z.request"],
|
|
"examples": [{"title": "Skip empty search", "code": "if (!request.params.q) throw new z.errors.StopRequestError('no query');"}],
|
|
"mcp_twin": None,
|
|
"sdk_twin": None,
|
|
},
|
|
{
|
|
"key": "z.errors.DehydrateError",
|
|
"category": "errors",
|
|
"title": "Hydration pointer failed",
|
|
"signature": "throw new z.errors.DehydrateError(message?)",
|
|
"high_level": "Bad dehydrate token or hydrator threw.",
|
|
"internals": "Runtime-raised when a hydrate pointer cannot be resolved.",
|
|
"inputs": "```ts\n(message?: string)\n```",
|
|
"outputs": "```ts\nnever\n```",
|
|
"related": ["z.dehydrate"],
|
|
"examples": [],
|
|
"mcp_twin": None,
|
|
"sdk_twin": None,
|
|
},
|
|
{
|
|
"key": "createAppTester",
|
|
"category": "testing",
|
|
"title": "Unit-test a perform or request template",
|
|
"signature": "createAppTester(appRaw, options?) → (func|request, bundle?) => Promise",
|
|
"high_level": "From zapier-platform-core. Invokes a perform with a partial bundle.",
|
|
"internals": "Builds a local z + bundle, runs the same execute path as production (minus some hook/file limits). Used by `zapier-platform test`.",
|
|
"inputs": "```ts\n(appRaw: object, options?: { customStoreKey?: string }) => AppTester\n```",
|
|
"outputs": "```ts\n(func | Request, bundle?: DeepPartial<Bundle>) => Promise<T>\n```",
|
|
"related": ["zapier.tools.env.inject", "zapier-platform test", "zapier-platform invoke"],
|
|
"examples": [{"title": "Jest", "code": "const tester = createAppTester(App);\nconst rows = await tester(App.triggers.new_contact.operation.perform, {\n authData: { api_key: process.env.API_KEY },\n inputData: {},\n});"}],
|
|
"mcp_twin": None,
|
|
"sdk_twin": None,
|
|
},
|
|
{
|
|
"key": "zapier.tools.env.inject",
|
|
"category": "testing",
|
|
"title": "Load .env into process.env",
|
|
"signature": "zapier.tools.env.inject(filename?)",
|
|
"high_level": "Called automatically by the test runner. Useful in custom setup.",
|
|
"internals": "Parses KEY=VAL lines. Auth fields for invoke live as authData_* in .env.",
|
|
"inputs": "```ts\n(filename?: string) => void\n```",
|
|
"outputs": "```ts\nvoid\n```",
|
|
"related": ["createAppTester", "zapier-platform invoke"],
|
|
"examples": [{"title": "Manual", "code": "require('zapier-platform-core').tools.env.inject();"}],
|
|
"mcp_twin": None,
|
|
"sdk_twin": None,
|
|
},
|
|
{
|
|
"key": "BeforeRequestMiddleware",
|
|
"category": "middleware",
|
|
"title": "Mutate every outbound z.request",
|
|
"signature": "(request, z, bundle) => request | Promise<request>",
|
|
"high_level": "App.beforeRequest. Typical: attach Bearer from bundle.authData.",
|
|
"internals": "Runs in order before fetch. Must return the request object (with url).",
|
|
"inputs": "```ts\n(request: HttpRequestOptions & { url: string }, z: ZObject, bundle: Bundle) => request | Promise<request>\n```",
|
|
"outputs": "```ts\nHttpRequestOptions & { url: string }\n```",
|
|
"related": ["AfterResponseMiddleware", "z.request"],
|
|
"examples": [{"title": "Bearer", "code": "const addAuth = (req, z, bundle) => {\n req.headers = req.headers || {};\n req.headers.Authorization = `Bearer ${bundle.authData.access_token}`;\n return req;\n};\nmodule.exports = { beforeRequest: [addAuth] };"}],
|
|
"mcp_twin": None,
|
|
"sdk_twin": None,
|
|
},
|
|
{
|
|
"key": "AfterResponseMiddleware",
|
|
"category": "middleware",
|
|
"title": "Inspect every HTTP response",
|
|
"signature": "(response, z, bundle) => response | Promise<response>",
|
|
"high_level": "App.afterResponse. Throw RefreshAuthError on 401; remap error envelopes.",
|
|
"internals": "Runs after fetch, before the perform sees the response. Can mutate response.data.",
|
|
"inputs": "```ts\n(response: HttpResponse, z: ZObject, bundle: Bundle) => response | Promise<response>\n```",
|
|
"outputs": "```ts\nHttpResponse\n```",
|
|
"related": ["BeforeRequestMiddleware", "z.errors.RefreshAuthError"],
|
|
"examples": [{"title": "401 → refresh", "code": "const maybeRefresh = (resp, z) => {\n if (resp.status === 401) throw new z.errors.RefreshAuthError();\n return resp;\n};"}],
|
|
"mcp_twin": None,
|
|
"sdk_twin": None,
|
|
},
|
|
{
|
|
"key": "PollingTriggerPerform",
|
|
"category": "perform",
|
|
"title": "Polling trigger perform",
|
|
"signature": "(z, bundle) => object[] | Promise<object[]>",
|
|
"high_level": "Must return an array of objects with id (or primary output fields).",
|
|
"internals": "Zapier dedupes by id. Newest-first recommended. meta.isPopulatingDedupe / isLoadingSample / isFillingDynamicDropdown change how many you should fetch.",
|
|
"inputs": "```ts\n(z: ZObject, bundle: Bundle) => object[] | Promise<object[]>\n```",
|
|
"outputs": "```ts\nArray<{ id: string } & Record<string, unknown>>\n```",
|
|
"related": ["z.cursor.get", "z.request", "TriggerSchema"],
|
|
"examples": [{"title": "List contacts", "code": "const r = await z.request('https://api.example.com/contacts');\nreturn r.data.map((c) => ({ id: c.id, name: c.name }));"}],
|
|
"mcp_twin": None,
|
|
"sdk_twin": "list-triggers / create-trigger-inbox",
|
|
},
|
|
{
|
|
"key": "WebhookTriggerPerform",
|
|
"category": "perform",
|
|
"title": "REST Hook perform",
|
|
"signature": "(z, bundle) => object[] | Promise<object[]>",
|
|
"high_level": "Parse bundle.cleanedRequest into an array of objects.",
|
|
"internals": "Also implement performSubscribe / performUnsubscribe / performList. subscribe stores bundle.subscribeData; unsubscribe uses it. targetUrl is the Zapier hook URL to register with the partner.",
|
|
"inputs": "```ts\n(z: ZObject, bundle: Bundle) => object[] | Promise<object[]>\n```",
|
|
"outputs": "```ts\nobject[]\n```",
|
|
"related": ["WebhookTriggerPerformSubscribe", "BasicHookOperationSchema"],
|
|
"examples": [{"title": "Unwrap", "code": "const evt = bundle.cleanedRequest;\nreturn [{ id: evt.id, ...evt.data }];"}],
|
|
"mcp_twin": None,
|
|
"sdk_twin": "create-trigger-inbox (hosted subscription)",
|
|
},
|
|
{
|
|
"key": "CreatePerform",
|
|
"category": "perform",
|
|
"title": "Create action perform",
|
|
"signature": "(z, bundle) => object | Promise<object>",
|
|
"high_level": "Must return one object, not an array.",
|
|
"internals": "Returning a non-object fails 'non-object from create'. Use performResume + z.generateCallbackUrl for long jobs. performBuffer for bulk.",
|
|
"inputs": "```ts\n(z: ZObject, bundle: Bundle) => object | Promise<object>\n```",
|
|
"outputs": "```ts\nobject\n```",
|
|
"related": ["z.request", "z.generateCallbackUrl", "performBuffer"],
|
|
"examples": [{"title": "Create contact", "code": "const r = await z.request({ url: '.../contacts', method: 'POST', json: bundle.inputData });\nreturn r.data;"}],
|
|
"mcp_twin": "execute_zapier_write_action",
|
|
"sdk_twin": "run-action <app> write <key>",
|
|
},
|
|
{
|
|
"key": "SearchPerform",
|
|
"category": "perform",
|
|
"title": "Search action perform",
|
|
"signature": "(z, bundle) => object[] | Promise<object[]>",
|
|
"high_level": "Must return an array (possibly empty). Empty = not found (needed for search-or-create).",
|
|
"internals": "May return { results, paging_token } envelope. meta.paging_token continues a previous page.",
|
|
"inputs": "```ts\n(z: ZObject, bundle: Bundle) => object[] | Promise<object[]>\n```",
|
|
"outputs": "```ts\nobject[] | { results: object[]; paging_token?: string }\n```",
|
|
"related": ["CreatePerform", "SearchOrCreateSchema"],
|
|
"examples": [{"title": "Find by email", "code": "const r = await z.request({ url: '.../contacts', params: { email: bundle.inputData.email } });\nreturn r.data.items || [];"}],
|
|
"mcp_twin": "execute_zapier_read_action",
|
|
"sdk_twin": "run-action <app> search <key>",
|
|
},
|
|
{
|
|
"key": "performBuffer",
|
|
"category": "perform",
|
|
"title": "Buffered / bulk create",
|
|
"signature": "(z, BufferedBundle) => Promise<{[id]: {outputData?, error?}}>",
|
|
"high_level": "Process bundle.buffer items in one API call. Return a map keyed by each item's meta.id.",
|
|
"internals": "Zapier groups creates and sends them together. Each result must be success (outputData) or error string.",
|
|
"inputs": "```ts\n(z: ZObject, bundle: { authData; buffer: { inputData; meta: { id: string } }[]; groupedBy }) => Promise<PerformBufferResult>\n```",
|
|
"outputs": "```ts\nRecord<string, { outputData?: object; error?: string }>\n```",
|
|
"related": ["CreatePerform"],
|
|
"examples": [{"title": "Bulk insert", "code": "const created = await postAll(bundle.buffer.map((i) => i.inputData));\nreturn Object.fromEntries(bundle.buffer.map((i, n) => [i.meta.id, { outputData: created[n] }]));"}],
|
|
"mcp_twin": None,
|
|
"sdk_twin": None,
|
|
},
|
|
]
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# CLI / SDK parsers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
CLI_INTERNALS = {
|
|
"init": "Copies an official example-apps template (oauth2, session-auth, …) into PATH. Does not call Zapier until register/push.",
|
|
"scaffold": "AST-edits index.js/ts to register a new trigger/search/create/resource file generated from packages/cli/scaffold templates.",
|
|
"convert": "Downloads a Visual Builder definition (or --json) and emits CLI source. Existing files are not clobbered.",
|
|
"validate": "Runs zapier-platform-schema JSON Schema plus optional live style checks against Zapier's servers.",
|
|
"build": "Temp copy → zapierwrapper.js entry → esbuild dep detection → build/build.zip + source.zip.",
|
|
"upload": "POSTs build.zip + source.zip for the version in package.json. Versions must be sequential.",
|
|
"push": "build then upload. --snapshot makes 0.0.0-LABEL for dev.",
|
|
"invoke": "Local (default, .env), relay (-a auth id, traffic via Zapier), or remote (-r, production). Emulates (z, bundle).",
|
|
"test": "Wrapper around npm/yarn/pnpm test after validate + env inject.",
|
|
"register": "Creates the integration on developer.zapier.com and writes .zapierapprc.",
|
|
"link": "Writes .zapierapprc pointing at an existing integration id.",
|
|
"login": "Stores a deploy key in ~/.zapierrc (SSO via --sso).",
|
|
"promote": "Marks a pushed version as the public default. Does not migrate users.",
|
|
"migrate": "Moves users FROM→TO (optional percent). Non-breaking only. Track with jobs.",
|
|
"deprecate": "Schedules removal (DATE ≥ 3 weeks). Users emailed at T-14d.",
|
|
"canary:create": "Temporary traffic split FROM→TO for duration seconds. Reverts when expired.",
|
|
}
|
|
|
|
|
|
SDK_CATEGORY_INTERNALS = {
|
|
"Accounts": "Browser OAuth to Zapier itself (not a third-party app). Writes SDK credentials used by createZapierSdk().",
|
|
"Apps": "Directory of 9,000+ integrations. Same catalog as MCP discover_zapier_actions.",
|
|
"Connections": "OAuth grants Zapier holds per app+user. MCP twins: list/manage_zapier_connections.",
|
|
"Actions": "Discover and run partner actions. MCP twins: inspect + execute_zapier_*_action.",
|
|
"Triggers": "Trigger Inbox API — subscribe to partner events, lease/ack messages.",
|
|
"Tables": "Zapier Tables CRUD (not partner apps).",
|
|
"HTTP Requests": "Authenticated raw HTTP through a connection (SDK fetch / curl).",
|
|
"Code Workflows": "Experimental durable workflows on Zapier infrastructure.",
|
|
"Client Credentials": "Deploy SDK in CI without a browser login.",
|
|
"Utilities": "Project init, type generation, local MCP server for the SDK.",
|
|
}
|
|
|
|
|
|
def parse_platform_cli(text: str) -> list[dict]:
|
|
parts = re.split(r"\n## ", text)
|
|
rows = []
|
|
for part in parts[1:]:
|
|
lines = part.splitlines()
|
|
name = lines[0].strip()
|
|
block = "\n".join(lines[1:]).strip()
|
|
usage = ""
|
|
m = re.search(r"\*\*Usage\*\*:\s*`([^`]+)`", block)
|
|
if m:
|
|
usage = m.group(1)
|
|
summary = ""
|
|
qm = re.search(r"^>\s*(.+)$", block, re.M)
|
|
if qm:
|
|
summary = qm.group(1).strip()
|
|
aliases = []
|
|
am = re.search(r"\*\*Aliases\*\*\s*((?:\n\* .*)+)", block)
|
|
if am:
|
|
aliases = re.findall(r"`([^`]+)`", am.group(1))
|
|
flags = []
|
|
for fl in re.findall(r"^\* (.+)$", block, re.M):
|
|
if "`" in fl or fl.startswith("(required)") or "--" in fl:
|
|
flags.append(fl.strip())
|
|
examples = re.findall(r"^\* (`[^`]+`)", block, re.M)
|
|
examples = [e.strip("`") for e in examples]
|
|
args = []
|
|
in_args = False
|
|
for line in lines:
|
|
if line.startswith("**Arguments**"):
|
|
in_args = True
|
|
continue
|
|
if in_args:
|
|
if line.startswith("**"):
|
|
break
|
|
if line.startswith("* "):
|
|
args.append(line[2:].strip())
|
|
rows.append(
|
|
{
|
|
"name": name,
|
|
"usage": usage or f"zapier-platform {name}",
|
|
"summary": summary,
|
|
"block": block,
|
|
"flags": flags,
|
|
"args": args,
|
|
"examples": examples,
|
|
"aliases": aliases + [f"zapier {name}"],
|
|
}
|
|
)
|
|
return rows
|
|
|
|
|
|
def parse_sdk_cli(text: str) -> list[dict]:
|
|
rows = []
|
|
category = "Utilities"
|
|
# headings: ### `get-profile` or #### `get-profile`
|
|
chunks = re.split(r"\n#{3,4} `([^`]+)`", text)
|
|
# chunks[0] preamble, then name, body, name, body...
|
|
intro = chunks[0]
|
|
cats = re.findall(r"^### ([A-Za-z ]+)$", intro, re.M)
|
|
for i in range(1, len(chunks), 2):
|
|
name = chunks[i].strip()
|
|
body = chunks[i + 1] if i + 1 < len(chunks) else ""
|
|
# category: last ### Title before this that is NOT a command
|
|
# infer from inventory or from nearby ### without backticks
|
|
cat_m = re.search(r"experimental", body[:80], re.I)
|
|
usage = ""
|
|
um = re.search(r"Usage:\s*zapier-sdk ([^\n]+)", body)
|
|
if um:
|
|
usage = "zapier-sdk " + um.group(1).strip()
|
|
summary = ""
|
|
# first non-empty prose line after heading extras
|
|
for line in body.splitlines():
|
|
s = line.strip()
|
|
if not s or s.startswith("Usage:") or s.startswith("```") or s.startswith("_(requires"):
|
|
continue
|
|
if s.startswith("Arguments:") or s.startswith("Options:"):
|
|
break
|
|
if s.startswith("(") and "experimental" in s:
|
|
continue
|
|
summary = s
|
|
break
|
|
args = []
|
|
opts = []
|
|
section = None
|
|
for line in body.splitlines():
|
|
if line.startswith("Arguments:"):
|
|
section = "args"
|
|
continue
|
|
if line.startswith("Options:"):
|
|
section = "opts"
|
|
continue
|
|
if section and line.strip() and not line.startswith(" ") and not line.startswith("-") and line[0].isalpha() and ":" not in line[:20]:
|
|
# new section heading
|
|
if line.strip() in {"Arguments:", "Options:"}:
|
|
continue
|
|
if section == "args" and re.match(r"^\s{2}\S", line):
|
|
args.append(line.strip())
|
|
if section == "opts" and re.match(r"^\s{2}-", line):
|
|
opts.append(line.strip())
|
|
# category from earlier inventory headings in file
|
|
rows.append(
|
|
{
|
|
"name": name,
|
|
"usage": usage or f"zapier-sdk {name}",
|
|
"summary": summary,
|
|
"block": body.strip(),
|
|
"args": args,
|
|
"flags": opts,
|
|
"examples": re.findall(r"zapier-sdk [^\n`]+", body)[:6],
|
|
"experimental": "experimental" in body[:400].lower() or "requires `--experimental`" in body,
|
|
"ts": kebab_to_camel(name),
|
|
}
|
|
)
|
|
# assign categories from the inventory section
|
|
cat_map = {}
|
|
current = "Utilities"
|
|
for line in text.splitlines():
|
|
m = re.match(r"^### ([A-Za-z ]+)$", line)
|
|
if m and not line.startswith("### `"):
|
|
current = m.group(1).strip()
|
|
cm = re.match(r"^- `([a-z0-9.:-]+)", line)
|
|
if cm:
|
|
cat_map[cm.group(1)] = current
|
|
for r in rows:
|
|
r["category"] = cat_map.get(r["name"], "Utilities")
|
|
return rows
|
|
|
|
|
|
def render_body(kind_label: str, rec: dict, internals: str, related: list[str], twins: dict, typed: str, outputs: str, examples_md: str) -> str:
|
|
parts = [
|
|
f"# `{rec['name']}`",
|
|
"",
|
|
f"> {rec.get('summary') or rec['name']}",
|
|
"",
|
|
"## High-level description",
|
|
"",
|
|
rec.get("summary") or rec["name"],
|
|
"",
|
|
"## Internals",
|
|
"",
|
|
internals,
|
|
"",
|
|
"## Typed inputs",
|
|
"",
|
|
typed,
|
|
"",
|
|
"## Outputs",
|
|
"",
|
|
outputs,
|
|
"",
|
|
f"## Surface: {kind_label}",
|
|
"",
|
|
f"- Usage: `{rec.get('usage') or rec['name']}`",
|
|
]
|
|
if rec.get("aliases"):
|
|
parts.append("- Aliases: " + ", ".join(f"`{a}`" for a in rec["aliases"]))
|
|
if rec.get("ts"):
|
|
parts.append(f"- TypeScript: `zapier.{rec['ts']}(...)`")
|
|
if rec.get("experimental"):
|
|
parts.append("- Experimental: requires `--experimental`")
|
|
parts += ["", "## Related functions", ""]
|
|
for rel in related:
|
|
parts.append(f"- `{rel}`")
|
|
if not related:
|
|
parts.append("- _(see same category)_")
|
|
parts += ["", "## Twins (MCP / other CLI)", ""]
|
|
for k, v in twins.items():
|
|
if v:
|
|
parts.append(f"- **{k}:** `{v}`")
|
|
if rec.get("block"):
|
|
parts += ["", "## Official text", "", rec["block"][:8000]]
|
|
if examples_md:
|
|
parts += ["", "## Examples", "", examples_md]
|
|
return "\n".join(parts)
|
|
|
|
|
|
def typed_from_cli(rec: dict) -> str:
|
|
lines = ["```ts", "type Input = {"]
|
|
for a in rec.get("args") or []:
|
|
name = a.split("|")[0].strip().split()[0].strip("`()")
|
|
req = "(required)" in a or a.lower().startswith("(required)")
|
|
desc = a.split("|", 1)[-1].strip() if "|" in a else a
|
|
lines.append(f" {name}{'?' if not req else ''}: string; // {desc[:140]}")
|
|
for fl in rec.get("flags") or []:
|
|
names = re.findall(r"`([^`]+)`", fl)
|
|
flag = names[0] if names else fl.split()[0]
|
|
flag = flag.lstrip("-")
|
|
if not flag or flag in {"d", "h", "help"}:
|
|
# still include long flags
|
|
longf = next((n.lstrip("-") for n in names if n.startswith("--")), flag)
|
|
flag = longf or flag
|
|
lines.append(f" {flag.replace('-', '_') }?: string | boolean; // {fl[:140]}")
|
|
if len(lines) == 2:
|
|
lines.append(" // see official flags")
|
|
lines += ["};", "```"]
|
|
return "\n".join(lines)
|
|
|
|
|
|
def examples_md(examples: list[str]) -> str:
|
|
if not examples:
|
|
return ""
|
|
return "\n".join(f"```bash\n{e}\n```" for e in examples[:8])
|
|
|
|
|
|
def core_row(item: dict) -> dict:
|
|
examples_md_s = "\n\n".join(
|
|
f"### {e['title']}\n\n```ts\n{e['code']}\n```" for e in item.get("examples") or []
|
|
)
|
|
body = "\n".join(
|
|
[
|
|
f"# `{item['key']}`",
|
|
"",
|
|
f"> {item['high_level']}",
|
|
"",
|
|
"## High-level description",
|
|
"",
|
|
item["high_level"],
|
|
"",
|
|
"## Internals",
|
|
"",
|
|
item["internals"],
|
|
"",
|
|
"## Typed inputs",
|
|
"",
|
|
item["inputs"],
|
|
"",
|
|
"## Outputs",
|
|
"",
|
|
item["outputs"],
|
|
"",
|
|
f"- Signature: `{item['signature']}`",
|
|
f"- Category: `{item['category']}`",
|
|
"",
|
|
"## Related functions",
|
|
"",
|
|
*([f"- `{r}`" for r in item.get("related") or []] or ["- —"]),
|
|
"",
|
|
"## Twins (MCP / SDK)",
|
|
"",
|
|
f"- MCP: `{item.get('mcp_twin') or '—'}`",
|
|
f"- SDK: `{item.get('sdk_twin') or '—'}`",
|
|
"",
|
|
"## Examples",
|
|
"",
|
|
examples_md_s or "_see signature_",
|
|
]
|
|
)
|
|
return {
|
|
"_id": f"core_function:{item['key'].lower()}",
|
|
"kind": "core_function",
|
|
"key": item["key"],
|
|
"title": f"{item['key']} — {item['title']}",
|
|
"summary": item["high_level"],
|
|
"body": body,
|
|
"usage": item["signature"].split("\n")[0],
|
|
"signature": item["signature"].split("\n")[0],
|
|
"aliases": [],
|
|
"flags": [],
|
|
"args": [],
|
|
"examples": item.get("examples") or [],
|
|
"source_url": "https://docs.zapier.com/integrations/build-cli/core",
|
|
"source_repo": "zapier/zapier-platform",
|
|
"source_path": "packages/core/types/custom.d.ts",
|
|
"section": "platform-core",
|
|
"tags": ["core", "function", item["category"], "zapier-platform-core"],
|
|
"related": item.get("related") or [],
|
|
"meta": {
|
|
"surface": "platform_core",
|
|
"category": item["category"],
|
|
"mcp_twin": item.get("mcp_twin"),
|
|
"sdk_twin": item.get("sdk_twin"),
|
|
"internals": item["internals"],
|
|
},
|
|
"ingested_at": NOW,
|
|
}
|
|
|
|
|
|
def main() -> int:
|
|
rows: list[dict] = []
|
|
|
|
# core
|
|
for item in CORE:
|
|
rows.append(core_row(item))
|
|
|
|
# platform CLI
|
|
if PLATFORM_CLI_MD.exists():
|
|
cli = parse_platform_cli(PLATFORM_CLI_MD.read_text(encoding="utf-8", errors="replace"))
|
|
else:
|
|
print("missing platform cli.md", file=sys.stderr)
|
|
cli = []
|
|
for rec in cli:
|
|
internals = CLI_INTERNALS.get(
|
|
rec["name"],
|
|
"Platform CLI talks to developer.zapier.com with the deploy key in ~/.zapierrc. "
|
|
"Implementation: zapier-platform/packages/cli/src/oclif/commands/.",
|
|
)
|
|
related = []
|
|
if rec["name"] in ("push", "upload", "build"):
|
|
related = ["build", "upload", "push", "validate"]
|
|
elif rec["name"] in ("promote", "migrate", "deprecate", "canary:create", "jobs"):
|
|
related = ["promote", "migrate", "deprecate", "jobs", "versions"]
|
|
elif rec["name"].startswith("invoke"):
|
|
related = ["invoke", "test", "createAppTester"]
|
|
elif rec["name"] in ("init", "scaffold", "convert"):
|
|
related = ["init", "scaffold", "convert", "register"]
|
|
twins = {
|
|
"deprecated alias": f"zapier {rec['name']}",
|
|
"MCP": "n/a (this builds integrations, MCP consumes them)",
|
|
}
|
|
body = render_body(
|
|
"zapier-platform CLI (build integrations)",
|
|
rec,
|
|
internals,
|
|
related,
|
|
twins,
|
|
typed_from_cli(rec),
|
|
"```ts\n// stdout (human table by default). --format json|raw|table|row|plain\n```",
|
|
examples_md(rec.get("examples") or []),
|
|
)
|
|
doc = {
|
|
"_id": f"cli_function:{rec['name']}",
|
|
"kind": "cli_function",
|
|
"key": rec["name"],
|
|
"title": f"zapier-platform {rec['name']}",
|
|
"summary": rec.get("summary") or rec["name"],
|
|
"body": body,
|
|
"usage": rec.get("usage"),
|
|
"signature": rec.get("usage"),
|
|
"aliases": rec.get("aliases") or [],
|
|
"flags": rec.get("flags") or [],
|
|
"args": rec.get("args") or [],
|
|
"examples": rec.get("examples") or [],
|
|
"source_url": "https://github.com/zapier/zapier-platform/blob/main/packages/cli/docs/cli.md",
|
|
"source_repo": "zapier/zapier-platform",
|
|
"source_path": "packages/cli/docs/cli.md",
|
|
"section": "platform-cli",
|
|
"tags": ["cli", "function", "zapier-platform-cli", rec["name"].split(":")[0]],
|
|
"related": related,
|
|
"meta": {"surface": "platform_cli", "internals": internals},
|
|
"ingested_at": NOW,
|
|
}
|
|
rows.append(doc)
|
|
# keep legacy kind in sync
|
|
legacy = dict(doc)
|
|
legacy["_id"] = f"cli_command:{rec['name']}"
|
|
legacy["kind"] = "cli_command"
|
|
rows.append(legacy)
|
|
|
|
# SDK CLI + TS methods
|
|
if SDK_CLI_MD.exists():
|
|
sdk = parse_sdk_cli(SDK_CLI_MD.read_text(encoding="utf-8", errors="replace"))
|
|
else:
|
|
print("missing sdk cli-commands.md", file=sys.stderr)
|
|
sdk = []
|
|
|
|
MCP_TWINS = {
|
|
"list-apps": "discover_zapier_actions",
|
|
"get-app": "discover_zapier_actions",
|
|
"list-actions": "inspect_zapier_actions / discover_zapier_actions",
|
|
"get-action": "inspect_zapier_actions",
|
|
"list-action-input-fields": "inspect_zapier_actions",
|
|
"get-action-input-fields-schema": "inspect_zapier_actions",
|
|
"list-action-input-field-choices": "inspect_zapier_actions (enum_property)",
|
|
"run-action": "execute_zapier_read_action / execute_zapier_write_action",
|
|
"list-connections": "list_zapier_connections",
|
|
"find-first-connection": "list_zapier_connections",
|
|
"find-unique-connection": "list_zapier_connections",
|
|
"create-connection": "manage_zapier_connections",
|
|
"get-connection-start-url": "manage_zapier_connections",
|
|
"wait-for-new-connection": "manage_zapier_connections (after auth_url)",
|
|
"curl": "write_code_action / z.request analog",
|
|
"mcp": "hosted Zapier MCP (different server)",
|
|
"feedback": "send_feedback",
|
|
}
|
|
|
|
for rec in sdk:
|
|
cat = rec.get("category") or "Utilities"
|
|
internals = SDK_CATEGORY_INTERNALS.get(cat, "Zapier SDK talks to api.zapier.com with SDK credentials.")
|
|
internals += f" TypeScript method: `zapier.{rec['ts']}`. CLI: `{rec['usage']}`."
|
|
related = [x["name"] for x in sdk if x.get("category") == cat and x["name"] != rec["name"]][:8]
|
|
twins = {
|
|
"TypeScript": f"zapier.{rec['ts']}()",
|
|
"MCP": MCP_TWINS.get(rec["name"], "—"),
|
|
"Platform CLI": "n/a (SDK consumes apps; Platform CLI publishes them)",
|
|
}
|
|
typed = typed_from_cli(rec)
|
|
# better TS blurb
|
|
typed = f"```ts\n// CLI: {rec['usage']}\n// TS: const {{ data }} = await zapier.{rec['ts']}({{ ... }})\n```\n" + typed
|
|
body = render_body(
|
|
f"Zapier SDK CLI / TypeScript ({cat})",
|
|
rec,
|
|
internals,
|
|
related,
|
|
twins,
|
|
typed,
|
|
"```ts\n{ data: T } | paginated { data: T[]; next_cursor?: string }\n// --json for raw. Always prefer live discovery over hardcoded keys.\n```",
|
|
examples_md(rec.get("examples") or [rec["usage"]]),
|
|
)
|
|
doc = {
|
|
"_id": f"sdk_function:{rec['name']}",
|
|
"kind": "sdk_function",
|
|
"key": rec["name"],
|
|
"title": f"zapier-sdk {rec['name']} / zapier.{rec['ts']}",
|
|
"summary": rec.get("summary") or rec["name"],
|
|
"body": body,
|
|
"usage": rec.get("usage"),
|
|
"signature": f"zapier.{rec['ts']}()",
|
|
"aliases": [rec["ts"], f"zapier.{rec['ts']}"],
|
|
"flags": rec.get("flags") or [],
|
|
"args": rec.get("args") or [],
|
|
"examples": rec.get("examples") or [],
|
|
"source_url": "https://docs.zapier.com/sdk/cli-reference",
|
|
"source_repo": "zapier/sdk",
|
|
"source_path": "skills/zapier-sdk/references/cli-commands.md",
|
|
"section": "sdk",
|
|
"tags": ["sdk", "function", "zapier-sdk", cat.lower().replace(" ", "-")],
|
|
"related": related,
|
|
"meta": {
|
|
"surface": "sdk",
|
|
"category": cat,
|
|
"typescript": rec["ts"],
|
|
"experimental": rec.get("experimental"),
|
|
"mcp_twin": MCP_TWINS.get(rec["name"]),
|
|
"internals": internals,
|
|
},
|
|
"ingested_at": NOW,
|
|
}
|
|
rows.append(doc)
|
|
|
|
# also write camelCase lookup aliases as thin pointers? skip — aliases field is enough
|
|
|
|
guide_body = f"""# Zapier functions — complete reference (CLI, core, SDK)
|
|
|
|
**Start here first:**
|
|
`db.platform_reference.findOne({{ kind: "guide", key: "build-new-connector" }})`
|
|
or [PLATFORM-REFERENCE.md](PLATFORM-REFERENCE.md).
|
|
|
|
This guide is the **function catalog**. MCP tools are documented separately as
|
|
`kind: "mcp_function"` and [MCP-REFERENCE.md](MCP-REFERENCE.md).
|
|
|
|
## Surfaces (do not mix)
|
|
|
|
| Surface | kind | Count | Package |
|
|
|---------|------|------:|---------|
|
|
| Hosted MCP meta-tools | `mcp_function` | 17 | mcp.zapier.com |
|
|
| Platform CLI (publish an integration) | `cli_function` | {sum(1 for r in rows if r['kind']=='cli_function')} | `zapier-platform-cli` |
|
|
| Platform core (`z`, perform, middleware) | `core_function` | {sum(1 for r in rows if r['kind']=='core_function')} | `zapier-platform-core` |
|
|
| SDK CLI + TypeScript (consume apps) | `sdk_function` | {sum(1 for r in rows if r['kind']=='sdk_function')} | `@zapier/zapier-sdk` / `@zapier/zapier-sdk-cli` |
|
|
|
|
```js
|
|
db.platform_reference.find({{ kind: "cli_function" }}).sort({{ key: 1 }})
|
|
db.platform_reference.find({{ kind: "core_function" }}).sort({{ key: 1 }})
|
|
db.platform_reference.find({{ kind: "sdk_function" }}).sort({{ key: 1 }})
|
|
db.platform_reference.findOne({{ kind: "sdk_function", key: "run-action" }})
|
|
db.platform_reference.findOne({{ kind: "core_function", key: "z.request" }})
|
|
```
|
|
|
|
Human index: [FUNCTIONS-REFERENCE.md](FUNCTIONS-REFERENCE.md).
|
|
|
|
## Build-an-integration call graph
|
|
|
|
```
|
|
zapier-platform login
|
|
→ init --template oauth2
|
|
→ scaffold trigger|create|search
|
|
→ implement (z, bundle) with z.request / z.errors.*
|
|
→ invoke auth start|test / test / validate
|
|
→ register → push → promote → migrate
|
|
```
|
|
|
|
## Consume-an-app call graph (SDK)
|
|
|
|
```
|
|
zapier-sdk login
|
|
→ list-apps → list-actions → list-action-input-fields
|
|
→ find-first-connection | create-connection
|
|
→ run-action
|
|
(events) list-triggers → create-trigger-inbox → lease/ack
|
|
(raw HTTP) curl --connection <id>
|
|
```
|
|
|
|
Same methods exist on `createZapierSdk()` as camelCase (`runAction`, `listApps`, …).
|
|
"""
|
|
rows.append(
|
|
{
|
|
"_id": "guide:zapier-functions",
|
|
"kind": "guide",
|
|
"key": "zapier-functions",
|
|
"title": "Zapier functions — CLI, core, SDK catalog",
|
|
"summary": "Complete function reference. Start from build-new-connector, then query cli_function / core_function / sdk_function.",
|
|
"body": guide_body,
|
|
"usage": 'db.platform_reference.findOne({kind:"guide", key:"zapier-functions"})',
|
|
"signature": "",
|
|
"aliases": ["functions playbook"],
|
|
"flags": [],
|
|
"args": [],
|
|
"examples": [],
|
|
"source_url": "",
|
|
"source_repo": "",
|
|
"source_path": "scripts/ingest-function-reference.py",
|
|
"section": "guide",
|
|
"tags": ["guide", "functions", "cli", "sdk", "core"],
|
|
"related": [],
|
|
"meta": {
|
|
"cli": sum(1 for r in rows if r["kind"] == "cli_function"),
|
|
"core": sum(1 for r in rows if r["kind"] == "core_function"),
|
|
"sdk": sum(1 for r in rows if r["kind"] == "sdk_function"),
|
|
},
|
|
"ingested_at": NOW,
|
|
}
|
|
)
|
|
|
|
# markdown index
|
|
md = [
|
|
"# Zapier function reference (CLI, core, SDK)",
|
|
"",
|
|
"**Grok start:** `db.platform_reference.findOne({ kind: \"guide\", key: \"build-new-connector\" })` ",
|
|
"MCP tools: [MCP-REFERENCE.md](MCP-REFERENCE.md). This file is everything else.",
|
|
"",
|
|
"Each function has a Mongo document with the same sections as MCP: high-level, internals, typed inputs, outputs, related, twins, examples.",
|
|
"",
|
|
"## Core (`kind: \"core_function\"`)",
|
|
"",
|
|
"| Function | Signature | Category |",
|
|
"|----------|-----------|----------|",
|
|
]
|
|
for r in sorted((x for x in rows if x["kind"] == "core_function"), key=lambda x: x["key"]):
|
|
md.append(f"| `{r['key']}` | `{r['signature']}` | {r['meta'].get('category','')} |")
|
|
md += ["", "## Platform CLI (`kind: \"cli_function\"`)", "", "| Command | Usage |", "|---------|-------|"]
|
|
for r in sorted((x for x in rows if x["kind"] == "cli_function"), key=lambda x: x["key"]):
|
|
md.append(f"| `{r['key']}` | `{r['usage']}` |")
|
|
md += ["", "## SDK (`kind: \"sdk_function\"`)", "", "| CLI | TypeScript | Category | MCP twin |", "|-----|------------|----------|----------|"]
|
|
for r in sorted((x for x in rows if x["kind"] == "sdk_function"), key=lambda x: (x["meta"].get("category",""), x["key"])):
|
|
md.append(
|
|
f"| `{r['key']}` | `zapier.{r['meta'].get('typescript')}` | {r['meta'].get('category')} | {r['meta'].get('mcp_twin') or '—'} |"
|
|
)
|
|
md += [
|
|
"",
|
|
"## Query",
|
|
"",
|
|
"```js",
|
|
'db.platform_reference.findOne({ kind: "guide", key: "zapier-functions" })',
|
|
'db.platform_reference.find({ kind: "core_function", key: "z.request" })',
|
|
'db.platform_reference.find({ kind: "sdk_function", "meta.category": "Actions" })',
|
|
"```",
|
|
"",
|
|
]
|
|
md_path = ROOT / "FUNCTIONS-REFERENCE.md"
|
|
md_path.write_text("\n".join(md) + "\n")
|
|
print(f"wrote {md_path}")
|
|
|
|
out = ROOT / "raw" / "function-reference.jsonl"
|
|
out.parent.mkdir(parents=True, exist_ok=True)
|
|
with out.open("w") as f:
|
|
for r in rows:
|
|
f.write(json.dumps(r, ensure_ascii=False) + "\n")
|
|
counts: dict[str, int] = {}
|
|
for r in rows:
|
|
counts[r["kind"]] = counts.get(r["kind"], 0) + 1
|
|
print("wrote", len(rows), "→", out, counts)
|
|
|
|
uri = os.environ.get("ZAPIER_MONGO_URI") or os.environ.get("MDB_MCP_CONNECTION_STRING")
|
|
if not uri:
|
|
print("ZAPIER_MONGO_URI unset; skipped mongo")
|
|
return 0
|
|
from pymongo import MongoClient, ReplaceOne
|
|
|
|
col = MongoClient(uri).get_database("zapier")["platform_reference"]
|
|
BATCH = 200
|
|
ops = [ReplaceOne({"_id": r["_id"]}, r, upsert=True) for r in rows]
|
|
for i in range(0, len(ops), BATCH):
|
|
col.bulk_write(ops[i : i + BATCH], ordered=False)
|
|
col.create_index([("kind", 1), ("key", 1)])
|
|
print(
|
|
"mongo",
|
|
{k: col.count_documents({"kind": k}) for k in ("cli_function", "core_function", "sdk_function", "mcp_function", "guide")},
|
|
)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|