@zapier/zapier-sdk-cli 0.46.1 → 0.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +1 -1
  3. package/dist/cli.cjs +569 -84
  4. package/dist/cli.mjs +569 -84
  5. package/dist/experimental.cjs +561 -86
  6. package/dist/experimental.mjs +560 -85
  7. package/dist/index.cjs +562 -87
  8. package/dist/index.mjs +561 -86
  9. package/dist/login.cjs +94 -25
  10. package/dist/login.d.mts +29 -2
  11. package/dist/login.d.ts +29 -2
  12. package/dist/login.mjs +90 -25
  13. package/dist/package.json +1 -1
  14. package/dist/src/login/config.d.ts +4 -0
  15. package/dist/src/login/config.js +21 -0
  16. package/dist/src/login/credentials-revoke.d.ts +13 -0
  17. package/dist/src/login/credentials-revoke.js +48 -0
  18. package/dist/src/login/credentials-store.d.ts +33 -0
  19. package/dist/src/login/credentials-store.js +142 -0
  20. package/dist/src/login/index.d.ts +5 -2
  21. package/dist/src/login/index.js +11 -27
  22. package/dist/src/login/legacy-jwt.d.ts +4 -0
  23. package/dist/src/login/legacy-jwt.js +18 -0
  24. package/dist/src/plugins/auth/credentials-base-url.d.ts +11 -0
  25. package/dist/src/plugins/auth/credentials-base-url.js +24 -0
  26. package/dist/src/plugins/login/index.d.ts +6 -1
  27. package/dist/src/plugins/login/index.js +154 -14
  28. package/dist/src/plugins/logout/index.d.ts +14 -0
  29. package/dist/src/plugins/logout/index.js +35 -3
  30. package/dist/src/utils/auth/client-credentials.d.ts +16 -0
  31. package/dist/src/utils/auth/client-credentials.js +53 -0
  32. package/dist/src/utils/auth/oauth-flow.d.ts +12 -0
  33. package/dist/src/utils/auth/{login.js → oauth-flow.js} +36 -58
  34. package/dist/src/utils/parameter-resolver.js +13 -0
  35. package/dist/src/utils/retry.d.ts +5 -0
  36. package/dist/src/utils/retry.js +21 -0
  37. package/dist/tsconfig.tsbuildinfo +1 -1
  38. package/package.json +3 -3
  39. 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';
@@ -898,11 +899,18 @@ Optional fields${pathContext}:`));
898
899
  const choices = [...initialChoices];
899
900
  let nextCursor = initialCursor;
900
901
  const LOAD_MORE_SENTINEL = Symbol("LOAD_MORE");
902
+ const CUSTOM_VALUE_SENTINEL = Symbol("CUSTOM_VALUE");
901
903
  while (true) {
902
904
  const promptChoices = choices.map((choice) => ({
903
905
  name: choice.label,
904
906
  value: choice.value
905
907
  }));
908
+ if (!fieldMeta.isMultiSelect) {
909
+ promptChoices.unshift({
910
+ name: chalk7.dim("(Enter custom value)"),
911
+ value: CUSTOM_VALUE_SENTINEL
912
+ });
913
+ }
906
914
  if (nextCursor) {
907
915
  promptChoices.push({
908
916
  name: chalk7.dim("(Load more...)"),
@@ -928,6 +936,9 @@ Optional fields${pathContext}:`));
928
936
  };
929
937
  const answer = await inquirer.prompt([promptConfig]);
930
938
  let selectedValue = answer[fieldMeta.key];
939
+ if (selectedValue === CUSTOM_VALUE_SENTINEL) {
940
+ return await this.promptFreeForm(fieldMeta);
941
+ }
931
942
  const wantsMore = fieldMeta.isMultiSelect ? Array.isArray(selectedValue) && selectedValue.includes(LOAD_MORE_SENTINEL) : selectedValue === LOAD_MORE_SENTINEL;
