@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
@@ -7,16 +7,17 @@ var fs = require('fs');
7
7
  var crypto = require('crypto');
8
8
  var path = require('path');
9
9
  var lockfile = require('proper-lockfile');
10
+ var zapierSdk = require('@zapier/zapier-sdk');
10
11
  var zod = require('zod');
11
12
  var experimental = require('@zapier/zapier-sdk/experimental');
12
13
  var os = require('os');
13
- var zapierSdk = require('@zapier/zapier-sdk');
14
14
  var inquirer = require('inquirer');
15
- var open = require('open');
16
15
  var express = require('express');
17
- var pkceChallenge = require('pkce-challenge');
18
- var ora = require('ora');
16
+ var promises$1 = require('readline/promises');
17
+ var open = require('open');
19
18
  var chalk3 = require('chalk');
19
+ var ora = require('ora');
20
+ var pkceChallenge = require('pkce-challenge');
20
21
  var zapierSdkMcp = require('@zapier/zapier-sdk-mcp');
21
22
  var esbuild = require('esbuild');
22
23
  var promises = require('fs/promises');
@@ -55,11 +56,11 @@ var crypto__default = /*#__PURE__*/_interopDefault(crypto);
55
56
  var path__namespace = /*#__PURE__*/_interopNamespace(path);
56
57
  var lockfile__namespace = /*#__PURE__*/_interopNamespace(lockfile);
57
58
  var inquirer__default = /*#__PURE__*/_interopDefault(inquirer);
58
- var open__default = /*#__PURE__*/_interopDefault(open);
59
59
  var express__default = /*#__PURE__*/_interopDefault(express);
60
- var pkceChallenge__default = /*#__PURE__*/_interopDefault(pkceChallenge);
61
- var ora__default = /*#__PURE__*/_interopDefault(ora);
60
+ var open__default = /*#__PURE__*/_interopDefault(open);
62
61
  var chalk3__default = /*#__PURE__*/_interopDefault(chalk3);
62
+ var ora__default = /*#__PURE__*/_interopDefault(ora);
63
+ var pkceChallenge__default = /*#__PURE__*/_interopDefault(pkceChallenge);
63
64
  var ts__namespace = /*#__PURE__*/_interopNamespace(ts);
64
65
  var Handlebars__default = /*#__PURE__*/_interopDefault(Handlebars);
65
66
 
