@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/cli.mjs CHANGED
@@ -1,21 +1,22 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command, CommanderError, Option } from 'commander';
3
3
  import { z } from 'zod';
4
- import { definePlugin, createPluginMethod, buildApplicationLifecycleEvent, OutputPropertySchema, ZapierBundleError, DEFAULT_CONFIG_PATH, ZapierValidationError, ZapierUnknownError, ZapierReleaseTriggerMessageSignal, injectCliLogin, BaseSdkOptionsSchema, isCredentialsObject, batch, toSnakeCase, ZapierAbortDrainSignal, createZapierSdk as createZapierSdk$1, ZapierError, isPositional, runWithTelemetryContext, buildCapabilityMessage, formatErrorMessage, getOsInfo, getPlatformVersions, getCiPlatform, isCi, getReleaseId, getCurrentTimestamp, generateEventId } from '@zapier/zapier-sdk';
4
+ import { definePlugin, createPluginMethod, getOrCreateApiClient, OutputPropertySchema, ZapierBundleError, DEFAULT_CONFIG_PATH, ZapierValidationError, ZapierUnknownError, ZapierReleaseTriggerMessageSignal, injectCliLogin, BaseSdkOptionsSchema, isCredentialsObject, invalidateCachedToken, buildApplicationLifecycleEvent, batch, toSnakeCase, ZapierAbortDrainSignal, createZapierSdk as createZapierSdk$1, ZapierError, isPositional, runWithTelemetryContext, buildCapabilityMessage, formatErrorMessage, getOsInfo, getPlatformVersions, getCiPlatform, isCi, getReleaseId, getCurrentTimestamp, generateEventId } from '@zapier/zapier-sdk';
5
5
  import inquirer from 'inquirer';
6
6
  import chalk7 from 'chalk';
7
7
  import ora from 'ora';
8
8
  import util from 'util';
9
9
  import wrapAnsi from 'wrap-ansi';
10
+ import * as jwt from 'jsonwebtoken';
11
+ import { deletePassword, getKeyring, getPassword, setPassword } from 'cross-keychain';
10
12
  import Conf from 'conf';
11
13
  import * as fs from 'fs';
12
14
  import { promises, createWriteStream, existsSync, readdirSync, rmSync, mkdirSync, writeFileSync, copyFileSync, readFileSync } from 'fs';
13
- import * as jwt from 'jsonwebtoken';
14
- import { getPassword, getKeyring, setPassword, deletePassword } from 'cross-keychain';
15
15
  import crypto, { createHash } from 'crypto';
16
16
  import * as path from 'path';
17
17
  import { resolve, join, dirname, basename, relative, extname } from 'path';
18
18
  import * as lockfile from 'proper-lockfile';
19
+ import { hostname } from 'os';
19
20
  import open from 'open';
20
21
  import express from 'express';
21
22
  import pkceChallenge from 'pkce-challenge';
@@ -1148,7 +1149,7 @@ var SHARED_COMMAND_CLI_OPTIONS = [
1148
1149
 
1149
1150
  // package.json
1150
1151
  var package_default = {
1151
- version: "0.47.0"};
1152
+ version: "0.48.1"};
1152
1153
 
1153
1154
  // src/telemetry/builders.ts
