@zapier/zapier-sdk-cli 0.47.0 → 0.48.1

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