@zapier/zapier-sdk-cli 0.52.12 → 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 (42) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +20 -0
  3. package/dist/cli.cjs +896 -395
  4. package/dist/cli.mjs +897 -396
  5. package/dist/experimental.cjs +897 -398
  6. package/dist/experimental.d.mts +1 -1
  7. package/dist/experimental.d.ts +1 -1
  8. package/dist/experimental.mjs +896 -397
  9. package/dist/index.cjs +898 -399
  10. package/dist/index.d.mts +1 -1
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.mjs +897 -398
  13. package/dist/package.json +1 -1
  14. package/dist/{sdk-Sa1HjzUj.d.mts → sdk-SOLizjno.d.mts} +40 -2
  15. package/dist/{sdk-Sa1HjzUj.d.ts → sdk-SOLizjno.d.ts} +40 -2
  16. package/dist/src/experimental.js +2 -1
  17. package/dist/src/plugins/index.d.ts +1 -0
  18. package/dist/src/plugins/index.js +1 -0
  19. package/dist/src/plugins/login/index.d.ts +2 -15
  20. package/dist/src/plugins/login/index.js +3 -191
  21. package/dist/src/plugins/signup/index.d.ts +25 -0
  22. package/dist/src/plugins/signup/index.js +12 -0
  23. package/dist/src/plugins/signup/schemas.d.ts +9 -0
  24. package/dist/src/plugins/signup/schemas.js +26 -0
  25. package/dist/src/plugins/signup/test-harness.d.ts +34 -0
  26. package/dist/src/plugins/signup/test-harness.js +74 -0
  27. package/dist/src/sdk.js +2 -1
  28. package/dist/src/types/sdk.d.ts +2 -1
  29. package/dist/src/utils/auth/account-auth.d.ts +32 -0
  30. package/dist/src/utils/auth/account-auth.js +265 -0
  31. package/dist/src/utils/auth/oauth-callback.d.ts +6 -0
  32. package/dist/src/utils/auth/oauth-callback.js +28 -0
  33. package/dist/src/utils/auth/oauth-errors.d.ts +2 -0
  34. package/dist/src/utils/auth/oauth-errors.js +39 -0
  35. package/dist/src/utils/auth/oauth-flow.d.ts +31 -6
  36. package/dist/src/utils/auth/oauth-flow.js +258 -106
  37. package/dist/src/utils/auth/oauth-transaction.d.ts +35 -0
  38. package/dist/src/utils/auth/oauth-transaction.js +69 -0
  39. package/dist/src/utils/non-interactive.d.ts +5 -4
  40. package/dist/src/utils/non-interactive.js +6 -5
  41. package/dist/tsconfig.tsbuildinfo +1 -1
  42. package/package.json +3 -3
@@ -12,11 +12,12 @@ var zod = require('zod');
12
12
  var experimental = require('@zapier/zapier-sdk/experimental');
13
13
  var os = require('os');
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
 
@@ -888,10 +889,27 @@ async function revokeCredentials({
888
889
  });
889
890
  emitAuthLogout(onEvent);
890
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
+ }
891
908
 
892
- // src/utils/constants.ts
893
- var LOGIN_PORTS = [49505, 50575, 52804, 55981, 61010, 63851];
894
- 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
+ }
895
913
  var ZapierCliError = class extends zapierSdk.ZapierError {
896
914
  };
897
915
  var ZapierCliUserCancellationError = class extends ZapierCliError {
@@ -919,22 +937,90 @@ var ZapierCliValidationError = class extends ZapierCliError {
919
937
  }
920
938
  };
921
939
 
922
- // src/utils/spinner.ts
923
- var spinPromise = async (promise, text) => {
924
- 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
+ );
925
978
  try {
926
- const result = await promise;
927
- spinner.succeed();
928
- return result;
929
- } catch (error) {
930
- if (error instanceof ZapierCliUserCancellationError) {
931
- spinner.stop();
932
- } else {
933
- 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
+ );
934
997
  }
935
- throw error;
998
+ throw storeErr;
936
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
+ };
937
1022
  };
