@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
package/dist/index.mjs CHANGED
@@ -1,20 +1,21 @@
1
+ import * as jwt from 'jsonwebtoken';
2
+ import { deletePassword, getKeyring, getPassword, setPassword } from 'cross-keychain';
1
3
  import Conf from 'conf';
2
4
  import * as fs from 'fs';
3
5
  import { promises, createWriteStream, existsSync, readdirSync, rmSync, mkdirSync, writeFileSync, copyFileSync, readFileSync } from 'fs';
4
- import * as jwt from 'jsonwebtoken';
5
- import { getPassword, getKeyring, setPassword, deletePassword } from 'cross-keychain';
6
6
  import crypto, { createHash } from 'crypto';
7
7
  import * as path from 'path';
8
8
  import { resolve, join, dirname, basename, relative, extname } from 'path';
9
9
  import * as lockfile from 'proper-lockfile';
10
- import { definePlugin, createPluginMethod, buildApplicationLifecycleEvent, OutputPropertySchema, ZapierBundleError, DEFAULT_CONFIG_PATH, ZapierValidationError, ZapierUnknownError, ZapierReleaseTriggerMessageSignal, injectCliLogin, isCredentialsObject, batch, toSnakeCase, ZapierAbortDrainSignal, ZapierError, createZapierSdk, getOsInfo, getPlatformVersions, getCiPlatform, isCi, getReleaseId, getCurrentTimestamp, generateEventId } from '@zapier/zapier-sdk';
10
+ import { z } from 'zod';
11
+ import { definePlugin, createPluginMethod, getOrCreateApiClient, OutputPropertySchema, ZapierBundleError, DEFAULT_CONFIG_PATH, ZapierValidationError, ZapierUnknownError, ZapierReleaseTriggerMessageSignal, injectCliLogin, isCredentialsObject, invalidateCachedToken, buildApplicationLifecycleEvent, batch, toSnakeCase, ZapierAbortDrainSignal, ZapierError, createZapierSdk, getOsInfo, getPlatformVersions, getCiPlatform, isCi, getReleaseId, getCurrentTimestamp, generateEventId } from '@zapier/zapier-sdk';
12
+ import { hostname } from 'os';
13
+ import inquirer from 'inquirer';
11
14
  import open from 'open';
12
15
  import express from 'express';
13
16
  import pkceChallenge from 'pkce-challenge';
14
17
  import ora from 'ora';
15
18
  import chalk3 from 'chalk';
16
- import inquirer from 'inquirer';
17
- import { z } from 'zod';
18
19
  import { startMcpServer } from '@zapier/zapier-sdk-mcp';
19
20
  import { buildSync } from 'esbuild';
20
21
  import { mkdir, writeFile, access } from 'fs/promises';