@@ -74,7 +75,7 @@ var login_exports = {};
74
75
  __export(login_exports, {
75
76
  AUTH_MODE_HEADER: () => AUTH_MODE_HEADER,
76
77
  DEFAULT_AUTH_BASE_URL: () => DEFAULT_AUTH_BASE_URL,
77
- ZapierAuthenticationError: () => ZapierAuthenticationError,
78
+ ZapierAuthenticationError: () => zapierSdk.ZapierAuthenticationError,
78
79
  clearTokensFromKeychain: () => clearTokensFromKeychain,
79
80
  createCache: () => createCache,
80
81
  getActiveCredentials: () => getActiveCredentials,
@@ -478,12 +479,6 @@ async function deleteStoredClientCredentials({
478
479
  }
479
480
 
480
481
  // src/login/index.ts
481
- var ZapierAuthenticationError = class extends Error {
482
- constructor(message) {
483
- super(message);
484
- this.name = "ZapierAuthenticationError";
485
- }
486
- };
487
482
  var DEFAULT_AUTH_CLIENT_ID = "grwWZD5hUWGvb4V8ODBuOtXer3h0DBEZ2HR8aay6";
488
483
  var TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1e3;
489
484
  function createDebugLog(enabled) {
@@ -493,9 +488,9 @@ function createDebugLog(enabled) {
493
488
  }
494
489
  return (message, data) => {
495
490
  if (data === void 0) {
496
- console.log(`[Zapier SDK CLI Login] ${message}`);
491
+ console.error(`[Zapier SDK CLI Login] ${message}`);
497
492
  } else {
498
- console.log(`[Zapier SDK CLI Login] ${message}`, data);
493
+ console.error(`[Zapier SDK CLI Login] ${message}`, data);
499
494
  }
500
495
  };
501
496
  }
@@ -741,7 +736,7 @@ async function getToken(options = {}) {
741
736
  return await resolveOrRefreshToken(options);
742
737
  } catch (error) {
743
738
  const message = error instanceof Error ? error.message : "Token refresh failed";
744
- throw new ZapierAuthenticationError(
739
+ throw new zapierSdk.ZapierAuthenticationError(
745
740
  `${message}
746
741
  Please run 'login' to authenticate again.`
747
742
  );
@@ -894,10 +889,27 @@ async function revokeCredentials({
894
889
  });
895
890
  emitAuthLogout(onEvent);
896
891
  }
892
+ function getBaseUrlFromResolvedCredentials(credentials) {
893
+ if (credentials && zapierSdk.isCredentialsObject(credentials)) {
894
+ return credentials.baseUrl;
895
+ }
896
+ return void 0;
897
+ }
898
+ function getBaseUrlFromOptionsCredentials(credentials) {
899
+ if (credentials && typeof credentials === "object" && "baseUrl" in credentials && typeof credentials.baseUrl === "string") {
900
+ return credentials.baseUrl;
901
+ }
902
+ return void 0;
903
+ }
904
+ async function resolveCredentialsBaseUrl(context) {
905
+ const resolvedCredentials = "resolvedCredentials" in context ? context.resolvedCredentials : await context.resolveCredentials?.();
906
+ return getBaseUrlFromResolvedCredentials(resolvedCredentials) ?? getBaseUrlFromOptionsCredentials(context.options?.credentials) ?? context.options?.baseUrl;
907
+ }
897
908
 
898
- // src/utils/constants.ts
899
- var LOGIN_PORTS = [49505, 50575, 52804, 55981, 61010, 63851];
900
- var LOGIN_TIMEOUT_MS = 3e5;
909
+ // src/utils/non-interactive.ts
910
+ function resolveNonInteractive(options) {
911
+ return options.nonInteractive === true || options.skipPrompts === true || !process.stdin.isTTY || !process.stdout.isTTY;
912
+ }
901
913
  var ZapierCliError = class extends zapierSdk.ZapierError {
902
914
  };
903
915
  var ZapierCliUserCancellationError = class extends ZapierCliError {
@@ -925,42 +937,164 @@ var ZapierCliValidationError = class extends ZapierCliError {
925
937
  }
926
938
  };
927
939
 
928
- // src/utils/spinner.ts
929
- var spinPromise = async (promise, text) => {
930
- const spinner = ora__default.default(text).start();
940
+ // src/utils/auth/client-credentials.ts
941
+ var CREDENTIALS_SCOPES = ["external", "credentials"];
942
+ var EMPTY_POLICY = {
943
+ version: 2,
944
+ statements: []
945
+ };
946
+ async function createCredentialsOnServer(api2, name, policy) {
947
+ const response = await api2.post(
948
+ "/api/v0/client-credentials",
949
+ {
950
+ name,
951
+ allowed_scopes: CREDENTIALS_SCOPES,
952
+ ...policy !== void 0 && { policy }
953
+ },
954
+ { authRequired: true, requiredScopes: ["credentials"] }
955
+ );
956
+ return {
957
+ clientId: response.data.client_id,
958
+ clientSecret: response.data.client_secret
959
+ };
960
+ }
961
+ async function deleteCredentialsOnServer(api2, clientId) {
962
+ await api2.delete(`/api/v0/client-credentials/${clientId}`, void 0, {
963
+ authRequired: true,
964
+ requiredScopes: ["credentials"]
965
+ });
966
+ }
967
+ async function setupClientCredentials({
968
+ api: api2,
969
+ name,
970
+ credentialsBaseUrl,
971
+ policy
972
+ }) {
973
+ const { clientId, clientSecret } = await createCredentialsOnServer(
974
+ api2,
975
+ name,
976
+ policy
977
+ );
931
978
  try {
932
- const result = await promise;
933
- spinner.succeed();
934
- return result;
935
- } catch (error) {
936
- if (error instanceof ZapierCliUserCancellationError) {
937
- spinner.stop();
938
- } else {
939
- spinner.fail();
979
+ await withRetry({
980
+ action: () => storeClientCredentials({
981
+ name,
982
+ clientId,
983
+ clientSecret,
984
+ scopes: [...CREDENTIALS_SCOPES],
985
+ baseUrl: credentialsBaseUrl
986
+ })
987
+ });
988
+ } catch (storeErr) {
989
+ try {
990
+ await withRetry({
991
+ action: () => deleteCredentialsOnServer(api2, clientId)
992
+ });
993
+ } catch {
994
+ console.error(
995
+ `Failed to roll back orphaned credential ${clientId}. Delete it manually with: zapier-sdk delete-client-credentials ${clientId}`
996
+ );
940
997
  }
941
- throw error;
998
+ throw storeErr;
942
999
  }
1000
+ return { clientId };
1001
+ }
1002
+
1003
+ // src/utils/constants.ts
1004
+ var LOGIN_PORTS = [49505, 50575, 52804, 55981, 61010, 63851];
1005
+ var LOGIN_TIMEOUT_MS = 3e5;
1006
+
1007
+ // src/utils/getCallablePromise.ts
1008
+ var getCallablePromise = () => {
1009
+ let resolve4 = () => {
1010
+ };
1011
+ let reject = () => {
1012
+ };
1013
+ const promise = new Promise((_resolve, _reject) => {
1014
+ resolve4 = _resolve;
1015
+ reject = _reject;
1016
+ });
1017
+ return {
1018
+ promise,
1019
+ resolve: resolve4,
1020
+ reject
1021
+ };
943
1022
  };
1023
+ var getCallablePromise_default = getCallablePromise;
944
1024
  var log = {
945
1025
  info: (message, ...args) => {
946
- console.log(chalk3__default.default.blue("\u2139"), message, ...args);
1026
+ console.error(chalk3__default.default.blue("\u2139"), message, ...args);
947
1027
  },
948
1028
  error: (message, ...args) => {
949
1029
  console.error(chalk3__default.default.red("\u2716"), message, ...args);
950
1030
  },
951
1031
  success: (message, ...args) => {
952
- console.log(chalk3__default.default.green("\u2713"), message, ...args);
1032
+ console.error(chalk3__default.default.green("\u2713"), message, ...args);
953
1033
  },
954
1034
  warn: (message, ...args) => {
955
- console.log(chalk3__default.default.yellow("\u26A0"), message, ...args);
1035
+ console.error(chalk3__default.default.yellow("\u26A0"), message, ...args);
956
1036
  },
957
1037
  debug: (message, ...args) => {
958
1038
  if (process.env.DEBUG === "true" || process.argv.includes("--debug")) {
959
- console.log(chalk3__default.default.gray("\u{1F41B}"), message, ...args);
1039
+ console.error(chalk3__default.default.gray("\u{1F41B}"), message, ...args);
960
1040
  }
961
1041
  }
962
1042
  };
963
1043
  var log_default = log;
1044
+ var spinPromise = async (promise, text) => {
1045
+ const spinner = ora__default.default(text).start();
1046
+ try {
1047
+ const result = await promise;
1048
+ spinner.succeed();
1049
+ return result;
1050
+ } catch (error) {
1051
+ if (error instanceof ZapierCliUserCancellationError) {
1052
+ spinner.stop();
1053
+ } else {
1054
+ spinner.fail();
1055
+ }
1056
+ throw error;
1057
+ }
1058
+ };
1059
+
1060
+ // src/utils/auth/oauth-callback.ts
1061
+ function getCallbackCode({
1062
+ callbackUrl,
1063
+ transaction,
1064
+ recoveryMessage
1065
+ }) {
1066
+ let parsed;
1067
+ try {
1068
+ parsed = new URL(callbackUrl.trim());
1069
+ } catch {
1070
+ throw new ZapierCliValidationError(
1071
+ "Paste the final OAuth callback URL from your browser."
1072
+ );
1073
+ }
1074
+ const expected = new URL(transaction.redirectUri);
1075
+ if (parsed.protocol !== "http:" || parsed.hostname !== expected.hostname || parsed.pathname !== expected.pathname || parsed.port !== expected.port) {
1076
+ throw new ZapierCliValidationError(
1077
+ `Expected the final OAuth callback URL to start with ${transaction.redirectUri}.`
1078
+ );
1079
+ }
1080
+ if (parsed.searchParams.get("state") !== transaction.state) {
1081
+ throw new ZapierCliValidationError(
1082
+ `OAuth state mismatch.${recoveryMessage ? ` ${recoveryMessage}` : ""}`
1083
+ );
1084
+ }
1085
+ if (parsed.searchParams.has("error")) {
1086
+ throw new ZapierCliValidationError(
1087
+ `Authorization denied: ${parsed.searchParams.get("error_description") ?? parsed.searchParams.get("error")}.${recoveryMessage ? ` ${recoveryMessage}` : ""}`
1088
+ );
1089
+ }
1090
+ const code = parsed.searchParams.get("code");
1091
+ if (!code) {
1092
+ throw new ZapierCliValidationError(
1093
+ "No authorization code found in the pasted callback URL."
1094
+ );
1095
+ }
1096
+ return code;
1097
+ }
964
1098
 
965
1099
  // src/utils/api/client.ts
966
1100
  var createApiClient = () => {
@@ -991,179 +1125,77 @@ var createApiClient = () => {
991
1125
  var api = createApiClient();
992
1126
  var client_default = api;
993
1127
 
994
- // src/utils/getCallablePromise.ts
995
- var getCallablePromise = () => {
996
- let resolve4 = () => {
997
- };
998
- let reject = () => {
999
- };
1000
- const promise = new Promise((_resolve, _reject) => {
1001
- resolve4 = _resolve;
1002
- reject = _reject;
1003
- });
1004
- return {
1005
- promise,
1006
- resolve: resolve4,
1007
- reject
1008
- };
1009
- };
1010
- var getCallablePromise_default = getCallablePromise;
1011
-
1012
- // src/utils/auth/oauth-flow.ts
1013
- var findAvailablePort = () => {
1014
- return new Promise((resolve4, reject) => {
1015
- let portIndex = 0;
1016
- const tryPort = (port) => {
1017
- const server = express__default.default().listen(port, () => {
1018
- server.close();
1019
- resolve4(port);
1020
- });
1021
- server.on("error", (err) => {
1022
- if (err.code === "EADDRINUSE") {
1023
- if (portIndex < LOGIN_PORTS.length) {
1024
- tryPort(LOGIN_PORTS[portIndex++]);
1025
- } else {
1026
- reject(
1027
- new Error(
1028
- `All configured OAuth callback ports are busy: ${LOGIN_PORTS.join(", ")}. Please try again later or close applications using these ports.`
1029
- )
1030
- );
1031
- }
1032
- } else {
1033
- reject(err);
1034
- }
1035
- });
1036
- };
1037
- if (LOGIN_PORTS.length > 0) {
1038
- tryPort(LOGIN_PORTS[portIndex++]);
1039
- } else {
1040
- reject(new Error("No OAuth callback ports configured"));
1041
- }
1042
- });
1043
- };
1044
- var generateRandomString = () => {
1128
+ // src/utils/auth/oauth-transaction.ts
1129
+ var OAUTH_LOOPBACK_HOST = "localhost";
1130
+ function buildBrowserAuthUrl({
1131
+ authorizeUrl,
1132
+ entryPoint = "login"
1133
+ }) {
1134
+ if (entryPoint === "login") return authorizeUrl;
1135
+ const parsedAuthorizeUrl = new URL(authorizeUrl);
1136
+ const signupUrl = new URL("/sign-up", parsedAuthorizeUrl);
1137
+ signupUrl.searchParams.set("skipOnboarding", "true");
1138
+ signupUrl.searchParams.set(
1139
+ "next",
1140
+ `${parsedAuthorizeUrl.pathname}${parsedAuthorizeUrl.search}`
1141
+ );
1142
+ return signupUrl.toString();
1143
+ }
1144
+ function generateRandomString() {
1045
1145
  const array = new Uint32Array(28);
1046
1146
  crypto__default.default.getRandomValues(array);
1047
1147
  return Array.from(array, (dec) => ("0" + dec.toString(16)).slice(-2)).join(
1048
1148
  ""
1049
1149
  );
1050
- };
1150
+ }
1051
1151
  function ensureOfflineAccess(scope) {
1052
- if (scope.includes("offline_access")) {
1053
- return scope;
1054
- }
1152
+ if (scope.includes("offline_access")) return scope;
1055
1153
  return `${scope} offline_access`;
1056
1154
  }
1057
- async function runOauthFlow({
1058
- timeoutMs = LOGIN_TIMEOUT_MS,
1155
+ async function prepareOauthTransaction({
1059
1156
  pkceCredentials,
1060
- baseUrl
1157
+ baseUrl,
1158
+ redirectUri,
1159
+ entryPoint = "login"
1061
1160
  }) {
1062
1161
  const { clientId, tokenUrl, authorizeUrl } = getPkceLoginConfig({
1063
1162
  credentials: pkceCredentials,
1064
1163
  baseUrl
1065
1164
  });
1066
- const scope = ensureOfflineAccess(
1067
- pkceCredentials?.scope || "internal credentials"
1068
- );
1069
- const availablePort = await findAvailablePort();
1070
- const redirectUri = `http://localhost:${availablePort}/oauth`;
1071
- log_default.info(`Using port ${availablePort} for OAuth callback`);
1072
- const {
1073
- promise: promisedCode,
1074
- resolve: setCode,
1075
- reject: rejectCode
1076
- } = getCallablePromise_default();
1077
- const oauthState = generateRandomString();
1078
- const expressApp = express__default.default();
1079
- expressApp.get("/oauth", (req, res) => {
1080
- res.setHeader("Connection", "close");
1081
- if (req.query.state !== oauthState) {
1082
- rejectCode(new Error("OAuth state mismatch \u2014 possible CSRF"));
1083
- res.status(400).end("Invalid state. You can close this tab.");
1084
- return;
1085
- }
1086
- if (req.query.error) {
1087
- const desc = req.query.error_description ?? req.query.error;
1088
- rejectCode(new Error(`Authorization denied: ${desc}`));
1089
- res.end("Authorization was denied. You can close this tab.");
1090
- return;
1091
- }
1092
- if (!req.query.code) {
1093
- rejectCode(new Error("No authorization code received"));
1094
- res.end("No authorization code received. You can close this tab.");
1095
- return;
1096
- }
1097
- setCode(String(req.query.code));
1098
- res.end("You can now close this tab and return to the CLI.");
1099
- });
1100
- const server = expressApp.listen(availablePort);
1101
- const connections = /* @__PURE__ */ new Set();
1102
- server.on("connection", (conn) => {
1103
- connections.add(conn);
1104
- conn.on("close", () => connections.delete(conn));
1105
- });
1106
- const cleanup = () => {
1107
- server.close();
1108
- log_default.info("\n\u274C Login cancelled by user");
1109
- rejectCode(new ZapierCliUserCancellationError());
1110
- };
1111
- process.on("SIGINT", cleanup);
1112
- process.on("SIGTERM", cleanup);
1113
1165
  const { code_verifier: codeVerifier, code_challenge: codeChallenge } = await pkceChallenge__default.default();
1166
+ const state = generateRandomString();
1114
1167
  const authUrl = `${authorizeUrl}?${new URLSearchParams({
1115
1168
  response_type: "code",
1116
1169
  client_id: clientId,
1117
1170
  redirect_uri: redirectUri,
1118
- scope,
1119
- state: oauthState,
1171
+ scope: ensureOfflineAccess(
1172
+ pkceCredentials?.scope || "internal credentials"
1173
+ ),
1174
+ state,
1120
1175
  code_challenge: codeChallenge,
1121
1176
  code_challenge_method: "S256"
1122
1177
  }).toString()}`;
1123
- log_default.info("Opening your browser to log in.");
1124
- log_default.info("If it doesn't open, visit:", authUrl);
1125
- open__default.default(authUrl);
1126
- let timeoutTimer;
1127
- try {
1128
- await spinPromise(
1129
- Promise.race([
1130
- promisedCode,
1131
- new Promise((_resolve, reject) => {
1132
- timeoutTimer = setTimeout(() => {
1133
- reject(
1134
- new Error(
1135
- `Login timed out after ${Math.round(timeoutMs / 1e3)} seconds.`
1136
- )
1137
- );
1138
- }, timeoutMs);
1139
- })
1140
- ]),
1141
- "Waiting for you to login and authorize"
1142
- );
1143
- } finally {
1144
- if (timeoutTimer) {
1145
- clearTimeout(timeoutTimer);
1146
- }
1147
- process.off("SIGINT", cleanup);
1148
- process.off("SIGTERM", cleanup);
1149
- await new Promise((resolve4) => {
1150
- const timeout = setTimeout(() => {
1151
- log_default.info("Server close timed out, forcing connection shutdown...");
1152
- connections.forEach((conn) => conn.destroy());
1153
- resolve4();
1154
- }, 1e3);
1155
- server.close(() => {
1156
- clearTimeout(timeout);
1157
- resolve4();
1158
- });
1159
- });
1160
- }
1161
- log_default.info("Exchanging authorization code for tokens...");
1178
+ return {
1179
+ browserAuthUrl: buildBrowserAuthUrl({ authorizeUrl: authUrl, entryPoint }),
1180
+ clientId,
1181
+ codeVerifier,
1182
+ redirectUri,
1183
+ state,
1184
+ tokenUrl
1185
+ };
1186
+ }
1187
+ async function exchangeOauthCode({
1188
+ tokenUrl,
1189
+ code,
1190
+ redirectUri,
1191
+ clientId,
1192
+ codeVerifier
1193
+ }) {
1162
1194
  const { data } = await client_default.post(
1163
1195
  tokenUrl,
1164
1196
  {
1165
1197
  grant_type: "authorization_code",
1166
- code: await promisedCode,
1198
+ code,
1167
1199
  redirect_uri: redirectUri,
1168
1200
  client_id: clientId,
1169
1201
  code_verifier: codeVerifier
@@ -1175,7 +1207,6 @@ async function runOauthFlow({
1175
1207
  }
1176
1208
  }
1177
1209
  );
1178
- log_default.info("Token exchange completed successfully");
1179
1210
  return {
1180
1211
  accessToken: data.access_token,
1181
1212
  refreshToken: data.refresh_token,
@@ -1183,105 +1214,467 @@ async function runOauthFlow({
1183
1214
  };
1184
1215
  }
1185
1216
 
1186
- // src/utils/auth/client-credentials.ts
1187
- var CREDENTIALS_SCOPES = ["external", "credentials"];
1188
- var EMPTY_POLICY = {
1189
- version: 2,
1190
- statements: []
1217
+ // src/utils/auth/oauth-flow.ts
1218
+ var OauthFlowTimeoutError = class extends Error {
1219
+ constructor(timeoutMs) {
1220
+ super("OAuth flow timed out");
1221
+ this.timeoutMs = timeoutMs;
1222
+ this.name = "OauthFlowTimeoutError";
1223
+ }
1191
1224
  };
1192
- async function createCredentialsOnServer(api2, name, policy) {
1193
- const response = await api2.post(
1194
- "/api/v0/client-credentials",
1195
- {
1196
- name,
1197
- allowed_scopes: CREDENTIALS_SCOPES,
1198
- ...policy !== void 0 && { policy }
1199
- },
1200
- { authRequired: true, requiredScopes: ["credentials"] }
1225
+ var OauthAuthorizationDeniedError = class extends Error {
1226
+ constructor(reason) {
1227
+ super("OAuth authorization denied");
1228
+ this.reason = reason;
1229
+ this.name = "OauthAuthorizationDeniedError";
1230
+ }
1231
+ };
1232
+ function findAvailablePort() {
1233
+ return new Promise((resolve4, reject) => {
1234
+ let portIndex = 0;
1235
+ const tryPort = (port) => {
1236
+ const server = express__default.default().listen(port, OAUTH_LOOPBACK_HOST, () => {
1237
+ server.close();
1238
+ resolve4(port);
1239
+ });
1240
+ server.on("error", (err) => {
1241
+ if (err.code === "EADDRINUSE" && portIndex < LOGIN_PORTS.length) {
1242
+ tryPort(LOGIN_PORTS[portIndex++]);
1243
+ } else if (err.code === "EADDRINUSE") {
1244
+ reject(
1245
+ new Error(
1246
+ `All configured OAuth callback ports are busy: ${LOGIN_PORTS.join(", ")}. Please try again later or close applications using these ports.`
1247
+ )
1248
+ );
1249
+ } else {
1250
+ reject(err);
1251
+ }
1252
+ });
1253
+ };
1254
+ if (LOGIN_PORTS.length > 0) tryPort(LOGIN_PORTS[portIndex++]);
1255
+ else reject(new Error("No OAuth callback ports configured"));
1256
+ });
1257
+ }
1258
+ async function runLoginOauthFlow(options) {
1259
+ return runOauthFlowEntryPoint({
1260
+ ...options,
1261
+ entryPoint: "login",
1262
+ authAction: "log in",
1263
+ flowName: "Login"
1264
+ });
1265
+ }
1266
+ async function runSignupOauthFlow(options) {
1267
+ if (options.headless) {
1268
+ return runOauthFlowEntryPoint({
1269
+ ...options,
1270
+ entryPoint: "signup",
1271
+ authAction: "sign up",
1272
+ flowName: "Signup",
1273
+ headless: true
1274
+ });
1275
+ }
1276
+ return runOauthFlowEntryPoint({
1277
+ ...options,
1278
+ entryPoint: "signup",
1279
+ authAction: "sign up",
1280
+ flowName: "Signup"
1281
+ });
1282
+ }
1283
+ async function runOauthFlowEntryPoint({
1284
+ flowName,
1285
+ ...options
1286
+ }) {
1287
+ try {
1288
+ return options.headless ? await runHeadlessSignupOauthFlow(options) : await runOauthFlow(options);
1289
+ } catch (error) {
1290
+ if (error instanceof OauthFlowTimeoutError) {
1291
+ throw new Error(
1292
+ withRecoveryMessage(
1293
+ `${flowName} timed out after ${Math.round(error.timeoutMs / 1e3)} seconds.`,
1294
+ options.recoveryMessage
1295
+ )
1296
+ );
1297
+ }
1298
+ if (error instanceof OauthAuthorizationDeniedError) {
1299
+ throw new Error(
1300
+ withRecoveryMessage(
1301
+ `Authorization denied: ${error.reason}.`,
1302
+ options.recoveryMessage
1303
+ )
1304
+ );
1305
+ }
1306
+ if (error instanceof ZapierCliUserCancellationError && !options.silent) {
1307
+ log_default.info(`
1308
+ \u274C ${flowName} cancelled by user`);
1309
+ }
1310
+ throw error;
1311
+ }
1312
+ }
1313
+ function withRecoveryMessage(message, recoveryMessage) {
1314
+ return recoveryMessage ? `${message} ${recoveryMessage}` : message;
1315
+ }
1316
+ async function runOauthFlow({
1317
+ timeoutMs = LOGIN_TIMEOUT_MS,
1318
+ pkceCredentials,
1319
+ baseUrl,
1320
+ entryPoint,
1321
+ authAction,
1322
+ silent = false,
1323
+ onProgress
1324
+ }) {
1325
+ const port = await findAvailablePort();
1326
+ if (!silent) log_default.info(`Using port ${port} for OAuth callback`);
1327
+ const transaction = await prepareOauthTransaction({
1328
+ pkceCredentials,
1329
+ baseUrl,
1330
+ redirectUri: `http://${OAUTH_LOOPBACK_HOST}:${port}/oauth`,
1331
+ entryPoint
1332
+ });
1333
+ const code = await collectLocalCallbackCode({
1334
+ transaction,
1335
+ timeoutMs,
1336
+ authAction,
1337
+ silent,
1338
+ onProgress
1339
+ });
1340
+ onProgress?.({ type: "callback_accepted" });
1341
+ if (!silent) log_default.info("Exchanging authorization code for tokens...");
1342
+ onProgress?.({ type: "token_exchange_started" });
1343
+ const tokens = await exchangeOauthCode({ ...transaction, code });
1344
+ if (!silent) log_default.info("Token exchange completed successfully");
1345
+ onProgress?.({ type: "token_exchange_completed" });
1346
+ return tokens;
1347
+ }
1348
+ async function readHeadlessCallbackUrl({
1349
+ timeoutMs,
1350
+ interactive,
1351
+ recoveryMessage
1352
+ }) {
1353
+ const timeoutMessage = withRecoveryMessage(
1354
+ `Signup timed out after ${Math.round(timeoutMs / 1e3)} seconds.`,
1355
+ recoveryMessage
1201
1356
  );
1202
- return {
1203
- clientId: response.data.client_id,
1204
- clientSecret: response.data.client_secret
1205
- };
1357
+ const missingCallbackUrlMessage = withRecoveryMessage(
1358
+ "Paste the final OAuth callback URL from your browser.",
1359
+ recoveryMessage
1360
+ );
1361
+ const rl = promises$1.createInterface({ input: process.stdin, output: process.stderr });
1362
+ const abortController = new AbortController();
1363
+ const timeoutTimer = setTimeout(() => abortController.abort(), timeoutMs);
1364
+ const readUrl = interactive ? rl.question("Paste the final OAuth callback URL: ", {
1365
+ signal: abortController.signal
1366
+ }) : new Promise((resolve4, reject) => {
1367
+ let settled = false;
1368
+ const settleResolve = (value) => {
1369
+ settled = true;
1370
+ resolve4(value);
1371
+ };
1372
+ const settleReject = (error) => {
1373
+ if (settled) return;
1374
+ settled = true;
1375
+ reject(error);
1376
+ };
1377
+ abortController.signal.addEventListener(
1378
+ "abort",
1379
+ () => settleReject(new Error(timeoutMessage)),
1380
+ { once: true }
1381
+ );
1382
+ rl.once("line", settleResolve);
1383
+ rl.once(
1384
+ "close",
1385
+ () => settleReject(new ZapierCliValidationError(missingCallbackUrlMessage))
1386
+ );
1387
+ rl.once("error", settleReject);
1388
+ });
1389
+ try {
1390
+ return await readUrl.catch((error) => {
1391
+ if (error instanceof Error && error.name === "AbortError") {
1392
+ throw new Error(timeoutMessage);
1393
+ }
1394
+ throw error;
1395
+ });
1396
+ } finally {
1397
+ clearTimeout(timeoutTimer);
1398
+ rl.close();
1399
+ }
1206
1400
  }
1207
- async function deleteCredentialsOnServer(api2, clientId) {
1208
- await api2.delete(`/api/v0/client-credentials/${clientId}`, void 0, {
1209
- authRequired: true,
1210
- requiredScopes: ["credentials"]
1401
+ async function runHeadlessSignupOauthFlow({
1402
+ timeoutMs = LOGIN_TIMEOUT_MS,
1403
+ pkceCredentials,
1404
+ baseUrl,
1405
+ interactive = true,
1406
+ onProgress,
1407
+ recoveryMessage
1408
+ }) {
1409
+ const port = LOGIN_PORTS[0];
1410
+ const transaction = await prepareOauthTransaction({
1411
+ pkceCredentials,
1412
+ baseUrl,
1413
+ redirectUri: `http://${OAUTH_LOOPBACK_HOST}:${port}/oauth`,
1414
+ entryPoint: "signup"
1415
+ });
1416
+ console.log(
1417
+ "Use this mode when signing up from a machine that has no browser."
1418
+ );
1419
+ console.log("Open this signup URL in a browser on another machine:");
1420
+ console.log(transaction.browserAuthUrl);
1421
+ console.log(
1422
+ `When the browser lands on ${transaction.redirectUri} and cannot connect, paste the full final URL back here.`
1423
+ );
1424
+ const callbackUrl = await readHeadlessCallbackUrl({
1425
+ timeoutMs,
1426
+ interactive,
1427
+ recoveryMessage
1428
+ });
1429
+ const code = getCallbackCode({
1430
+ callbackUrl,
1431
+ transaction,
1432
+ recoveryMessage
1211
1433
  });
1434
+ onProgress?.({ type: "callback_accepted" });
1435
+ console.log("Exchanging authorization code for tokens...");
1436
+ onProgress?.({ type: "token_exchange_started" });
1437
+ const tokens = await exchangeOauthCode({ ...transaction, code });
1438
+ onProgress?.({ type: "token_exchange_completed" });
1439
+ return tokens;
1212
1440
  }
1213
- async function setupClientCredentials({
1214
- api: api2,
1215
- name,
1216
- credentialsBaseUrl,
1217
- policy
1441
+ async function collectLocalCallbackCode({
1442
+ transaction,
1443
+ timeoutMs,
1444
+ authAction,
1445
+ silent,
1446
+ onProgress
1218
1447
  }) {
1219
- const { clientId, clientSecret } = await createCredentialsOnServer(
1220
- api2,
1221
- name,
1222
- policy
1448
+ const { promise, resolve: resolve4, reject } = getCallablePromise_default();
1449
+ const app = express__default.default();
1450
+ app.get("/oauth", (req, res) => {
1451
+ res.setHeader("Connection", "close");
1452
+ if (req.query.state !== transaction.state) {
1453
+ res.status(400).end("Invalid state. You can close this tab.");
1454
+ } else if (req.query.error) {
1455
+ reject(
1456
+ new OauthAuthorizationDeniedError(
1457
+ String(req.query.error_description ?? req.query.error)
1458
+ )
1459
+ );
1460
+ res.end("Authorization was denied. You can close this tab.");
1461
+ } else if (!req.query.code) {
1462
+ reject(new Error("No authorization code received"));
1463
+ res.end("No authorization code received. You can close this tab.");
1464
+ } else {
1465
+ resolve4(String(req.query.code));
1466
+ res.end("You can now close this tab and return to the CLI.");
1467
+ }
1468
+ });
1469
+ const server = app.listen(
1470
+ Number(new URL(transaction.redirectUri).port),
1471
+ OAUTH_LOOPBACK_HOST
1223
1472
  );
1473
+ const connections = /* @__PURE__ */ new Set();
1474
+ server.on("connection", (conn) => {
1475
+ connections.add(conn);
1476
+ conn.on("close", () => connections.delete(conn));
1477
+ });
1478
+ const cleanup = () => {
1479
+ server.close();
1480
+ reject(new ZapierCliUserCancellationError());
1481
+ };
1482
+ process.on("SIGINT", cleanup);
1483
+ process.on("SIGTERM", cleanup);
1484
+ let timeoutTimer;
1485
+ try {
1486
+ await waitForServerListening(server);
1487
+ await openBrowser({ transaction, authAction, silent, onProgress });
1488
+ const waitForCode = Promise.race([
1489
+ promise,
1490
+ new Promise((_resolve, rejectTimeout) => {
1491
+ timeoutTimer = setTimeout(() => {
1492
+ rejectTimeout(new OauthFlowTimeoutError(timeoutMs));
1493
+ }, timeoutMs);
1494
+ })
1495
+ ]);
1496
+ onProgress?.({ type: "callback_waiting" });
1497
+ return silent ? await waitForCode : await spinPromise(
1498
+ waitForCode,
1499
+ `Waiting for you to ${authAction} and authorize`
1500
+ );
1501
+ } finally {
1502
+ if (timeoutTimer) clearTimeout(timeoutTimer);
1503
+ process.off("SIGINT", cleanup);
1504
+ process.off("SIGTERM", cleanup);
1505
+ await closeServer({ server, connections, silent });
1506
+ }
1507
+ }
1508
+ async function waitForServerListening(server) {
1509
+ if (server.listening) return;
1510
+ await new Promise((resolve4, reject) => {
1511
+ const cleanup = () => {
1512
+ server.off("listening", handleListening);
1513
+ server.off("error", handleError);
1514
+ };
1515
+ const handleListening = () => {
1516
+ cleanup();
1517
+ resolve4();
1518
+ };
1519
+ const handleError = (error) => {
1520
+ cleanup();
1521
+ reject(error);
1522
+ };
1523
+ server.once("listening", handleListening);
1524
+ server.once("error", handleError);
1525
+ });
1526
+ }
1527
+ async function openBrowser({
1528
+ transaction,
1529
+ authAction,
1530
+ silent,
1531
+ onProgress
1532
+ }) {
1533
+ if (!silent) {
1534
+ log_default.info(`Opening your browser to ${authAction}.`);
1535
+ log_default.info("If it doesn't open, visit:", transaction.browserAuthUrl);
1536
+ }
1537
+ onProgress?.({ type: "browser_opening", url: transaction.browserAuthUrl });
1224
1538
  try {
1225
- await withRetry({
1226
- action: () => storeClientCredentials({
1227
- name,
1228
- clientId,
1229
- clientSecret,
1230
- scopes: [...CREDENTIALS_SCOPES],
1231
- baseUrl: credentialsBaseUrl
1232
- })
1233
- });
1234
- } catch (storeErr) {
1235
- try {
1236
- await withRetry({
1237
- action: () => deleteCredentialsOnServer(api2, clientId)
1238
- });
1239
- } catch {
1240
- console.error(
1241
- `Failed to roll back orphaned credential ${clientId}. Delete it manually with: zapier-sdk delete-client-credentials ${clientId}`
1539
+ await open__default.default(transaction.browserAuthUrl);
1540
+ onProgress?.({ type: "browser_opened", url: transaction.browserAuthUrl });
1541
+ } catch (err) {
1542
+ const reason = err instanceof Error ? err.message : String(err);
1543
+ if (!silent) {
1544
+ log_default.info(
1545
+ `Browser did not open automatically to ${authAction}: ${reason}`
1242
1546
  );
1547
+ log_default.info("Visit this URL manually:", transaction.browserAuthUrl);
1243
1548
  }
1244
- throw storeErr;
1549
+ onProgress?.({
1550
+ type: "browser_open_failed",
1551
+ url: transaction.browserAuthUrl,
1552
+ reason
1553
+ });
1245
1554
  }
1246
- return { clientId };
1247
1555
  }
1248
- function getBaseUrlFromResolvedCredentials(credentials) {
1249
- if (credentials && zapierSdk.isCredentialsObject(credentials)) {
1250
- return credentials.baseUrl;
1251
- }
1252
- return void 0;
1556
+ async function closeServer({
1557
+ server,
1558
+ connections,
1559
+ silent
1560
+ }) {
1561
+ await new Promise((resolve4) => {
1562
+ const timeout = setTimeout(() => {
1563
+ if (!silent)
1564
+ log_default.info("Server close timed out, forcing connection shutdown...");
1565
+ connections.forEach((conn) => conn.destroy());
1566
+ resolve4();
1567
+ }, 1e3);
1568
+ server.close(() => {
1569
+ clearTimeout(timeout);
1570
+ resolve4();
1571
+ });
1572
+ });
1253
1573
  }
1254
- function getBaseUrlFromOptionsCredentials(credentials) {
1255
- if (credentials && typeof credentials === "object" && "baseUrl" in credentials && typeof credentials.baseUrl === "string") {
1256
- return credentials.baseUrl;
1257
- }
1258
- return void 0;
1574
+
1575
+ // src/utils/auth/oauth-errors.ts
1576
+ var SENSITIVE_OAUTH_FIELDS = [
1577
+ "access_token",
1578
+ "refresh_token",
1579
+ "id_token",
1580
+ "client_secret",
1581
+ "code_verifier",
1582
+ "code_challenge"
1583
+ ];
1584
+ function getErrorMessage(error) {
1585
+ return error instanceof Error ? error.message : String(error);
1259
1586
  }
1260
- async function resolveCredentialsBaseUrl(context) {
1261
- const resolvedCredentials = "resolvedCredentials" in context ? context.resolvedCredentials : await context.resolveCredentials?.();
1262
- return getBaseUrlFromResolvedCredentials(resolvedCredentials) ?? getBaseUrlFromOptionsCredentials(context.options?.credentials) ?? context.options?.baseUrl;
1587
+ function toCamelCase(field) {
1588
+ return field.replace(
1589
+ /_([a-z])/g,
1590
+ (_match, letter) => letter.toUpperCase()
1591
+ );
1263
1592
  }
1264
-
1265
- // src/utils/non-interactive.ts
1266
- function resolveNonInteractive(options) {
1267
- return (options.nonInteractive ?? options.skipPrompts) === true || !process.stdin.isTTY || !process.stdout.isTTY;
1593
+ function escapeRegExp(value) {
1594
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1595
+ }
1596
+ var sensitiveOauthFieldPattern = Array.from(
1597
+ new Set(
1598
+ SENSITIVE_OAUTH_FIELDS.flatMap((field) => [field, toCamelCase(field)])
1599
+ )
1600
+ ).map(escapeRegExp).join("|");
1601
+ var sensitiveQueryParamPattern = new RegExp(
1602
+ `([?&])(${sensitiveOauthFieldPattern})(=)[^&#\\s"'<>]*`,
1603
+ "gi"
1604
+ );
1605
+ function redactSensitiveOauthErrorMessage(message) {
1606
+ return message.replace(
1607
+ sensitiveQueryParamPattern,
1608
+ (_match, prefix, key, separator) => `${prefix}${key}${separator}[REDACTED]`
1609
+ ).replace(
1610
+ new RegExp(`"(${sensitiveOauthFieldPattern})"(\\s*:\\s*)"[^"]*"`, "g"),
1611
+ (_match, key, separator) => `"${key}"${separator}"[REDACTED]"`
1612
+ );
1613
+ }
1614
+ function toRedactedOauthError(error) {
1615
+ const message = redactSensitiveOauthErrorMessage(getErrorMessage(error));
1616
+ if (error instanceof ZapierCliValidationError) {
1617
+ return new ZapierCliValidationError(message);
1618
+ }
1619
+ if (error instanceof Error) {
1620
+ const redactedError = new Error(message);
1621
+ redactedError.name = error.name;
1622
+ return redactedError;
1623
+ }
1624
+ return new ZapierCliValidationError(message);
1268
1625
  }
1269
- var LoginSchema = zod.z.object({
1270
- timeout: zod.z.string().optional().describe("Login timeout in seconds (default: 300)"),
1271
- useApprovals: zod.z.boolean().optional().describe(
1272
- "Require approvals for actions performed with these credentials"
1273
- ),
1274
- nonInteractive: zod.z.boolean().optional().describe(
1275
- "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."
1276
- ),
1277
- /** @deprecated Use `nonInteractive` instead. */
1278
- skipPrompts: zod.z.boolean().optional().meta({
1279
- deprecated: true,
1280
- deprecationMessage: "Use --non-interactive instead."
1281
- })
1282
- }).describe("Log in to Zapier to access your account");
1283
1626
 
1284
- // src/plugins/login/index.ts
1627
+ // src/utils/auth/account-auth.ts
1628
+ var 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?";
1629
+ var SIGNUP_RECOVERY_MESSAGE = "Restart `zapier-sdk signup` to generate a fresh signup URL and try again.";
1630
+ var HEADLESS_SIGNUP_RECOVERY_MESSAGE = "Restart `zapier-sdk signup --headless` to generate a fresh signup URL and try again.";
1631
+ function getEntryPointLabel(entryPoint) {
1632
+ return entryPoint === "signup" ? "Signup" : "Login";
1633
+ }
1634
+ function getActiveCredentialsAction(entryPoint) {
1635
+ return entryPoint === "signup" ? "continue signup" : "log in again";
1636
+ }
1637
+ function getCredentialsPromptMessage(entryPoint) {
1638
+ return entryPoint === "signup" ? "Enter a name to identify these credentials:" : "Enter a name to identify them:";
1639
+ }
1640
+ function getProfileMessage(entryPoint, email) {
1641
+ return entryPoint === "signup" ? `\u{1F464} Authenticated as ${email}` : `\u{1F464} Logged in as ${email}`;
1642
+ }
1643
+ function defaultCredentialsName(email) {
1644
+ return `${email}@${os.hostname()}`;
1645
+ }
1646
+ function validateCredentialsName(name) {
1647
+ const trimmedName = name.trim();
1648
+ if (!trimmedName) throw new ZapierCliValidationError("Name cannot be empty");
1649
+ return trimmedName;
1650
+ }
1651
+ async function promptCredentialsName({
1652
+ email,
1653
+ promptMessage
1654
+ }) {
1655
+ const { credentialName } = await inquirer__default.default.prompt([
1656
+ {
1657
+ type: "input",
1658
+ name: "credentialName",
1659
+ message: promptMessage,
1660
+ default: defaultCredentialsName(email),
1661
+ validate: (input) => {
1662
+ try {
1663
+ validateCredentialsName(input);
1664
+ return true;
1665
+ } catch (err) {
1666
+ return err instanceof Error ? err.message : String(err);
1667
+ }
1668
+ }
1669
+ }
1670
+ ]);
1671
+ return validateCredentialsName(credentialName);
1672
+ }
1673
+ function resolveDefaultCredentialsName({
1674
+ email
1675
+ }) {
1676
+ return validateCredentialsName(defaultCredentialsName(email));
1677
+ }
1285
1678
  function toPkceCredentials(credentials) {
1286
1679
  if (credentials && zapierSdk.isCredentialsObject(credentials) && !("clientSecret" in credentials)) {
1287
1680
  return {
@@ -1293,104 +1686,125 @@ function toPkceCredentials(credentials) {
1293
1686
  }
1294
1687
  return void 0;
1295
1688
  }
1296
- async function confirmRevokeAndRelogin(activeCredentials, nonInteractive) {
1297
- if (nonInteractive) {
1689
+ function parseTimeoutSeconds(timeout) {
1690
+ if (timeout === void 0) return 300;
1691
+ const timeoutSeconds = Number(timeout);
1692
+ if (!Number.isInteger(timeoutSeconds) || timeoutSeconds <= 0) {
1298
1693
  throw new ZapierCliValidationError(
1299
- `Already logged in as "${activeCredentials.name}". Run \`logout\` first or use an interactive terminal to re-authenticate.`
1694
+ "Timeout must be a positive integer (seconds)."
1300
1695
  );
1301
1696
  }
1697
+ return timeoutSeconds;
1698
+ }
1699
+ async function promptConfirm({
1700
+ message,
1701
+ defaultValue
1702
+ }) {
1302
1703
  const { confirmed } = await inquirer__default.default.prompt([
1303
- {
1304
- type: "confirm",
1305
- name: "confirmed",
1704
+ { type: "confirm", name: "confirmed", message, default: defaultValue }
1705
+ ]);
1706
+ return confirmed;
1707
+ }
1708
+ function promptlessCredentialResetError(credentials) {
1709
+ throw new ZapierCliValidationError(
1710
+ `Already logged in as "${credentials.name}". Run \`logout\` first or use an interactive terminal to re-authenticate.`
1711
+ );
1712
+ }
1713
+ function promptlessLegacyJwtUpgradeError() {
1714
+ throw new ZapierCliValidationError(
1715
+ "Legacy JWT login detected. Run `logout` first or use an interactive terminal to migrate to client credentials."
1716
+ );
1717
+ }
1718
+ async function clearExistingAuthState({
1719
+ sdk,
1720
+ baseUrl,
1721
+ interactive,
1722
+ entryPoint
1723
+ }) {
1724
+ const activeCredentials = getActiveCredentials({ baseUrl });
1725
+ const flowLabel = getEntryPointLabel(entryPoint);
1726
+ if (activeCredentials) {
1727
+ const confirmed = interactive ? await promptConfirm({
1728
+ defaultValue: false,
1306
1729
  message: `You are already logged in as "${activeCredentials.name}".
1307
1730
  Logging out will delete these credentials and may interrupt other Zapier SDK or CLI sessions using them.
1308
- Log out and log in again?`,
1309
- default: false
1731
+ Log out and ${getActiveCredentialsAction(entryPoint)}?`
1732
+ }) : promptlessCredentialResetError(activeCredentials);
1733
+ if (!confirmed) {
1734
+ console.log(`${flowLabel} cancelled.`);
1735
+ return false;
1310
1736
  }
1311
- ]);
1312
- if (!confirmed) {
1313
- console.log("Login cancelled.");
1314
- return false;
1315
- }
1316
- return true;
1317
- }
1318
- async function confirmJwtMigration(nonInteractive) {
1319
- if (nonInteractive) {
1320
- throw new ZapierCliValidationError(
1321
- "Legacy JWT login detected. Run `logout` first or use an interactive terminal to migrate to client credentials."
1322
- );
1323
- }
1324
- const { confirmed } = await inquirer__default.default.prompt([
1325
- {
1326
- type: "confirm",
1327
- name: "confirmed",
1328
- message: "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?",
1329
- default: true
1737
+ try {
1738
+ await revokeCredentials({
1739
+ api: sdk.context.api,
1740
+ credentials: activeCredentials
1741
+ });
1742
+ } catch {
1743
+ if (!interactive) {
1744
+ throw new ZapierCliValidationError(
1745
+ `${flowLabel} cleanup failed and cannot be reset without confirmation. Re-run with an interactive terminal.`
1746
+ );
1747
+ }
1748
+ const reset = await promptConfirm({
1749
+ defaultValue: false,
1750
+ message: `${flowLabel} cleanup failed. Reset local session state and continue?`
1751
+ });
1752
+ if (!reset) {
1753
+ console.log(`${flowLabel} cancelled.`);
1754
+ return false;
1755
+ }
1756
+ await deleteStoredClientCredentials({
1757
+ name: activeCredentials.name,
1758
+ baseUrl: activeCredentials.baseUrl
1759
+ });
1330
1760
  }
1331
- ]);
1332
- if (!confirmed) {
1333
- console.log("Login cancelled.");
1334
- return false;
1335
- }
1336
- return true;
1337
- }
1338
- async function confirmLocalLoginReset(nonInteractive) {
1339
- if (nonInteractive) {
1340
- throw new ZapierCliValidationError(
1341
- "Login cleanup failed and cannot be reset without confirmation. Re-run with an interactive terminal."
1342
- );
1343
- }
1344
- const { confirmed } = await inquirer__default.default.prompt([
1345
- {
1346
- type: "confirm",
1347
- name: "confirmed",
1348
- message: "Login cleanup failed. Reset local session state and continue?",
1349
- default: false
1761
+ } else if (hasLegacyJwtConfig()) {
1762
+ const confirmed = interactive ? await promptConfirm({
1763
+ defaultValue: true,
1764
+ message: LEGACY_JWT_UPGRADE_PROMPT
1765
+ }) : promptlessLegacyJwtUpgradeError();
1766
+ if (!confirmed) {
1767
+ console.log(`${flowLabel} cancelled.`);
1768
+ return false;
1350
1769
  }
1351
- ]);
1352
- if (!confirmed) {
1353
- console.log("Login cancelled.");
1354
- return false;
1355
1770
  }
1356
1771
  return true;
1357
1772
  }
1358
- function parseTimeoutSeconds(timeout) {
1359
- const timeoutSeconds = timeout ? parseInt(timeout, 10) : 300;
1360
- if (isNaN(timeoutSeconds) || timeoutSeconds <= 0) {
1361
- throw new Error("Timeout must be a positive number");
1362
- }
1363
- return timeoutSeconds;
1773
+ async function getProfile(api2) {
1774
+ return api2.get("/zapier/api/v4/profile/", {
1775
+ authRequired: true
1776
+ });
1364
1777
  }
1365
- async function promptCredentialsName(email, nonInteractive) {
1366
- const fallback = `${email}@${os.hostname()}`;
1367
- if (nonInteractive) {
1368
- return fallback;
1778
+ async function saveClientCredentials({
1779
+ api: api2,
1780
+ name,
1781
+ credentialsBaseUrl,
1782
+ useApprovals,
1783
+ cleanupLogPrefix
1784
+ }) {
1785
+ await setupClientCredentials({
1786
+ api: api2,
1787
+ name,
1788
+ credentialsBaseUrl,
1789
+ ...useApprovals && { policy: EMPTY_POLICY }
1790
+ });
1791
+ try {
1792
+ await clearLegacyJwtState();
1793
+ } catch (err) {
1794
+ console.error(
1795
+ `[${cleanupLogPrefix}] Best-effort legacy JWT cleanup failed:`,
1796
+ err
1797
+ );
1369
1798
  }
1370
- const { credentialName } = await inquirer__default.default.prompt([
1371
- {
1372
- type: "input",
1373
- name: "credentialName",
1374
- message: "Enter a name to identify them:",
1375
- default: fallback,
1376
- validate: (input) => {
1377
- if (!input.trim()) return "Name cannot be empty";
1378
- return true;
1379
- }
1380
- }
1381
- ]);
1382
- return credentialName;
1383
1799
  }
1384
- function emitLoginSuccess({
1800
+ function emitAccountAuthSuccess({
1385
1801
  sdk,
1386
1802
  profile
1387
1803
  }) {
1388
1804
  sdk.context.eventEmission.emit(
1389
1805
  "platform.sdk.ApplicationLifecycleEvent",
1390
1806
  zapierSdk.buildApplicationLifecycleEvent(
1391
- {
1392
- lifecycle_event_type: "login_success"
1393
- },
1807
+ { lifecycle_event_type: "login_success" },
1394
1808
  {
1395
1809
  customuser_id: profile.user_id,
1396
1810
  account_id: profile.roles[0]?.account_id ?? null
@@ -1398,18 +1812,128 @@ function emitLoginSuccess({
1398
1812
  )
1399
1813
  );
1400
1814
  }
1401
- async function getProfile(api2) {
1402
- return api2.get("/zapier/api/v4/profile/", {
1403
- authRequired: true
1404
- });
1815
+ function emitSignupSuccess({
1816
+ sdk
1817
+ }) {
1818
+ sdk.context.eventEmission.emit(
1819
+ "platform.sdk.ApplicationLifecycleEvent",
1820
+ zapierSdk.buildApplicationLifecycleEvent({ lifecycle_event_type: "signup_success" })
1821
+ );
1405
1822
  }
1406
- async function bestEffortClearLegacyJwtState() {
1823
+ async function runOauthWithRedaction(runOauth) {
1407
1824
  try {
1408
- await clearLegacyJwtState();
1409
- } catch (err) {
1410
- console.error("[login] Best-effort legacy JWT cleanup failed:", err);
1825
+ return await runOauth();
1826
+ } catch (error) {
1827
+ if (error instanceof ZapierCliUserCancellationError) throw error;
1828
+ throw toRedactedOauthError(error);
1829
+ }
1830
+ }
1831
+ async function runOauthForEntryPoint({
1832
+ sdk,
1833
+ entryPoint,
1834
+ timeoutMs,
1835
+ pkceCredentials,
1836
+ baseUrl,
1837
+ headless,
1838
+ interactive
1839
+ }) {
1840
+ if (entryPoint === "signup") {
1841
+ return runOauthWithRedaction(
1842
+ () => runSignupOauthFlow({
1843
+ timeoutMs,
1844
+ pkceCredentials,
1845
+ baseUrl,
1846
+ headless,
1847
+ interactive,
1848
+ recoveryMessage: headless ? HEADLESS_SIGNUP_RECOVERY_MESSAGE : SIGNUP_RECOVERY_MESSAGE,
1849
+ onProgress: (event) => {
1850
+ if (event.type === "callback_accepted") {
1851
+ emitSignupSuccess({ sdk });
1852
+ }
1853
+ }
1854
+ })
1855
+ );
1856
+ }
1857
+ return runOauthWithRedaction(
1858
+ () => runLoginOauthFlow({ timeoutMs, pkceCredentials, baseUrl })
1859
+ );
1860
+ }
1861
+ async function runAccountAuth({
1862
+ sdk,
1863
+ options,
1864
+ entryPoint
1865
+ }) {
1866
+ const timeoutSeconds = parseTimeoutSeconds(options.timeout);
1867
+ const interactive = !resolveNonInteractive(options);
1868
+ const resolvedCredentials = await sdk.context.resolveCredentials();
1869
+ const pkceCredentials = toPkceCredentials(resolvedCredentials);
1870
+ const credentialsBaseUrl = await resolveCredentialsBaseUrl({
1871
+ ...sdk.context,
1872
+ resolvedCredentials
1873
+ });
1874
+ if (!await clearExistingAuthState({
1875
+ sdk,
1876
+ baseUrl: credentialsBaseUrl,
1877
+ interactive,
1878
+ entryPoint
1879
+ })) {
1880
+ return;
1881
+ }
1882
+ const { accessToken } = await runOauthForEntryPoint({
1883
+ sdk,
1884
+ entryPoint,
1885
+ timeoutMs: timeoutSeconds * 1e3,
1886
+ pkceCredentials,
1887
+ baseUrl: credentialsBaseUrl,
1888
+ headless: options.headless === true,
1889
+ interactive
1890
+ });
1891
+ const scopedApi = zapierSdk.getOrCreateApiClient({
1892
+ credentials: accessToken,
1893
+ baseUrl: credentialsBaseUrl
1894
+ });
1895
+ const profile = await getProfile(scopedApi);
1896
+ console.log(getProfileMessage(entryPoint, profile.email));
1897
+ console.log(
1898
+ "\nGenerating credentials so this machine can make authenticated requests on your behalf."
1899
+ );
1900
+ const resolveCredentialsName = interactive ? ({ email }) => promptCredentialsName({
1901
+ email,
1902
+ promptMessage: getCredentialsPromptMessage(entryPoint)
1903
+ }) : resolveDefaultCredentialsName;
1904
+ const credentialName = await resolveCredentialsName({ email: profile.email });
1905
+ const useApprovals = options.useApprovals === true;
1906
+ await saveClientCredentials({
1907
+ api: scopedApi,
1908
+ name: credentialName,
1909
+ credentialsBaseUrl,
1910
+ useApprovals,
1911
+ cleanupLogPrefix: entryPoint
1912
+ });
1913
+ console.log(
1914
+ `\u2705 Credentials "${credentialName}" created and set as default. You are ready to use the Zapier SDK.`
1915
+ );
1916
+ if (useApprovals) {
1917
+ console.log("\u{1F510} Approvals are enabled for these credentials.");
1411
1918
  }
1919
+ emitAccountAuthSuccess({ sdk, profile });
1412
1920
  }
1921
+ var LoginSchema = zod.z.object({
1922
+ timeout: zod.z.string().optional().describe("Login timeout in seconds (default: 300)"),
1923
+ useApprovals: zod.z.boolean().optional().describe(
1924
+ "Require approvals for actions performed with these credentials"
1925
+ ),
1926
+ nonInteractive: zod.z.boolean().optional().describe(
1927
+ "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."
1928
+ ),
1929
+ /** @deprecated Use `nonInteractive` instead. */
1930
+ skipPrompts: zod.z.boolean().optional().meta({
1931
+ deprecated: true,
1932
+ deprecationMessage: "Use --non-interactive instead."
1933
+ })
1934
+ }).describe("Log in to Zapier to access your account");
1935
+
1936
+ // src/plugins/login/index.ts
1413
1937
  var loginPlugin = zapierSdk.definePlugin(
1414
1938
  (sdk) => zapierSdk.createPluginMethod(sdk, {
1415
1939
  name: "login",
@@ -1417,68 +1941,37 @@ var loginPlugin = zapierSdk.definePlugin(
1417
1941
  inputSchema: LoginSchema,
1418
1942
  supportsJsonOutput: false,
1419
1943
  handler: async ({ sdk: sdk2, options }) => {
1420
- const timeoutSeconds = parseTimeoutSeconds(options.timeout);
1421
- const nonInteractive = resolveNonInteractive(options);
1422
- const resolvedCredentials = await sdk2.context.resolveCredentials();
1423
- const pkceCredentials = toPkceCredentials(resolvedCredentials);
1424
- const credentialsBaseUrl = await resolveCredentialsBaseUrl({
1425
- ...sdk2.context,
1426
- resolvedCredentials
1427
- });
1428
- const activeCredentials = getActiveCredentials({
1429
- baseUrl: credentialsBaseUrl
1430
- });
1431
- if (activeCredentials) {
1432
- if (!await confirmRevokeAndRelogin(activeCredentials, nonInteractive))
1433
- return;
1434
- try {
1435
- await revokeCredentials({
1436
- api: sdk2.context.api,
1437
- credentials: activeCredentials
1438
- });
1439
- } catch {
1440
- if (!await confirmLocalLoginReset(nonInteractive)) return;
1441
- await deleteStoredClientCredentials({
1442
- name: activeCredentials.name,
1443
- baseUrl: activeCredentials.baseUrl
1444
- });
1445
- }
1446
- } else if (hasLegacyJwtConfig()) {
1447
- if (!await confirmJwtMigration(nonInteractive)) return;
1448
- }
1449
- const { accessToken } = await runOauthFlow({
1450
- timeoutMs: timeoutSeconds * 1e3,
1451
- pkceCredentials,
1452
- baseUrl: credentialsBaseUrl
1453
- });
1454
- const scopedApi = zapierSdk.getOrCreateApiClient({
1455
- credentials: accessToken,
1456
- baseUrl: credentialsBaseUrl
1457
- });
1458
- const profile = await getProfile(scopedApi);
1459
- console.log(`\u{1F464} Logged in as ${profile.email}`);
1460
- console.log(
1461
- "\nGenerating credentials so this machine can make authenticated requests on your behalf."
1462
- );
1463
- const credentialName = await promptCredentialsName(
1464
- profile.email,
1465
- nonInteractive
1466
- );
1467
- const useApprovals = options.useApprovals === true;
1468
- await setupClientCredentials({
1469
- api: scopedApi,
1470
- name: credentialName,
1471
- credentialsBaseUrl,
1472
- ...useApprovals && { policy: EMPTY_POLICY }
1473
- });
1474
- await bestEffortClearLegacyJwtState();
1475
- console.log(
1476
- `\u2705 Credentials "${credentialName}" created and set as default. You are ready to use the Zapier SDK.`
1477
- );
1478
- if (useApprovals) {
1479
- console.log("\u{1F510} Approvals are enabled for these credentials.");
1480
- }
1481
- emitLoginSuccess({ sdk: sdk2, profile });
1944
+ await runAccountAuth({ sdk: sdk2, options, entryPoint: "login" });
1945
+ }
1946
+ })
1947
+ );
1948
+ var SignupSchema = zod.z.object({
1949
+ timeout: zod.z.string().optional().describe("Signup timeout in seconds (default: 300)"),
1950
+ useApprovals: zod.z.boolean().optional().describe(
1951
+ "Require approvals for actions performed with these credentials"
1952
+ ),
1953
+ nonInteractive: zod.z.boolean().optional().describe(
1954
+ "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."
1955
+ ),
1956
+ /** @deprecated Use `nonInteractive` instead. */
1957
+ skipPrompts: zod.z.boolean().optional().meta({
1958
+ deprecated: true,
1959
+ deprecationMessage: "Use --non-interactive instead."
1960
+ }),
1961
+ headless: zod.z.boolean().optional().describe(
1962
+ "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."
1963
+ )
1964
+ }).describe("Set up Zapier account access and SDK credentials");
1965
+
1966
+ // src/plugins/signup/index.ts
1967
+ var signupPlugin = zapierSdk.definePlugin(
1968
+ (sdk) => zapierSdk.createPluginMethod(sdk, {
1969
+ name: "signup",
1970
+ categories: ["account"],
1971
+ inputSchema: SignupSchema,
1972
+ supportsJsonOutput: false,
1973
+ handler: async ({ sdk: sdk2, options }) => {
1974
+ await runAccountAuth({ sdk: sdk2, options, entryPoint: "signup" });
1482
1975
  }
1483
1976
  })
1484
1977
  );
@@ -1545,7 +2038,8 @@ var BundleCodeSchema = zod.z.object({
1545
2038
  var bundleCodePlugin = zapierSdk.definePlugin(
1546
2039
  (sdk) => zapierSdk.createPluginMethod(sdk, {
1547
2040
  name: "bundleCode",
1548
- categories: ["utility", "deprecated"],
2041
+ categories: ["utility"],
2042
+ deprecation: { message: "bundleCode is no longer maintained." },
1549
2043
  inputSchema: BundleCodeSchema,
1550
2044
  handler: async ({ options }) => bundleCode(options)
1551
2045
  })
@@ -1641,7 +2135,7 @@ async function detectTypesOutputDirectory() {
1641
2135
  }
1642
2136
  return "./zapier/apps/";
1643
2137
  }
1644
- var addPlugin = zapierSdk.definePlugin(
2138
+ var addAppsPlugin = zapierSdk.definePlugin(
1645
2139
  (sdk) => zapierSdk.createPluginMethod(sdk, {
1646
2140
  name: "add",
1647
2141
  categories: ["utility"],
@@ -3046,10 +3540,6 @@ var cliOverridesPlugin = zapierSdk.definePlugin(
3046
3540
  if (sdk.context.meta.fetch) {
3047
3541
  meta.fetch = {
3048
3542
  ...sdk.context.meta.fetch,
3049
- categories: [
3050
- ...sdk.context.meta.fetch.categories || [],
3051
- "deprecated"
3052
- ],
3053
3543
  deprecation: {
3054
3544
  message: "This command is deprecated and will be removed soon. Use `curl` instead. Learn more: https://docs.zapier.com/sdk/cli-reference#curl"
3055
3545
  }
@@ -4013,7 +4503,7 @@ var watchTriggerInboxCliPlugin = zapierSdk.definePlugin(
4013
4503
  // package.json with { type: 'json' }
4014
4504
  var package_default = {
4015
4505
  name: "@zapier/zapier-sdk-cli",
4016
- version: "0.52.10"};
4506
+ version: "0.53.0"};
4017
4507
 
4018
4508
  // src/experimental.ts
4019
4509
  experimental.injectCliLogin(login_exports);
@@ -4025,21 +4515,21 @@ function createZapierCliSdk(options = {}) {
4025
4515
  const experimentalContextPlugin = () => ({
4026
4516
  context: { experimental: true }
4027
4517
  });
4028
- let chain = experimental.createZapierSdk({
4518
+ const sdk = experimental.createZapierSdkStack({
4029
4519
  ...sdkOptions,
4030
4520
  eventEmission: { ...sdkOptions.eventEmission, callContext: "cli" },
4031
4521
  callerPackage: { name: package_default.name, version: package_default.version }
4032
- }).addPlugin(extensionsContextPlugin).addPlugin(experimentalContextPlugin).addPlugin(generateAppTypesPlugin).addPlugin(buildManifestPlugin).addPlugin(bundleCodePlugin).addPlugin(getLoginConfigPathPlugin).addPlugin(addPlugin).addPlugin(feedbackPlugin).addPlugin(curlPlugin).addPlugin(initPlugin).addPlugin(drainTriggerInboxCliPlugin, { override: true }).addPlugin(watchTriggerInboxCliPlugin, { override: true }).addPlugin(mcpPlugin).addPlugin(loginPlugin).addPlugin(logoutPlugin).addPlugin(cliOverridesPlugin);
4522
+ }).use(extensionsContextPlugin).use(experimentalContextPlugin).use(generateAppTypesPlugin).use(buildManifestPlugin).use(bundleCodePlugin).use(getLoginConfigPathPlugin).use(addAppsPlugin).use(feedbackPlugin).use(curlPlugin).use(initPlugin).use(drainTriggerInboxCliPlugin, { override: true }).use(watchTriggerInboxCliPlugin, { override: true }).use(mcpPlugin).use(loginPlugin).use(signupPlugin).use(logoutPlugin).use(cliOverridesPlugin, { override: true }).toSdk();
4033
4523
  for (const ext of extensions) {
4034
4524
  try {
4035
- chain = chain.addPlugin(ext);
4525
+ experimental.addPlugin(sdk, ext);
4036
4526
  } catch (err) {
4037
4527
  console.warn(
4038
4528
  `Extension plugin failed to construct: ${err.message}; skipping.`
4039
4529
  );
4040
4530
  }
4041
4531
  }
4042
- return chain;
4532
+ return sdk;
4043
4533
  }
4044
4534
 
4045
4535
  exports.createZapierCliSdk = createZapierCliSdk;