1023
+ var getCallablePromise_default = getCallablePromise;
938
1024
  var log = {
939
1025
  info: (message, ...args) => {
940
1026
  console.error(chalk3__default.default.blue("\u2139"), message, ...args);
@@ -955,6 +1041,60 @@ var log = {
955
1041
  }
956
1042
  };
957
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
+ }
958
1098
 
959
1099
  // src/utils/api/client.ts
960
1100
  var createApiClient = () => {
@@ -985,179 +1125,77 @@ var createApiClient = () => {
985
1125
  var api = createApiClient();
986
1126
  var client_default = api;
987
1127
 
988
- // src/utils/getCallablePromise.ts
989
- var getCallablePromise = () => {
990
- let resolve4 = () => {
991
- };
992
- let reject = () => {
993
- };
994
- const promise = new Promise((_resolve, _reject) => {
995
- resolve4 = _resolve;
996
- reject = _reject;
997
- });
998
- return {
999
- promise,
1000
- resolve: resolve4,
1001
- reject
1002
- };
1003
- };
1004
- var getCallablePromise_default = getCallablePromise;
1005
-
1006
- // src/utils/auth/oauth-flow.ts
1007
- var findAvailablePort = () => {
1008
- return new Promise((resolve4, reject) => {
1009
- let portIndex = 0;
1010
- const tryPort = (port) => {
1011
- const server = express__default.default().listen(port, () => {
1012
- server.close();
1013
- resolve4(port);
1014
- });
1015
- server.on("error", (err) => {
1016
- if (err.code === "EADDRINUSE") {
1017
- if (portIndex < LOGIN_PORTS.length) {
1018
- tryPort(LOGIN_PORTS[portIndex++]);
1019
- } else {
1020
- reject(
1021
- new Error(
1022
- `All configured OAuth callback ports are busy: ${LOGIN_PORTS.join(", ")}. Please try again later or close applications using these ports.`
1023
- )
1024
- );
1025
- }
1026
- } else {
1027
- reject(err);
1028
- }
1029
- });
1030
- };
1031
- if (LOGIN_PORTS.length > 0) {
1032
- tryPort(LOGIN_PORTS[portIndex++]);
1033
- } else {
1034
- reject(new Error("No OAuth callback ports configured"));
1035
- }
1036
- });
1037
- };
1038
- 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() {
1039
1145
  const array = new Uint32Array(28);
1040
1146
  crypto__default.default.getRandomValues(array);
1041
1147
  return Array.from(array, (dec) => ("0" + dec.toString(16)).slice(-2)).join(
1042
1148
  ""
1043
1149
  );
1044
- };
1150
+ }
1045
1151
  function ensureOfflineAccess(scope) {
1046
- if (scope.includes("offline_access")) {
1047
- return scope;
1048
- }
1152
+ if (scope.includes("offline_access")) return scope;
1049
1153
  return `${scope} offline_access`;
1050
1154
  }
1051
- async function runOauthFlow({
1052
- timeoutMs = LOGIN_TIMEOUT_MS,
1155
+ async function prepareOauthTransaction({
1053
1156
  pkceCredentials,
1054
- baseUrl
1157
+ baseUrl,
1158
+ redirectUri,
1159
+ entryPoint = "login"
1055
1160
  }) {
1056
1161
  const { clientId, tokenUrl, authorizeUrl } = getPkceLoginConfig({
1057
1162
  credentials: pkceCredentials,
1058
1163
  baseUrl
1059
1164
  });
1060
- const scope = ensureOfflineAccess(
1061
- pkceCredentials?.scope || "internal credentials"
1062
- );
1063
- const availablePort = await findAvailablePort();
1064
- const redirectUri = `http://localhost:${availablePort}/oauth`;
1065
- log_default.info(`Using port ${availablePort} for OAuth callback`);
1066
- const {
1067
- promise: promisedCode,
1068
- resolve: setCode,
1069
- reject: rejectCode
1070
- } = getCallablePromise_default();
1071
- const oauthState = generateRandomString();
1072
- const expressApp = express__default.default();
1073
- expressApp.get("/oauth", (req, res) => {
1074
- res.setHeader("Connection", "close");
1075
- if (req.query.state !== oauthState) {
1076
- rejectCode(new Error("OAuth state mismatch \u2014 possible CSRF"));
1077
- res.status(400).end("Invalid state. You can close this tab.");
1078
- return;
1079
- }
1080
- if (req.query.error) {
1081
- const desc = req.query.error_description ?? req.query.error;
1082
- rejectCode(new Error(`Authorization denied: ${desc}`));
1083
- res.end("Authorization was denied. You can close this tab.");
1084
- return;
1085
- }
1086
- if (!req.query.code) {
1087
- rejectCode(new Error("No authorization code received"));
1088
- res.end("No authorization code received. You can close this tab.");
1089
- return;
1090
- }
1091
- setCode(String(req.query.code));
1092
- res.end("You can now close this tab and return to the CLI.");
1093
- });
1094
- const server = expressApp.listen(availablePort);
1095
- const connections = /* @__PURE__ */ new Set();
1096
- server.on("connection", (conn) => {
1097
- connections.add(conn);
1098
- conn.on("close", () => connections.delete(conn));
1099
- });
1100
- const cleanup = () => {
1101
- server.close();
1102
- log_default.info("\n\u274C Login cancelled by user");
1103
- rejectCode(new ZapierCliUserCancellationError());
1104
- };
1105
- process.on("SIGINT", cleanup);
1106
- process.on("SIGTERM", cleanup);
1107
1165
  const { code_verifier: codeVerifier, code_challenge: codeChallenge } = await pkceChallenge__default.default();
1166
+ const state = generateRandomString();
1108
1167
  const authUrl = `${authorizeUrl}?${new URLSearchParams({
1109
1168
  response_type: "code",
1110
1169
  client_id: clientId,
1111
1170
  redirect_uri: redirectUri,
1112
- scope,
1113
- state: oauthState,
1171
+ scope: ensureOfflineAccess(
1172
+ pkceCredentials?.scope || "internal credentials"
1173
+ ),
1174
+ state,
1114
1175
  code_challenge: codeChallenge,
1115
1176
  code_challenge_method: "S256"
1116
1177
  }).toString()}`;
1117
- log_default.info("Opening your browser to log in.");
1118
- log_default.info("If it doesn't open, visit:", authUrl);
1119
- open__default.default(authUrl);
1120
- let timeoutTimer;
1121
- try {
1122
- await spinPromise(
1123
- Promise.race([
1124
- promisedCode,
1125
- new Promise((_resolve, reject) => {
1126
- timeoutTimer = setTimeout(() => {
1127
- reject(
1128
- new Error(
1129
- `Login timed out after ${Math.round(timeoutMs / 1e3)} seconds.`
1130
- )
1131
- );
1132
- }, timeoutMs);
1133
- })
1134
- ]),
1135
- "Waiting for you to login and authorize"
1136
- );
1137
- } finally {
1138
- if (timeoutTimer) {
1139
- clearTimeout(timeoutTimer);
1140
- }
1141
- process.off("SIGINT", cleanup);
1142
- process.off("SIGTERM", cleanup);
1143
- await new Promise((resolve4) => {
1144
- const timeout = setTimeout(() => {
1145
- log_default.info("Server close timed out, forcing connection shutdown...");
1146
- connections.forEach((conn) => conn.destroy());
1147
- resolve4();
1148
- }, 1e3);
1149
- server.close(() => {
1150
- clearTimeout(timeout);
1151
- resolve4();
1152
- });
1153
- });
1154
- }
1155
- 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
+ }) {
1156
1194
  const { data } = await client_default.post(
1157
1195
  tokenUrl,
1158
1196
  {
1159
1197
  grant_type: "authorization_code",
1160
- code: await promisedCode,
1198
+ code,
1161
1199
  redirect_uri: redirectUri,
1162
1200
  client_id: clientId,
1163
1201
  code_verifier: codeVerifier
@@ -1169,7 +1207,6 @@ async function runOauthFlow({
1169
1207
  }
1170
1208
  }
1171
1209
  );
1172
- log_default.info("Token exchange completed successfully");
1173
1210
  return {
1174
1211
  accessToken: data.access_token,
1175
1212
  refreshToken: data.refresh_token,
@@ -1177,105 +1214,467 @@ async function runOauthFlow({
1177
1214
  };
1178
1215
  }
1179
1216
 
1180
- // src/utils/auth/client-credentials.ts
1181
- var CREDENTIALS_SCOPES = ["external", "credentials"];
1182
- var EMPTY_POLICY = {
1183
- version: 2,
1184
- 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
+ }
1185
1224
  };
1186
- async function createCredentialsOnServer(api2, name, policy) {
1187
- const response = await api2.post(
1188
- "/api/v0/client-credentials",
1189
- {
1190
- name,
1191
- allowed_scopes: CREDENTIALS_SCOPES,
1192
- ...policy !== void 0 && { policy }
1193
- },
1194
- { authRequired: true, requiredScopes: ["credentials"] }
1195
- );
1196
- return {
1197
- clientId: response.data.client_id,
1198
- clientSecret: response.data.client_secret
1199
- };
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
+ });
1200
1257
  }
1201
- async function deleteCredentialsOnServer(api2, clientId) {
1202
- await api2.delete(`/api/v0/client-credentials/${clientId}`, void 0, {
1203
- authRequired: true,
1204
- requiredScopes: ["credentials"]
1258
+ async function runLoginOauthFlow(options) {
1259
+ return runOauthFlowEntryPoint({
1260
+ ...options,
1261
+ entryPoint: "login",
1262
+ authAction: "log in",
1263
+ flowName: "Login"
1205
1264
  });
1206
1265
  }
1207
- async function setupClientCredentials({
1208
- api: api2,
1209
- name,
1210
- credentialsBaseUrl,
1211
- policy
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
1212
1286
  }) {
1213
- const { clientId, clientSecret } = await createCredentialsOnServer(
1214
- api2,
1215
- name,
1216
- policy
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
1356
+ );
1357
+ const missingCallbackUrlMessage = withRecoveryMessage(
1358
+ "Paste the final OAuth callback URL from your browser.",
1359
+ recoveryMessage
1217
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
+ });
1218
1389
  try {
1219
- await withRetry({
1220
- action: () => storeClientCredentials({
1221
- name,
1222
- clientId,
1223
- clientSecret,
1224
- scopes: [...CREDENTIALS_SCOPES],
1225
- baseUrl: credentialsBaseUrl
1226
- })
1390
+ return await readUrl.catch((error) => {
1391
+ if (error instanceof Error && error.name === "AbortError") {
1392
+ throw new Error(timeoutMessage);
1393
+ }
1394
+ throw error;
1227
1395
  });
1228
- } catch (storeErr) {
1229
- try {
1230
- await withRetry({
1231
- action: () => deleteCredentialsOnServer(api2, clientId)
1232
- });
1233
- } catch {
1234
- console.error(
1235
- `Failed to roll back orphaned credential ${clientId}. Delete it manually with: zapier-sdk delete-client-credentials ${clientId}`
1396
+ } finally {
1397
+ clearTimeout(timeoutTimer);
1398
+ rl.close();
1399
+ }
1400
+ }
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
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;
1440
+ }
1441
+ async function collectLocalCallbackCode({
1442
+ transaction,
1443
+ timeoutMs,
1444
+ authAction,
1445
+ silent,
1446
+ onProgress
1447
+ }) {
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
+ )
1236
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.");
1237
1467
  }
1238
- throw storeErr;
1468
+ });
1469
+ const server = app.listen(
1470
+ Number(new URL(transaction.redirectUri).port),
1471
+ OAUTH_LOOPBACK_HOST
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 });
1239
1506
  }
1240
- return { clientId };
1241
1507
  }
1242
- function getBaseUrlFromResolvedCredentials(credentials) {
1243
- if (credentials && zapierSdk.isCredentialsObject(credentials)) {
1244
- return credentials.baseUrl;
1245
- }
1246
- return void 0;
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
+ });
1247
1526
  }
