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