@zapier/zapier-sdk-cli 0.44.0 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +392 -40
  3. package/bin/zapier-sdk-experimental.mjs +14 -0
  4. package/dist/cli.cjs +585 -37
  5. package/dist/cli.mjs +584 -36
  6. package/dist/experimental.cjs +3519 -0
  7. package/dist/experimental.d.mts +39 -0
  8. package/dist/experimental.d.ts +39 -0
  9. package/dist/experimental.mjs +3483 -0
  10. package/dist/index.cjs +507 -26
  11. package/dist/index.d.mts +3 -514
  12. package/dist/index.d.ts +3 -514
  13. package/dist/index.mjs +505 -24
  14. package/dist/package.json +14 -2
  15. package/dist/sdk-B3nKAZdN.d.mts +516 -0
  16. package/dist/sdk-B3nKAZdN.d.ts +516 -0
  17. package/dist/src/cli.js +26 -2
  18. package/dist/src/experimental.d.ts +33 -0
  19. package/dist/src/experimental.js +83 -0
  20. package/dist/src/generators/ast-generator.d.ts +2 -2
  21. package/dist/src/generators/ast-generator.js +1 -1
  22. package/dist/src/plugins/add/index.d.ts +2 -2
  23. package/dist/src/plugins/bundleCode/index.js +3 -12
  24. package/dist/src/plugins/curl/index.js +2 -2
  25. package/dist/src/plugins/curl/utils.d.ts +11 -1
  26. package/dist/src/plugins/curl/utils.js +14 -5
  27. package/dist/src/plugins/drainTriggerInbox/index.d.ts +46 -0
  28. package/dist/src/plugins/drainTriggerInbox/index.js +178 -0
  29. package/dist/src/plugins/generateAppTypes/index.d.ts +2 -2
  30. package/dist/src/plugins/index.d.ts +2 -0
  31. package/dist/src/plugins/index.js +2 -0
  32. package/dist/src/plugins/mcp/index.d.ts +1 -0
  33. package/dist/src/plugins/mcp/index.js +5 -1
  34. package/dist/src/plugins/watchTriggerInbox/index.d.ts +45 -0
  35. package/dist/src/plugins/watchTriggerInbox/index.js +157 -0
  36. package/dist/src/sdk.js +5 -1
  37. package/dist/src/utils/cli-generator.js +18 -1
  38. package/dist/src/utils/cli-renderer.d.ts +12 -0
  39. package/dist/src/utils/cli-renderer.js +22 -1
  40. package/dist/src/utils/parameter-resolver.d.ts +1 -0
  41. package/dist/src/utils/parameter-resolver.js +55 -9
  42. package/dist/src/utils/triggerDrain.d.ts +144 -0
  43. package/dist/src/utils/triggerDrain.js +351 -0
  44. package/dist/tsconfig.tsbuildinfo +1 -1
  45. package/package.json +16 -4
