@zapier/zapier-sdk-cli 0.52.10 → 0.53.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 (71) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +39 -1
  3. package/dist/cli.cjs +972 -473
  4. package/dist/cli.mjs +973 -474
  5. package/dist/experimental.cjs +914 -424
  6. package/dist/experimental.d.mts +1 -1
  7. package/dist/experimental.d.ts +1 -1
  8. package/dist/experimental.mjs +910 -420
  9. package/dist/index.cjs +914 -424
  10. package/dist/index.d.mts +1 -1
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.mjs +910 -420
  13. package/dist/login.cjs +8 -10
  14. package/dist/login.d.mts +2 -10
  15. package/dist/login.d.ts +2 -10
  16. package/dist/login.mjs +5 -9
  17. package/dist/package.json +1 -1
  18. package/dist/{sdk-B3nKAZdN.d.mts → sdk-SOLizjno.d.mts} +54 -16
  19. package/dist/{sdk-B3nKAZdN.d.ts → sdk-SOLizjno.d.ts} +54 -16
  20. package/dist/src/experimental.js +30 -27
  21. package/dist/src/login/index.d.ts +1 -9
  22. package/dist/src/login/index.js +12 -14
  23. package/dist/src/plugins/add/index.d.ts +15 -15
  24. package/dist/src/plugins/add/index.js +1 -1
  25. package/dist/src/plugins/buildManifest/index.d.ts +2 -2
  26. package/dist/src/plugins/bundleCode/index.d.ts +1 -1
  27. package/dist/src/plugins/bundleCode/index.js +2 -1
  28. package/dist/src/plugins/cliOverrides/index.d.ts +5 -10
  29. package/dist/src/plugins/cliOverrides/index.js +2 -6
  30. package/dist/src/plugins/curl/index.d.ts +2 -2
  31. package/dist/src/plugins/curl/schemas.d.ts +2 -2
  32. package/dist/src/plugins/feedback/index.d.ts +1 -1
  33. package/dist/src/plugins/generateAppTypes/index.d.ts +11 -11
  34. package/dist/src/plugins/getLoginConfigPath/index.d.ts +1 -1
  35. package/dist/src/plugins/index.d.ts +2 -1
  36. package/dist/src/plugins/index.js +2 -1
  37. package/dist/src/plugins/init/index.d.ts +1 -1
  38. package/dist/src/plugins/login/index.d.ts +3 -16
  39. package/dist/src/plugins/login/index.js +3 -191
  40. package/dist/src/plugins/logout/index.d.ts +1 -1
  41. package/dist/src/plugins/mcp/index.d.ts +1 -1
  42. package/dist/src/plugins/signup/index.d.ts +25 -0
  43. package/dist/src/plugins/signup/index.js +12 -0
  44. package/dist/src/plugins/signup/schemas.d.ts +9 -0
  45. package/dist/src/plugins/signup/schemas.js +26 -0
  46. package/dist/src/plugins/signup/test-harness.d.ts +34 -0
  47. package/dist/src/plugins/signup/test-harness.js +74 -0
  48. package/dist/src/sdk.js +32 -20
  49. package/dist/src/types/sdk.d.ts +2 -1
  50. package/dist/src/utils/auth/account-auth.d.ts +32 -0
  51. package/dist/src/utils/auth/account-auth.js +265 -0
  52. package/dist/src/utils/auth/oauth-callback.d.ts +6 -0
  53. package/dist/src/utils/auth/oauth-callback.js +28 -0
  54. package/dist/src/utils/auth/oauth-errors.d.ts +2 -0
  55. package/dist/src/utils/auth/oauth-errors.js +39 -0
  56. package/dist/src/utils/auth/oauth-flow.d.ts +31 -6
  57. package/dist/src/utils/auth/oauth-flow.js +258 -106
  58. package/dist/src/utils/auth/oauth-transaction.d.ts +35 -0
  59. package/dist/src/utils/auth/oauth-transaction.js +69 -0
  60. package/dist/src/utils/cli-generator.js +14 -7
  61. package/dist/src/utils/cli-renderer.d.ts +13 -3
  62. package/dist/src/utils/cli-renderer.js +27 -20
  63. package/dist/src/utils/log.js +9 -4
  64. package/dist/src/utils/non-interactive.d.ts +5 -4
  65. package/dist/src/utils/non-interactive.js +6 -5
  66. package/dist/src/utils/parameter-resolver.js +3 -1
  67. package/dist/src/utils/schema-formatter.d.ts +2 -2
  68. package/dist/src/utils/schema-formatter.js +4 -30
  69. package/dist/src/utils/version-checker.js +8 -3
  70. package/dist/tsconfig.tsbuildinfo +1 -1
  71. package/package.json +3 -3