1248
- function getBaseUrlFromOptionsCredentials(credentials) {
1249
- if (credentials && typeof credentials === "object" && "baseUrl" in credentials && typeof credentials.baseUrl === "string") {
1250
- return credentials.baseUrl;
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 });
1538
+ try {
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}`
1546
+ );
1547
+ log_default.info("Visit this URL manually:", transaction.browserAuthUrl);
1548
+ }
1549
+ onProgress?.({
1550
+ type: "browser_open_failed",
1551
+ url: transaction.browserAuthUrl,
1552
+ reason
1553
+ });
1251
1554
  }
1252
- return void 0;
1253
1555
  }
1254
- async function resolveCredentialsBaseUrl(context) {
1255
- const resolvedCredentials = "resolvedCredentials" in context ? context.resolvedCredentials : await context.resolveCredentials?.();
1256
- return getBaseUrlFromResolvedCredentials(resolvedCredentials) ?? getBaseUrlFromOptionsCredentials(context.options?.credentials) ?? context.options?.baseUrl;
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
+ });
1257
1573
  }
1258
1574
 
1259
- // src/utils/non-interactive.ts
1260
- function resolveNonInteractive(options) {
1261
- return (options.nonInteractive ?? options.skipPrompts) === true || !process.stdin.isTTY || !process.stdout.isTTY;
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);
1586
+ }
1587
+ function toCamelCase(field) {
1588
+ return field.replace(
1589
+ /_([a-z])/g,
1590
+ (_match, letter) => letter.toUpperCase()
1591
+ );
1592
+ }
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);
1262
1625
  }
1263
- var LoginSchema = zod.z.object({
1264
- timeout: zod.z.string().optional().describe("Login timeout in seconds (default: 300)"),
1265
- useApprovals: zod.z.boolean().optional().describe(
1266
- "Require approvals for actions performed with these credentials"
1267
- ),
1268
- nonInteractive: zod.z.boolean().optional().describe(
1269
- "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."
1270
- ),
1271
- /** @deprecated Use `nonInteractive` instead. */
1272
- skipPrompts: zod.z.boolean().optional().meta({
1273
- deprecated: true,
1274
- deprecationMessage: "Use --non-interactive instead."
1275
- })
1276
- }).describe("Log in to Zapier to access your account");
1277
1626
 
1278
- // 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
+ }
1279
1678
  function toPkceCredentials(credentials) {
1280
1679
  if (credentials && zapierSdk.isCredentialsObject(credentials) && !("clientSecret" in credentials)) {
1281
1680
  return {
@@ -1287,104 +1686,125 @@ function toPkceCredentials(credentials) {
1287
1686
  }
1288
1687
  return void 0;
1289
1688
  }
1290
- async function confirmRevokeAndRelogin(activeCredentials, nonInteractive) {
1291
- 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) {
1292
1693
  throw new ZapierCliValidationError(
1293
- `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)."
1294
1695
  );