932
943
  if (wantsMore && nextCursor && context) {
933
944
  if (fieldMeta.isMultiSelect && Array.isArray(selectedValue)) {
@@ -1138,7 +1149,7 @@ var SHARED_COMMAND_CLI_OPTIONS = [
1138
1149
 
1139
1150
  // package.json
1140
1151
  var package_default = {
1141
- version: "0.46.1"};
1152
+ version: "0.48.0"};
1142
1153
 
1143
1154
  // src/telemetry/builders.ts
1144
1155
  function createCliBaseEvent(context = {}) {
@@ -2290,8 +2301,11 @@ function convertValue(value, type, elementType) {
2290
2301
  var login_exports = {};
2291
2302
  __export(login_exports, {
2292
2303
  AUTH_MODE_HEADER: () => AUTH_MODE_HEADER,
2304
+ DEFAULT_AUTH_BASE_URL: () => DEFAULT_AUTH_BASE_URL,
2293
2305
  ZapierAuthenticationError: () => ZapierAuthenticationError,
2306
+ clearTokensFromKeychain: () => clearTokensFromKeychain,
2294
2307
  createCache: () => createCache,
2308
+ getActiveCredentials: () => getActiveCredentials,
2295
2309
  getAuthAuthorizeUrl: () => getAuthAuthorizeUrl,
2296
2310
  getAuthTokenUrl: () => getAuthTokenUrl,
2297
2311
  getConfig: () => getConfig,
@@ -2299,6 +2313,7 @@ __export(login_exports, {
2299
2313
  getLoggedInUser: () => getLoggedInUser,
2300
2314
  getLoginStorageMode: () => getLoginStorageMode,
2301
2315
  getPkceLoginConfig: () => getPkceLoginConfig,
2316
+ getStoredClientCredentials: () => getStoredClientCredentials,
2302
2317
  getToken: () => getToken,
2303
2318
  logout: () => logout,
2304
2319
  unloadConfig: () => unloadConfig,
@@ -2379,6 +2394,38 @@ async function clearTokensFromKeychain({
2379
2394
  }
2380
2395
  });
2381
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
+ }
2382
2429
  var SERVICE2 = "zapier-sdk-cache";
2383
2430
  var CONFIG_KEY = "cache";
2384
2431
  var LOCK_UPDATE_MS = 5e3;
@@ -2506,6 +2553,163 @@ function createCache() {
2506
2553
  }
2507
2554
  };
2508
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
+ }
2509
2713
 
2510
2714
  // src/login/index.ts
2511
2715
  var ZapierAuthenticationError = class extends Error {
@@ -2514,7 +2718,6 @@ var ZapierAuthenticationError = class extends Error {
2514
2718
  this.name = "ZapierAuthenticationError";
2515
2719
  }
2516
2720
  };
2517
- var config = null;
2518
2721
  var DEFAULT_AUTH_CLIENT_ID = "grwWZD5hUWGvb4V8ODBuOtXer3h0DBEZ2HR8aay6";
2519
2722
  var TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1e3;
2520
2723
  function createDebugLog(enabled) {
@@ -2540,7 +2743,6 @@ function getAuthClientId(clientId) {
2540
2743
  return clientId || DEFAULT_AUTH_CLIENT_ID;
2541
2744
  }
2542
2745
  var AUTH_MODE_HEADER = "X-Auth";
2543
- var DEFAULT_AUTH_BASE_URL = "https://zapier.com";
2544
2746
  function getAuthTokenUrl(options) {
2545
2747
  const authBaseUrl = options?.baseUrl || DEFAULT_AUTH_BASE_URL;
2546
2748
  return `${authBaseUrl}/oauth/token/`;
@@ -2550,29 +2752,16 @@ function getAuthAuthorizeUrl(options) {
2550
2752
  return `${authBaseUrl}/oauth/authorize/`;
2551
2753
  }
2552
2754
  function getPkceLoginConfig(options) {
2755
+ const effectiveBaseUrl = options?.credentials?.baseUrl ?? options?.baseUrl;
2553
2756
  return {
2554
2757
  clientId: getAuthClientId(options?.credentials?.clientId),
2555
- tokenUrl: getAuthTokenUrl({ baseUrl: options?.credentials?.baseUrl }),
2556
- authorizeUrl: getAuthAuthorizeUrl({
2557
- baseUrl: options?.credentials?.baseUrl
2558
- })
2758
+ tokenUrl: getAuthTokenUrl({ baseUrl: effectiveBaseUrl }),
2759
+ authorizeUrl: getAuthAuthorizeUrl({ baseUrl: effectiveBaseUrl })
2559
2760
  };
2560
2761
  }
2561
2762
  var cachedLogin;
2562
- function getConfig() {
2563
- if (!config) {
2564
- config = new Conf({ projectName: "zapier-sdk-cli" });
2565
- if (!config.has("login_storage_mode")) {
2566
- config.set(
2567
- "login_storage_mode",
2568
- existsSync(config.path) ? "config" : "keychain"
2569
- );
2570
- }
2571
- }
2572
- return config;
2573
- }
2574
2763
  function unloadConfig() {
2575
- config = null;
2764
+ resetConfig();
2576
2765
  cachedLogin = void 0;
2577
2766
  }
2578
2767
  async function updateLogin(loginData, options = {}) {
@@ -2845,9 +3034,7 @@ async function logout(options = {}) {
2845
3034
  await clearTokensFromKeychain();
2846
3035
  const cfg = getConfig();
2847
3036
  cfg.set("login_storage_mode", mode);
2848
- cfg.delete("login_expires_at");
2849
- cfg.delete("login_jwt");
2850
- cfg.delete("login_refresh_token");
3037
+ clearLegacyJwtConfigKeys(cfg);
2851
3038
  onEvent?.({
2852
3039
  type: "auth_logout",
2853
3040
  payload: { message: "Logged out successfully", operation: "logout" },
@@ -2859,6 +3046,79 @@ function getConfigPath() {
2859
3046
  return cfg.path;
2860
3047
  }
2861
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
+
2862
3122
  // src/utils/constants.ts
2863
3123
  var LOGIN_PORTS = [49505, 50575, 52804, 55981, 61010, 63851];
2864
3124
  var LOGIN_TIMEOUT_MS = 3e5;
@@ -2944,6 +3204,8 @@ var getCallablePromise = () => {
2944
3204
  };
2945
3205
  };
2946
3206
  var getCallablePromise_default = getCallablePromise;
3207
+
3208
+ // src/utils/auth/oauth-flow.ts
2947
3209
  var findAvailablePort = () => {
2948
3210
  return new Promise((resolve4, reject) => {
2949
3211
  let portIndex = 0;
@@ -2978,10 +3240,9 @@ var findAvailablePort = () => {
2978
3240
  var generateRandomString = () => {
2979
3241
  const array = new Uint32Array(28);
2980
3242
  crypto.getRandomValues(array);
2981
- return Array.from(
2982
- array,
2983
- (dec) => ("0" + dec.toString(16)).substring(-2)
2984
- ).join("");
3243
+ return Array.from(array, (dec) => ("0" + dec.toString(16)).slice(-2)).join(
3244
+ ""
3245
+ );
2985
3246
  };
2986
3247
  function ensureOfflineAccess(scope) {
2987
3248
  if (scope.includes("offline_access")) {
@@ -2989,17 +3250,18 @@ function ensureOfflineAccess(scope) {
2989
3250
  }
2990
3251
  return `${scope} offline_access`;
2991
3252
  }
2992
- var login = async ({
3253
+ async function runOauthFlow({
2993
3254
  timeoutMs = LOGIN_TIMEOUT_MS,
2994
- credentials: credentials2
2995
- }) => {
3255
+ pkceCredentials,
3256
+ baseUrl: baseUrl2
3257
+ }) {
2996
3258
  const { clientId, tokenUrl, authorizeUrl } = getPkceLoginConfig({
2997
- credentials: credentials2
3259
+ credentials: pkceCredentials,
3260
+ baseUrl: baseUrl2
2998
3261
  });
2999
3262
  const scope = ensureOfflineAccess(
3000
- credentials2?.scope || "internal credentials"
3263
+ pkceCredentials?.scope || "internal credentials"
3001
3264
  );
3002
- await logout();
3003
3265
  const availablePort = await findAvailablePort();
3004
3266
  const redirectUri = `http://localhost:${availablePort}/oauth`;
3005
3267
  log_default.info(`Using port ${availablePort} for OAuth callback`);
@@ -3008,13 +3270,30 @@ var login = async ({
3008
3270
  resolve: setCode,
3009
3271
  reject: rejectCode
3010
3272
  } = getCallablePromise_default();
3011
- const app = express();
3012
- app.get("/oauth", (req, res) => {
3013
- setCode(String(req.query.code));
3273
+ const oauthState = generateRandomString();
3274
+ const expressApp = express();
3275
+ expressApp.get("/oauth", (req, res) => {
3014
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));
3015
3294
  res.end("You can now close this tab and return to the CLI.");
3016
3295
  });
3017
- const server = app.listen(availablePort);
3296
+ const server = expressApp.listen(availablePort);
3018
3297
  const connections = /* @__PURE__ */ new Set();
3019
3298
  server.on("connection", (conn) => {
3020
3299
  connections.add(conn);
@@ -3033,7 +3312,7 @@ var login = async ({
3033
3312
  client_id: clientId,
3034
3313
  redirect_uri: redirectUri,
3035
3314
  scope,
3036
- state: generateRandomString(),
3315
+ state: oauthState,
3037
3316
  code_challenge: codeChallenge,
3038
3317
  code_challenge_method: "S256"
3039
3318
  }).toString()}`;
@@ -3092,36 +3371,79 @@ var login = async ({
3092
3371
  }
3093
3372
  }
3094
3373
  );
3095
- let targetStorage;
3096
- if (getLoginStorageMode() === "config") {
3097
- const { upgrade } = await inquirer.prompt([
3098
- {
3099
- type: "confirm",
3100
- name: "upgrade",
3101
- 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.",
3102
- default: true
3103
- }
3104
- ]);
3105
- targetStorage = upgrade ? "keychain" : "config";
3106
- } else {
3107
- targetStorage = "keychain";
3108
- }
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);
3109
3407
  try {
3110
- await updateLogin(data, { storage: targetStorage });
3111
- } catch (err) {
3112
- if (targetStorage === "keychain") {
3113
- log_default.warn(
3114
- `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}`
3115
3425
  );
3116
- await updateLogin(data, { storage: "config" });
3117
- } else {
3118
- throw err;
3119
3426
  }
3427
+ throw storeErr;
3120
3428
  }
3121
- log_default.info("Token exchange completed successfully");
3122
- return data.access_token;
3123
- };
3124
- 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
+ }
3125
3447
  var LoginSchema = z.object({
3126
3448
  timeout: z.string().optional().describe("Login timeout in seconds (default: 300)")
3127
3449
  }).describe("Log in to Zapier to access your account");
@@ -3138,6 +3460,105 @@ function toPkceCredentials(credentials2) {
3138
3460
  }
3139
3461
  return void 0;
3140
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
+ }
3141
3562
  var loginPlugin = definePlugin(
3142
3563
  (sdk) => createPluginMethod(sdk, {
3143
3564
  name: "login",
@@ -3145,25 +3566,61 @@ var loginPlugin = definePlugin(
3145
3566
  inputSchema: LoginSchema,
3146
3567
  supportsJsonOutput: false,
3147
3568
  handler: async ({ sdk: sdk2, options }) => {
3148
- const timeoutSeconds = options.timeout ? parseInt(options.timeout, 10) : 300;
3149
- if (isNaN(timeoutSeconds) || timeoutSeconds <= 0) {
3150
- throw new Error("Timeout must be a positive number");
3151
- }
3569
+ const timeoutSeconds = parseTimeoutSeconds(options.timeout);
3152
3570
  const resolvedCredentials = await sdk2.context.resolveCredentials();
3153
3571
  const pkceCredentials = toPkceCredentials(resolvedCredentials);
3154
- 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({
3155
3597
  timeoutMs: timeoutSeconds * 1e3,
3156
- credentials: pkceCredentials
3598
+ pkceCredentials,
3599
+ baseUrl: credentialsBaseUrl2
3157
3600
  });
3158
- const user = await getLoggedInUser();
3159
- sdk2.context.eventEmission.emit(
3160
- "platform.sdk.ApplicationLifecycleEvent",
3161
- buildApplicationLifecycleEvent(
3162
- { lifecycle_event_type: "login_success" },
3163
- { customuser_id: user.customUserId, account_id: user.accountId }
3164
- )
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
3613
+ );
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.`
3165
3622
  );
3166
- console.log(`\u2705 Successfully logged in as ${user.email}`);
3623
+ emitLoginSuccess({ sdk: sdk2, profile });
3167
3624
  }
3168
3625
  })
3169
3626
  );
@@ -3176,8 +3633,36 @@ var logoutPlugin = definePlugin(
3176
3633
  categories: ["account"],
3177
3634
  inputSchema: LogoutSchema,
3178
3635
  supportsJsonOutput: false,
3179
- handler: async () => {
3180
- 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
+ });
3181
3666
  console.log("\u2705 Successfully logged out");
3182
3667
  }
3183
3668
  })
@@ -5694,7 +6179,7 @@ var watchTriggerInboxCliPlugin = definePlugin(
5694
6179
  // package.json with { type: 'json' }
5695
6180
  var package_default2 = {
5696
6181
  name: "@zapier/zapier-sdk-cli",
5697
- version: "0.46.1"};
6182
+ version: "0.48.0"};
5698
6183
 
5699
6184
  // src/sdk.ts
5700
6185
  injectCliLogin(login_exports);