@@ -35,8 +36,11 @@ var __export = (target, all) => {
35
36
  var login_exports = {};
36
37
  __export(login_exports, {
37
38
  AUTH_MODE_HEADER: () => AUTH_MODE_HEADER,
39
+ DEFAULT_AUTH_BASE_URL: () => DEFAULT_AUTH_BASE_URL,
38
40
  ZapierAuthenticationError: () => ZapierAuthenticationError,
41
+ clearTokensFromKeychain: () => clearTokensFromKeychain,
39
42
  createCache: () => createCache,
43
+ getActiveCredentials: () => getActiveCredentials,
40
44
  getAuthAuthorizeUrl: () => getAuthAuthorizeUrl,
41
45
  getAuthTokenUrl: () => getAuthTokenUrl,
42
46
  getConfig: () => getConfig,
@@ -44,6 +48,7 @@ __export(login_exports, {
44
48
  getLoggedInUser: () => getLoggedInUser,
45
49
  getLoginStorageMode: () => getLoginStorageMode,
46
50
  getPkceLoginConfig: () => getPkceLoginConfig,
51
+ getStoredClientCredentials: () => getStoredClientCredentials,
47
52
  getToken: () => getToken,
48
53
  logout: () => logout,
49
54
  unloadConfig: () => unloadConfig,
@@ -124,6 +129,38 @@ async function clearTokensFromKeychain({
124
129
  }
125
130
  });
126
131
  }
132
+ var DEFAULT_AUTH_BASE_URL = "https://zapier.com";
133
+ var config = null;
134
+ function getConfig() {
135
+ if (!config) {
136
+ config = new Conf({ projectName: "zapier-sdk-cli" });
137
+ if (!config.has("login_storage_mode")) {
138
+ config.set(
139
+ "login_storage_mode",
140
+ existsSync(config.path) ? "config" : "keychain"
141
+ );
142
+ }
143
+ }
144
+ return config;
145
+ }
146
+ function resetConfig() {
147
+ config = null;
148
+ }
149
+
150
+ // src/login/legacy-jwt.ts
151
+ function clearLegacyJwtConfigKeys(config2) {
152
+ config2.delete("login_jwt");
153
+ config2.delete("login_refresh_token");
154
+ config2.delete("login_expires_at");
155
+ }
156
+ async function clearLegacyJwtState() {
157
+ clearLegacyJwtConfigKeys(getConfig());
158
+ await clearTokensFromKeychain();
159
+ }
160
+ function hasLegacyJwtConfig() {
161
+ const cfg = getConfig();
162
+ return typeof cfg.get("login_jwt") === "string" || typeof cfg.get("login_refresh_token") === "string" || typeof cfg.get("login_expires_at") === "number";
163
+ }
127
164
  var SERVICE2 = "zapier-sdk-cache";
128
165
  var CONFIG_KEY = "cache";
129
166
  var LOCK_UPDATE_MS = 5e3;
@@ -251,6 +288,163 @@ function createCache() {
251
288
  }
252
289
  };
253
290
  }
291
+ var SERVICE3 = "zapier-sdk-cli";
292
+ var CREDENTIALS_KEY = "credentials";
293
+ var REGISTRY_KEY = "credentialsRegistry";
294
+ var CredentialsEntrySchema = z.object({
295
+ name: z.string(),
296
+ clientId: z.string(),
297
+ createdAt: z.number(),
298
+ scopes: z.array(z.string()),
299
+ baseUrl: z.string()
300
+ });
301
+ function normalizeBaseUrl(baseUrl) {
302
+ return baseUrl ?? DEFAULT_AUTH_BASE_URL;
303
+ }
304
+ function keychainAccount2(key) {
305
+ return createHash("sha256").update(key).digest("hex");
306
+ }
307
+ function buildKeychainKey(clientId, scopes, baseUrl) {
308
+ const sortedScopes = [...scopes].sort().join(",");
309
+ return `zapier-sdk/client-credentials-secret/${clientId}:${sortedScopes}:${baseUrl}`;
310
+ }
311
+ function findEntry(registry, name, baseUrl) {
312
+ return registry.find((e) => e.name === name && e.baseUrl === baseUrl);
313
+ }
314
+ function readRegistry() {
315
+ const stored = getConfig().get(REGISTRY_KEY);
316
+ if (!Array.isArray(stored)) return [];
317
+ return stored.flatMap((entry) => {
318
+ const result = CredentialsEntrySchema.safeParse(entry);
319
+ return result.success ? [result.data] : [];
320
+ });
321
+ }
322
+ function getActiveCredentials(options) {
323
+ const name = getConfig().get(CREDENTIALS_KEY);
324
+ if (!name) return void 0;
325
+ return findEntry(readRegistry(), name, normalizeBaseUrl(options?.baseUrl));
326
+ }
327
+ async function storeClientCredentials({
328
+ name,
329
+ clientId,
330
+ clientSecret,
331
+ scopes,
332
+ baseUrl
333
+ }) {
334
+ if (!name || typeof name !== "string") {
335
+ throw new Error("storeClientCredentials: name is required");
336
+ }
337
+ if (!clientId || typeof clientId !== "string") {
338
+ throw new Error("storeClientCredentials: clientId is required");
339
+ }
340
+ if (!clientSecret || typeof clientSecret !== "string") {
341
+ throw new Error("storeClientCredentials: clientSecret is required");
342
+ }
343
+ if (!Array.isArray(scopes) || scopes.length === 0) {
344
+ throw new Error("storeClientCredentials: scopes must be a non-empty array");
345
+ }
346
+ const sortedScopes = [...scopes].sort();
347
+ const resolvedBaseUrl = normalizeBaseUrl(baseUrl);
348
+ const keychainKey = buildKeychainKey(clientId, sortedScopes, resolvedBaseUrl);
349
+ const existingEntry = findEntry(readRegistry(), name, resolvedBaseUrl);
350
+ const existingKeychainKey = existingEntry ? buildKeychainKey(
351
+ existingEntry.clientId,
352
+ existingEntry.scopes,
353
+ existingEntry.baseUrl
354
+ ) : void 0;
355
+ await enqueue(async () => {
356
+ await getBackendInfo();
357
+ await setPassword(SERVICE3, keychainAccount2(keychainKey), clientSecret);
358
+ });
359
+ const entry = {
360
+ name,
361
+ clientId,
362
+ createdAt: Date.now(),
363
+ scopes: sortedScopes,
364
+ baseUrl: resolvedBaseUrl
365
+ };
366
+ const registry = readRegistry().filter(
367
+ (e) => !(e.name === name && e.baseUrl === resolvedBaseUrl)
368
+ );
369
+ registry.push(entry);
370
+ const cfg = getConfig();
371
+ cfg.set(REGISTRY_KEY, registry);
372
+ cfg.set(CREDENTIALS_KEY, name);
373
+ if (existingEntry && existingKeychainKey !== keychainKey) {
374
+ await deleteKeychainSecret(existingEntry);
375
+ }
376
+ }
377
+ function credentialsNameExists({
378
+ name,
379
+ baseUrl
380
+ }) {
381
+ return !!findEntry(readRegistry(), name, normalizeBaseUrl(baseUrl));
382
+ }
383
+ async function getStoredClientCredentials(options) {
384
+ const entry = options?.name ? findEntry(readRegistry(), options.name, normalizeBaseUrl(options.baseUrl)) : getActiveCredentials(options);
385
+ if (!entry) return void 0;
386
+ const keychainKey = buildKeychainKey(
387
+ entry.clientId,
388
+ entry.scopes,
389
+ entry.baseUrl
390
+ );
391
+ const clientSecret = await enqueue(async () => {
392
+ await getBackendInfo();
393
+ return getPassword(SERVICE3, keychainAccount2(keychainKey));
394
+ });
395
+ if (!clientSecret) return void 0;
396
+ return {
397
+ type: "client_credentials",
398
+ clientId: entry.clientId,
399
+ clientSecret,
400
+ baseUrl: entry.baseUrl,
401
+ scope: [...entry.scopes].sort().join(" ")
402
+ };
403
+ }
404
+ function deleteRegistryEntry(registry, name, baseUrl) {
405
+ const idx = registry.findIndex(
406
+ (e) => e.name === name && e.baseUrl === baseUrl
407
+ );
408
+ if (idx === -1) return void 0;
409
+ const [removed] = registry.splice(idx, 1);
410
+ return removed;
411
+ }
412
+ function unsetMatchingCredentialsKey(cfg, name) {
413
+ const activeName = cfg.get(CREDENTIALS_KEY);
414
+ if (activeName === name && !readRegistry().some((e) => e.name === name)) {
415
+ cfg.delete(CREDENTIALS_KEY);
416
+ }
417
+ }
418
+ async function deleteKeychainSecret(entry) {
419
+ const keychainKey = buildKeychainKey(
420
+ entry.clientId,
421
+ entry.scopes,
422
+ entry.baseUrl
423
+ );
424
+ try {
425
+ await enqueue(async () => {
426
+ await getBackendInfo();
427
+ await deletePassword(SERVICE3, keychainAccount2(keychainKey));
428
+ });
429
+ } catch {
430
+ }
431
+ }
432
+ async function deleteStoredClientCredentials({
433
+ name,
434
+ baseUrl
435
+ }) {
436
+ const registry = readRegistry();
437
+ const removed = deleteRegistryEntry(
438
+ registry,
439
+ name,
440
+ normalizeBaseUrl(baseUrl)
441
+ );
442
+ if (!removed) return;
443
+ const cfg = getConfig();
444
+ cfg.set(REGISTRY_KEY, registry);
445
+ unsetMatchingCredentialsKey(cfg, name);
446
+ await deleteKeychainSecret(removed);
447
+ }
254
448
 
255
449
  // src/login/index.ts
256
450
  var ZapierAuthenticationError = class extends Error {
@@ -259,7 +453,6 @@ var ZapierAuthenticationError = class extends Error {
259
453
  this.name = "ZapierAuthenticationError";
260
454
  }
261
455
  };
262
- var config = null;
263
456
  var DEFAULT_AUTH_CLIENT_ID = "grwWZD5hUWGvb4V8ODBuOtXer3h0DBEZ2HR8aay6";
264
457
  var TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1e3;
265
458
  function createDebugLog(enabled) {
@@ -285,7 +478,6 @@ function getAuthClientId(clientId) {
285
478
  return clientId || DEFAULT_AUTH_CLIENT_ID;
286
479
  }
287
480
  var AUTH_MODE_HEADER = "X-Auth";
288
- var DEFAULT_AUTH_BASE_URL = "https://zapier.com";
289
481
  function getAuthTokenUrl(options) {
290
482
  const authBaseUrl = options?.baseUrl || DEFAULT_AUTH_BASE_URL;
291
483
  return `${authBaseUrl}/oauth/token/`;
@@ -295,29 +487,16 @@ function getAuthAuthorizeUrl(options) {
295
487
  return `${authBaseUrl}/oauth/authorize/`;
296
488
  }
297
489
  function getPkceLoginConfig(options) {
490
+ const effectiveBaseUrl = options?.credentials?.baseUrl ?? options?.baseUrl;
298
491
  return {
299
492
  clientId: getAuthClientId(options?.credentials?.clientId),
300
- tokenUrl: getAuthTokenUrl({ baseUrl: options?.credentials?.baseUrl }),
301
- authorizeUrl: getAuthAuthorizeUrl({
302
- baseUrl: options?.credentials?.baseUrl
303
- })
493
+ tokenUrl: getAuthTokenUrl({ baseUrl: effectiveBaseUrl }),
494
+ authorizeUrl: getAuthAuthorizeUrl({ baseUrl: effectiveBaseUrl })
304
495
  };
305
496
  }
306
497
  var cachedLogin;
307
- function getConfig() {
308
- if (!config) {
309
- config = new Conf({ projectName: "zapier-sdk-cli" });
310
- if (!config.has("login_storage_mode")) {
311
- config.set(
312
- "login_storage_mode",
313
- existsSync(config.path) ? "config" : "keychain"
314
- );
315
- }
316
- }
317
- return config;
318
- }
319
498
  function unloadConfig() {
320
- config = null;
499
+ resetConfig();
321
500
  cachedLogin = void 0;
322
501
  }
323
502
  async function updateLogin(loginData, options = {}) {
@@ -590,9 +769,7 @@ async function logout(options = {}) {
590
769
  await clearTokensFromKeychain();
591
770
  const cfg = getConfig();
592
771
  cfg.set("login_storage_mode", mode);
593
- cfg.delete("login_expires_at");
594
- cfg.delete("login_jwt");
595
- cfg.delete("login_refresh_token");
772
+ clearLegacyJwtConfigKeys(cfg);
596
773
  onEvent?.({
597
774
  type: "auth_logout",
598
775
  payload: { message: "Logged out successfully", operation: "logout" },
@@ -604,6 +781,79 @@ function getConfigPath() {
604
781
  return cfg.path;
605
782
  }
606
783
 
784
+ // src/utils/retry.ts
785
+ function sleep(ms) {
786
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
787
+ }
788
+ async function withRetry({
789
+ action,
790
+ attempts = 3,
791
+ initialDelayMs = 100
792
+ }) {
793
+ if (attempts <= 0) {
794
+ throw new Error("withRetry: attempts must be greater than 0");
795
+ }
796
+ let lastError;
797
+ for (let i = 0; i < attempts; i++) {
798
+ try {
799
+ return await action();
800
+ } catch (err) {
801
+ lastError = err;
802
+ if (i < attempts - 1) {
803
+ await sleep(initialDelayMs * 2 ** i);
804
+ }
805
+ }
806
+ }
807
+ throw lastError;
808
+ }
809
+
810
+ // src/login/credentials-revoke.ts
811
+ function emitAuthLogout(onEvent) {
812
+ onEvent?.({
813
+ type: "auth_logout",
814
+ payload: { message: "Logged out successfully", operation: "logout" },
815
+ timestamp: Date.now()
816
+ });
817
+ }
818
+ function isNotFoundError(err) {
819
+ return typeof err === "object" && err !== null && "statusCode" in err && err.statusCode === 404;
820
+ }
821
+ async function revokeCredentials({
822
+ api: api2,
823
+ credentials,
824
+ onEvent
825
+ }) {
826
+ await withRetry({
827
+ action: async () => {
828
+ try {
829
+ await api2.delete(
830
+ `/api/v0/client-credentials/${credentials.clientId}`,
831
+ void 0,
832
+ { authRequired: true, requiredScopes: ["credentials"] }
833
+ );
834
+ } catch (err) {
835
+ if (isNotFoundError(err)) return;
836
+ throw err;
837
+ }
838
+ }
839
+ });
840
+ try {
841
+ await deleteStoredClientCredentials({
842
+ name: credentials.name,
843
+ baseUrl: credentials.baseUrl
844
+ });
845
+ } catch (err) {
846
+ console.warn("[revokeCredentials] Local store cleanup failed:", err);
847
+ }
848
+ await clearLegacyJwtState();
849
+ await invalidateCachedToken({
850
+ clientId: credentials.clientId,
851
+ scopes: credentials.scopes,
852
+ baseUrl: credentials.baseUrl
853
+ });
854
+ emitAuthLogout(onEvent);
855
+ }
856
+
607
857
  // src/utils/constants.ts
608
858
  var LOGIN_PORTS = [49505, 50575, 52804, 55981, 61010, 63851];
609
859
  var LOGIN_TIMEOUT_MS = 3e5;
@@ -717,6 +967,8 @@ var getCallablePromise = () => {
717
967
  };
718
968
  };
719
969
  var getCallablePromise_default = getCallablePromise;
970
+
971
+ // src/utils/auth/oauth-flow.ts
720
972
  var findAvailablePort = () => {
721
973
  return new Promise((resolve4, reject) => {
722
974
  let portIndex = 0;
@@ -751,10 +1003,9 @@ var findAvailablePort = () => {
751
1003
  var generateRandomString = () => {
752
1004
  const array = new Uint32Array(28);
753
1005
  crypto.getRandomValues(array);
754
- return Array.from(
755
- array,
756
- (dec) => ("0" + dec.toString(16)).substring(-2)
757
- ).join("");
1006
+ return Array.from(array, (dec) => ("0" + dec.toString(16)).slice(-2)).join(
1007
+ ""
1008
+ );
758
1009
  };
759
1010
  function ensureOfflineAccess(scope) {
760
1011
  if (scope.includes("offline_access")) {
@@ -762,17 +1013,18 @@ function ensureOfflineAccess(scope) {
762
1013
  }
763
1014
  return `${scope} offline_access`;
764
1015
  }
765
- var login = async ({
1016
+ async function runOauthFlow({
766
1017
  timeoutMs = LOGIN_TIMEOUT_MS,
767
- credentials
768
- }) => {
1018
+ pkceCredentials,
1019
+ baseUrl
1020
+ }) {
769
1021
  const { clientId, tokenUrl, authorizeUrl } = getPkceLoginConfig({
770
- credentials
1022
+ credentials: pkceCredentials,
1023
+ baseUrl
771
1024
  });
772
1025
  const scope = ensureOfflineAccess(
773
- credentials?.scope || "internal credentials"
1026
+ pkceCredentials?.scope || "internal credentials"
774
1027
  );
775
- await logout();
776
1028
  const availablePort = await findAvailablePort();
777
1029
  const redirectUri = `http://localhost:${availablePort}/oauth`;
778
1030
  log_default.info(`Using port ${availablePort} for OAuth callback`);
@@ -781,13 +1033,30 @@ var login = async ({
781
1033
  resolve: setCode,
782
1034
  reject: rejectCode
783
1035
  } = getCallablePromise_default();
784
- const app = express();
785
- app.get("/oauth", (req, res) => {
786
- setCode(String(req.query.code));
1036
+ const oauthState = generateRandomString();
1037
+ const expressApp = express();
1038
+ expressApp.get("/oauth", (req, res) => {
787
1039
  res.setHeader("Connection", "close");
1040
+ if (req.query.state !== oauthState) {
1041
+ rejectCode(new Error("OAuth state mismatch \u2014 possible CSRF"));
1042
+ res.status(400).end("Invalid state. You can close this tab.");
1043
+ return;
1044
+ }
1045
+ if (req.query.error) {
1046
+ const desc = req.query.error_description ?? req.query.error;
1047
+ rejectCode(new Error(`Authorization denied: ${desc}`));
1048
+ res.end("Authorization was denied. You can close this tab.");
1049
+ return;
1050
+ }
1051
+ if (!req.query.code) {
1052
+ rejectCode(new Error("No authorization code received"));
1053
+ res.end("No authorization code received. You can close this tab.");
1054
+ return;
1055
+ }
1056
+ setCode(String(req.query.code));
788
1057
  res.end("You can now close this tab and return to the CLI.");
789
1058
  });
790
- const server = app.listen(availablePort);
1059
+ const server = expressApp.listen(availablePort);
791
1060
  const connections = /* @__PURE__ */ new Set();
792
1061
  server.on("connection", (conn) => {
793
1062
  connections.add(conn);
@@ -806,7 +1075,7 @@ var login = async ({
806
1075
  client_id: clientId,
807
1076
  redirect_uri: redirectUri,
808
1077
  scope,
809
- state: generateRandomString(),
1078
+ state: oauthState,
810
1079
  code_challenge: codeChallenge,
811
1080
  code_challenge_method: "S256"
812
1081
  }).toString()}`;
@@ -865,36 +1134,79 @@ var login = async ({
865
1134
  }
866
1135
  }
867
1136
  );
868
- let targetStorage;
869
- if (getLoginStorageMode() === "config") {
870
- const { upgrade } = await inquirer.prompt([
871
- {
872
- type: "confirm",
873
- name: "upgrade",
874
- 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.",
875
- default: true
876
- }
877
- ]);
878
- targetStorage = upgrade ? "keychain" : "config";
879
- } else {
880
- targetStorage = "keychain";
881
- }
1137
+ log_default.info("Token exchange completed successfully");
1138
+ return {
1139
+ accessToken: data.access_token,
1140
+ refreshToken: data.refresh_token,
1141
+ expiresIn: data.expires_in
1142
+ };
1143
+ }
1144
+
1145
+ // src/utils/auth/client-credentials.ts
1146
+ var CREDENTIALS_SCOPES = ["external", "credentials"];
1147
+ async function createCredentialsOnServer(api2, name) {
1148
+ const response = await api2.post(
1149
+ "/api/v0/client-credentials",
1150
+ { name, allowed_scopes: CREDENTIALS_SCOPES },
1151
+ { authRequired: true, requiredScopes: ["credentials"] }
1152
+ );
1153
+ return {
1154
+ clientId: response.data.client_id,
1155
+ clientSecret: response.data.client_secret
1156
+ };
1157
+ }
1158
+ async function deleteCredentialsOnServer(api2, clientId) {
1159
+ await api2.delete(`/api/v0/client-credentials/${clientId}`, void 0, {
1160
+ authRequired: true,
1161
+ requiredScopes: ["credentials"]
1162
+ });
1163
+ }
1164
+ async function setupClientCredentials({
1165
+ api: api2,
1166
+ name,
1167
+ credentialsBaseUrl
1168
+ }) {
1169
+ const { clientId, clientSecret } = await createCredentialsOnServer(api2, name);
882
1170
  try {
883
- await updateLogin(data, { storage: targetStorage });
884
- } catch (err) {
885
- if (targetStorage === "keychain") {
886
- log_default.warn(
887
- `Could not store credentials in system keychain. Storing in plaintext at ${getConfigPath()}.`
1171
+ await withRetry({
1172
+ action: () => storeClientCredentials({
1173
+ name,
1174
+ clientId,
1175
+ clientSecret,
1176
+ scopes: [...CREDENTIALS_SCOPES],
1177
+ baseUrl: credentialsBaseUrl
1178
+ })
1179
+ });
1180
+ } catch (storeErr) {
1181
+ try {
1182
+ await withRetry({
1183
+ action: () => deleteCredentialsOnServer(api2, clientId)
1184
+ });
1185
+ } catch {
1186
+ console.error(
1187
+ `Failed to roll back orphaned credential ${clientId}. Delete it manually with: zapier-sdk delete-client-credentials ${clientId}`
888
1188
  );
889
- await updateLogin(data, { storage: "config" });
890
- } else {
891
- throw err;
892
1189
  }
1190
+ throw storeErr;
893
1191
  }
894
- log_default.info("Token exchange completed successfully");
895
- return data.access_token;
896
- };
897
- var login_default = login;
1192
+ return { clientId };
1193
+ }
1194
+ function getBaseUrlFromResolvedCredentials(credentials) {
1195
+ if (credentials && isCredentialsObject(credentials)) {
1196
+ return credentials.baseUrl;
1197
+ }
1198
+ return void 0;
1199
+ }
1200
+ function getBaseUrlFromOptionsCredentials(credentials) {
1201
+ if (credentials && typeof credentials === "object" && "baseUrl" in credentials && typeof credentials.baseUrl === "string") {
1202
+ return credentials.baseUrl;
1203
+ }
1204
+ return void 0;
1205
+ }
1206
+ async function resolveCredentialsBaseUrl(context) {
1207
+ const resolvedCredentials = "resolvedCredentials" in context ? context.resolvedCredentials : await context.resolveCredentials?.();
1208
+ return getBaseUrlFromResolvedCredentials(resolvedCredentials) ?? getBaseUrlFromOptionsCredentials(context.options?.credentials) ?? context.options?.baseUrl;
1209
+ }
898
1210
  var LoginSchema = z.object({
899
1211
  timeout: z.string().optional().describe("Login timeout in seconds (default: 300)")
900
1212
  }).describe("Log in to Zapier to access your account");
@@ -911,6 +1223,105 @@ function toPkceCredentials(credentials) {
911
1223
  }
912
1224
  return void 0;
913
1225
  }
1226
+ async function confirmRevokeAndRelogin(activeCredentials) {
1227
+ const { confirmed } = await inquirer.prompt([
1228
+ {
1229
+ type: "confirm",
1230
+ name: "confirmed",
1231
+ message: `You are already logged in as "${activeCredentials.name}".
1232
+ Logging out will delete these credentials and may interrupt other Zapier SDK or CLI sessions using them.
1233
+ Log out and log in again?`,
1234
+ default: false
1235
+ }
1236
+ ]);
1237
+ if (!confirmed) {
1238
+ console.log("Login cancelled.");
1239
+ return false;
1240
+ }
1241
+ return true;
1242
+ }
1243
+ async function confirmJwtMigration() {
1244
+ const { confirmed } = await inquirer.prompt([
1245
+ {
1246
+ type: "confirm",
1247
+ name: "confirmed",
1248
+ 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?",
1249
+ default: true
1250
+ }
1251
+ ]);
1252
+ if (!confirmed) {
1253
+ console.log("Login cancelled.");
1254
+ return false;
1255
+ }
1256
+ return true;
1257
+ }
1258
+ async function confirmLocalLoginReset() {
1259
+ const { confirmed } = await inquirer.prompt([
1260
+ {
1261
+ type: "confirm",
1262
+ name: "confirmed",
1263
+ message: "Login cleanup failed. Reset local session state and continue?",
1264
+ default: false
1265
+ }
1266
+ ]);
1267
+ if (!confirmed) {
1268
+ console.log("Login cancelled.");
1269
+ return false;
1270
+ }
1271
+ return true;
1272
+ }
1273
+ function parseTimeoutSeconds(timeout) {
1274
+ const timeoutSeconds = timeout ? parseInt(timeout, 10) : 300;
1275
+ if (isNaN(timeoutSeconds) || timeoutSeconds <= 0) {
1276
+ throw new Error("Timeout must be a positive number");
1277
+ }
1278
+ return timeoutSeconds;
1279
+ }
1280
+ async function promptCredentialsName(email, baseUrl) {
1281
+ const { credentialName } = await inquirer.prompt([
1282
+ {
1283
+ type: "input",
1284
+ name: "credentialName",
1285
+ message: "Enter a name to identify them:",
1286
+ default: `${email}@${hostname()}`,
1287
+ validate: (input) => {
1288
+ if (!input.trim()) return "Name cannot be empty";
1289
+ if (credentialsNameExists({ name: input.trim(), baseUrl })) {
1290
+ return `Credentials named "${input.trim()}" already exist. Please provide a different name.`;
1291
+ }
1292
+ return true;
1293
+ }
1294
+ }
1295
+ ]);
1296
+ return credentialName;
1297
+ }
1298
+ function emitLoginSuccess({
1299
+ sdk,
1300
+ profile
1301
+ }) {
1302
+ sdk.context.eventEmission.emit(
1303
+ "platform.sdk.ApplicationLifecycleEvent",
1304
+ buildApplicationLifecycleEvent(
1305
+ { lifecycle_event_type: "login_success" },
1306
+ {
1307
+ customuser_id: profile.user_id,
1308
+ account_id: profile.roles[0]?.account_id ?? null
1309
+ }
1310
+ )
1311
+ );
1312
+ }
1313
+ async function getProfile(api2) {
1314
+ return api2.get("/zapier/api/v4/profile/", {
1315
+ authRequired: true
1316
+ });
1317
+ }
1318
+ async function bestEffortClearLegacyJwtState() {
1319
+ try {
1320
+ await clearLegacyJwtState();
1321
+ } catch (err) {
1322
+ console.error("[login] Best-effort legacy JWT cleanup failed:", err);
1323
+ }
1324
+ }
914
1325
  var loginPlugin = definePlugin(
915
1326
  (sdk) => createPluginMethod(sdk, {
916
1327
  name: "login",
@@ -918,25 +1329,61 @@ var loginPlugin = definePlugin(
918
1329
  inputSchema: LoginSchema,
919
1330
  supportsJsonOutput: false,
920
1331
  handler: async ({ sdk: sdk2, options }) => {
921
- const timeoutSeconds = options.timeout ? parseInt(options.timeout, 10) : 300;
922
- if (isNaN(timeoutSeconds) || timeoutSeconds <= 0) {
923
- throw new Error("Timeout must be a positive number");
924
- }
1332
+ const timeoutSeconds = parseTimeoutSeconds(options.timeout);
925
1333
  const resolvedCredentials = await sdk2.context.resolveCredentials();
926
1334
  const pkceCredentials = toPkceCredentials(resolvedCredentials);
927
- await login_default({
1335
+ const credentialsBaseUrl = await resolveCredentialsBaseUrl({
1336
+ ...sdk2.context,
1337
+ resolvedCredentials
1338
+ });
1339
+ const activeCredentials = getActiveCredentials({
1340
+ baseUrl: credentialsBaseUrl
1341
+ });
1342
+ if (activeCredentials) {
1343
+ if (!await confirmRevokeAndRelogin(activeCredentials)) return;
1344
+ try {
1345
+ await revokeCredentials({
1346
+ api: sdk2.context.api,
1347
+ credentials: activeCredentials
1348
+ });
1349
+ } catch {
1350
+ if (!await confirmLocalLoginReset()) return;
1351
+ await deleteStoredClientCredentials({
1352
+ name: activeCredentials.name,
1353
+ baseUrl: activeCredentials.baseUrl
1354
+ });
1355
+ }
1356
+ } else if (hasLegacyJwtConfig()) {
1357
+ if (!await confirmJwtMigration()) return;
1358
+ }
1359
+ const { accessToken } = await runOauthFlow({
928
1360
  timeoutMs: timeoutSeconds * 1e3,
929
- credentials: pkceCredentials
1361
+ pkceCredentials,
1362
+ baseUrl: credentialsBaseUrl
930
1363
  });
931
- const user = await getLoggedInUser();
932
- sdk2.context.eventEmission.emit(
933
- "platform.sdk.ApplicationLifecycleEvent",
934
- buildApplicationLifecycleEvent(
935
- { lifecycle_event_type: "login_success" },
936
- { customuser_id: user.customUserId, account_id: user.accountId }
937
- )
1364
+ const scopedApi = getOrCreateApiClient({
1365
+ credentials: accessToken,
1366
+ baseUrl: credentialsBaseUrl
1367
+ });
1368
+ const profile = await getProfile(scopedApi);
1369
+ console.log(`\u{1F464} Logged in as ${profile.email}`);
1370
+ console.log(
1371
+ "\nGenerating credentials so this machine can make authenticated requests on your behalf."
1372
+ );
1373
+ const credentialName = await promptCredentialsName(
1374
+ profile.email,
1375
+ credentialsBaseUrl
938
1376
  );
939
- console.log(`\u2705 Successfully logged in as ${user.email}`);
1377
+ await setupClientCredentials({
1378
+ api: scopedApi,
1379
+ name: credentialName,
1380
+ credentialsBaseUrl
1381
+ });
1382
+ await bestEffortClearLegacyJwtState();
1383
+ console.log(
1384
+ `\u2705 Credentials "${credentialName}" created and set as default. You are ready to use the Zapier SDK.`
1385
+ );
1386
+ emitLoginSuccess({ sdk: sdk2, profile });
940
1387
  }
941
1388
  })
942
1389
  );
@@ -949,8 +1396,36 @@ var logoutPlugin = definePlugin(
949
1396
  categories: ["account"],
950
1397
  inputSchema: LogoutSchema,
951
1398
  supportsJsonOutput: false,
952
- handler: async () => {
953
- await logout();
1399
+ handler: async ({ sdk: sdk2 }) => {
1400
+ const credentialsBaseUrl = await resolveCredentialsBaseUrl(sdk2.context);
1401
+ const activeCredentials = getActiveCredentials({
1402
+ baseUrl: credentialsBaseUrl
1403
+ });
1404
+ const onEvent = sdk2.context.options?.onEvent;
1405
+ if (!activeCredentials) {
1406
+ await logout({ onEvent });
1407
+ console.log("\u2705 Successfully logged out");
1408
+ return;
1409
+ }
1410
+ const { confirmed } = await inquirer.prompt([
1411
+ {
1412
+ type: "confirm",
1413
+ name: "confirmed",
1414
+ message: `Logging out will delete credentials "${activeCredentials.name}".
1415
+ This may interrupt other Zapier SDK or CLI sessions using them.
1416
+ Do you want to continue?`,
1417
+ default: true
1418
+ }
1419
+ ]);
1420
+ if (!confirmed) {
1421
+ console.log("Logout cancelled.");
1422
+ return;
1423
+ }
1424
+ await revokeCredentials({
1425
+ api: sdk2.context.api,
1426
+ credentials: activeCredentials,
1427
+ onEvent
1428
+ });
954
1429
  console.log("\u2705 Successfully logged out");
955
1430
  }
956
1431
  })
@@ -969,6 +1444,7 @@ var mcpPlugin = definePlugin(
969
1444
  await startMcpServer({
970
1445
  ...options,
971
1446
  debug: sdk2.context.options?.debug,
1447
+ maxConcurrentRequests: sdk2.context.options?.maxConcurrentRequests,
972
1448
  extensions: sdk2.context.extensions,
973
1449
  experimental: sdk2.context.experimental
974
1450
  });
@@ -3450,7 +3926,7 @@ definePlugin(
3450
3926
  // package.json with { type: 'json' }
3451
3927
  var package_default = {
3452
3928
  name: "@zapier/zapier-sdk-cli",
3453
- version: "0.47.0"};
3929
+ version: "0.48.1"};
3454
3930
 
3455
3931
  // src/sdk.ts
3456
3932
  injectCliLogin(login_exports);
@@ -3478,7 +3954,7 @@ function createZapierCliSdk(options = {}) {
3478
3954
 
3479
3955
  // package.json
3480
3956
  var package_default2 = {
3481
- version: "0.47.0"};
3957
+ version: "0.48.1"};
3482
3958
 
3483
3959
  // src/telemetry/builders.ts
3484
3960
  function createCliBaseEvent(context = {}) {