1295
1696
  }
1697
+ return timeoutSeconds;
1698
+ }
1699
+ async function promptConfirm({
1700
+ message,
1701
+ defaultValue
1702
+ }) {
1296
1703
  const { confirmed } = await inquirer__default.default.prompt([
1297
- {
1298
- type: "confirm",
1299
- 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,
1300
1729
  message: `You are already logged in as "${activeCredentials.name}".
1301
1730
  Logging out will delete these credentials and may interrupt other Zapier SDK or CLI sessions using them.
1302
- Log out and log in again?`,
1303
- default: false
1731
+ Log out and ${getActiveCredentialsAction(entryPoint)}?`
1732
+ }) : promptlessCredentialResetError(activeCredentials);
1733
+ if (!confirmed) {
1734
+ console.log(`${flowLabel} cancelled.`);
1735
+ return false;
1304
1736
  }
1305
- ]);
1306
- if (!confirmed) {
1307
- console.log("Login cancelled.");
1308
- return false;
1309
- }
1310
- return true;
1311
- }
1312
- async function confirmJwtMigration(nonInteractive) {
1313
- if (nonInteractive) {
1314
- throw new ZapierCliValidationError(
1315
- "Legacy JWT login detected. Run `logout` first or use an interactive terminal to migrate to client credentials."
1316
- );
1317
- }
1318
- const { confirmed } = await inquirer__default.default.prompt([
1319
- {
1320
- type: "confirm",
1321
- name: "confirmed",
1322
- 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?",
1323
- 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
+ });
1324
1760
  }
1325
- ]);
1326
- if (!confirmed) {
1327
- console.log("Login cancelled.");
1328
- return false;
1329
- }
1330
- return true;
1331
- }
1332
- async function confirmLocalLoginReset(nonInteractive) {
1333
- if (nonInteractive) {
1334
- throw new ZapierCliValidationError(
1335
- "Login cleanup failed and cannot be reset without confirmation. Re-run with an interactive terminal."
1336
- );
1337
- }
1338
- const { confirmed } = await inquirer__default.default.prompt([
1339
- {
1340
- type: "confirm",
1341
- name: "confirmed",
1342
- message: "Login cleanup failed. Reset local session state and continue?",
1343
- 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;
1344
1769
  }
1345
- ]);
1346
- if (!confirmed) {
1347
- console.log("Login cancelled.");
1348
- return false;
1349
1770
  }
1350
1771
  return true;
1351
1772
  }
1352
- function parseTimeoutSeconds(timeout) {
1353
- const timeoutSeconds = timeout ? parseInt(timeout, 10) : 300;
1354
- if (isNaN(timeoutSeconds) || timeoutSeconds <= 0) {
1355
- throw new Error("Timeout must be a positive number");
1356
- }
1357
- return timeoutSeconds;
1773
+ async function getProfile(api2) {
1774
+ return api2.get("/zapier/api/v4/profile/", {
1775
+ authRequired: true
1776
+ });
1358
1777
  }
1359
- async function promptCredentialsName(email, nonInteractive) {
1360
- const fallback = `${email}@${os.hostname()}`;
1361
- if (nonInteractive) {
1362
- 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
+ );
1363
1798
  }
1364
- const { credentialName } = await inquirer__default.default.prompt([
1365
- {
1366
- type: "input",
1367
- name: "credentialName",
1368
- message: "Enter a name to identify them:",
1369
- default: fallback,
1370
- validate: (input) => {
1371
- if (!input.trim()) return "Name cannot be empty";
1372
- return true;
1373
- }
1374
- }
1375
- ]);
1376
- return credentialName;
1377
1799
  }
1378
- function emitLoginSuccess({
1800
+ function emitAccountAuthSuccess({
1379
1801
  sdk,
1380
1802
  profile
1381
1803
  }) {
1382
1804
  sdk.context.eventEmission.emit(
1383
1805
  "platform.sdk.ApplicationLifecycleEvent",
1384
1806
  zapierSdk.buildApplicationLifecycleEvent(
1385
- {
1386
- lifecycle_event_type: "login_success"
1387
- },
1807
+ { lifecycle_event_type: "login_success" },
1388
1808
  {
1389
1809
  customuser_id: profile.user_id,
1390
1810
  account_id: profile.roles[0]?.account_id ?? null
@@ -1392,18 +1812,128 @@ function emitLoginSuccess({
1392
1812
  )
1393
1813
  );
1394
1814
  }
1395
- async function getProfile(api2) {
1396
- return api2.get("/zapier/api/v4/profile/", {
1397
- authRequired: true
1398
- });
1815
+ function emitSignupSuccess({
1816
+ sdk
1817
+ }) {
1818
+ sdk.context.eventEmission.emit(
1819
+ "platform.sdk.ApplicationLifecycleEvent",
1820
+ zapierSdk.buildApplicationLifecycleEvent({ lifecycle_event_type: "signup_success" })
1821
+ );
1399
1822
  }
1400
- async function bestEffortClearLegacyJwtState() {
1823
+ async function runOauthWithRedaction(runOauth) {
1401
1824
  try {
1402
- await clearLegacyJwtState();
1403
- } catch (err) {
1404
- 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
+ );
1405
1856
  }
1857
+ return runOauthWithRedaction(
1858
+ () => runLoginOauthFlow({ timeoutMs, pkceCredentials, baseUrl })
1859
+ );
1406
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.");
1918
+ }
1919
+ emitAccountAuthSuccess({ sdk, profile });
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
1407
1937
  var loginPlugin = zapierSdk.definePlugin(
1408
1938
  (sdk) => zapierSdk.createPluginMethod(sdk, {
1409
1939
  name: "login",
@@ -1411,68 +1941,37 @@ var loginPlugin = zapierSdk.definePlugin(
1411
1941
  inputSchema: LoginSchema,
1412
1942
  supportsJsonOutput: false,
1413
1943
  handler: async ({ sdk: sdk2, options }) => {
1414
- const timeoutSeconds = parseTimeoutSeconds(options.timeout);
1415
- const nonInteractive = resolveNonInteractive(options);
1416
- const resolvedCredentials = await sdk2.context.resolveCredentials();
1417
- const pkceCredentials = toPkceCredentials(resolvedCredentials);
1418
- const credentialsBaseUrl = await resolveCredentialsBaseUrl({
1419
- ...sdk2.context,
1420
- resolvedCredentials
1421
- });
1422
- const activeCredentials = getActiveCredentials({
1423
- baseUrl: credentialsBaseUrl
1424
- });
1425
- if (activeCredentials) {
1426
- if (!await confirmRevokeAndRelogin(activeCredentials, nonInteractive))
1427
- return;
1428
- try {
1429
- await revokeCredentials({
1430
- api: sdk2.context.api,
1431
- credentials: activeCredentials
1432
- });
1433
- } catch {
1434
- if (!await confirmLocalLoginReset(nonInteractive)) return;
1435
- await deleteStoredClientCredentials({
1436
- name: activeCredentials.name,
1437
- baseUrl: activeCredentials.baseUrl
1438
- });
1439
- }
1440
- } else if (hasLegacyJwtConfig()) {
1441
- if (!await confirmJwtMigration(nonInteractive)) return;
1442
- }
1443
- const { accessToken } = await runOauthFlow({
1444
- timeoutMs: timeoutSeconds * 1e3,
1445
- pkceCredentials,
1446
- baseUrl: credentialsBaseUrl
1447
- });
1448
- const scopedApi = zapierSdk.getOrCreateApiClient({
1449
- credentials: accessToken,
1450
- baseUrl: credentialsBaseUrl
1451
- });
1452
- const profile = await getProfile(scopedApi);
1453
- console.log(`\u{1F464} Logged in as ${profile.email}`);
1454
- console.log(
1455
- "\nGenerating credentials so this machine can make authenticated requests on your behalf."
1456
- );
1457
- const credentialName = await promptCredentialsName(
1458
- profile.email,
1459
- nonInteractive
1460
- );
1461
- const useApprovals = options.useApprovals === true;
1462
- await setupClientCredentials({
1463
- api: scopedApi,
1464
- name: credentialName,
1465
- credentialsBaseUrl,
1466
- ...useApprovals && { policy: EMPTY_POLICY }
1467
- });
1468
- await bestEffortClearLegacyJwtState();
1469
- console.log(
1470
- `\u2705 Credentials "${credentialName}" created and set as default. You are ready to use the Zapier SDK.`
1471
- );
1472
- if (useApprovals) {
1473
- console.log("\u{1F510} Approvals are enabled for these credentials.");
1474
- }
1475
- 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" });
1476
1975
  }
1477
1976
  })
1478
1977
  );
@@ -4004,7 +4503,7 @@ var watchTriggerInboxCliPlugin = zapierSdk.definePlugin(
4004
4503
  // package.json with { type: 'json' }
4005
4504
  var package_default = {
4006
4505
  name: "@zapier/zapier-sdk-cli",
4007
- version: "0.52.12"};
4506
+ version: "0.53.0"};
4008
4507
 
4009
4508
  // src/experimental.ts
4010
4509
  experimental.injectCliLogin(login_exports);
@@ -4020,7 +4519,7 @@ function createZapierCliSdk(options = {}) {
4020
4519
  ...sdkOptions,
4021
4520
  eventEmission: { ...sdkOptions.eventEmission, callContext: "cli" },
4022
4521
  callerPackage: { name: package_default.name, version: package_default.version }
4023
- }).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(logoutPlugin).use(cliOverridesPlugin, { override: true }).toSdk();
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();
4024
4523
  for (const ext of extensions) {
4025
4524
  try {
4026
4525
  experimental.addPlugin(sdk, ext);