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