1154
1155
  function createCliBaseEvent(context = {}) {
@@ -2300,8 +2301,11 @@ function convertValue(value, type, elementType) {
2300
2301
  var login_exports = {};
2301
2302
  __export(login_exports, {
2302
2303
  AUTH_MODE_HEADER: () => AUTH_MODE_HEADER,
2304
+ DEFAULT_AUTH_BASE_URL: () => DEFAULT_AUTH_BASE_URL,
2303
2305
  ZapierAuthenticationError: () => ZapierAuthenticationError,
2306
+ clearTokensFromKeychain: () => clearTokensFromKeychain,
2304
2307
  createCache: () => createCache,
2308
+ getActiveCredentials: () => getActiveCredentials,
2305
2309
  getAuthAuthorizeUrl: () => getAuthAuthorizeUrl,
2306
2310
  getAuthTokenUrl: () => getAuthTokenUrl,
2307
2311
  getConfig: () => getConfig,
@@ -2309,6 +2313,7 @@ __export(login_exports, {
2309
2313
  getLoggedInUser: () => getLoggedInUser,
2310
2314
  getLoginStorageMode: () => getLoginStorageMode,
2311
2315
  getPkceLoginConfig: () => getPkceLoginConfig,
2316
+ getStoredClientCredentials: () => getStoredClientCredentials,
2312
2317
  getToken: () => getToken,
2313
2318
  logout: () => logout,
2314
2319
  unloadConfig: () => unloadConfig,
@@ -2389,6 +2394,38 @@ async function clearTokensFromKeychain({
2389
2394
  }
2390
2395
  });
2391
2396
  }
2397
+ var DEFAULT_AUTH_BASE_URL = "https://zapier.com";
2398
+ var config = null;
2399
+ function getConfig() {
2400
+ if (!config) {
2401
+ config = new Conf({ projectName: "zapier-sdk-cli" });
2402
+ if (!config.has("login_storage_mode")) {
2403
+ config.set(
2404
+ "login_storage_mode",
2405
+ existsSync(config.path) ? "config" : "keychain"
2406
+ );
2407
+ }
2408
+ }
2409
+ return config;
2410
+ }
2411
+ function resetConfig() {
2412
+ config = null;
2413
+ }
2414
+
2415
+ // src/login/legacy-jwt.ts
2416
+ function clearLegacyJwtConfigKeys(config2) {
2417
+ config2.delete("login_jwt");
2418
+ config2.delete("login_refresh_token");
2419
+ config2.delete("login_expires_at");
2420
+ }
2421
+ async function clearLegacyJwtState() {
2422
+ clearLegacyJwtConfigKeys(getConfig());
2423
+ await clearTokensFromKeychain();
2424
+ }
2425
+ function hasLegacyJwtConfig() {
2426
+ const cfg = getConfig();
2427
+ return typeof cfg.get("login_jwt") === "string" || typeof cfg.get("login_refresh_token") === "string" || typeof cfg.get("login_expires_at") === "number";
2428
+ }
2392
2429
  var SERVICE2 = "zapier-sdk-cache";
2393
2430
  var CONFIG_KEY = "cache";
2394
2431
  var LOCK_UPDATE_MS = 5e3;
@@ -2516,6 +2553,163 @@ function createCache() {
2516
2553
  }
2517
2554
  };
2518
2555
  }
2556
+ var SERVICE3 = "zapier-sdk-cli";
2557
+ var CREDENTIALS_KEY = "credentials";
2558
+ var REGISTRY_KEY = "credentialsRegistry";
2559
+ var CredentialsEntrySchema = z.object({
2560
+ name: z.string(),
2561
+ clientId: z.string(),
2562
+ createdAt: z.number(),
2563
+ scopes: z.array(z.string()),
2564
+ baseUrl: z.string()
2565
+ });
2566
+ function normalizeBaseUrl(baseUrl2) {
2567
+ return baseUrl2 ?? DEFAULT_AUTH_BASE_URL;
2568
+ }
2569
+ function keychainAccount2(key) {
2570
+ return createHash("sha256").update(key).digest("hex");
2571
+ }
2572
+ function buildKeychainKey(clientId, scopes, baseUrl2) {
2573
+ const sortedScopes = [...scopes].sort().join(",");
2574
+ return `zapier-sdk/client-credentials-secret/${clientId}:${sortedScopes}:${baseUrl2}`;
2575
+ }
2576
+ function findEntry(registry, name, baseUrl2) {
2577
+ return registry.find((e) => e.name === name && e.baseUrl === baseUrl2);
2578
+ }
2579
+ function readRegistry() {
2580
+ const stored = getConfig().get(REGISTRY_KEY);
2581
+ if (!Array.isArray(stored)) return [];
2582
+ return stored.flatMap((entry) => {
2583
+ const result = CredentialsEntrySchema.safeParse(entry);
2584
+ return result.success ? [result.data] : [];
2585
+ });
2586
+ }
2587
+ function getActiveCredentials(options) {
2588
+ const name = getConfig().get(CREDENTIALS_KEY);
2589
+ if (!name) return void 0;
2590
+ return findEntry(readRegistry(), name, normalizeBaseUrl(options?.baseUrl));
2591
+ }
2592
+ async function storeClientCredentials({
2593
+ name,
2594
+ clientId,
2595
+ clientSecret,
2596
+ scopes,
2597
+ baseUrl: baseUrl2
2598
+ }) {
2599
+ if (!name || typeof name !== "string") {
2600
+ throw new Error("storeClientCredentials: name is required");
2601
+ }
2602
+ if (!clientId || typeof clientId !== "string") {
2603
+ throw new Error("storeClientCredentials: clientId is required");
2604
+ }
2605
+ if (!clientSecret || typeof clientSecret !== "string") {
2606
+ throw new Error("storeClientCredentials: clientSecret is required");
2607
+ }
2608
+ if (!Array.isArray(scopes) || scopes.length === 0) {
2609
+ throw new Error("storeClientCredentials: scopes must be a non-empty array");
2610
+ }
2611
+ const sortedScopes = [...scopes].sort();
2612
+ const resolvedBaseUrl = normalizeBaseUrl(baseUrl2);
2613
+ const keychainKey = buildKeychainKey(clientId, sortedScopes, resolvedBaseUrl);
2614
+ const existingEntry = findEntry(readRegistry(), name, resolvedBaseUrl);
2615
+ const existingKeychainKey = existingEntry ? buildKeychainKey(
2616
+ existingEntry.clientId,
2617
+ existingEntry.scopes,
2618
+ existingEntry.baseUrl
2619
+ ) : void 0;
2620
+ await enqueue(async () => {
2621
+ await getBackendInfo();
2622
+ await setPassword(SERVICE3, keychainAccount2(keychainKey), clientSecret);
2623
+ });
2624
+ const entry = {
2625
+ name,
2626
+ clientId,
2627
+ createdAt: Date.now(),
2628
+ scopes: sortedScopes,
2629
+ baseUrl: resolvedBaseUrl
2630
+ };
2631
+ const registry = readRegistry().filter(
2632
+ (e) => !(e.name === name && e.baseUrl === resolvedBaseUrl)
2633
+ );
2634
+ registry.push(entry);
2635
+ const cfg = getConfig();
2636
+ cfg.set(REGISTRY_KEY, registry);
2637
+ cfg.set(CREDENTIALS_KEY, name);
2638
+ if (existingEntry && existingKeychainKey !== keychainKey) {
2639
+ await deleteKeychainSecret(existingEntry);
2640
+ }
2641
+ }
2642
+ function credentialsNameExists({
2643
+ name,
2644
+ baseUrl: baseUrl2
2645
+ }) {
2646
+ return !!findEntry(readRegistry(), name, normalizeBaseUrl(baseUrl2));
2647
+ }
2648
+ async function getStoredClientCredentials(options) {
2649
+ const entry = options?.name ? findEntry(readRegistry(), options.name, normalizeBaseUrl(options.baseUrl)) : getActiveCredentials(options);
2650
+ if (!entry) return void 0;
2651
+ const keychainKey = buildKeychainKey(
2652
+ entry.clientId,
2653
+ entry.scopes,
2654
+ entry.baseUrl
2655
+ );
2656
+ const clientSecret = await enqueue(async () => {
2657
+ await getBackendInfo();
2658
+ return getPassword(SERVICE3, keychainAccount2(keychainKey));
2659
+ });
2660
+ if (!clientSecret) return void 0;
2661
+ return {
2662
+ type: "client_credentials",
2663
+ clientId: entry.clientId,
2664
+ clientSecret,
2665
+ baseUrl: entry.baseUrl,
2666
+ scope: [...entry.scopes].sort().join(" ")
2667
+ };
2668
+ }
2669
+ function deleteRegistryEntry(registry, name, baseUrl2) {
2670
+ const idx = registry.findIndex(
2671
+ (e) => e.name === name && e.baseUrl === baseUrl2
2672
+ );
2673
+ if (idx === -1) return void 0;
2674
+ const [removed] = registry.splice(idx, 1);
2675
+ return removed;
2676
+ }
2677
+ function unsetMatchingCredentialsKey(cfg, name) {
2678
+ const activeName = cfg.get(CREDENTIALS_KEY);
2679
+ if (activeName === name && !readRegistry().some((e) => e.name === name)) {
2680
+ cfg.delete(CREDENTIALS_KEY);
2681
+ }
2682
+ }
2683
+ async function deleteKeychainSecret(entry) {
2684
+ const keychainKey = buildKeychainKey(
2685
+ entry.clientId,
2686
+ entry.scopes,
2687
+ entry.baseUrl
2688
+ );
2689
+ try {
2690
+ await enqueue(async () => {
2691
+ await getBackendInfo();
2692
+ await deletePassword(SERVICE3, keychainAccount2(keychainKey));
2693
+ });
2694
+ } catch {
2695
+ }
2696
+ }
2697
+ async function deleteStoredClientCredentials({
2698
+ name,
2699
+ baseUrl: baseUrl2
2700
+ }) {
2701
+ const registry = readRegistry();
2702
+ const removed = deleteRegistryEntry(
2703
+ registry,
2704
+ name,
2705
+ normalizeBaseUrl(baseUrl2)
2706
+ );
2707
+ if (!removed) return;
2708
+ const cfg = getConfig();
2709
+ cfg.set(REGISTRY_KEY, registry);
2710
+ unsetMatchingCredentialsKey(cfg, name);
2711
+ await deleteKeychainSecret(removed);
2712
+ }
2519
2713
 
2520
2714
  // src/login/index.ts
2521
2715
  var ZapierAuthenticationError = class extends Error {
@@ -2524,7 +2718,6 @@ var ZapierAuthenticationError = class extends Error {
2524
2718
  this.name = "ZapierAuthenticationError";
2525
2719
  }
2526
2720
  };
2527
- var config = null;
2528
2721
  var DEFAULT_AUTH_CLIENT_ID = "grwWZD5hUWGvb4V8ODBuOtXer3h0DBEZ2HR8aay6";
2529
2722
  var TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1e3;
2530
2723
  function createDebugLog(enabled) {
@@ -2550,7 +2743,6 @@ function getAuthClientId(clientId) {
2550
2743
  return clientId || DEFAULT_AUTH_CLIENT_ID;
2551
2744
  }
2552
2745
  var AUTH_MODE_HEADER = "X-Auth";
2553
- var DEFAULT_AUTH_BASE_URL = "https://zapier.com";
2554
2746
  function getAuthTokenUrl(options) {
2555
2747
  const authBaseUrl = options?.baseUrl || DEFAULT_AUTH_BASE_URL;
2556
2748
  return `${authBaseUrl}/oauth/token/`;
@@ -2560,29 +2752,16 @@ function getAuthAuthorizeUrl(options) {
2560
2752
  return `${authBaseUrl}/oauth/authorize/`;
2561
2753
  }
2562
2754
  function getPkceLoginConfig(options) {
2755
+ const effectiveBaseUrl = options?.credentials?.baseUrl ?? options?.baseUrl;
2563
2756
  return {
2564
2757
  clientId: getAuthClientId(options?.credentials?.clientId),
2565
- tokenUrl: getAuthTokenUrl({ baseUrl: options?.credentials?.baseUrl }),
2566
- authorizeUrl: getAuthAuthorizeUrl({
2567
- baseUrl: options?.credentials?.baseUrl
2568
- })
2758
+ tokenUrl: getAuthTokenUrl({ baseUrl: effectiveBaseUrl }),
2759
+ authorizeUrl: getAuthAuthorizeUrl({ baseUrl: effectiveBaseUrl })
2569
2760
  };
2570
2761
  }
2571
2762
  var cachedLogin;
2572
- function getConfig() {
2573
- if (!config) {
2574
- config = new Conf({ projectName: "zapier-sdk-cli" });
2575
- if (!config.has("login_storage_mode")) {
2576
- config.set(
2577
- "login_storage_mode",
2578
- existsSync(config.path) ? "config" : "keychain"
2579
- );
2580
- }
2581
- }
2582
- return config;
2583
- }
2584
2763
  function unloadConfig() {
2585
- config = null;
2764
+ resetConfig();
2586
2765
  cachedLogin = void 0;
2587
2766
  }
2588
2767
  async function updateLogin(loginData, options = {}) {
@@ -2855,9 +3034,7 @@ async function logout(options = {}) {
2855
3034
  await clearTokensFromKeychain();
2856
3035
  const cfg = getConfig();
2857
3036
  cfg.set("login_storage_mode", mode);
2858
- cfg.delete("login_expires_at");
2859
- cfg.delete("login_jwt");
2860
- cfg.delete("login_refresh_token");
3037
+ clearLegacyJwtConfigKeys(cfg);
2861
3038
  onEvent?.({
2862
3039
  type: "auth_logout",
2863
3040
  payload: { message: "Logged out successfully", operation: "logout" },
@@ -2869,6 +3046,79 @@ function getConfigPath() {
2869
3046
  return cfg.path;
2870
3047
  }
2871
3048
 
3049
+ // src/utils/retry.ts
3050
+ function sleep(ms) {
3051
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
3052
+ }
3053
+ async function withRetry({
3054
+ action,
3055
+ attempts = 3,
3056
+ initialDelayMs = 100
3057
+ }) {
3058
+ if (attempts <= 0) {
3059
+ throw new Error("withRetry: attempts must be greater than 0");
3060
+ }
3061
+ let lastError;
3062
+ for (let i = 0; i < attempts; i++) {
3063
+ try {
3064
+ return await action();
3065
+ } catch (err) {
3066
+ lastError = err;
3067
+ if (i < attempts - 1) {
3068
+ await sleep(initialDelayMs * 2 ** i);
3069
+ }
3070
+ }
3071
+ }
3072
+ throw lastError;
3073
+ }
3074
+
3075
+ // src/login/credentials-revoke.ts
3076
+ function emitAuthLogout(onEvent) {
3077
+ onEvent?.({
3078
+ type: "auth_logout",
3079
+ payload: { message: "Logged out successfully", operation: "logout" },
3080
+ timestamp: Date.now()
3081
+ });
3082
+ }
3083
+ function isNotFoundError(err) {
3084
+ return typeof err === "object" && err !== null && "statusCode" in err && err.statusCode === 404;
3085
+ }
3086
+ async function revokeCredentials({
3087
+ api: api2,
3088
+ credentials: credentials2,
3089
+ onEvent
3090
+ }) {
3091
+ await withRetry({
3092
+ action: async () => {
3093
+ try {
3094
+ await api2.delete(
3095
+ `/api/v0/client-credentials/${credentials2.clientId}`,
3096
+ void 0,
3097
+ { authRequired: true, requiredScopes: ["credentials"] }
3098
+ );
3099
+ } catch (err) {
3100
+ if (isNotFoundError(err)) return;
3101
+ throw err;
3102
+ }
3103
+ }
3104
+ });
3105
+ try {
3106
+ await deleteStoredClientCredentials({
3107
+ name: credentials2.name,
3108
+ baseUrl: credentials2.baseUrl
3109
+ });
3110
+ } catch (err) {
3111
+ console.warn("[revokeCredentials] Local store cleanup failed:", err);
3112
+ }
3113
+ await clearLegacyJwtState();
3114
+ await invalidateCachedToken({
3115
+ clientId: credentials2.clientId,
3116
+ scopes: credentials2.scopes,
3117
+ baseUrl: credentials2.baseUrl
3118
+ });
3119
+ emitAuthLogout(onEvent);
3120
+ }
3121
+
2872
3122
  // src/utils/constants.ts
2873
3123
  var LOGIN_PORTS = [49505, 50575, 52804, 55981, 61010, 63851];
2874
3124
  var LOGIN_TIMEOUT_MS = 3e5;
@@ -2954,6 +3204,8 @@ var getCallablePromise = () => {
2954
3204
  };
2955
3205
  };
2956
3206
  var getCallablePromise_default = getCallablePromise;
3207
+
3208
+ // src/utils/auth/oauth-flow.ts
2957
3209
  var findAvailablePort = () => {
2958
3210
  return new Promise((resolve4, reject) => {
2959
3211
  let portIndex = 0;
@@ -2988,10 +3240,9 @@ var findAvailablePort = () => {
2988
3240
  var generateRandomString = () => {
2989
3241
  const array = new Uint32Array(28);
2990
3242
  crypto.getRandomValues(array);
2991
- return Array.from(
2992
- array,
2993
- (dec) => ("0" + dec.toString(16)).substring(-2)
2994
- ).join("");
3243
+ return Array.from(array, (dec) => ("0" + dec.toString(16)).slice(-2)).join(
3244
+ ""
3245
+ );
2995
3246
  };
2996
3247
  function ensureOfflineAccess(scope) {
2997
3248
  if (scope.includes("offline_access")) {
@@ -2999,17 +3250,18 @@ function ensureOfflineAccess(scope) {
2999
3250
  }
3000
3251
  return `${scope} offline_access`;
3001
3252
  }
3002
- var login = async ({
3253
+ async function runOauthFlow({
3003
3254
  timeoutMs = LOGIN_TIMEOUT_MS,
3004
- credentials: credentials2
3005
- }) => {
3255
+ pkceCredentials,
3256
+ baseUrl: baseUrl2
3257
+ }) {
3006
3258
  const { clientId, tokenUrl, authorizeUrl } = getPkceLoginConfig({
3007
- credentials: credentials2
3259
+ credentials: pkceCredentials,
3260
+ baseUrl: baseUrl2
3008
3261
  });
3009
3262
  const scope = ensureOfflineAccess(
3010
- credentials2?.scope || "internal credentials"
3263
+ pkceCredentials?.scope || "internal credentials"
3011
3264
  );
3012
- await logout();
3013
3265
  const availablePort = await findAvailablePort();
3014
3266
  const redirectUri = `http://localhost:${availablePort}/oauth`;
3015
3267
  log_default.info(`Using port ${availablePort} for OAuth callback`);
@@ -3018,13 +3270,30 @@ var login = async ({
3018
3270
  resolve: setCode,
3019
3271
  reject: rejectCode
3020
3272
  } = getCallablePromise_default();
3021
- const app = express();
3022
- app.get("/oauth", (req, res) => {
3023
- setCode(String(req.query.code));
3273
+ const oauthState = generateRandomString();
3274
+ const expressApp = express();
3275
+ expressApp.get("/oauth", (req, res) => {
3024
3276
  res.setHeader("Connection", "close");
3277
+ if (req.query.state !== oauthState) {
3278
+ rejectCode(new Error("OAuth state mismatch \u2014 possible CSRF"));
3279
+ res.status(400).end("Invalid state. You can close this tab.");
3280
+ return;
3281
+ }
3282
+ if (req.query.error) {
3283
+ const desc = req.query.error_description ?? req.query.error;
3284
+ rejectCode(new Error(`Authorization denied: ${desc}`));
3285
+ res.end("Authorization was denied. You can close this tab.");
3286
+ return;
3287
+ }
3288
+ if (!req.query.code) {
3289
+ rejectCode(new Error("No authorization code received"));
3290
+ res.end("No authorization code received. You can close this tab.");
3291
+ return;
3292
+ }
3293
+ setCode(String(req.query.code));
3025
3294
  res.end("You can now close this tab and return to the CLI.");
3026
3295
  });
3027
- const server = app.listen(availablePort);
3296
+ const server = expressApp.listen(availablePort);
3028
3297
  const connections = /* @__PURE__ */ new Set();
3029
3298
  server.on("connection", (conn) => {
3030
3299
  connections.add(conn);
@@ -3043,7 +3312,7 @@ var login = async ({
3043
3312
  client_id: clientId,
3044
3313
  redirect_uri: redirectUri,
3045
3314
  scope,
3046
- state: generateRandomString(),
3315
+ state: oauthState,
3047
3316
  code_challenge: codeChallenge,
3048
3317
  code_challenge_method: "S256"
3049
3318
  }).toString()}`;
@@ -3102,36 +3371,79 @@ var login = async ({
3102
3371
  }
3103
3372
  }
3104
3373
  );
3105
- let targetStorage;
3106
- if (getLoginStorageMode() === "config") {
3107
- const { upgrade } = await inquirer.prompt([
3108
- {
3109
- type: "confirm",
3110
- name: "upgrade",
3111
- 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.",
3112
- default: true
3113
- }
3114
- ]);
3115
- targetStorage = upgrade ? "keychain" : "config";
3116
- } else {
3117
- targetStorage = "keychain";
3118
- }
3374
+ log_default.info("Token exchange completed successfully");
3375
+ return {
3376
+ accessToken: data.access_token,
3377
+ refreshToken: data.refresh_token,
3378
+ expiresIn: data.expires_in
3379
+ };
3380
+ }
3381
+
3382
+ // src/utils/auth/client-credentials.ts
3383
+ var CREDENTIALS_SCOPES = ["external", "credentials"];
3384
+ async function createCredentialsOnServer(api2, name) {
3385
+ const response = await api2.post(
3386
+ "/api/v0/client-credentials",
3387
+ { name, allowed_scopes: CREDENTIALS_SCOPES },
3388
+ { authRequired: true, requiredScopes: ["credentials"] }
3389
+ );
3390
+ return {
3391
+ clientId: response.data.client_id,
3392
+ clientSecret: response.data.client_secret
3393
+ };
3394
+ }
3395
+ async function deleteCredentialsOnServer(api2, clientId) {
3396
+ await api2.delete(`/api/v0/client-credentials/${clientId}`, void 0, {
3397
+ authRequired: true,
3398
+ requiredScopes: ["credentials"]
3399
+ });
3400
+ }
3401
+ async function setupClientCredentials({
3402
+ api: api2,
3403
+ name,
3404
+ credentialsBaseUrl: credentialsBaseUrl2
3405
+ }) {
3406
+ const { clientId, clientSecret } = await createCredentialsOnServer(api2, name);
3119
3407
  try {
3120
- await updateLogin(data, { storage: targetStorage });
3121
- } catch (err) {
3122
- if (targetStorage === "keychain") {
3123
- log_default.warn(
3124
- `Could not store credentials in system keychain. Storing in plaintext at ${getConfigPath()}.`
3408
+ await withRetry({
3409
+ action: () => storeClientCredentials({
3410
+ name,
3411
+ clientId,
3412
+ clientSecret,
3413
+ scopes: [...CREDENTIALS_SCOPES],
3414
+ baseUrl: credentialsBaseUrl2
3415
+ })
3416
+ });
3417
+ } catch (storeErr) {
3418
+ try {
3419
+ await withRetry({
3420
+ action: () => deleteCredentialsOnServer(api2, clientId)
3421
+ });
3422
+ } catch {
3423
+ console.error(
3424
+ `Failed to roll back orphaned credential ${clientId}. Delete it manually with: zapier-sdk delete-client-credentials ${clientId}`
3125
3425
  );
3126
- await updateLogin(data, { storage: "config" });
3127
- } else {
3128
- throw err;
3129
3426
  }
3427
+ throw storeErr;
3130
3428
  }
3131
- log_default.info("Token exchange completed successfully");
3132
- return data.access_token;
3133
- };
3134
- var login_default = login;
3429
+ return { clientId };
3430
+ }
3431
+ function getBaseUrlFromResolvedCredentials(credentials2) {
3432
+ if (credentials2 && isCredentialsObject(credentials2)) {
3433
+ return credentials2.baseUrl;
3434
+ }
3435
+ return void 0;
3436
+ }
3437
+ function getBaseUrlFromOptionsCredentials(credentials2) {
3438
+ if (credentials2 && typeof credentials2 === "object" && "baseUrl" in credentials2 && typeof credentials2.baseUrl === "string") {
3439
+ return credentials2.baseUrl;
3440
+ }
3441
+ return void 0;
3442
+ }
3443
+ async function resolveCredentialsBaseUrl(context) {
3444
+ const resolvedCredentials = "resolvedCredentials" in context ? context.resolvedCredentials : await context.resolveCredentials?.();
3445
+ return getBaseUrlFromResolvedCredentials(resolvedCredentials) ?? getBaseUrlFromOptionsCredentials(context.options?.credentials) ?? context.options?.baseUrl;
3446
+ }
3135
3447
  var LoginSchema = z.object({
3136
3448
  timeout: z.string().optional().describe("Login timeout in seconds (default: 300)")
3137
3449
  }).describe("Log in to Zapier to access your account");
@@ -3148,6 +3460,105 @@ function toPkceCredentials(credentials2) {
3148
3460
  }
3149
3461
  return void 0;
3150
3462
  }
3463
+ async function confirmRevokeAndRelogin(activeCredentials) {
3464
+ const { confirmed } = await inquirer.prompt([
3465
+ {
3466
+ type: "confirm",
3467
+ name: "confirmed",
3468
+ message: `You are already logged in as "${activeCredentials.name}".
3469
+ Logging out will delete these credentials and may interrupt other Zapier SDK or CLI sessions using them.
3470
+ Log out and log in again?`,
3471
+ default: false
3472
+ }
3473
+ ]);
3474
+ if (!confirmed) {
3475
+ console.log("Login cancelled.");
3476
+ return false;
3477
+ }
3478
+ return true;
3479
+ }
3480
+ async function confirmJwtMigration() {
3481
+ const { confirmed } = await inquirer.prompt([
3482
+ {
3483
+ type: "confirm",
3484
+ name: "confirmed",
3485
+ 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?",
3486
+ default: true
3487
+ }
3488
+ ]);
3489
+ if (!confirmed) {
3490
+ console.log("Login cancelled.");
3491
+ return false;
3492
+ }
3493
+ return true;
3494
+ }
3495
+ async function confirmLocalLoginReset() {
3496
+ const { confirmed } = await inquirer.prompt([
3497
+ {
3498
+ type: "confirm",
3499
+ name: "confirmed",
3500
+ message: "Login cleanup failed. Reset local session state and continue?",
3501
+ default: false
3502
+ }
3503
+ ]);
3504
+ if (!confirmed) {
3505
+ console.log("Login cancelled.");
3506
+ return false;
3507
+ }
3508
+ return true;
3509
+ }
3510
+ function parseTimeoutSeconds(timeout) {
3511
+ const timeoutSeconds = timeout ? parseInt(timeout, 10) : 300;
3512
+ if (isNaN(timeoutSeconds) || timeoutSeconds <= 0) {
3513
+ throw new Error("Timeout must be a positive number");
3514
+ }
3515
+ return timeoutSeconds;
3516
+ }
3517
+ async function promptCredentialsName(email, baseUrl2) {
3518
+ const { credentialName } = await inquirer.prompt([
3519
+ {
3520
+ type: "input",
3521
+ name: "credentialName",
3522
+ message: "Enter a name to identify them:",
3523
+ default: `${email}@${hostname()}`,
3524
+ validate: (input) => {
3525
+ if (!input.trim()) return "Name cannot be empty";
3526
+ if (credentialsNameExists({ name: input.trim(), baseUrl: baseUrl2 })) {
3527
+ return `Credentials named "${input.trim()}" already exist. Please provide a different name.`;
3528
+ }
3529
+ return true;
3530
+ }
3531
+ }
3532
+ ]);
3533
+ return credentialName;
3534
+ }
3535
+ function emitLoginSuccess({
3536
+ sdk,
3537
+ profile
3538
+ }) {
3539
+ sdk.context.eventEmission.emit(
3540
+ "platform.sdk.ApplicationLifecycleEvent",
3541
+ buildApplicationLifecycleEvent(
3542
+ { lifecycle_event_type: "login_success" },
3543
+ {
3544
+ customuser_id: profile.user_id,
3545
+ account_id: profile.roles[0]?.account_id ?? null
3546
+ }
3547
+ )
3548
+ );
3549
+ }
3550
+ async function getProfile(api2) {
3551
+ return api2.get("/zapier/api/v4/profile/", {
3552
+ authRequired: true
3553
+ });
3554
+ }
3555
+ async function bestEffortClearLegacyJwtState() {
3556
+ try {
3557
+ await clearLegacyJwtState();
3558
+ } catch (err) {
3559
+ console.error("[login] Best-effort legacy JWT cleanup failed:", err);
3560
+ }
3561
+ }
3151
3562
  var loginPlugin = definePlugin(
3152
3563
  (sdk) => createPluginMethod(sdk, {
3153
3564
  name: "login",
@@ -3155,25 +3566,61 @@ var loginPlugin = definePlugin(
3155
3566
  inputSchema: LoginSchema,
3156
3567
  supportsJsonOutput: false,
3157
3568
  handler: async ({ sdk: sdk2, options }) => {
3158
- const timeoutSeconds = options.timeout ? parseInt(options.timeout, 10) : 300;
3159
- if (isNaN(timeoutSeconds) || timeoutSeconds <= 0) {
3160
- throw new Error("Timeout must be a positive number");
3161
- }
3569
+ const timeoutSeconds = parseTimeoutSeconds(options.timeout);
3162
3570
  const resolvedCredentials = await sdk2.context.resolveCredentials();
3163
3571
  const pkceCredentials = toPkceCredentials(resolvedCredentials);
3164
- await login_default({
3572
+ const credentialsBaseUrl2 = await resolveCredentialsBaseUrl({
3573
+ ...sdk2.context,
3574
+ resolvedCredentials
3575
+ });
3576
+ const activeCredentials = getActiveCredentials({
3577
+ baseUrl: credentialsBaseUrl2
3578
+ });
3579
+ if (activeCredentials) {
3580
+ if (!await confirmRevokeAndRelogin(activeCredentials)) return;
3581
+ try {
3582
+ await revokeCredentials({
3583
+ api: sdk2.context.api,
3584
+ credentials: activeCredentials
3585
+ });
3586
+ } catch {
3587
+ if (!await confirmLocalLoginReset()) return;
3588
+ await deleteStoredClientCredentials({
3589
+ name: activeCredentials.name,
3590
+ baseUrl: activeCredentials.baseUrl
3591
+ });
3592
+ }
3593
+ } else if (hasLegacyJwtConfig()) {
3594
+ if (!await confirmJwtMigration()) return;
3595
+ }
3596
+ const { accessToken } = await runOauthFlow({
3165
3597
  timeoutMs: timeoutSeconds * 1e3,
3166
- credentials: pkceCredentials
3598
+ pkceCredentials,
3599
+ baseUrl: credentialsBaseUrl2
3167
3600
  });
3168
- const user = await getLoggedInUser();
3169
- sdk2.context.eventEmission.emit(
3170
- "platform.sdk.ApplicationLifecycleEvent",
3171
- buildApplicationLifecycleEvent(
3172
- { lifecycle_event_type: "login_success" },
3173
- { customuser_id: user.customUserId, account_id: user.accountId }
3174
- )
3601
+ const scopedApi = getOrCreateApiClient({
3602
+ credentials: accessToken,
3603
+ baseUrl: credentialsBaseUrl2
3604
+ });
3605
+ const profile = await getProfile(scopedApi);
3606
+ console.log(`\u{1F464} Logged in as ${profile.email}`);
3607
+ console.log(
3608
+ "\nGenerating credentials so this machine can make authenticated requests on your behalf."
3609
+ );
3610
+ const credentialName = await promptCredentialsName(
3611
+ profile.email,
3612
+ credentialsBaseUrl2
3175
3613
  );
3176
- console.log(`\u2705 Successfully logged in as ${user.email}`);
3614
+ await setupClientCredentials({
3615
+ api: scopedApi,
3616
+ name: credentialName,
3617
+ credentialsBaseUrl: credentialsBaseUrl2
3618
+ });
3619
+ await bestEffortClearLegacyJwtState();
3620
+ console.log(
3621
+ `\u2705 Credentials "${credentialName}" created and set as default. You are ready to use the Zapier SDK.`
3622
+ );
3623
+ emitLoginSuccess({ sdk: sdk2, profile });
3177
3624
  }
3178
3625
  })
3179
3626
  );
@@ -3186,8 +3633,36 @@ var logoutPlugin = definePlugin(
3186
3633
  categories: ["account"],
3187
3634
  inputSchema: LogoutSchema,
3188
3635
  supportsJsonOutput: false,
3189
- handler: async () => {
3190
- await logout();
3636
+ handler: async ({ sdk: sdk2 }) => {
3637
+ const credentialsBaseUrl2 = await resolveCredentialsBaseUrl(sdk2.context);
3638
+ const activeCredentials = getActiveCredentials({
3639
+ baseUrl: credentialsBaseUrl2
3640
+ });
3641
+ const onEvent = sdk2.context.options?.onEvent;
3642
+ if (!activeCredentials) {
3643
+ await logout({ onEvent });
3644
+ console.log("\u2705 Successfully logged out");
3645
+ return;
3646
+ }
3647
+ const { confirmed } = await inquirer.prompt([
3648
+ {
3649
+ type: "confirm",
3650
+ name: "confirmed",
3651
+ message: `Logging out will delete credentials "${activeCredentials.name}".
3652
+ This may interrupt other Zapier SDK or CLI sessions using them.
3653
+ Do you want to continue?`,
3654
+ default: true
3655
+ }
3656
+ ]);
3657
+ if (!confirmed) {
3658
+ console.log("Logout cancelled.");
3659
+ return;
3660
+ }
3661
+ await revokeCredentials({
3662
+ api: sdk2.context.api,
3663
+ credentials: activeCredentials,
3664
+ onEvent
3665
+ });
3191
3666
  console.log("\u2705 Successfully logged out");
3192
3667
  }
3193
3668
  })
@@ -3206,6 +3681,7 @@ var mcpPlugin = definePlugin(
3206
3681
  await startMcpServer({
3207
3682
  ...options,
3208
3683
  debug: sdk2.context.options?.debug,
3684
+ maxConcurrentRequests: sdk2.context.options?.maxConcurrentRequests,
3209
3685
  extensions: sdk2.context.extensions,
3210
3686
  experimental: sdk2.context.experimental
3211
3687
  });
@@ -5704,7 +6180,7 @@ var watchTriggerInboxCliPlugin = definePlugin(
5704
6180
  // package.json with { type: 'json' }
5705
6181
  var package_default2 = {
5706
6182
  name: "@zapier/zapier-sdk-cli",
5707
- version: "0.47.0"};
6183
+ version: "0.48.1"};
5708
6184
 
5709
6185
  // src/sdk.ts
5710
6186
  injectCliLogin(login_exports);
@@ -5966,6 +6442,9 @@ program.name("zapier-sdk").description("CLI for Zapier SDK").version(
5966
6442
  ).option(
5967
6443
  "--max-network-retry-delay-ms <ms>",
5968
6444
  "Max delay in ms to wait for rate limit retry (default: 60000)"
6445
+ ).option(
6446
+ "--max-concurrent-requests <count>",
6447
+ "Max concurrent in-flight HTTP requests (default: 200). Pass 'Infinity' to disable."
5969
6448
  ).option("--experimental", "Use the experimental SDK / CLI surface");
5970
6449
  var booleanFlags = [];
5971
6450
  for (const [key, fieldSchema] of Object.entries(
@@ -5989,8 +6468,13 @@ program.helpOption(
5989
6468
  );
5990
6469
  var isDebugMode = process.env.DEBUG === "true" || process.argv.includes("--debug");
5991
6470
  function getFlagValue(flagName) {
5992
- const index = process.argv.indexOf(flagName);
5993
- return index !== -1 ? process.argv[index + 1] : void 0;
6471
+ const prefix = `${flagName}=`;
6472
+ for (let i = 0; i < process.argv.length; i++) {
6473
+ const arg = process.argv[i];
6474
+ if (arg === flagName) return process.argv[i + 1];
6475
+ if (arg.startsWith(prefix)) return arg.slice(prefix.length);
6476
+ }
6477
+ return void 0;
5994
6478
  }
5995
6479
  var baseUrl = getFlagValue("--base-url");
5996
6480
  var credentialsToken = getFlagValue("--credentials");
@@ -6002,6 +6486,16 @@ var maxNetworkRetriesStr = getFlagValue("--max-network-retries");
6002
6486
  var maxNetworkRetries = maxNetworkRetriesStr ? parseInt(maxNetworkRetriesStr, 10) : void 0;
6003
6487
  var maxNetworkRetryDelayMsStr = getFlagValue("--max-network-retry-delay-ms");
6004
6488
  var maxNetworkRetryDelayMs = maxNetworkRetryDelayMsStr ? parseInt(maxNetworkRetryDelayMsStr, 10) : void 0;
6489
+ var maxConcurrentRequestsStr = getFlagValue("--max-concurrent-requests");
6490
+ var maxConcurrentRequests;
6491
+ if (maxConcurrentRequestsStr === void 0) {
6492
+ maxConcurrentRequests = void 0;
6493
+ } else if (maxConcurrentRequestsStr === "Infinity") {
6494
+ maxConcurrentRequests = Infinity;
6495
+ } else {
6496
+ const parsed = parseInt(maxConcurrentRequestsStr, 10);
6497
+ maxConcurrentRequests = Number.isFinite(parsed) ? parsed : NaN;
6498
+ }
6005
6499
  function buildCredentialsFromFlags() {
6006
6500
  if (credentialsToken) {
6007
6501
  return credentialsToken;
@@ -6044,6 +6538,7 @@ program.exitOverride();
6044
6538
  trackingBaseUrl,
6045
6539
  maxNetworkRetries,
6046
6540
  maxNetworkRetryDelayMs,
6541
+ maxConcurrentRequests,
6047
6542
  ...flagOverrides,
6048
6543
  extensions
6049
6544
  });