@zapier/zapier-sdk-cli 0.47.0 → 0.48.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 (38) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +1 -1
  3. package/dist/cli.cjs +559 -84
  4. package/dist/cli.mjs +559 -84
  5. package/dist/experimental.cjs +561 -86
  6. package/dist/experimental.mjs +560 -85
  7. package/dist/index.cjs +562 -87
  8. package/dist/index.mjs +561 -86
  9. package/dist/login.cjs +94 -25
  10. package/dist/login.d.mts +29 -2
  11. package/dist/login.d.ts +29 -2
  12. package/dist/login.mjs +90 -25
  13. package/dist/package.json +1 -1
  14. package/dist/src/login/config.d.ts +4 -0
  15. package/dist/src/login/config.js +21 -0
  16. package/dist/src/login/credentials-revoke.d.ts +13 -0
  17. package/dist/src/login/credentials-revoke.js +48 -0
  18. package/dist/src/login/credentials-store.d.ts +33 -0
  19. package/dist/src/login/credentials-store.js +142 -0
  20. package/dist/src/login/index.d.ts +5 -2
  21. package/dist/src/login/index.js +11 -27
  22. package/dist/src/login/legacy-jwt.d.ts +4 -0
  23. package/dist/src/login/legacy-jwt.js +18 -0
  24. package/dist/src/plugins/auth/credentials-base-url.d.ts +11 -0
  25. package/dist/src/plugins/auth/credentials-base-url.js +24 -0
  26. package/dist/src/plugins/login/index.d.ts +6 -1
  27. package/dist/src/plugins/login/index.js +154 -14
  28. package/dist/src/plugins/logout/index.d.ts +14 -0
  29. package/dist/src/plugins/logout/index.js +35 -3
  30. package/dist/src/utils/auth/client-credentials.d.ts +16 -0
  31. package/dist/src/utils/auth/client-credentials.js +53 -0
  32. package/dist/src/utils/auth/oauth-flow.d.ts +12 -0
  33. package/dist/src/utils/auth/{login.js → oauth-flow.js} +36 -58
  34. package/dist/src/utils/retry.d.ts +5 -0
  35. package/dist/src/utils/retry.js +21 -0
  36. package/dist/tsconfig.tsbuildinfo +1 -1
  37. package/package.json +3 -3
  38. package/dist/src/utils/auth/login.d.ts +0 -7
package/dist/index.cjs CHANGED
@@ -1,20 +1,21 @@
1
1
  'use strict';
2
2
 
3
- var Conf = require('conf');
4
- var fs = require('fs');
5
3
  var jwt = require('jsonwebtoken');
6
4
  var crossKeychain = require('cross-keychain');
5
+ var Conf = require('conf');
6
+ 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');
10
11
  var zapierSdk = require('@zapier/zapier-sdk');
12
+ var os = require('os');
13
+ var inquirer = require('inquirer');
11
14
  var open = require('open');
12
15
  var express = require('express');
13
16
  var pkceChallenge = require('pkce-challenge');
14
17
  var ora = require('ora');
15
18
  var chalk3 = require('chalk');
16
- var inquirer = require('inquirer');
17
- var zod = require('zod');
18
19
  var zapierSdkMcp = require('@zapier/zapier-sdk-mcp');
19
20
  var esbuild = require('esbuild');
20
21
  var promises = require('fs/promises');
@@ -46,18 +47,18 @@ function _interopNamespace(e) {
46
47
  return Object.freeze(n);
47
48
  }
48
49
 
50
+ var jwt__namespace = /*#__PURE__*/_interopNamespace(jwt);
49
51
  var Conf__default = /*#__PURE__*/_interopDefault(Conf);
50
52
  var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
51
- var jwt__namespace = /*#__PURE__*/_interopNamespace(jwt);
52
53
  var crypto__default = /*#__PURE__*/_interopDefault(crypto);
53
54
  var path__namespace = /*#__PURE__*/_interopNamespace(path);
54
55
  var lockfile__namespace = /*#__PURE__*/_interopNamespace(lockfile);
56
+ var inquirer__default = /*#__PURE__*/_interopDefault(inquirer);
55
57
  var open__default = /*#__PURE__*/_interopDefault(open);
56
58
  var express__default = /*#__PURE__*/_interopDefault(express);
57
59
  var pkceChallenge__default = /*#__PURE__*/_interopDefault(pkceChallenge);
58
60
  var ora__default = /*#__PURE__*/_interopDefault(ora);
59
61
  var chalk3__default = /*#__PURE__*/_interopDefault(chalk3);
60
- var inquirer__default = /*#__PURE__*/_interopDefault(inquirer);
61
62
  var ts__namespace = /*#__PURE__*/_interopNamespace(ts);
62
63
  var Handlebars__default = /*#__PURE__*/_interopDefault(Handlebars);
63
64
 