@@ -27,7 +27,7 @@ export class AstTypeGenerator {
27
27
  // Fetch all input fields with concurrency limiting for better reliability
28
28
  // Using batch() instead of Promise.allSettled() prevents overwhelming the API
29
29
  // and triggering rate limits when apps have many actions
30
- const inputFieldsTasks = actions.map((action) => () => sdk.listInputFields({
30
+ const inputFieldsTasks = actions.map((action) => () => sdk.listActionInputFields({
31
31
  appKey: app.implementation_id,
32
32
  actionKey: action.key,
33
33
  actionType: action.action_type,
@@ -105,7 +105,7 @@ export declare const addPlugin: (sdk: {
105
105
  };
106
106
  };
107
107
  } & {
108
- listInputFields: (options?: (({
108
+ listActionInputFields: (options?: (({
109
109
  app: string;
110
110
  actionType: "filter" | "read" | "read_bulk" | "run" | "search" | "search_and_write" | "search_or_write" | "write";
111
111
  action: string;
@@ -155,7 +155,7 @@ export declare const addPlugin: (sdk: {
155
155
  } & {
156
156
  context: {
157
157
  meta: {
158
- listInputFields: import("@zapier/zapier-sdk").PluginMeta;
158
+ listActionInputFields: import("@zapier/zapier-sdk").PluginMeta;
159
159
  };
160
160
  };
161
161
  } & {
@@ -1,5 +1,5 @@
1
1
  import { BundleCodeSchema } from "./schemas";
2
- import { createPluginMethod, definePlugin, } from "@zapier/zapier-sdk";
2
+ import { createPluginMethod, definePlugin, ZapierBundleError, } from "@zapier/zapier-sdk";
3
3
  import { buildSync } from "esbuild";
4
4
  import * as fs from "fs";
5
5
  import * as path from "path";
@@ -9,15 +9,6 @@ export const bundleCodePlugin = definePlugin((sdk) => createPluginMethod(sdk, {
9
9
  inputSchema: BundleCodeSchema,
10
10
  handler: async ({ options }) => bundleCode(options),
11
11
  }));
12
- class ZapierBundleError extends Error {
13
- constructor(message, details, originalError) {
14
- super(message);
15
- this.code = "ZAPIER_BUNDLE_ERROR";
16
- this.name = "ZapierBundleError";
17
- this.details = details;
18
- this.originalError = originalError;
19
- }
20
- }
21
12
  /**
22
13
  * Bundle TypeScript code into executable JavaScript (CLI version)
23
14
  */
@@ -42,7 +33,7 @@ async function bundleCode(options) {
42
33
  });
43
34
  if (result.errors.length > 0) {
44
35
  const errorMessages = result.errors.map((e) => e.text);
45
- throw new ZapierBundleError(`Bundle failed: ${errorMessages.join(", ")}`, errorMessages);
36
+ throw new ZapierBundleError(`Bundle failed: ${errorMessages.join(", ")}`, { buildErrors: errorMessages });
46
37
  }
47
38
  const bundledCode = result.outputFiles?.[0]?.text;
48
39
  if (!bundledCode) {
@@ -65,6 +56,6 @@ async function bundleCode(options) {
65
56
  if (error instanceof ZapierBundleError) {
66
57
  throw error;
67
58
  }
68
- throw new ZapierBundleError(`Bundle failed: ${error instanceof Error ? error.message : "Unknown error"}`, undefined, error instanceof Error ? error : undefined);
59
+ throw new ZapierBundleError(`Bundle failed: ${error instanceof Error ? error.message : "Unknown error"}`, { cause: error instanceof Error ? error : undefined });
69
60
  }
70
61
  }
@@ -3,7 +3,7 @@ import { createWriteStream } from "fs";
3
3
  import { promises as fs } from "fs";
4
4
  import { dirname } from "path";
5
5
  import { CurlSchema } from "./schemas";
6
- import { CurlExitError, parseHeaderLine, basicAuthHeader, deriveRemoteFilename, formatWriteOut, appendQueryParams, readAllStdin, resolveDataArgText, resolveDataArgBinary, buildFormData, } from "./utils";
6
+ import { ZapierCurlExitError, parseHeaderLine, basicAuthHeader, deriveRemoteFilename, formatWriteOut, appendQueryParams, readAllStdin, resolveDataArgText, resolveDataArgBinary, buildFormData, } from "./utils";
7
7
  export const curlPlugin = definePlugin((sdk) => {
8
8
  async function curl(options) {
9
9
  const { url: rawUrl, request, header = [], data = [], dataRaw = [], dataAscii = [], dataBinary = [], dataUrlencode = [], json, form = [], formString = [], get: forceGet, head: forceHead, location, include, output, remoteName, verbose, silent, showError, fail, failWithBody, writeOut, maxTime, user, compressed, connection: connectionParam, connectionId, } = options;
@@ -195,7 +195,7 @@ export const curlPlugin = definePlugin((sdk) => {
195
195
  if (!silent || showError) {
196
196
  process.stderr.write(`curl: (22) The requested URL returned error: ${response.status}\n`);
197
197
  }
198
- throw new CurlExitError("HTTP request failed", 22);
198
+ throw new ZapierCurlExitError("HTTP request failed", 22);
199
199
  }
200
200
  return undefined;
201
201
  }
@@ -1,7 +1,17 @@
1
- export declare class CurlExitError extends Error {
1
+ import { ZapierError } from "@zapier/zapier-sdk";
2
+ /**
3
+ * Thrown when a curl-style invocation fails — captures the exit code so
4
+ * the CLI can surface it (mirrors curl's own non-zero exit codes for
5
+ * different failure modes).
6
+ */
7
+ export declare class ZapierCurlExitError extends ZapierError {
8
+ readonly name = "ZapierCurlExitError";
9
+ readonly code: "ZAPIER_CURL_EXIT_ERROR";
2
10
  exitCode: number;
3
11
  constructor(message: string, exitCode: number);
4
12
  }
13
+ /** @deprecated Use {@link ZapierCurlExitError} instead. */
14
+ export declare const CurlExitError: typeof ZapierCurlExitError;
5
15
  export declare function parseHeaderLine(input: string): {
6
16
  key: string;
7
17
  value: string;
@@ -1,13 +1,22 @@
1
1
  import { createHash } from "crypto";
2
2
  import { promises as fs } from "fs";
3
3
  import { basename } from "path";
4
- export class CurlExitError extends Error {
4
+ import { ZapierError } from "@zapier/zapier-sdk";
5
+ /**
6
+ * Thrown when a curl-style invocation fails — captures the exit code so
7
+ * the CLI can surface it (mirrors curl's own non-zero exit codes for
8
+ * different failure modes).
9
+ */
10
+ export class ZapierCurlExitError extends ZapierError {
5
11
  constructor(message, exitCode) {
6
12
  super(message);
13
+ this.name = "ZapierCurlExitError";
14
+ this.code = "ZAPIER_CURL_EXIT_ERROR";
7
15
  this.exitCode = exitCode;
8
- this.name = "CurlExitError";
9
16
  }
10
17
  }
18
+ /** @deprecated Use {@link ZapierCurlExitError} instead. */
19
+ export const CurlExitError = ZapierCurlExitError;
11
20
  export function parseHeaderLine(input) {
12
21
  const idx = input.indexOf(":");
13
22
  if (idx === -1) {
@@ -102,18 +111,18 @@ export async function resolveDataArgBinary(raw) {
102
111
  }
103
112
  export async function buildFormData(formArgs, formStringArgs) {
104
113
  if (typeof FormData === "undefined") {
105
- throw new CurlExitError("FormData is not available in this runtime; cannot use --form.", 2);
114
+ throw new ZapierCurlExitError("FormData is not available in this runtime; cannot use --form.", 2);
106
115
  }
107
116
  const fd = new FormData();
108
117
  const addField = async (item, forceString) => {
109
118
  const idx = item.indexOf("=");
110
119
  if (idx === -1) {
111
- throw new CurlExitError(`Invalid form field: '${item}'. Expected 'name=value' or 'name=@file'.`, 2);
120
+ throw new ZapierCurlExitError(`Invalid form field: '${item}'. Expected 'name=value' or 'name=@file'.`, 2);
112
121
  }
113
122
  const name = item.slice(0, idx);
114
123
  const value = item.slice(idx + 1);
115
124
  if (!name) {
116
- throw new CurlExitError(`Invalid form field: '${item}'. Field name cannot be empty.`, 2);
125
+ throw new ZapierCurlExitError(`Invalid form field: '${item}'. Field name cannot be empty.`, 2);
117
126
  }
118
127
  if (!forceString && value.startsWith("@")) {
119
128
  const filePath = value.slice(1);
@@ -0,0 +1,46 @@
1
+ import { z } from "zod";
2
+ import type { ZapierSdk } from "@zapier/zapier-sdk/experimental";
3
+ type SdkWithDrainTriggerInbox = {
4
+ drainTriggerInbox: ZapierSdk["drainTriggerInbox"];
5
+ context: {
6
+ meta: {
7
+ drainTriggerInbox: {
8
+ inputSchema?: z.ZodObject;
9
+ [key: string]: unknown;
10
+ };
11
+ };
12
+ };
13
+ };
14
+ /**
15
+ * CLI command for `drain-trigger-inbox`. `--continue-on-error` flows
16
+ * through from the auto-generated SDK schema flag; without it, the
17
+ * first handler failure rejects (fail-fast). For exec / exec-shell
18
+ * and interactive modes, errors print live to stderr via `onError` so
19
+ * they appear next to the message that failed (rather than in a
20
+ * deferred dump at the end).
21
+ *
22
+ * `--json` mode collects messages into a `data` array and prints
23
+ * `{ data, errors }` once at the end — that's its contract. We
24
+ * always pass `continueOnError: true` in JSON mode so partial
25
+ * failures don't truncate the envelope.
26
+ */
27
+ export declare const drainTriggerInboxCliPlugin: (sdk: SdkWithDrainTriggerInbox & {
28
+ context: {
29
+ meta: Record<string, import("@zapier/zapier-sdk").PluginMeta>;
30
+ };
31
+ }) => {
32
+ drainTriggerInbox: ZapierSdk["drainTriggerInbox"];
33
+ context: {
34
+ meta: {
35
+ drainTriggerInbox: {
36
+ inputSchema: z.ZodObject<{
37
+ exec: z.ZodOptional<z.ZodString>;
38
+ execShell: z.ZodOptional<z.ZodString>;
39
+ json: z.ZodOptional<z.ZodBoolean>;
40
+ }, z.core.$strip>;
41
+ packages: undefined;
42
+ };
43
+ };
44
+ };
45
+ };
46
+ export {};
@@ -0,0 +1,178 @@
1
+ import { z } from "zod";
2
+ import { definePlugin, ZapierReleaseTriggerMessageSignal, } from "@zapier/zapier-sdk";
3
+ import { jsonReplacer } from "../../utils/cli-renderer";
4
+ import { CliSkipLeaseExpireError, combineSignals, createInteractiveCallback, getPostDashArgs, printDrainError, printDrainSummary, rejectExecJsonMutex, requireInteractiveTty, runExecCommand, runShellCommand, warnInteractiveContinueOnErrorOverride, } from "../../utils/triggerDrain";
5
+ const JsonProperty = z
6
+ .boolean()
7
+ .optional()
8
+ .describe("Format the drained result as a JSON object on stdout: { data, errors }. Use for scripts or piping. Mutually exclusive with --exec / --exec-shell and the interactive default.");
9
+ // Both `--exec` and `--exec-shell` are CLI-only: the SDK exposes only
10
+ // `onMessage`, and the CLI plugin wraps these flags into an onMessage
11
+ // before delegating. On the CLI, the binary is the flag value and any
12
+ // argv after a literal `--` is appended (xargs / npm-run convention).
13
+ const ExecCliProperty = z
14
+ .string()
15
+ .optional()
16
+ .describe("Run a binary per message with no shell interpretation. Message JSON is piped to stdin; exit code 0 acks, non-zero records the error per the same rules as a thrown handler. Pass extra argv after `--` (e.g. `--exec ./handler -- --verbose`). Mutually exclusive with --exec-shell and --json.");
17
+ const ExecShellCliProperty = z
18
+ .string()
19
+ .optional()
20
+ .describe("Run a shell command per message. Message JSON is piped to the subprocess on stdin; exit code 0 acks, non-zero records the error per the same rules as a thrown handler. Interpreted by the platform's default shell (sh on POSIX, cmd.exe on Windows). Mutually exclusive with --exec and --json.");
21
+ /**
22
+ * CLI command for `drain-trigger-inbox`. `--continue-on-error` flows
23
+ * through from the auto-generated SDK schema flag; without it, the
24
+ * first handler failure rejects (fail-fast). For exec / exec-shell
25
+ * and interactive modes, errors print live to stderr via `onError` so
26
+ * they appear next to the message that failed (rather than in a
27
+ * deferred dump at the end).
28
+ *
29
+ * `--json` mode collects messages into a `data` array and prints
30
+ * `{ data, errors }` once at the end — that's its contract. We
31
+ * always pass `continueOnError: true` in JSON mode so partial
32
+ * failures don't truncate the envelope.
33
+ */
34
+ export const drainTriggerInboxCliPlugin = definePlugin((sdk) => {
35
+ const original = sdk.drainTriggerInbox;
36
+ const existingMeta = sdk.context.meta.drainTriggerInbox;
37
+ const baseInputSchema = existingMeta.inputSchema;
38
+ // `.extend` overrides `exec` from the SDK's array form to the CLI's
39
+ // single-string form. The post-`--` argv is reattached by the
40
+ // handler below before `runExecCommand`.
41
+ const extendedInputSchema = baseInputSchema
42
+ ? baseInputSchema.extend({
43
+ exec: ExecCliProperty,
44
+ execShell: ExecShellCliProperty,
45
+ json: JsonProperty,
46
+ })
47
+ : z.object({
48
+ exec: ExecCliProperty,
49
+ execShell: ExecShellCliProperty,
50
+ json: JsonProperty,
51
+ });
52
+ return {
53
+ drainTriggerInbox: (async (options) => {
54
+ const { json, exec, execShell, ...sdkArgs } = options;
55
+ rejectExecJsonMutex({ exec, execShell, json });
56
+ // Validate TTY before installing the SIGINT handler / finally,
57
+ // so a preflight failure doesn't print a misleading
58
+ // "Processed 0 messages" summary on its way out.
59
+ if (!exec && !execShell && !json) {
60
+ requireInteractiveTty("drain-trigger-inbox");
61
+ }
62
+ // SIGINT in non-interactive modes → kill subprocesses cleanly.
63
+ const sigintController = new AbortController();
64
+ const onSigint = () => sigintController.abort();
65
+ process.on("SIGINT", onSigint);
66
+ const combined = combineSignals(sdkArgs.signal, sigintController.signal);
67
+ let fulfilled = 0;
68
+ let rejected = 0;
69
+ let skipped = 0;
70
+ const liveOnError = (reason, message) => {
71
+ rejected++;
72
+ printDrainError(reason, message);
73
+ };
74
+ try {
75
+ // --exec: direct binary invocation (no shell). Append any
76
+ // argv after a literal `--` so users can pass flags to the
77
+ // exec'd binary without quoting tricks.
78
+ if (exec) {
79
+ const execArgv = [exec, ...getPostDashArgs()];
80
+ await original({
81
+ ...sdkArgs,
82
+ signal: combined.signal,
83
+ onMessage: async (message) => {
84
+ await runExecCommand(execArgv, message, combined.signal);
85
+ fulfilled++;
86
+ },
87
+ onError: liveOnError,
88
+ });
89
+ return;
90
+ }
91
+ // --exec-shell: shell handler. Errors print live; user's
92
+ // --continue-on-error decides whether to keep going.
93
+ if (execShell) {
94
+ await original({
95
+ ...sdkArgs,
96
+ signal: combined.signal,
97
+ onMessage: async (message) => {
98
+ await runShellCommand(execShell, message, combined.signal);
99
+ fulfilled++;
100
+ },
101
+ onError: liveOnError,
102
+ });
103
+ return;
104
+ }
105
+ // --json: collect into envelope. Always continue-on-error
106
+ // because partial-result truncation isn't useful here.
107
+ if (json) {
108
+ const data = [];
109
+ const errors = [];
110
+ await original({
111
+ ...sdkArgs,
112
+ signal: combined.signal,
113
+ continueOnError: true,
114
+ onMessage: (message) => {
115
+ data.push(message);
116
+ },
117
+ onError: (reason, message) => {
118
+ errors.push({ reason, message });
119
+ },
120
+ });
121
+ process.stdout.write(JSON.stringify({ data, errors }, jsonReplacer, 2) + "\n");
122
+ return;
123
+ }
124
+ // Default: interactive triage. continueOnError forced true
125
+ // so "skip (let lease expire)" doesn't tear down the loop.
126
+ // No onError — user-driven skips aren't errors worth
127
+ // reporting, and real SDK failures still propagate. TTY
128
+ // validation already ran above the try block.
129
+ if (sdkArgs.continueOnError === false) {
130
+ warnInteractiveContinueOnErrorOverride();
131
+ }
132
+ const interactive = createInteractiveCallback();
133
+ await original({
134
+ ...sdkArgs,
135
+ signal: combined.signal,
136
+ concurrency: 1,
137
+ continueOnError: true,
138
+ onMessage: async (message) => {
139
+ try {
140
+ await interactive(message);
141
+ fulfilled++;
142
+ }
143
+ catch (err) {
144
+ // Count both skip flavors (release-after-draining +
145
+ // let-lease-expire) toward `skipped` so the final
146
+ // summary's total matches what the user actually saw.
147
+ // Quit (ZapierAbortDrainSignal) and unknown errors
148
+ // fall through uncounted — quit ends the drain and
149
+ // unknown errors are pre-existing silently-swallowed
150
+ // behavior in interactive mode.
151
+ if (err instanceof ZapierReleaseTriggerMessageSignal ||
152
+ err instanceof CliSkipLeaseExpireError) {
153
+ skipped++;
154
+ }
155
+ throw err;
156
+ }
157
+ },
158
+ });
159
+ }
160
+ finally {
161
+ process.off("SIGINT", onSigint);
162
+ combined.dispose();
163
+ if (!json) {
164
+ printDrainSummary({ fulfilled, rejected, skipped });
165
+ }
166
+ }
167
+ }),
168
+ context: {
169
+ meta: {
170
+ drainTriggerInbox: {
171
+ ...existingMeta,
172
+ inputSchema: extendedInputSchema,
173
+ packages: undefined,
174
+ },
175
+ },
176
+ },
177
+ };
178
+ });
@@ -106,7 +106,7 @@ export declare const generateAppTypesPlugin: (sdk: {
106
106
  };
107
107
  };
108
108
  } & {
109
- listInputFields: (options?: (({
109
+ listActionInputFields: (options?: (({
110
110
  app: string;
111
111
  actionType: "filter" | "read" | "read_bulk" | "run" | "search" | "search_and_write" | "search_or_write" | "write";
112
112
  action: string;
@@ -156,7 +156,7 @@ export declare const generateAppTypesPlugin: (sdk: {
156
156
  } & {
157
157
  context: {
158
158
  meta: {
159
- listInputFields: import("@zapier/zapier-sdk").PluginMeta;
159
+ listActionInputFields: import("@zapier/zapier-sdk").PluginMeta;
160
160
  };
161
161
  };
162
162
  } & {
@@ -10,3 +10,5 @@ export { feedbackPlugin } from "./feedback";
10
10
  export { curlPlugin } from "./curl";
11
11
  export { cliOverridesPlugin } from "./cliOverrides";
12
12
  export { initPlugin } from "./init";
13
+ export { drainTriggerInboxCliPlugin } from "./drainTriggerInbox";
14
+ export { watchTriggerInboxCliPlugin } from "./watchTriggerInbox";
@@ -10,3 +10,5 @@ export { feedbackPlugin } from "./feedback";
10
10
  export { curlPlugin } from "./curl";
11
11
  export { cliOverridesPlugin } from "./cliOverrides";
12
12
  export { initPlugin } from "./init";
13
+ export { drainTriggerInboxCliPlugin } from "./drainTriggerInbox";
14
+ export { watchTriggerInboxCliPlugin } from "./watchTriggerInbox";
@@ -5,6 +5,7 @@ export declare const mcpPlugin: (sdk: {
5
5
  debug?: boolean;
6
6
  };
7
7
  extensions?: Plugin<unknown, PluginProvides>[];
8
+ experimental?: boolean;
8
9
  };
9
10
  } & {
10
11
  context: import("@zapier/zapier-sdk").EventEmissionContext;
@@ -9,11 +9,15 @@ export const mcpPlugin = definePlugin((sdk) => createPluginMethod(sdk, {
9
9
  // Forward debug + the extensions resolved at CLI startup so the MCP
10
10
  // server's registry includes extension functions as tools. Without
11
11
  // this forward, MCP would build a vanilla SDK and the CLI/MCP
12
- // surfaces would diverge.
12
+ // surfaces would diverge. The `experimental` flag (set by the
13
+ // experimental CLI factory) tells the MCP server to build against
14
+ // `@zapier/zapier-sdk/experimental` so experimental tools are
15
+ // exposed.
13
16
  await startMcpServer({
14
17
  ...options,
15
18
  debug: sdk.context.options?.debug,
16
19
  extensions: sdk.context.extensions,
20
+ experimental: sdk.context.experimental,
17
21
  });
18
22
  },
19
23
  }));
@@ -0,0 +1,45 @@
1
+ import { z } from "zod";
2
+ import type { ZapierSdk } from "@zapier/zapier-sdk/experimental";
3
+ type SdkWithWatchTriggerInbox = {
4
+ watchTriggerInbox: ZapierSdk["watchTriggerInbox"];
5
+ context: {
6
+ meta: {
7
+ watchTriggerInbox: {
8
+ inputSchema?: z.ZodObject;
9
+ [key: string]: unknown;
10
+ };
11
+ };
12
+ };
13
+ };
14
+ /**
15
+ * CLI command for `watch-trigger-inbox`. Same flag dispatch as
16
+ * drain-trigger-inbox. `--continue-on-error` flows through from the
17
+ * auto-generated SDK schema flag; without it, the first handler
18
+ * failure rejects (fail-fast). Errors print live to stderr via
19
+ * `onError`, not buffered until the end — for a forever loop "the
20
+ * end" is Ctrl-C, which is too late.
21
+ *
22
+ * `--json` for watch is NDJSON streaming (one record per line)
23
+ * rather than a final envelope. The watch loop has no terminal
24
+ * aggregate to format.
25
+ */
26
+ export declare const watchTriggerInboxCliPlugin: (sdk: SdkWithWatchTriggerInbox & {
27
+ context: {
28
+ meta: Record<string, import("@zapier/zapier-sdk").PluginMeta>;
29
+ };
30
+ }) => {
31
+ watchTriggerInbox: ZapierSdk["watchTriggerInbox"];
32
+ context: {
33
+ meta: {
34
+ watchTriggerInbox: {
35
+ inputSchema: z.ZodObject<{
36
+ exec: z.ZodOptional<z.ZodString>;
37
+ execShell: z.ZodOptional<z.ZodString>;
38
+ json: z.ZodOptional<z.ZodBoolean>;
39
+ }, z.core.$strip>;
40
+ packages: undefined;
41
+ };
42
+ };
43
+ };
44
+ };
45
+ export {};
@@ -0,0 +1,157 @@
1
+ import { z } from "zod";
2
+ import { definePlugin, ZapierReleaseTriggerMessageSignal, } from "@zapier/zapier-sdk";
3
+ import { CliSkipLeaseExpireError, combineSignals, createInteractiveCallback, createNdjsonCallback, getPostDashArgs, printDrainError, printDrainSummary, rejectExecJsonMutex, requireInteractiveTty, runExecCommand, runShellCommand, warnInteractiveContinueOnErrorOverride, } from "../../utils/triggerDrain";
4
+ const JsonProperty = z
5
+ .boolean()
6
+ .optional()
7
+ .describe("Stream each message as JSON to stdout (one record per line, NDJSON), acking as each write completes. Use for piping to other tools. Mutually exclusive with --exec / --exec-shell and the interactive default.");
8
+ // Both `--exec` and `--exec-shell` are CLI-only — see drainTriggerInbox/index.ts.
9
+ const ExecCliProperty = z
10
+ .string()
11
+ .optional()
12
+ .describe("Run a binary per message with no shell interpretation. Message JSON is piped to stdin; exit code 0 acks, non-zero records the error per the same rules as a thrown handler. Pass extra argv after `--` (e.g. `--exec ./handler -- --verbose`). Mutually exclusive with --exec-shell and --json.");
13
+ const ExecShellCliProperty = z
14
+ .string()
15
+ .optional()
16
+ .describe("Run a shell command per message. Message JSON is piped to the subprocess on stdin; exit code 0 acks, non-zero records the error per the same rules as a thrown handler. Interpreted by the platform's default shell (sh on POSIX, cmd.exe on Windows). Mutually exclusive with --exec and --json.");
17
+ /**
18
+ * CLI command for `watch-trigger-inbox`. Same flag dispatch as
19
+ * drain-trigger-inbox. `--continue-on-error` flows through from the
20
+ * auto-generated SDK schema flag; without it, the first handler
21
+ * failure rejects (fail-fast). Errors print live to stderr via
22
+ * `onError`, not buffered until the end — for a forever loop "the
23
+ * end" is Ctrl-C, which is too late.
24
+ *
25
+ * `--json` for watch is NDJSON streaming (one record per line)
26
+ * rather than a final envelope. The watch loop has no terminal
27
+ * aggregate to format.
28
+ */
29
+ export const watchTriggerInboxCliPlugin = definePlugin((sdk) => {
30
+ const original = sdk.watchTriggerInbox;
31
+ const existingMeta = sdk.context.meta.watchTriggerInbox;
32
+ const baseInputSchema = existingMeta.inputSchema;
33
+ const extendedInputSchema = baseInputSchema
34
+ ? baseInputSchema.extend({
35
+ exec: ExecCliProperty,
36
+ execShell: ExecShellCliProperty,
37
+ json: JsonProperty,
38
+ })
39
+ : z.object({
40
+ exec: ExecCliProperty,
41
+ execShell: ExecShellCliProperty,
42
+ json: JsonProperty,
43
+ });
44
+ return {
45
+ watchTriggerInbox: (async (options) => {
46
+ const { json, exec, execShell, ...sdkArgs } = options;
47
+ rejectExecJsonMutex({ exec, execShell, json });
48
+ // Validate TTY before installing the SIGINT handler / finally,
49
+ // so a preflight failure doesn't print a misleading
50
+ // "Processed 0 messages" summary on its way out.
51
+ if (!exec && !execShell && !json) {
52
+ requireInteractiveTty("watch-trigger-inbox");
53
+ }
54
+ // SIGINT + user signal → combined.
55
+ const sigintController = new AbortController();
56
+ const onSigint = () => sigintController.abort();
57
+ process.on("SIGINT", onSigint);
58
+ const combined = combineSignals(sdkArgs.signal, sigintController.signal);
59
+ let fulfilled = 0;
60
+ let rejected = 0;
61
+ let skipped = 0;
62
+ const liveOnError = (reason, message) => {
63
+ rejected++;
64
+ printDrainError(reason, message);
65
+ };
66
+ try {
67
+ if (exec) {
68
+ const execArgv = [exec, ...getPostDashArgs()];
69
+ await original({
70
+ ...sdkArgs,
71
+ signal: combined.signal,
72
+ onMessage: async (message) => {
73
+ await runExecCommand(execArgv, message, combined.signal);
74
+ fulfilled++;
75
+ },
76
+ onError: liveOnError,
77
+ });
78
+ }
79
+ else if (execShell) {
80
+ await original({
81
+ ...sdkArgs,
82
+ signal: combined.signal,
83
+ onMessage: async (message) => {
84
+ await runShellCommand(execShell, message, combined.signal);
85
+ fulfilled++;
86
+ },
87
+ onError: liveOnError,
88
+ });
89
+ }
90
+ else if (json) {
91
+ const ndjson = createNdjsonCallback();
92
+ await original({
93
+ ...sdkArgs,
94
+ signal: combined.signal,
95
+ onMessage: async (message) => {
96
+ await ndjson(message);
97
+ fulfilled++;
98
+ },
99
+ onError: liveOnError,
100
+ });
101
+ }
102
+ else {
103
+ // Interactive: continueOnError is forced true so the user's
104
+ // "skip (let lease expire)" choice (which throws a plain
105
+ // Error) doesn't tear down the loop. We don't pass onError
106
+ // here — user-driven skips aren't errors worth reporting,
107
+ // and SDK-level failures still propagate as a rejection.
108
+ // TTY validation already ran above the try block.
109
+ if (sdkArgs.continueOnError === false) {
110
+ warnInteractiveContinueOnErrorOverride();
111
+ }
112
+ const interactive = createInteractiveCallback();
113
+ await original({
114
+ ...sdkArgs,
115
+ signal: combined.signal,
116
+ concurrency: 1,
117
+ continueOnError: true,
118
+ onMessage: async (message) => {
119
+ try {
120
+ await interactive(message);
121
+ fulfilled++;
122
+ }
123
+ catch (err) {
124
+ // See drainTriggerInbox for the counting policy: both
125
+ // skip flavors land in `skipped`; quit and unknown
126
+ // errors fall through uncounted.
127
+ if (err instanceof ZapierReleaseTriggerMessageSignal ||
128
+ err instanceof CliSkipLeaseExpireError) {
129
+ skipped++;
130
+ }
131
+ throw err;
132
+ }
133
+ },
134
+ });
135
+ }
136
+ }
137
+ finally {
138
+ process.off("SIGINT", onSigint);
139
+ combined.dispose();
140
+ // NDJSON mode emitted everything per-line; printing a
141
+ // summary on top would mix with the data stream.
142
+ if (!json) {
143
+ printDrainSummary({ fulfilled, rejected, skipped });
144
+ }
145
+ }
146
+ }),
147
+ context: {
148
+ meta: {
149
+ watchTriggerInbox: {
150
+ ...existingMeta,
151
+ inputSchema: extendedInputSchema,
152
+ packages: undefined,
153
+ },
154
+ },
155
+ },
156
+ };
157
+ });
package/dist/src/sdk.js CHANGED
@@ -1,6 +1,10 @@
1
1
  import * as cliLogin from "./login";
2
2
  import { createZapierSdk, injectCliLogin, } from "@zapier/zapier-sdk";
3
- import { loginPlugin, logoutPlugin, mcpPlugin, bundleCodePlugin, getLoginConfigPathPlugin, addPlugin, generateAppTypesPlugin, buildManifestPlugin, feedbackPlugin, curlPlugin, cliOverridesPlugin, initPlugin, } from "./plugins/index";
3
+ import { loginPlugin, logoutPlugin, mcpPlugin, bundleCodePlugin, getLoginConfigPathPlugin, addPlugin, generateAppTypesPlugin, buildManifestPlugin, feedbackPlugin, curlPlugin, cliOverridesPlugin, initPlugin,
4
+ // drainTriggerInboxForeverCliPlugin is experimental — registered only
5
+ // in `./experimental.ts`. See the "Experimental gating" section in
6
+ // `docs/design/2026-05-01-triggers-in-sdk.md`.
7
+ } from "./plugins/index";
4
8
  import packageJson from "../package.json" with { type: "json" };
5
9
  injectCliLogin(cliLogin);
6
10
  /**