@@ -0,0 +1,25 @@
1
+ import { type AccountAuthCliContext } from "../../utils/auth/account-auth";
2
+ export declare const signupPlugin: (sdk: {
3
+ context: import("@zapier/zapier-sdk").EventEmissionContext;
4
+ } & {
5
+ context: AccountAuthCliContext;
6
+ } & {
7
+ context: {
8
+ meta: Record<string, import("@zapier/zapier-sdk").PluginMeta>;
9
+ };
10
+ }) => {
11
+ signup: (options?: {
12
+ timeout?: string | undefined;
13
+ useApprovals?: boolean | undefined;
14
+ nonInteractive?: boolean | undefined;
15
+ skipPrompts?: boolean | undefined;
16
+ headless?: boolean | undefined;
17
+ } | undefined) => Promise<void>;
18
+ } & {
19
+ context: {
20
+ meta: {
21
+ signup: import("@zapier/zapier-sdk").PluginMeta<unknown>;
22
+ };
23
+ };
24
+ };
25
+ export type SignupPluginProvides = ReturnType<typeof signupPlugin>;
@@ -0,0 +1,12 @@
1
+ import { createPluginMethod, definePlugin, } from "@zapier/zapier-sdk";
2
+ import { runAccountAuth, } from "../../utils/auth/account-auth";
3
+ import { SignupSchema } from "./schemas";
4
+ export const signupPlugin = definePlugin((sdk) => createPluginMethod(sdk, {
5
+ name: "signup",
6
+ categories: ["account"],
7
+ inputSchema: SignupSchema,
8
+ supportsJsonOutput: false,
9
+ handler: async ({ sdk, options }) => {
10
+ await runAccountAuth({ sdk, options, entryPoint: "signup" });
11
+ },
12
+ }));
@@ -0,0 +1,9 @@
1
+ import { z } from "zod";
2
+ export declare const SignupSchema: z.ZodObject<{
3
+ timeout: z.ZodOptional<z.ZodString>;
4
+ useApprovals: z.ZodOptional<z.ZodBoolean>;
5
+ nonInteractive: z.ZodOptional<z.ZodBoolean>;
6
+ skipPrompts: z.ZodOptional<z.ZodBoolean>;
7
+ headless: z.ZodOptional<z.ZodBoolean>;
8
+ }, z.core.$strip>;
9
+ export type SignupOptions = z.infer<typeof SignupSchema>;
@@ -0,0 +1,26 @@
1
+ import { z } from "zod";
2
+ export const SignupSchema = z
3
+ .object({
4
+ timeout: z
5
+ .string()
6
+ .optional()
7
+ .describe("Signup timeout in seconds (default: 300)"),
8
+ useApprovals: z
9
+ .boolean()
10
+ .optional()
11
+ .describe("Require approvals for actions performed with these credentials"),
12
+ nonInteractive: z
13
+ .boolean()
14
+ .optional()
15
+ .describe("Skip interactive prompts. Uses defaults where possible; errors instead of prompting when input is required. Useful in CI, piped output, or environments where TTY detection is unreliable."),
16
+ /** @deprecated Use `nonInteractive` instead. */
17
+ skipPrompts: z.boolean().optional().meta({
18
+ deprecated: true,
19
+ deprecationMessage: "Use --non-interactive instead.",
20
+ }),
21
+ headless: z
22
+ .boolean()
23
+ .optional()
24
+ .describe("Use when signing up from a machine that has no browser. Prints a signup link to open elsewhere, then accepts the pasted loopback callback URL."),
25
+ })
26
+ .describe("Set up Zapier account access and SDK credentials");
@@ -0,0 +1,34 @@
1
+ import { type EventEmissionContext, type EventEmissionProvides } from "@zapier/zapier-sdk";
2
+ import type { vi } from "vitest";
3
+ import type { CliContext } from "../../utils/auth/account-auth";
4
+ import { type SignupPluginProvides } from "./index";
5
+ type Vi = typeof vi;
6
+ type EventEmit = EventEmissionProvides["context"]["eventEmission"]["emit"];
7
+ type SignupTestContext = CliContext & EventEmissionContext;
8
+ interface SpyWithCalls {
9
+ mock: {
10
+ calls: unknown[][];
11
+ };
12
+ }
13
+ export interface SignupTestTtyState {
14
+ stdinIsTty: boolean | undefined;
15
+ stdoutIsTty: boolean | undefined;
16
+ }
17
+ export declare function getSignupTestTty(): SignupTestTtyState;
18
+ export declare function setSignupTestTty(value: boolean): void;
19
+ export declare function restoreSignupTestTty(state: SignupTestTtyState): void;
20
+ export declare function getSignupTestOutput(spy: SpyWithCalls): string;
21
+ export declare function getSignupTestLogs(spy: SpyWithCalls): string;
22
+ export declare function existingSignupCredentials(): {
23
+ name: string;
24
+ clientId: string;
25
+ createdAt: number;
26
+ scopes: string[];
27
+ baseUrl: string;
28
+ };
29
+ export declare function buildSignupTestContext(vi: Vi, mockEmit?: EventEmit): SignupTestContext;
30
+ export declare function createSignupTestSdk({ vi, context, }: {
31
+ vi: Vi;
32
+ context?: SignupTestContext;
33
+ }): SignupPluginProvides;
34
+ export {};
@@ -0,0 +1,74 @@
1
+ import { createSdk, } from "@zapier/zapier-sdk";
2
+ import { signupPlugin } from "./index";
3
+ export function getSignupTestTty() {
4
+ return {
5
+ stdinIsTty: process.stdin.isTTY,
6
+ stdoutIsTty: process.stdout.isTTY,
7
+ };
8
+ }
9
+ export function setSignupTestTty(value) {
10
+ const descriptor = { value, configurable: true, writable: true };
11
+ Object.defineProperty(process.stdin, "isTTY", descriptor);
12
+ Object.defineProperty(process.stdout, "isTTY", descriptor);
13
+ }
14
+ export function restoreSignupTestTty(state) {
15
+ Object.defineProperty(process.stdin, "isTTY", {
16
+ value: state.stdinIsTty,
17
+ configurable: true,
18
+ writable: true,
19
+ });
20
+ Object.defineProperty(process.stdout, "isTTY", {
21
+ value: state.stdoutIsTty,
22
+ configurable: true,
23
+ writable: true,
24
+ });
25
+ }
26
+ export function getSignupTestOutput(spy) {
27
+ return spy.mock.calls.map((call) => String(call[0])).join("");
28
+ }
29
+ export function getSignupTestLogs(spy) {
30
+ return spy.mock.calls.map((call) => call.map(String).join(" ")).join("\n");
31
+ }
32
+ export function existingSignupCredentials() {
33
+ return {
34
+ name: "existing",
35
+ clientId: "old-id",
36
+ createdAt: 1,
37
+ scopes: ["external"],
38
+ baseUrl: "https://zapier.com",
39
+ };
40
+ }
41
+ async function emptyApiResult() {
42
+ return undefined;
43
+ }
44
+ function createSignupTestApi() {
45
+ return {
46
+ get: emptyApiResult,
47
+ post: emptyApiResult,
48
+ put: emptyApiResult,
49
+ patch: emptyApiResult,
50
+ delete: emptyApiResult,
51
+ poll: emptyApiResult,
52
+ fetch: () => Promise.resolve(new Response()),
53
+ };
54
+ }
55
+ export function buildSignupTestContext(vi, mockEmit = () => undefined) {
56
+ return {
57
+ resolveCredentials: vi.fn().mockResolvedValue(undefined),
58
+ api: createSignupTestApi(),
59
+ eventEmission: {
60
+ transport: { emit: vi.fn(), close: vi.fn() },
61
+ config: { enabled: true },
62
+ emit: mockEmit,
63
+ createBaseEvent: vi.fn().mockResolvedValue({}),
64
+ flush: vi.fn().mockResolvedValue(undefined),
65
+ close: vi.fn().mockResolvedValue(undefined),
66
+ },
67
+ hooks: { onMethodEnd: vi.fn() },
68
+ };
69
+ }
70
+ export function createSignupTestSdk({ vi, context = buildSignupTestContext(vi), }) {
71
+ return createSdk()
72
+ .addPlugin(() => ({ context }))
73
+ .addPlugin(signupPlugin);
74
+ }
package/dist/src/sdk.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as cliLogin from "./login";
2
- import { createZapierSdk, injectCliLogin, } from "@zapier/zapier-sdk";
3
- import { loginPlugin, logoutPlugin, mcpPlugin, bundleCodePlugin, getLoginConfigPathPlugin, addPlugin, generateAppTypesPlugin, buildManifestPlugin, feedbackPlugin, curlPlugin, cliOverridesPlugin, initPlugin,
2
+ import { addPlugin, createZapierSdkStack, injectCliLogin, } from "@zapier/zapier-sdk";
3
+ import { loginPlugin, signupPlugin, logoutPlugin, mcpPlugin, bundleCodePlugin, getLoginConfigPathPlugin, addAppsPlugin, generateAppTypesPlugin, buildManifestPlugin, feedbackPlugin, curlPlugin, cliOverridesPlugin, initPlugin,
4
4
  // drainTriggerInboxForeverCliPlugin is experimental — registered only
5
5
  // in `./experimental.ts`. See the "Experimental gating" section in
6
6
  // `docs/design/2026-05-01-triggers-in-sdk.md`.
@@ -19,37 +19,49 @@ export function createZapierCliSdk(options = {}) {
19
19
  const extensionsContextPlugin = () => ({
20
20
  context: { extensions },
21
21
  });
22
- let chain = createZapierSdk({
22
+ const sdk = createZapierSdkStack({
23
23
  ...sdkOptions,
24
24
  eventEmission: { ...sdkOptions.eventEmission, callContext: "cli" },
25
25
  callerPackage: { name: packageJson.name, version: packageJson.version },
26
26
  })
27
- .addPlugin(extensionsContextPlugin)
28
- .addPlugin(generateAppTypesPlugin)
29
- .addPlugin(buildManifestPlugin)
30
- .addPlugin(bundleCodePlugin)
31
- .addPlugin(getLoginConfigPathPlugin)
32
- .addPlugin(addPlugin)
33
- .addPlugin(feedbackPlugin)
34
- .addPlugin(curlPlugin)
35
- .addPlugin(initPlugin)
36
- .addPlugin(mcpPlugin)
37
- .addPlugin(loginPlugin)
38
- .addPlugin(logoutPlugin)
39
- .addPlugin(cliOverridesPlugin);
27
+ .use(extensionsContextPlugin)
28
+ .use(generateAppTypesPlugin)
29
+ .use(buildManifestPlugin)
30
+ .use(bundleCodePlugin)
31
+ .use(getLoginConfigPathPlugin)
32
+ .use(addAppsPlugin)
33
+ .use(feedbackPlugin)
34
+ .use(curlPlugin)
35
+ .use(initPlugin)
36
+ .use(mcpPlugin)
37
+ .use(loginPlugin)
38
+ .use(signupPlugin)
39
+ .use(logoutPlugin)
40
+ // cliOverridesPlugin patches existing meta entries (e.g. flags `fetch`
41
+ // as deprecated) without re-registering the method. `.use` throws on
42
+ // meta-key collisions by default, so the intent has to be explicit.
43
+ .use(cliOverridesPlugin, { override: true })
44
+ .toSdk();
40
45
  // Construct extensions defensively. The loader (`utils/extensions.ts`)
41
46
  // already isolates *load*-time failures (failed dynamic imports);
42
47
  // here we cover the second failure mode — a successfully-imported
43
48
  // plugin that throws during construction (bad context lookup, schema
44
- // build error, composePlugins collision, etc.). Honors the
45
- // design-doc promise that "a broken extension does not kill the CLI."
49
+ // build error, plugin collision, etc.). Honors the design-doc promise
50
+ // that "a broken extension does not kill the CLI."
51
+ //
52
+ // `addPlugin` mutates `sdk` in place and `mergeContribution` is
53
+ // atomic (validate-all-then-apply), so a failed extension leaves
54
+ // the sdk in its prior consistent state. Base plugins run exactly
55
+ // once regardless of how many extensions are installed, so the
56
+ // `eventEmission` startup payload reaches the wire once per
57
+ // process.
46
58
  for (const ext of extensions) {
47
59
  try {
48
- chain = chain.addPlugin(ext);
60
+ addPlugin(sdk, ext);
49
61
  }
50
62
  catch (err) {
51
63
  console.warn(`Extension plugin failed to construct: ${err.message}; skipping.`);
52
64
  }
53
65
  }
54
- return chain;
66
+ return sdk;
55
67
  }
@@ -2,4 +2,5 @@ import type { ZapierSdk } from "@zapier/zapier-sdk";
2
2
  import type { BuildManifestPluginProvides } from "../plugins/buildManifest";
3
3
  import type { FeedbackPluginProvides } from "../plugins/feedback";
4
4
  import type { GenerateAppTypesPluginProvides } from "../plugins/generateAppTypes";
5
- export type ZapierSdkCli = ZapierSdk & BuildManifestPluginProvides & FeedbackPluginProvides & GenerateAppTypesPluginProvides;
5
+ import type { SignupPluginProvides } from "../plugins/signup";
6
+ export type ZapierSdkCli = ZapierSdk & BuildManifestPluginProvides & FeedbackPluginProvides & GenerateAppTypesPluginProvides & SignupPluginProvides;
@@ -0,0 +1,32 @@
1
+ import { type ApiClient, type EventEmissionProvides, type ResolvedCredentials } from "@zapier/zapier-sdk";
2
+ export type AccountAuthEntryPoint = "login" | "signup";
3
+ export interface AccountAuthOptions {
4
+ timeout?: string;
5
+ useApprovals?: boolean;
6
+ nonInteractive?: boolean;
7
+ /** @deprecated Use `nonInteractive` instead. */
8
+ skipPrompts?: boolean;
9
+ headless?: boolean;
10
+ }
11
+ export interface AccountAuthCliContext {
12
+ session_id?: string | null;
13
+ selected_api?: string | null;
14
+ app_id?: number | null;
15
+ app_version_id?: number | null;
16
+ resolveCredentials: () => Promise<ResolvedCredentials | undefined>;
17
+ api: ApiClient;
18
+ options?: {
19
+ baseUrl?: string;
20
+ credentials?: unknown;
21
+ };
22
+ }
23
+ export type CliContext = AccountAuthCliContext;
24
+ interface RunAccountAuthOptions<TOptions extends AccountAuthOptions> {
25
+ sdk: EventEmissionProvides & {
26
+ context: AccountAuthCliContext;
27
+ };
28
+ options: TOptions;
29
+ entryPoint: AccountAuthEntryPoint;
30
+ }
31
+ export declare function runAccountAuth<TOptions extends AccountAuthOptions>({ sdk, options, entryPoint, }: RunAccountAuthOptions<TOptions>): Promise<void>;
32
+ export {};
@@ -0,0 +1,265 @@
1
+ import { hostname } from "node:os";
2
+ import inquirer from "inquirer";
3
+ import { buildApplicationLifecycleEvent, getOrCreateApiClient, isCredentialsObject, } from "@zapier/zapier-sdk";
4
+ import { revokeCredentials } from "../../login/credentials-revoke";
5
+ import { deleteStoredClientCredentials, getActiveCredentials, } from "../../login/credentials-store";
6
+ import { clearLegacyJwtState, hasLegacyJwtConfig, } from "../../login/legacy-jwt";
7
+ import { resolveCredentialsBaseUrl } from "../../plugins/auth/credentials-base-url";
8
+ import { resolveNonInteractive } from "../non-interactive";
9
+ import { ZapierCliUserCancellationError, ZapierCliValidationError, } from "../errors";
10
+ import { EMPTY_POLICY, setupClientCredentials } from "./client-credentials";
11
+ import { runLoginOauthFlow, runSignupOauthFlow, } from "./oauth-flow";
12
+ import { toRedactedOauthError } from "./oauth-errors";
13
+ const LEGACY_JWT_UPGRADE_PROMPT = "We're upgrading your login to client credentials for a simpler, more reliable experience and to support future security controls. Older Zapier SDK/CLI versions on this machine may stop working after the upgrade. Continue?";
14
+ const SIGNUP_RECOVERY_MESSAGE = "Restart `zapier-sdk signup` to generate a fresh signup URL and try again.";
15
+ const HEADLESS_SIGNUP_RECOVERY_MESSAGE = "Restart `zapier-sdk signup --headless` to generate a fresh signup URL and try again.";
16
+ function getEntryPointLabel(entryPoint) {
17
+ return entryPoint === "signup" ? "Signup" : "Login";
18
+ }
19
+ function getActiveCredentialsAction(entryPoint) {
20
+ return entryPoint === "signup" ? "continue signup" : "log in again";
21
+ }
22
+ function getCredentialsPromptMessage(entryPoint) {
23
+ return entryPoint === "signup"
24
+ ? "Enter a name to identify these credentials:"
25
+ : "Enter a name to identify them:";
26
+ }
27
+ function getProfileMessage(entryPoint, email) {
28
+ return entryPoint === "signup"
29
+ ? `👤 Authenticated as ${email}`
30
+ : `👤 Logged in as ${email}`;
31
+ }
32
+ function defaultCredentialsName(email) {
33
+ return `${email}@${hostname()}`;
34
+ }
35
+ function validateCredentialsName(name) {
36
+ const trimmedName = name.trim();
37
+ if (!trimmedName)
38
+ throw new ZapierCliValidationError("Name cannot be empty");
39
+ return trimmedName;
40
+ }
41
+ async function promptCredentialsName({ email, promptMessage, }) {
42
+ const { credentialName } = await inquirer.prompt([
43
+ {
44
+ type: "input",
45
+ name: "credentialName",
46
+ message: promptMessage,
47
+ default: defaultCredentialsName(email),
48
+ validate: (input) => {
49
+ try {
50
+ validateCredentialsName(input);
51
+ return true;
52
+ }
53
+ catch (err) {
54
+ return err instanceof Error ? err.message : String(err);
55
+ }
56
+ },
57
+ },
58
+ ]);
59
+ return validateCredentialsName(credentialName);
60
+ }
61
+ function resolveDefaultCredentialsName({ email, }) {
62
+ return validateCredentialsName(defaultCredentialsName(email));
63
+ }
64
+ function toPkceCredentials(credentials) {
65
+ if (credentials &&
66
+ isCredentialsObject(credentials) &&
67
+ !("clientSecret" in credentials)) {
68
+ return {
69
+ type: "pkce",
70
+ clientId: credentials.clientId,
71
+ baseUrl: credentials.baseUrl,
72
+ scope: credentials.scope,
73
+ };
74
+ }
75
+ return undefined;
76
+ }
77
+ function parseTimeoutSeconds(timeout) {
78
+ if (timeout === undefined)
79
+ return 300;
80
+ const timeoutSeconds = Number(timeout);
81
+ if (!Number.isInteger(timeoutSeconds) || timeoutSeconds <= 0) {
82
+ throw new ZapierCliValidationError("Timeout must be a positive integer (seconds).");
83
+ }
84
+ return timeoutSeconds;
85
+ }
86
+ async function promptConfirm({ message, defaultValue, }) {
87
+ const { confirmed } = await inquirer.prompt([
88
+ { type: "confirm", name: "confirmed", message, default: defaultValue },
89
+ ]);
90
+ return confirmed;
91
+ }
92
+ function promptlessCredentialResetError(credentials) {
93
+ throw new ZapierCliValidationError(`Already logged in as "${credentials.name}". Run \`logout\` first or use an interactive terminal to re-authenticate.`);
94
+ }
95
+ function promptlessLegacyJwtUpgradeError() {
96
+ throw new ZapierCliValidationError("Legacy JWT login detected. Run `logout` first or use an interactive terminal to migrate to client credentials.");
97
+ }
98
+ async function clearExistingAuthState({ sdk, baseUrl, interactive, entryPoint, }) {
99
+ const activeCredentials = getActiveCredentials({ baseUrl });
100
+ const flowLabel = getEntryPointLabel(entryPoint);
101
+ if (activeCredentials) {
102
+ const confirmed = interactive
103
+ ? await promptConfirm({
104
+ defaultValue: false,
105
+ message: `You are already logged in as "${activeCredentials.name}".\n` +
106
+ "Logging out will delete these credentials and may interrupt other Zapier SDK or CLI sessions using them.\n" +
107
+ `Log out and ${getActiveCredentialsAction(entryPoint)}?`,
108
+ })
109
+ : promptlessCredentialResetError(activeCredentials);
110
+ if (!confirmed) {
111
+ console.log(`${flowLabel} cancelled.`);
112
+ return false;
113
+ }
114
+ try {
115
+ await revokeCredentials({
116
+ api: sdk.context.api,
117
+ credentials: activeCredentials,
118
+ });
119
+ }
120
+ catch {
121
+ if (!interactive) {
122
+ throw new ZapierCliValidationError(`${flowLabel} cleanup failed and cannot be reset without confirmation. Re-run with an interactive terminal.`);
123
+ }
124
+ const reset = await promptConfirm({
125
+ defaultValue: false,
126
+ message: `${flowLabel} cleanup failed. Reset local session state and continue?`,
127
+ });
128
+ if (!reset) {
129
+ console.log(`${flowLabel} cancelled.`);
130
+ return false;
131
+ }
132
+ await deleteStoredClientCredentials({
133
+ name: activeCredentials.name,
134
+ baseUrl: activeCredentials.baseUrl,
135
+ });
136
+ }
137
+ }
138
+ else if (hasLegacyJwtConfig()) {
139
+ const confirmed = interactive
140
+ ? await promptConfirm({
141
+ defaultValue: true,
142
+ message: LEGACY_JWT_UPGRADE_PROMPT,
143
+ })
144
+ : promptlessLegacyJwtUpgradeError();
145
+ if (!confirmed) {
146
+ console.log(`${flowLabel} cancelled.`);
147
+ return false;
148
+ }
149
+ }
150
+ return true;
151
+ }
152
+ async function getProfile(api) {
153
+ return api.get("/zapier/api/v4/profile/", {
154
+ authRequired: true,
155
+ });
156
+ }
157
+ async function saveClientCredentials({ api, name, credentialsBaseUrl, useApprovals, cleanupLogPrefix, }) {
158
+ await setupClientCredentials({
159
+ api,
160
+ name,
161
+ credentialsBaseUrl,
162
+ ...(useApprovals && { policy: EMPTY_POLICY }),
163
+ });
164
+ try {
165
+ await clearLegacyJwtState();
166
+ }
167
+ catch (err) {
168
+ console.error(`[${cleanupLogPrefix}] Best-effort legacy JWT cleanup failed:`, err);
169
+ }
170
+ }
171
+ function emitAccountAuthSuccess({ sdk, profile, }) {
172
+ sdk.context.eventEmission.emit("platform.sdk.ApplicationLifecycleEvent", buildApplicationLifecycleEvent({ lifecycle_event_type: "login_success" }, {
173
+ customuser_id: profile.user_id,
174
+ account_id: profile.roles[0]?.account_id ?? null,
175
+ }));
176
+ }
177
+ function emitSignupSuccess({ sdk, }) {
178
+ // `signup_success` tracks a valid signup OAuth callback being accepted.
179
+ // It does not mean a new Zapier account was created or local credentials were provisioned.
180
+ sdk.context.eventEmission.emit("platform.sdk.ApplicationLifecycleEvent", buildApplicationLifecycleEvent({ lifecycle_event_type: "signup_success" }));
181
+ }
182
+ async function runOauthWithRedaction(runOauth) {
183
+ try {
184
+ return await runOauth();
185
+ }
186
+ catch (error) {
187
+ if (error instanceof ZapierCliUserCancellationError)
188
+ throw error;
189
+ throw toRedactedOauthError(error);
190
+ }
191
+ }
192
+ async function runOauthForEntryPoint({ sdk, entryPoint, timeoutMs, pkceCredentials, baseUrl, headless, interactive, }) {
193
+ if (entryPoint === "signup") {
194
+ return runOauthWithRedaction(() => runSignupOauthFlow({
195
+ timeoutMs,
196
+ pkceCredentials,
197
+ baseUrl,
198
+ headless,
199
+ interactive,
200
+ recoveryMessage: headless
201
+ ? HEADLESS_SIGNUP_RECOVERY_MESSAGE
202
+ : SIGNUP_RECOVERY_MESSAGE,
203
+ onProgress: (event) => {
204
+ if (event.type === "callback_accepted") {
205
+ emitSignupSuccess({ sdk });
206
+ }
207
+ },
208
+ }));
209
+ }
210
+ return runOauthWithRedaction(() => runLoginOauthFlow({ timeoutMs, pkceCredentials, baseUrl }));
211
+ }
212
+ export async function runAccountAuth({ sdk, options, entryPoint, }) {
213
+ const timeoutSeconds = parseTimeoutSeconds(options.timeout);
214
+ const interactive = !resolveNonInteractive(options);
215
+ const resolvedCredentials = await sdk.context.resolveCredentials();
216
+ const pkceCredentials = toPkceCredentials(resolvedCredentials);
217
+ const credentialsBaseUrl = await resolveCredentialsBaseUrl({
218
+ ...sdk.context,
219
+ resolvedCredentials,
220
+ });
221
+ if (!(await clearExistingAuthState({
222
+ sdk,
223
+ baseUrl: credentialsBaseUrl,
224
+ interactive,
225
+ entryPoint,
226
+ }))) {
227
+ return;
228
+ }
229
+ const { accessToken } = await runOauthForEntryPoint({
230
+ sdk,
231
+ entryPoint,
232
+ timeoutMs: timeoutSeconds * 1000,
233
+ pkceCredentials,
234
+ baseUrl: credentialsBaseUrl,
235
+ headless: options.headless === true,
236
+ interactive,
237
+ });
238
+ const scopedApi = getOrCreateApiClient({
239
+ credentials: accessToken,
240
+ baseUrl: credentialsBaseUrl,
241
+ });
242
+ const profile = await getProfile(scopedApi);
243
+ console.log(getProfileMessage(entryPoint, profile.email));
244
+ console.log("\nGenerating credentials so this machine can make authenticated requests on your behalf.");
245
+ const resolveCredentialsName = interactive
246
+ ? ({ email }) => promptCredentialsName({
247
+ email,
248
+ promptMessage: getCredentialsPromptMessage(entryPoint),
249
+ })
250
+ : resolveDefaultCredentialsName;
251
+ const credentialName = await resolveCredentialsName({ email: profile.email });
252
+ const useApprovals = options.useApprovals === true;
253
+ await saveClientCredentials({
254
+ api: scopedApi,
255
+ name: credentialName,
256
+ credentialsBaseUrl,
257
+ useApprovals,
258
+ cleanupLogPrefix: entryPoint,
259
+ });
260
+ console.log(`✅ Credentials "${credentialName}" created and set as default. You are ready to use the Zapier SDK.`);
261
+ if (useApprovals) {
262
+ console.log("🔐 Approvals are enabled for these credentials.");
263
+ }
264
+ emitAccountAuthSuccess({ sdk, profile });
265
+ }
@@ -0,0 +1,6 @@
1
+ import type { OauthTransaction } from "./oauth-transaction";
2
+ export declare function getCallbackCode({ callbackUrl, transaction, recoveryMessage, }: {
3
+ callbackUrl: string;
4
+ transaction: Pick<OauthTransaction, "redirectUri" | "state">;
5
+ recoveryMessage?: string;
6
+ }): string;
@@ -0,0 +1,28 @@
1
+ import { ZapierCliValidationError } from "../errors";
2
+ export function getCallbackCode({ callbackUrl, transaction, recoveryMessage, }) {
3
+ let parsed;
4
+ try {
5
+ parsed = new URL(callbackUrl.trim());
6
+ }
7
+ catch {
8
+ throw new ZapierCliValidationError("Paste the final OAuth callback URL from your browser.");
9
+ }
10
+ const expected = new URL(transaction.redirectUri);
11
+ if (parsed.protocol !== "http:" ||
12
+ parsed.hostname !== expected.hostname ||
13
+ parsed.pathname !== expected.pathname ||
14
+ parsed.port !== expected.port) {
15
+ throw new ZapierCliValidationError(`Expected the final OAuth callback URL to start with ${transaction.redirectUri}.`);
16
+ }
17
+ if (parsed.searchParams.get("state") !== transaction.state) {
18
+ throw new ZapierCliValidationError(`OAuth state mismatch.${recoveryMessage ? ` ${recoveryMessage}` : ""}`);
19
+ }
20
+ if (parsed.searchParams.has("error")) {
21
+ throw new ZapierCliValidationError(`Authorization denied: ${parsed.searchParams.get("error_description") ?? parsed.searchParams.get("error")}.${recoveryMessage ? ` ${recoveryMessage}` : ""}`);
22
+ }
23
+ const code = parsed.searchParams.get("code");
24
+ if (!code) {
25
+ throw new ZapierCliValidationError("No authorization code found in the pasted callback URL.");
26
+ }
27
+ return code;
28
+ }
@@ -0,0 +1,2 @@
1
+ export declare function redactSensitiveOauthErrorMessage(message: string): string;
2
+ export declare function toRedactedOauthError(error: unknown): Error;
@@ -0,0 +1,39 @@
1
+ import { ZapierCliValidationError } from "../errors";
2
+ const SENSITIVE_OAUTH_FIELDS = [
3
+ "access_token",
4
+ "refresh_token",
5
+ "id_token",
6
+ "client_secret",
7
+ "code_verifier",
8
+ "code_challenge",
9
+ ];
10
+ function getErrorMessage(error) {
11
+ return error instanceof Error ? error.message : String(error);
12
+ }
13
+ function toCamelCase(field) {
14
+ return field.replace(/_([a-z])/g, (_match, letter) => letter.toUpperCase());
15
+ }
16
+ function escapeRegExp(value) {
17
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
18
+ }
19
+ const sensitiveOauthFieldPattern = Array.from(new Set(SENSITIVE_OAUTH_FIELDS.flatMap((field) => [field, toCamelCase(field)])))
20
+ .map(escapeRegExp)
21
+ .join("|");
22
+ const sensitiveQueryParamPattern = new RegExp(`([?&])(${sensitiveOauthFieldPattern})(=)[^&#\\s"'<>]*`, "gi");
23
+ export function redactSensitiveOauthErrorMessage(message) {
24
+ return message
25
+ .replace(sensitiveQueryParamPattern, (_match, prefix, key, separator) => `${prefix}${key}${separator}[REDACTED]`)
26
+ .replace(new RegExp(`"(${sensitiveOauthFieldPattern})"(\\s*:\\s*)"[^"]*"`, "g"), (_match, key, separator) => `"${key}"${separator}"[REDACTED]"`);
27
+ }
28
+ export function toRedactedOauthError(error) {
29
+ const message = redactSensitiveOauthErrorMessage(getErrorMessage(error));
30
+ if (error instanceof ZapierCliValidationError) {
31
+ return new ZapierCliValidationError(message);
32
+ }
33
+ if (error instanceof Error) {
34
+ const redactedError = new Error(message);
35
+ redactedError.name = error.name;
36
+ return redactedError;
37
+ }
38
+ return new ZapierCliValidationError(message);
39
+ }