@@ -71,8 +72,11 @@ var __export = (target, all) => {
71
72
  var login_exports = {};
72
73
  __export(login_exports, {
73
74
  AUTH_MODE_HEADER: () => AUTH_MODE_HEADER,
75
+ DEFAULT_AUTH_BASE_URL: () => DEFAULT_AUTH_BASE_URL,
74
76
  ZapierAuthenticationError: () => ZapierAuthenticationError,
77
+ clearTokensFromKeychain: () => clearTokensFromKeychain,
75
78
  createCache: () => createCache,
79
+ getActiveCredentials: () => getActiveCredentials,
76
80
  getAuthAuthorizeUrl: () => getAuthAuthorizeUrl,
77
81
  getAuthTokenUrl: () => getAuthTokenUrl,
78
82
  getConfig: () => getConfig,
@@ -80,6 +84,7 @@ __export(login_exports, {
80
84
  getLoggedInUser: () => getLoggedInUser,
81
85
  getLoginStorageMode: () => getLoginStorageMode,
82
86
  getPkceLoginConfig: () => getPkceLoginConfig,
87
+ getStoredClientCredentials: () => getStoredClientCredentials,
83
88
  getToken: () => getToken,
84
89
  logout: () => logout,
85
90
  unloadConfig: () => unloadConfig,
@@ -160,6 +165,38 @@ async function clearTokensFromKeychain({
160
165
  }
161
166
  });
162
167
  }
168
+ var DEFAULT_AUTH_BASE_URL = "https://zapier.com";
169
+ var config = null;
170
+ function getConfig() {
171
+ if (!config) {
172
+ config = new Conf__default.default({ projectName: "zapier-sdk-cli" });
173
+ if (!config.has("login_storage_mode")) {
174
+ config.set(
175
+ "login_storage_mode",
176
+ fs.existsSync(config.path) ? "config" : "keychain"
177
+ );
178
+ }
179
+ }
180
+ return config;
181
+ }
182
+ function resetConfig() {
183
+ config = null;
184
+ }
185
+
186
+ // src/login/legacy-jwt.ts
187
+ function clearLegacyJwtConfigKeys(config2) {
188
+ config2.delete("login_jwt");
189
+ config2.delete("login_refresh_token");
190
+ config2.delete("login_expires_at");
191
+ }
192
+ async function clearLegacyJwtState() {
193
+ clearLegacyJwtConfigKeys(getConfig());
194
+ await clearTokensFromKeychain();
195
+ }
196
+ function hasLegacyJwtConfig() {
197
+ const cfg = getConfig();
198
+ return typeof cfg.get("login_jwt") === "string" || typeof cfg.get("login_refresh_token") === "string" || typeof cfg.get("login_expires_at") === "number";
199
+ }
163
200
  var SERVICE2 = "zapier-sdk-cache";
164
201
  var CONFIG_KEY = "cache";
165
202
  var LOCK_UPDATE_MS = 5e3;
@@ -287,6 +324,163 @@ function createCache() {
287
324
  }
288
325
  };
289
326
  }
327
+ var SERVICE3 = "zapier-sdk-cli";
328
+ var CREDENTIALS_KEY = "credentials";
329
+ var REGISTRY_KEY = "credentialsRegistry";
330
+ var CredentialsEntrySchema = zod.z.object({
331
+ name: zod.z.string(),
332
+ clientId: zod.z.string(),
333
+ createdAt: zod.z.number(),
334
+ scopes: zod.z.array(zod.z.string()),
335
+ baseUrl: zod.z.string()
336
+ });
337
+ function normalizeBaseUrl(baseUrl) {
338
+ return baseUrl ?? DEFAULT_AUTH_BASE_URL;
339
+ }
340
+ function keychainAccount2(key) {
341
+ return crypto.createHash("sha256").update(key).digest("hex");
342
+ }
343
+ function buildKeychainKey(clientId, scopes, baseUrl) {
344
+ const sortedScopes = [...scopes].sort().join(",");
345
+ return `zapier-sdk/client-credentials-secret/${clientId}:${sortedScopes}:${baseUrl}`;
346
+ }
347
+ function findEntry(registry, name, baseUrl) {
348
+ return registry.find((e) => e.name === name && e.baseUrl === baseUrl);
349
+ }
350
+ function readRegistry() {
351
+ const stored = getConfig().get(REGISTRY_KEY);
352
+ if (!Array.isArray(stored)) return [];
353
+ return stored.flatMap((entry) => {
354
+ const result = CredentialsEntrySchema.safeParse(entry);
355
+ return result.success ? [result.data] : [];
356
+ });
357
+ }
358
+ function getActiveCredentials(options) {
359
+ const name = getConfig().get(CREDENTIALS_KEY);
360
+ if (!name) return void 0;
361
+ return findEntry(readRegistry(), name, normalizeBaseUrl(options?.baseUrl));
362
+ }
363
+ async function storeClientCredentials({
364
+ name,
365
+ clientId,
366
+ clientSecret,
367
+ scopes,
368
+ baseUrl
369
+ }) {
370
+ if (!name || typeof name !== "string") {
371
+ throw new Error("storeClientCredentials: name is required");
372
+ }
373
+ if (!clientId || typeof clientId !== "string") {
374
+ throw new Error("storeClientCredentials: clientId is required");
375
+ }
376
+ if (!clientSecret || typeof clientSecret !== "string") {
377
+ throw new Error("storeClientCredentials: clientSecret is required");
378
+ }
379
+ if (!Array.isArray(scopes) || scopes.length === 0) {
380
+ throw new Error("storeClientCredentials: scopes must be a non-empty array");
381
+ }
382
+ const sortedScopes = [...scopes].sort();
383
+ const resolvedBaseUrl = normalizeBaseUrl(baseUrl);
384
+ const keychainKey = buildKeychainKey(clientId, sortedScopes, resolvedBaseUrl);
385
+ const existingEntry = findEntry(readRegistry(), name, resolvedBaseUrl);
386
+ const existingKeychainKey = existingEntry ? buildKeychainKey(
387
+ existingEntry.clientId,
388
+ existingEntry.scopes,
389
+ existingEntry.baseUrl
390
+ ) : void 0;
391
+ await enqueue(async () => {
392
+ await getBackendInfo();
393
+ await crossKeychain.setPassword(SERVICE3, keychainAccount2(keychainKey), clientSecret);
394
+ });
395
+ const entry = {
396
+ name,
397
+ clientId,
398
+ createdAt: Date.now(),
399
+ scopes: sortedScopes,
400
+ baseUrl: resolvedBaseUrl
401
+ };
402
+ const registry = readRegistry().filter(
403
+ (e) => !(e.name === name && e.baseUrl === resolvedBaseUrl)
404
+ );
405
+ registry.push(entry);
406
+ const cfg = getConfig();
407
+ cfg.set(REGISTRY_KEY, registry);
408
+ cfg.set(CREDENTIALS_KEY, name);
409
+ if (existingEntry && existingKeychainKey !== keychainKey) {
410
+ await deleteKeychainSecret(existingEntry);
411
+ }
412
+ }
413
+ function credentialsNameExists({
414
+ name,
415
+ baseUrl
416
+ }) {
417
+ return !!findEntry(readRegistry(), name, normalizeBaseUrl(baseUrl));
418
+ }
419
+ async function getStoredClientCredentials(options) {
420
+ const entry = options?.name ? findEntry(readRegistry(), options.name, normalizeBaseUrl(options.baseUrl)) : getActiveCredentials(options);
421
+ if (!entry) return void 0;
422
+ const keychainKey = buildKeychainKey(
423
+ entry.clientId,
424
+ entry.scopes,
425
+ entry.baseUrl
426
+ );
427
+ const clientSecret = await enqueue(async () => {
428
+ await getBackendInfo();
429
+ return crossKeychain.getPassword(SERVICE3, keychainAccount2(keychainKey));
430
+ });
431
+ if (!clientSecret) return void 0;
432
+ return {
433
+ type: "client_credentials",
434
+ clientId: entry.clientId,
435
+ clientSecret,
436
+ baseUrl: entry.baseUrl,
437
+ scope: [...entry.scopes].sort().join(" ")
438
+ };
439
+ }
440
+ function deleteRegistryEntry(registry, name, baseUrl) {
441
+ const idx = registry.findIndex(
442
+ (e) => e.name === name && e.baseUrl === baseUrl
443
+ );
444
+ if (idx === -1) return void 0;
445
+ const [removed] = registry.splice(idx, 1);
446
+ return removed;
447
+ }
448
+ function unsetMatchingCredentialsKey(cfg, name) {
449
+ const activeName = cfg.get(CREDENTIALS_KEY);
450
+ if (activeName === name && !readRegistry().some((e) => e.name === name)) {
451
+ cfg.delete(CREDENTIALS_KEY);
452
+ }
453
+ }
454
+ async function deleteKeychainSecret(entry) {
455
+ const keychainKey = buildKeychainKey(
456
+ entry.clientId,
457
+ entry.scopes,
458
+ entry.baseUrl
459
+ );
460
+ try {
461
+ await enqueue(async () => {
462
+ await getBackendInfo();
463
+ await crossKeychain.deletePassword(SERVICE3, keychainAccount2(keychainKey));
464
+ });
465
+ } catch {
466
+ }
467
+ }
468
+ async function deleteStoredClientCredentials({
469
+ name,
470
+ baseUrl
471
+ }) {
472
+ const registry = readRegistry();
473
+ const removed = deleteRegistryEntry(
474
+ registry,
475
+ name,
476
+ normalizeBaseUrl(baseUrl)
477
+ );
478
+ if (!removed) return;
479
+ const cfg = getConfig();
480
+ cfg.set(REGISTRY_KEY, registry);
481
+ unsetMatchingCredentialsKey(cfg, name);
482
+ await deleteKeychainSecret(removed);
483
+ }
290
484
 
291
485
  // src/login/index.ts
292
486
  var ZapierAuthenticationError = class extends Error {
@@ -295,7 +489,6 @@ var ZapierAuthenticationError = class extends Error {
295
489
  this.name = "ZapierAuthenticationError";
296
490
  }
297
491
  };
298
- var config = null;
299
492
  var DEFAULT_AUTH_CLIENT_ID = "grwWZD5hUWGvb4V8ODBuOtXer3h0DBEZ2HR8aay6";
300
493
  var TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1e3;
301
494
  function createDebugLog(enabled) {
@@ -321,7 +514,6 @@ function getAuthClientId(clientId) {
321
514
  return clientId || DEFAULT_AUTH_CLIENT_ID;
322
515
  }
323
516
  var AUTH_MODE_HEADER = "X-Auth";
324
- var DEFAULT_AUTH_BASE_URL = "https://zapier.com";
325
517
  function getAuthTokenUrl(options) {
326
518
  const authBaseUrl = options?.baseUrl || DEFAULT_AUTH_BASE_URL;
327
519
  return `${authBaseUrl}/oauth/token/`;
@@ -331,29 +523,16 @@ function getAuthAuthorizeUrl(options) {
331
523
  return `${authBaseUrl}/oauth/authorize/`;
332
524
  }
333
525
  function getPkceLoginConfig(options) {
526
+ const effectiveBaseUrl = options?.credentials?.baseUrl ?? options?.baseUrl;
334
527
  return {
335
528
  clientId: getAuthClientId(options?.credentials?.clientId),
336
- tokenUrl: getAuthTokenUrl({ baseUrl: options?.credentials?.baseUrl }),
337
- authorizeUrl: getAuthAuthorizeUrl({
338
- baseUrl: options?.credentials?.baseUrl
339
- })
529
+ tokenUrl: getAuthTokenUrl({ baseUrl: effectiveBaseUrl }),
530
+ authorizeUrl: getAuthAuthorizeUrl({ baseUrl: effectiveBaseUrl })
340
531
  };
341
532
  }
342
533
  var cachedLogin;
343
- function getConfig() {
344
- if (!config) {
345
- config = new Conf__default.default({ projectName: "zapier-sdk-cli" });
346
- if (!config.has("login_storage_mode")) {
347
- config.set(
348
- "login_storage_mode",
349
- fs.existsSync(config.path) ? "config" : "keychain"
350
- );
351
- }
352
- }
353
- return config;
354
- }
355
534
  function unloadConfig() {
356
- config = null;
535
+ resetConfig();
357
536
  cachedLogin = void 0;
358
537
  }
359
538
  async function updateLogin(loginData, options = {}) {
@@ -626,9 +805,7 @@ async function logout(options = {}) {
626
805
  await clearTokensFromKeychain();
627
806
  const cfg = getConfig();
628
807
  cfg.set("login_storage_mode", mode);
629
- cfg.delete("login_expires_at");
630
- cfg.delete("login_jwt");
631
- cfg.delete("login_refresh_token");
808
+ clearLegacyJwtConfigKeys(cfg);
632
809
  onEvent?.({
633
810
  type: "auth_logout",
634
811
  payload: { message: "Logged out successfully", operation: "logout" },
@@ -640,6 +817,79 @@ function getConfigPath() {
640
817
  return cfg.path;
641
818
  }
642
819
 
820
+ // src/utils/retry.ts
821
+ function sleep(ms) {
822
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
823
+ }
824
+ async function withRetry({
825
+ action,
826
+ attempts = 3,
827
+ initialDelayMs = 100
828
+ }) {
829
+ if (attempts <= 0) {
830
+ throw new Error("withRetry: attempts must be greater than 0");
831
+ }
832
+ let lastError;
833
+ for (let i = 0; i < attempts; i++) {
834
+ try {
835
+ return await action();
836
+ } catch (err) {
837
+ lastError = err;
838
+ if (i < attempts - 1) {
839
+ await sleep(initialDelayMs * 2 ** i);
840
+ }
841
+ }
842
+ }
843
+ throw lastError;
844
+ }
845
+
846
+ // src/login/credentials-revoke.ts
847
+ function emitAuthLogout(onEvent) {
848
+ onEvent?.({
849
+ type: "auth_logout",
850
+ payload: { message: "Logged out successfully", operation: "logout" },
851
+ timestamp: Date.now()
852
+ });
853
+ }
854
+ function isNotFoundError(err) {
855
+ return typeof err === "object" && err !== null && "statusCode" in err && err.statusCode === 404;
856
+ }
857
+ async function revokeCredentials({
858
+ api: api2,
859
+ credentials,
860
+ onEvent
861
+ }) {
862
+ await withRetry({
863
+ action: async () => {
864
+ try {
865
+ await api2.delete(
866
+ `/api/v0/client-credentials/${credentials.clientId}`,
867
+ void 0,
868
+ { authRequired: true, requiredScopes: ["credentials"] }
869
+ );
870
+ } catch (err) {
871
+ if (isNotFoundError(err)) return;
872
+ throw err;
873
+ }
874
+ }
875
+ });
876
+ try {
877
+ await deleteStoredClientCredentials({
878
+ name: credentials.name,
879
+ baseUrl: credentials.baseUrl
880
+ });
881
+ } catch (err) {
882
+ console.warn("[revokeCredentials] Local store cleanup failed:", err);
883
+ }
884
+ await clearLegacyJwtState();
885
+ await zapierSdk.invalidateCachedToken({
886
+ clientId: credentials.clientId,
887
+ scopes: credentials.scopes,
888
+ baseUrl: credentials.baseUrl
889
+ });
890
+ emitAuthLogout(onEvent);
891
+ }
892
+
643
893
  // src/utils/constants.ts
644
894
  var LOGIN_PORTS = [49505, 50575, 52804, 55981, 61010, 63851];
645
895
  var LOGIN_TIMEOUT_MS = 3e5;
@@ -753,6 +1003,8 @@ var getCallablePromise = () => {
753
1003
  };
754
1004
  };
755
1005
  var getCallablePromise_default = getCallablePromise;
1006
+
1007
+ // src/utils/auth/oauth-flow.ts
756
1008
  var findAvailablePort = () => {
757
1009
  return new Promise((resolve4, reject) => {
758
1010
  let portIndex = 0;
@@ -787,10 +1039,9 @@ var findAvailablePort = () => {
787
1039
  var generateRandomString = () => {
788
1040
  const array = new Uint32Array(28);
789
1041
  crypto__default.default.getRandomValues(array);
790
- return Array.from(
791
- array,
792
- (dec) => ("0" + dec.toString(16)).substring(-2)
793
- ).join("");
1042
+ return Array.from(array, (dec) => ("0" + dec.toString(16)).slice(-2)).join(
1043
+ ""
1044
+ );
794
1045
  };
795
1046
  function ensureOfflineAccess(scope) {
796
1047
  if (scope.includes("offline_access")) {
@@ -798,17 +1049,18 @@ function ensureOfflineAccess(scope) {
798
1049
  }
799
1050
  return `${scope} offline_access`;
800
1051
  }
801
- var login = async ({
1052
+ async function runOauthFlow({
802
1053
  timeoutMs = LOGIN_TIMEOUT_MS,
803
- credentials
804
- }) => {
1054
+ pkceCredentials,
1055
+ baseUrl
1056
+ }) {
805
1057
  const { clientId, tokenUrl, authorizeUrl } = getPkceLoginConfig({
806
- credentials
1058
+ credentials: pkceCredentials,
1059
+ baseUrl
807
1060
  });
808
1061
  const scope = ensureOfflineAccess(
809
- credentials?.scope || "internal credentials"
1062
+ pkceCredentials?.scope || "internal credentials"
810
1063
  );
811
- await logout();
812
1064
  const availablePort = await findAvailablePort();
813
1065
  const redirectUri = `http://localhost:${availablePort}/oauth`;
814
1066
  log_default.info(`Using port ${availablePort} for OAuth callback`);
@@ -817,13 +1069,30 @@ var login = async ({
817
1069
  resolve: setCode,
818
1070
  reject: rejectCode
819
1071
  } = getCallablePromise_default();
820
- const app = express__default.default();
821
- app.get("/oauth", (req, res) => {
822
- setCode(String(req.query.code));
1072
+ const oauthState = generateRandomString();
1073
+ const expressApp = express__default.default();
1074
+ expressApp.get("/oauth", (req, res) => {
823
1075
  res.setHeader("Connection", "close");
1076
+ if (req.query.state !== oauthState) {
1077
+ rejectCode(new Error("OAuth state mismatch \u2014 possible CSRF"));
1078
+ res.status(400).end("Invalid state. You can close this tab.");
1079
+ return;
1080
+ }
1081
+ if (req.query.error) {
1082
+ const desc = req.query.error_description ?? req.query.error;
1083
+ rejectCode(new Error(`Authorization denied: ${desc}`));
1084
+ res.end("Authorization was denied. You can close this tab.");
1085
+ return;
1086
+ }
1087
+ if (!req.query.code) {
1088
+ rejectCode(new Error("No authorization code received"));
1089
+ res.end("No authorization code received. You can close this tab.");
1090
+ return;
1091
+ }
1092
+ setCode(String(req.query.code));
824
1093
  res.end("You can now close this tab and return to the CLI.");
825
1094
  });
826
- const server = app.listen(availablePort);
1095
+ const server = expressApp.listen(availablePort);
827
1096
  const connections = /* @__PURE__ */ new Set();
828
1097
  server.on("connection", (conn) => {
829
1098
  connections.add(conn);
@@ -842,7 +1111,7 @@ var login = async ({
842
1111
  client_id: clientId,
843
1112
  redirect_uri: redirectUri,
844
1113
  scope,
845
- state: generateRandomString(),
1114
+ state: oauthState,
846
1115
  code_challenge: codeChallenge,
847
1116
  code_challenge_method: "S256"
848
1117
  }).toString()}`;
@@ -901,36 +1170,79 @@ var login = async ({
901
1170
  }
902
1171
  }
903
1172
  );
904
- let targetStorage;
905
- if (getLoginStorageMode() === "config") {
906
- const { upgrade } = await inquirer__default.default.prompt([
907
- {
908
- type: "confirm",
909
- name: "upgrade",
910
- message: "Would you like to upgrade to system keychain storage? This is recommended to securely store your credentials. However, note that older SDK/CLI versions will NOT be able to read these credentials, so you will want to upgrade them to the latest version.",
911
- default: true
912
- }
913
- ]);
914
- targetStorage = upgrade ? "keychain" : "config";
915
- } else {
916
- targetStorage = "keychain";
917
- }
1173
+ log_default.info("Token exchange completed successfully");
1174
+ return {
1175
+ accessToken: data.access_token,
1176
+ refreshToken: data.refresh_token,
1177
+ expiresIn: data.expires_in
1178
+ };
1179
+ }
1180
+
1181
+ // src/utils/auth/client-credentials.ts
1182
+ var CREDENTIALS_SCOPES = ["external", "credentials"];
1183
+ async function createCredentialsOnServer(api2, name) {
1184
+ const response = await api2.post(
1185
+ "/api/v0/client-credentials",
1186
+ { name, allowed_scopes: CREDENTIALS_SCOPES },
1187
+ { authRequired: true, requiredScopes: ["credentials"] }
1188
+ );
1189
+ return {
1190
+ clientId: response.data.client_id,
1191
+ clientSecret: response.data.client_secret
1192
+ };
1193
+ }
1194
+ async function deleteCredentialsOnServer(api2, clientId) {
1195
+ await api2.delete(`/api/v0/client-credentials/${clientId}`, void 0, {
1196
+ authRequired: true,
1197
+ requiredScopes: ["credentials"]
1198
+ });
1199
+ }
1200
+ async function setupClientCredentials({
1201
+ api: api2,
1202
+ name,
1203
+ credentialsBaseUrl
1204
+ }) {
1205
+ const { clientId, clientSecret } = await createCredentialsOnServer(api2, name);
918
1206
  try {
919
- await updateLogin(data, { storage: targetStorage });
920
- } catch (err) {
921
- if (targetStorage === "keychain") {
922
- log_default.warn(
923
- `Could not store credentials in system keychain. Storing in plaintext at ${getConfigPath()}.`
1207
+ await withRetry({
1208
+ action: () => storeClientCredentials({
1209
+ name,
1210
+ clientId,
1211
+ clientSecret,
1212
+ scopes: [...CREDENTIALS_SCOPES],
1213
+ baseUrl: credentialsBaseUrl
1214
+ })
1215
+ });
1216
+ } catch (storeErr) {
1217
+ try {
1218
+ await withRetry({
1219
+ action: () => deleteCredentialsOnServer(api2, clientId)
1220
+ });
1221
+ } catch {
1222
+ console.error(
1223
+ `Failed to roll back orphaned credential ${clientId}. Delete it manually with: zapier-sdk delete-client-credentials ${clientId}`
924
1224
  );
925
- await updateLogin(data, { storage: "config" });
926
- } else {
927
- throw err;
928
1225
  }
1226
+ throw storeErr;
929
1227
  }
930
- log_default.info("Token exchange completed successfully");
931
- return data.access_token;
932
- };
933
- var login_default = login;
1228
+ return { clientId };
1229
+ }
1230
+ function getBaseUrlFromResolvedCredentials(credentials) {
1231
+ if (credentials && zapierSdk.isCredentialsObject(credentials)) {
1232
+ return credentials.baseUrl;
1233
+ }
1234
+ return void 0;
1235
+ }
1236
+ function getBaseUrlFromOptionsCredentials(credentials) {
1237
+ if (credentials && typeof credentials === "object" && "baseUrl" in credentials && typeof credentials.baseUrl === "string") {
1238
+ return credentials.baseUrl;
1239
+ }
1240
+ return void 0;
1241
+ }
1242
+ async function resolveCredentialsBaseUrl(context) {
1243
+ const resolvedCredentials = "resolvedCredentials" in context ? context.resolvedCredentials : await context.resolveCredentials?.();
1244
+ return getBaseUrlFromResolvedCredentials(resolvedCredentials) ?? getBaseUrlFromOptionsCredentials(context.options?.credentials) ?? context.options?.baseUrl;
1245
+ }
934
1246
  var LoginSchema = zod.z.object({
935
1247
  timeout: zod.z.string().optional().describe("Login timeout in seconds (default: 300)")
936
1248
  }).describe("Log in to Zapier to access your account");
@@ -947,6 +1259,105 @@ function toPkceCredentials(credentials) {
947
1259
  }
948
1260
  return void 0;
949
1261
  }
1262
+ async function confirmRevokeAndRelogin(activeCredentials) {
1263
+ const { confirmed } = await inquirer__default.default.prompt([
1264
+ {
1265
+ type: "confirm",
1266
+ name: "confirmed",
1267
+ message: `You are already logged in as "${activeCredentials.name}".
1268
+ Logging out will delete these credentials and may interrupt other Zapier SDK or CLI sessions using them.
1269
+ Log out and log in again?`,
1270
+ default: false
1271
+ }
1272
+ ]);
1273
+ if (!confirmed) {
1274
+ console.log("Login cancelled.");
1275
+ return false;
1276
+ }
1277
+ return true;
1278
+ }
1279
+ async function confirmJwtMigration() {
1280
+ const { confirmed } = await inquirer__default.default.prompt([
1281
+ {
1282
+ type: "confirm",
1283
+ name: "confirmed",
1284
+ 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?",
1285
+ default: true
1286
+ }
1287
+ ]);
1288
+ if (!confirmed) {
1289
+ console.log("Login cancelled.");
1290
+ return false;
1291
+ }
1292
+ return true;
1293
+ }
1294
+ async function confirmLocalLoginReset() {
1295
+ const { confirmed } = await inquirer__default.default.prompt([
1296
+ {
1297
+ type: "confirm",
1298
+ name: "confirmed",
1299
+ message: "Login cleanup failed. Reset local session state and continue?",
1300
+ default: false
1301
+ }
1302
+ ]);
1303
+ if (!confirmed) {
1304
+ console.log("Login cancelled.");
1305
+ return false;
1306
+ }
1307
+ return true;
1308
+ }
1309
+ function parseTimeoutSeconds(timeout) {
1310
+ const timeoutSeconds = timeout ? parseInt(timeout, 10) : 300;
1311
+ if (isNaN(timeoutSeconds) || timeoutSeconds <= 0) {
1312
+ throw new Error("Timeout must be a positive number");
1313
+ }
1314
+ return timeoutSeconds;
1315
+ }
1316
+ async function promptCredentialsName(email, baseUrl) {
1317
+ const { credentialName } = await inquirer__default.default.prompt([
1318
+ {
1319
+ type: "input",
1320
+ name: "credentialName",
1321
+ message: "Enter a name to identify them:",
1322
+ default: `${email}@${os.hostname()}`,
1323
+ validate: (input) => {
1324
+ if (!input.trim()) return "Name cannot be empty";
1325
+ if (credentialsNameExists({ name: input.trim(), baseUrl })) {
1326
+ return `Credentials named "${input.trim()}" already exist. Please provide a different name.`;
1327
+ }
1328
+ return true;
1329
+ }
1330
+ }
1331
+ ]);
1332
+ return credentialName;
1333
+ }
1334
+ function emitLoginSuccess({
1335
+ sdk,
1336
+ profile
1337
+ }) {
1338
+ sdk.context.eventEmission.emit(
1339
+ "platform.sdk.ApplicationLifecycleEvent",
1340
+ zapierSdk.buildApplicationLifecycleEvent(
1341
+ { lifecycle_event_type: "login_success" },
1342
+ {
1343
+ customuser_id: profile.user_id,
1344
+ account_id: profile.roles[0]?.account_id ?? null
1345
+ }
1346
+ )
1347
+ );
1348
+ }
1349
+ async function getProfile(api2) {
1350
+ return api2.get("/zapier/api/v4/profile/", {
1351
+ authRequired: true
1352
+ });
1353
+ }
1354
+ async function bestEffortClearLegacyJwtState() {
1355
+ try {
1356
+ await clearLegacyJwtState();
1357
+ } catch (err) {
1358
+ console.error("[login] Best-effort legacy JWT cleanup failed:", err);
1359
+ }
1360
+ }
950
1361
  var loginPlugin = zapierSdk.definePlugin(
951
1362
  (sdk) => zapierSdk.createPluginMethod(sdk, {
952
1363
  name: "login",
@@ -954,25 +1365,61 @@ var loginPlugin = zapierSdk.definePlugin(
954
1365
  inputSchema: LoginSchema,
955
1366
  supportsJsonOutput: false,
956
1367
  handler: async ({ sdk: sdk2, options }) => {
957
- const timeoutSeconds = options.timeout ? parseInt(options.timeout, 10) : 300;
958
- if (isNaN(timeoutSeconds) || timeoutSeconds <= 0) {
959
- throw new Error("Timeout must be a positive number");
960
- }
1368
+ const timeoutSeconds = parseTimeoutSeconds(options.timeout);
961
1369
  const resolvedCredentials = await sdk2.context.resolveCredentials();
962
1370
  const pkceCredentials = toPkceCredentials(resolvedCredentials);
963
- await login_default({
1371
+ const credentialsBaseUrl = await resolveCredentialsBaseUrl({
1372
+ ...sdk2.context,
1373
+ resolvedCredentials
1374
+ });
1375
+ const activeCredentials = getActiveCredentials({
1376
+ baseUrl: credentialsBaseUrl
1377
+ });
1378
+ if (activeCredentials) {
1379
+ if (!await confirmRevokeAndRelogin(activeCredentials)) return;
1380
+ try {
1381
+ await revokeCredentials({
1382
+ api: sdk2.context.api,
1383
+ credentials: activeCredentials
1384
+ });
1385
+ } catch {
1386
+ if (!await confirmLocalLoginReset()) return;
1387
+ await deleteStoredClientCredentials({
1388
+ name: activeCredentials.name,
1389
+ baseUrl: activeCredentials.baseUrl
1390
+ });
1391
+ }
1392
+ } else if (hasLegacyJwtConfig()) {
1393
+ if (!await confirmJwtMigration()) return;
1394
+ }
1395
+ const { accessToken } = await runOauthFlow({
964
1396
  timeoutMs: timeoutSeconds * 1e3,
965
- credentials: pkceCredentials
1397
+ pkceCredentials,
1398
+ baseUrl: credentialsBaseUrl
966
1399
  });
967
- const user = await getLoggedInUser();
968
- sdk2.context.eventEmission.emit(
969
- "platform.sdk.ApplicationLifecycleEvent",
970
- zapierSdk.buildApplicationLifecycleEvent(
971
- { lifecycle_event_type: "login_success" },
972
- { customuser_id: user.customUserId, account_id: user.accountId }
973
- )
1400
+ const scopedApi = zapierSdk.getOrCreateApiClient({
1401
+ credentials: accessToken,
1402
+ baseUrl: credentialsBaseUrl
1403
+ });
1404
+ const profile = await getProfile(scopedApi);
1405
+ console.log(`\u{1F464} Logged in as ${profile.email}`);
1406
+ console.log(
1407
+ "\nGenerating credentials so this machine can make authenticated requests on your behalf."
1408
+ );
1409
+ const credentialName = await promptCredentialsName(
1410
+ profile.email,
1411
+ credentialsBaseUrl
974
1412
  );
975
- console.log(`\u2705 Successfully logged in as ${user.email}`);
1413
+ await setupClientCredentials({
1414
+ api: scopedApi,
1415
+ name: credentialName,
1416
+ credentialsBaseUrl
1417
+ });
1418
+ await bestEffortClearLegacyJwtState();
1419
+ console.log(
1420
+ `\u2705 Credentials "${credentialName}" created and set as default. You are ready to use the Zapier SDK.`
1421
+ );
1422
+ emitLoginSuccess({ sdk: sdk2, profile });
976
1423
  }
977
1424
  })
978
1425
  );
@@ -985,8 +1432,36 @@ var logoutPlugin = zapierSdk.definePlugin(
985
1432
  categories: ["account"],
986
1433
  inputSchema: LogoutSchema,
987
1434
  supportsJsonOutput: false,
988
- handler: async () => {
989
- await logout();
1435
+ handler: async ({ sdk: sdk2 }) => {
1436
+ const credentialsBaseUrl = await resolveCredentialsBaseUrl(sdk2.context);
1437
+ const activeCredentials = getActiveCredentials({
1438
+ baseUrl: credentialsBaseUrl
1439
+ });
1440
+ const onEvent = sdk2.context.options?.onEvent;
1441
+ if (!activeCredentials) {
1442
+ await logout({ onEvent });
1443
+ console.log("\u2705 Successfully logged out");
1444
+ return;
1445
+ }
1446
+ const { confirmed } = await inquirer__default.default.prompt([
1447
+ {
1448
+ type: "confirm",
1449
+ name: "confirmed",
1450
+ message: `Logging out will delete credentials "${activeCredentials.name}".
1451
+ This may interrupt other Zapier SDK or CLI sessions using them.
1452
+ Do you want to continue?`,
1453
+ default: true
1454
+ }
1455
+ ]);
1456
+ if (!confirmed) {
1457
+ console.log("Logout cancelled.");
1458
+ return;
1459
+ }
1460
+ await revokeCredentials({
1461
+ api: sdk2.context.api,
1462
+ credentials: activeCredentials,
1463
+ onEvent
1464
+ });
990
1465
  console.log("\u2705 Successfully logged out");
991
1466
  }
992
1467
  })
@@ -3486,7 +3961,7 @@ zapierSdk.definePlugin(
3486
3961
  // package.json with { type: 'json' }
3487
3962
  var package_default = {
3488
3963
  name: "@zapier/zapier-sdk-cli",
3489
- version: "0.47.0"};
3964
+ version: "0.48.0"};
3490
3965
 
3491
3966
  // src/sdk.ts
3492
3967
  zapierSdk.injectCliLogin(login_exports);
@@ -3514,7 +3989,7 @@ function createZapierCliSdk(options = {}) {
3514
3989
 
3515
3990
  // package.json
3516
3991
  var package_default2 = {
3517
- version: "0.47.0"};
3992
+ version: "0.48.0"};
3518
3993
 
3519
3994
  // src/telemetry/builders.ts
3520
3995
  function createCliBaseEvent(context = {}) {