@zapier/zapier-sdk-cli 0.52.12 → 0.53.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 (45) hide show
  1. package/AGENTS.md +326 -0
  2. package/CHANGELOG.md +22 -0
  3. package/CLAUDE.md +3 -324
  4. package/README.md +20 -0
  5. package/dist/cli.cjs +896 -395
  6. package/dist/cli.mjs +897 -396
  7. package/dist/experimental.cjs +897 -398
  8. package/dist/experimental.d.mts +1 -1
  9. package/dist/experimental.d.ts +1 -1
  10. package/dist/experimental.mjs +896 -397
  11. package/dist/index.cjs +898 -399
  12. package/dist/index.d.mts +1 -1
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.mjs +897 -398
  15. package/dist/package.json +2 -1
  16. package/dist/{sdk-Sa1HjzUj.d.mts → sdk-SOLizjno.d.mts} +40 -2
  17. package/dist/{sdk-Sa1HjzUj.d.ts → sdk-SOLizjno.d.ts} +40 -2
  18. package/dist/src/experimental.js +2 -1
  19. package/dist/src/plugins/index.d.ts +1 -0
  20. package/dist/src/plugins/index.js +1 -0
  21. package/dist/src/plugins/login/index.d.ts +2 -15
  22. package/dist/src/plugins/login/index.js +3 -191
  23. package/dist/src/plugins/signup/index.d.ts +25 -0
  24. package/dist/src/plugins/signup/index.js +12 -0
  25. package/dist/src/plugins/signup/schemas.d.ts +9 -0
  26. package/dist/src/plugins/signup/schemas.js +26 -0
  27. package/dist/src/plugins/signup/test-harness.d.ts +34 -0
  28. package/dist/src/plugins/signup/test-harness.js +74 -0
  29. package/dist/src/sdk.js +2 -1
  30. package/dist/src/types/sdk.d.ts +2 -1
  31. package/dist/src/utils/auth/account-auth.d.ts +32 -0
  32. package/dist/src/utils/auth/account-auth.js +265 -0
  33. package/dist/src/utils/auth/oauth-callback.d.ts +6 -0
  34. package/dist/src/utils/auth/oauth-callback.js +28 -0
  35. package/dist/src/utils/auth/oauth-errors.d.ts +2 -0
  36. package/dist/src/utils/auth/oauth-errors.js +39 -0
  37. package/dist/src/utils/auth/oauth-flow.d.ts +31 -6
  38. package/dist/src/utils/auth/oauth-flow.js +258 -106
  39. package/dist/src/utils/auth/oauth-transaction.d.ts +35 -0
  40. package/dist/src/utils/auth/oauth-transaction.js +69 -0
  41. package/dist/src/utils/non-interactive.d.ts +5 -4
  42. package/dist/src/utils/non-interactive.js +6 -5
  43. package/dist/tsconfig.tsbuildinfo +1 -1
  44. package/package.json +4 -3
  45. package/templates/basic/AGENTS.md.hbs +2 -2
package/dist/cli.cjs CHANGED
@@ -18,8 +18,9 @@ var crypto = require('crypto');
18
18
  var path = require('path');
19
19
  var lockfile = require('proper-lockfile');
20
20
  var os = require('os');
21
- var open = require('open');
22
21
  var express = require('express');
22
+ var promises$1 = require('readline/promises');
23
+ var open = require('open');
23
24
  var pkceChallenge = require('pkce-challenge');
24
25
  var zapierSdkMcp = require('@zapier/zapier-sdk-mcp');
25
26
  var esbuild = require('esbuild');
@@ -66,8 +67,8 @@ var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
66
67
  var crypto__default = /*#__PURE__*/_interopDefault(crypto);
67
68
  var path__namespace = /*#__PURE__*/_interopNamespace(path);
68
69
  var lockfile__namespace = /*#__PURE__*/_interopNamespace(lockfile);
69
- var open__default = /*#__PURE__*/_interopDefault(open);
70
70
  var express__default = /*#__PURE__*/_interopDefault(express);
71
+ var open__default = /*#__PURE__*/_interopDefault(open);
71
72
  var pkceChallenge__default = /*#__PURE__*/_interopDefault(pkceChallenge);
72
73
  var ts__namespace = /*#__PURE__*/_interopNamespace(ts);
73
74
  var isInstalledGlobally__default = /*#__PURE__*/_interopDefault(isInstalledGlobally);
@@ -1572,7 +1573,7 @@ var SHARED_COMMAND_CLI_OPTIONS = [
1572
1573
 
1573
1574
  // package.json
1574
1575
  var package_default = {
1575
- version: "0.52.12"};
1576
+ version: "0.53.1"};
1576
1577
 
1577
1578
  // src/telemetry/builders.ts
1578
1579
  function createCliBaseEvent(context = {}) {
@@ -3544,25 +3545,112 @@ async function revokeCredentials({
3544
3545
  });
3545
3546
  emitAuthLogout(onEvent);
3546
3547
  }
3548
+ function getBaseUrlFromResolvedCredentials(credentials2) {
3549
+ if (credentials2 && zapierSdk.isCredentialsObject(credentials2)) {
3550
+ return credentials2.baseUrl;
3551
+ }
3552
+ return void 0;
3553
+ }
3554
+ function getBaseUrlFromOptionsCredentials(credentials2) {
3555
+ if (credentials2 && typeof credentials2 === "object" && "baseUrl" in credentials2 && typeof credentials2.baseUrl === "string") {
3556
+ return credentials2.baseUrl;
3557
+ }
3558
+ return void 0;
3559
+ }
3560
+ async function resolveCredentialsBaseUrl(context) {
3561
+ const resolvedCredentials = "resolvedCredentials" in context ? context.resolvedCredentials : await context.resolveCredentials?.();
3562
+ return getBaseUrlFromResolvedCredentials(resolvedCredentials) ?? getBaseUrlFromOptionsCredentials(context.options?.credentials) ?? context.options?.baseUrl;
3563
+ }
3547
3564
 
3548
- // src/utils/constants.ts
3549
- var LOGIN_PORTS = [49505, 50575, 52804, 55981, 61010, 63851];
3550
- var LOGIN_TIMEOUT_MS = 3e5;
3551
- var spinPromise = async (promise, text) => {
3552
- const spinner = ora__default.default(text).start();
3565
+ // src/utils/non-interactive.ts
3566
+ function resolveNonInteractive(options) {
3567
+ return options.nonInteractive === true || options.skipPrompts === true || !process.stdin.isTTY || !process.stdout.isTTY;
3568
+ }
3569
+
3570
+ // src/utils/auth/client-credentials.ts
3571
+ var CREDENTIALS_SCOPES = ["external", "credentials"];
3572
+ var EMPTY_POLICY = {
3573
+ version: 2,
3574
+ statements: []
3575
+ };
3576
+ async function createCredentialsOnServer(api2, name, policy) {
3577
+ const response = await api2.post(
3578
+ "/api/v0/client-credentials",
3579
+ {
3580
+ name,
3581
+ allowed_scopes: CREDENTIALS_SCOPES,
3582
+ ...policy !== void 0 && { policy }
3583
+ },
3584
+ { authRequired: true, requiredScopes: ["credentials"] }
3585
+ );
3586
+ return {
3587
+ clientId: response.data.client_id,
3588
+ clientSecret: response.data.client_secret
3589
+ };
3590
+ }
3591
+ async function deleteCredentialsOnServer(api2, clientId) {
3592
+ await api2.delete(`/api/v0/client-credentials/${clientId}`, void 0, {
3593
+ authRequired: true,
3594
+ requiredScopes: ["credentials"]
3595
+ });
3596
+ }
3597
+ async function setupClientCredentials({
3598
+ api: api2,
3599
+ name,
3600
+ credentialsBaseUrl: credentialsBaseUrl2,
3601
+ policy
3602
+ }) {
3603
+ const { clientId, clientSecret } = await createCredentialsOnServer(
3604
+ api2,
3605
+ name,
3606
+ policy
3607
+ );
3553
3608
  try {
3554
- const result = await promise;
3555
- spinner.succeed();
3556
- return result;
3557
- } catch (error) {
3558
- if (error instanceof ZapierCliUserCancellationError) {
3559
- spinner.stop();
3560
- } else {
3561
- spinner.fail();
3609
+ await withRetry({
3610
+ action: () => storeClientCredentials({
3611
+ name,
3612
+ clientId,
3613
+ clientSecret,
3614
+ scopes: [...CREDENTIALS_SCOPES],
3615
+ baseUrl: credentialsBaseUrl2
3616
+ })
3617
+ });
3618
+ } catch (storeErr) {
3619
+ try {
3620
+ await withRetry({
3621
+ action: () => deleteCredentialsOnServer(api2, clientId)
3622
+ });
3623
+ } catch {
3624
+ console.error(
3625
+ `Failed to roll back orphaned credential ${clientId}. Delete it manually with: zapier-sdk delete-client-credentials ${clientId}`
3626
+ );
3562
3627
  }
3563
- throw error;
3628
+ throw storeErr;
3564
3629
  }
3630
+ return { clientId };
3631
+ }
3632
+
3633
+ // src/utils/constants.ts
3634
+ var LOGIN_PORTS = [49505, 50575, 52804, 55981, 61010, 63851];
3635
+ var LOGIN_TIMEOUT_MS = 3e5;
3636
+
3637
+ // src/utils/getCallablePromise.ts
3638
+ var getCallablePromise = () => {
3639
+ let resolve4 = () => {
3640
+ };
3641
+ let reject = () => {
3642
+ };
3643
+ const promise = new Promise((_resolve, _reject) => {
3644
+ resolve4 = _resolve;
3645
+ reject = _reject;
3646
+ });
3647
+ return {
3648
+ promise,
3649
+ resolve: resolve4,
3650
+ reject
3651
+ };
3565
3652
  };
3653
+ var getCallablePromise_default = getCallablePromise;
3566
3654
  var log = {
3567
3655
  info: (message, ...args) => {
3568
3656
  console.error(chalk__default.default.blue("\u2139"), message, ...args);
@@ -3583,6 +3671,60 @@ var log = {
3583
3671
  }
3584
3672
  };
3585
3673
  var log_default = log;
3674
+ var spinPromise = async (promise, text) => {
3675
+ const spinner = ora__default.default(text).start();
3676
+ try {
3677
+ const result = await promise;
3678
+ spinner.succeed();
3679
+ return result;
3680
+ } catch (error) {
3681
+ if (error instanceof ZapierCliUserCancellationError) {
3682
+ spinner.stop();
3683
+ } else {
3684
+ spinner.fail();
3685
+ }
3686
+ throw error;
3687
+ }
3688
+ };
3689
+
3690
+ // src/utils/auth/oauth-callback.ts
3691
+ function getCallbackCode({
3692
+ callbackUrl,
3693
+ transaction,
3694
+ recoveryMessage
3695
+ }) {
3696
+ let parsed;
3697
+ try {
3698
+ parsed = new URL(callbackUrl.trim());
3699
+ } catch {
3700
+ throw new ZapierCliValidationError(
3701
+ "Paste the final OAuth callback URL from your browser."
3702
+ );
3703
+ }
3704
+ const expected = new URL(transaction.redirectUri);
3705
+ if (parsed.protocol !== "http:" || parsed.hostname !== expected.hostname || parsed.pathname !== expected.pathname || parsed.port !== expected.port) {
3706
+ throw new ZapierCliValidationError(
3707
+ `Expected the final OAuth callback URL to start with ${transaction.redirectUri}.`
3708
+ );
3709
+ }
3710
+ if (parsed.searchParams.get("state") !== transaction.state) {
3711
+ throw new ZapierCliValidationError(
3712
+ `OAuth state mismatch.${recoveryMessage ? ` ${recoveryMessage}` : ""}`
3713
+ );
3714
+ }
3715
+ if (parsed.searchParams.has("error")) {
3716
+ throw new ZapierCliValidationError(
3717
+ `Authorization denied: ${parsed.searchParams.get("error_description") ?? parsed.searchParams.get("error")}.${recoveryMessage ? ` ${recoveryMessage}` : ""}`
3718
+ );
3719
+ }
3720
+ const code = parsed.searchParams.get("code");
3721
+ if (!code) {
3722
+ throw new ZapierCliValidationError(
3723
+ "No authorization code found in the pasted callback URL."
3724
+ );
3725
+ }
3726
+ return code;
3727
+ }
3586
3728
 
3587
3729
  // src/utils/api/client.ts
3588
3730
  var createApiClient = () => {
@@ -3613,179 +3755,77 @@ var createApiClient = () => {
3613
3755
  var api = createApiClient();
3614
3756
  var client_default = api;
3615
3757
 
3616
- // src/utils/getCallablePromise.ts
3617
- var getCallablePromise = () => {
3618
- let resolve4 = () => {
3619
- };
3620
- let reject = () => {
3621
- };
3622
- const promise = new Promise((_resolve, _reject) => {
3623
- resolve4 = _resolve;
3624
- reject = _reject;
3625
- });
3626
- return {
3627
- promise,
3628
- resolve: resolve4,
3629
- reject
3630
- };
3631
- };
3632
- var getCallablePromise_default = getCallablePromise;
3633
-
3634
- // src/utils/auth/oauth-flow.ts
3635
- var findAvailablePort = () => {
3636
- return new Promise((resolve4, reject) => {
3637
- let portIndex = 0;
3638
- const tryPort = (port) => {
3639
- const server = express__default.default().listen(port, () => {
3640
- server.close();
3641
- resolve4(port);
3642
- });
3643
- server.on("error", (err) => {
3644
- if (err.code === "EADDRINUSE") {
3645
- if (portIndex < LOGIN_PORTS.length) {
3646
- tryPort(LOGIN_PORTS[portIndex++]);
3647
- } else {
3648
- reject(
3649
- new Error(
3650
- `All configured OAuth callback ports are busy: ${LOGIN_PORTS.join(", ")}. Please try again later or close applications using these ports.`
3651
- )
3652
- );
3653
- }
3654
- } else {
3655
- reject(err);
3656
- }
3657
- });
3658
- };
3659
- if (LOGIN_PORTS.length > 0) {
3660
- tryPort(LOGIN_PORTS[portIndex++]);
3661
- } else {
3662
- reject(new Error("No OAuth callback ports configured"));
3663
- }
3664
- });
3665
- };
3666
- var generateRandomString = () => {
3758
+ // src/utils/auth/oauth-transaction.ts
3759
+ var OAUTH_LOOPBACK_HOST = "localhost";
3760
+ function buildBrowserAuthUrl({
3761
+ authorizeUrl,
3762
+ entryPoint = "login"
3763
+ }) {
3764
+ if (entryPoint === "login") return authorizeUrl;
3765
+ const parsedAuthorizeUrl = new URL(authorizeUrl);
3766
+ const signupUrl = new URL("/sign-up", parsedAuthorizeUrl);
3767
+ signupUrl.searchParams.set("skipOnboarding", "true");
3768
+ signupUrl.searchParams.set(
3769
+ "next",
3770
+ `${parsedAuthorizeUrl.pathname}${parsedAuthorizeUrl.search}`
3771
+ );
3772
+ return signupUrl.toString();
3773
+ }
3774
+ function generateRandomString() {
3667
3775
  const array = new Uint32Array(28);
3668
3776
  crypto__default.default.getRandomValues(array);
3669
3777
  return Array.from(array, (dec) => ("0" + dec.toString(16)).slice(-2)).join(
3670
3778
  ""
3671
3779
  );
3672
- };
3780
+ }
3673
3781
  function ensureOfflineAccess(scope) {
3674
- if (scope.includes("offline_access")) {
3675
- return scope;
3676
- }
3782
+ if (scope.includes("offline_access")) return scope;
3677
3783
  return `${scope} offline_access`;
3678
3784
  }
3679
- async function runOauthFlow({
3680
- timeoutMs = LOGIN_TIMEOUT_MS,
3785
+ async function prepareOauthTransaction({
3681
3786
  pkceCredentials,
3682
- baseUrl: baseUrl2
3787
+ baseUrl: baseUrl2,
3788
+ redirectUri,
3789
+ entryPoint = "login"
3683
3790
  }) {
3684
3791
  const { clientId, tokenUrl, authorizeUrl } = getPkceLoginConfig({
3685
3792
  credentials: pkceCredentials,
3686
3793
  baseUrl: baseUrl2
3687
3794
  });
3688
- const scope = ensureOfflineAccess(
3689
- pkceCredentials?.scope || "internal credentials"
3690
- );
3691
- const availablePort = await findAvailablePort();
3692
- const redirectUri = `http://localhost:${availablePort}/oauth`;
3693
- log_default.info(`Using port ${availablePort} for OAuth callback`);
3694
- const {
3695
- promise: promisedCode,
3696
- resolve: setCode,
3697
- reject: rejectCode
3698
- } = getCallablePromise_default();
3699
- const oauthState = generateRandomString();
3700
- const expressApp = express__default.default();
3701
- expressApp.get("/oauth", (req, res) => {
3702
- res.setHeader("Connection", "close");
3703
- if (req.query.state !== oauthState) {
3704
- rejectCode(new Error("OAuth state mismatch \u2014 possible CSRF"));
3705
- res.status(400).end("Invalid state. You can close this tab.");
3706
- return;
3707
- }
3708
- if (req.query.error) {
3709
- const desc = req.query.error_description ?? req.query.error;
3710
- rejectCode(new Error(`Authorization denied: ${desc}`));
3711
- res.end("Authorization was denied. You can close this tab.");
3712
- return;
3713
- }
3714
- if (!req.query.code) {
3715
- rejectCode(new Error("No authorization code received"));
3716
- res.end("No authorization code received. You can close this tab.");
3717
- return;
3718
- }
3719
- setCode(String(req.query.code));
3720
- res.end("You can now close this tab and return to the CLI.");
3721
- });
3722
- const server = expressApp.listen(availablePort);
3723
- const connections = /* @__PURE__ */ new Set();
3724
- server.on("connection", (conn) => {
3725
- connections.add(conn);
3726
- conn.on("close", () => connections.delete(conn));
3727
- });
3728
- const cleanup = () => {
3729
- server.close();
3730
- log_default.info("\n\u274C Login cancelled by user");
3731
- rejectCode(new ZapierCliUserCancellationError());
3732
- };
3733
- process.on("SIGINT", cleanup);
3734
- process.on("SIGTERM", cleanup);
3735
3795
  const { code_verifier: codeVerifier, code_challenge: codeChallenge } = await pkceChallenge__default.default();
3796
+ const state = generateRandomString();
3736
3797
  const authUrl = `${authorizeUrl}?${new URLSearchParams({
3737
3798
  response_type: "code",
3738
3799
  client_id: clientId,
3739
3800
  redirect_uri: redirectUri,
3740
- scope,
3741
- state: oauthState,
3801
+ scope: ensureOfflineAccess(
3802
+ pkceCredentials?.scope || "internal credentials"
3803
+ ),
3804
+ state,
3742
3805
  code_challenge: codeChallenge,
3743
3806
  code_challenge_method: "S256"
3744
3807
  }).toString()}`;
3745
- log_default.info("Opening your browser to log in.");
3746
- log_default.info("If it doesn't open, visit:", authUrl);
3747
- open__default.default(authUrl);
3748
- let timeoutTimer;
3749
- try {
3750
- await spinPromise(
3751
- Promise.race([
3752
- promisedCode,
3753
- new Promise((_resolve, reject) => {
3754
- timeoutTimer = setTimeout(() => {
3755
- reject(
3756
- new Error(
3757
- `Login timed out after ${Math.round(timeoutMs / 1e3)} seconds.`
3758
- )
3759
- );
3760
- }, timeoutMs);
3761
- })
3762
- ]),
3763
- "Waiting for you to login and authorize"
3764
- );
3765
- } finally {
3766
- if (timeoutTimer) {
3767
- clearTimeout(timeoutTimer);
3768
- }
3769
- process.off("SIGINT", cleanup);
3770
- process.off("SIGTERM", cleanup);
3771
- await new Promise((resolve4) => {
3772
- const timeout = setTimeout(() => {
3773
- log_default.info("Server close timed out, forcing connection shutdown...");
3774
- connections.forEach((conn) => conn.destroy());
3775
- resolve4();
3776
- }, 1e3);
3777
- server.close(() => {
3778
- clearTimeout(timeout);
3779
- resolve4();
3780
- });
3781
- });
3782
- }
3783
- log_default.info("Exchanging authorization code for tokens...");
3808
+ return {
3809
+ browserAuthUrl: buildBrowserAuthUrl({ authorizeUrl: authUrl, entryPoint }),
3810
+ clientId,
3811
+ codeVerifier,
3812
+ redirectUri,
3813
+ state,
3814
+ tokenUrl
3815
+ };
3816
+ }
3817
+ async function exchangeOauthCode({
3818
+ tokenUrl,
3819
+ code,
3820
+ redirectUri,
3821
+ clientId,
3822
+ codeVerifier
3823
+ }) {
3784
3824
  const { data } = await client_default.post(
3785
3825
  tokenUrl,
3786
3826
  {
3787
3827
  grant_type: "authorization_code",
3788
- code: await promisedCode,
3828
+ code,
3789
3829
  redirect_uri: redirectUri,
3790
3830
  client_id: clientId,
3791
3831
  code_verifier: codeVerifier
@@ -3797,7 +3837,6 @@ async function runOauthFlow({
3797
3837
  }
3798
3838
  }
3799
3839
  );
3800
- log_default.info("Token exchange completed successfully");
3801
3840
  return {
3802
3841
  accessToken: data.access_token,
3803
3842
  refreshToken: data.refresh_token,
@@ -3805,105 +3844,467 @@ async function runOauthFlow({
3805
3844
  };
3806
3845
  }
3807
3846
 
3808
- // src/utils/auth/client-credentials.ts
3809
- var CREDENTIALS_SCOPES = ["external", "credentials"];
3810
- var EMPTY_POLICY = {
3811
- version: 2,
3812
- statements: []
3847
+ // src/utils/auth/oauth-flow.ts
3848
+ var OauthFlowTimeoutError = class extends Error {
3849
+ constructor(timeoutMs) {
3850
+ super("OAuth flow timed out");
3851
+ this.timeoutMs = timeoutMs;
3852
+ this.name = "OauthFlowTimeoutError";
3853
+ }
3813
3854
  };
3814
- async function createCredentialsOnServer(api2, name, policy) {
3815
- const response = await api2.post(
3816
- "/api/v0/client-credentials",
3817
- {
3818
- name,
3819
- allowed_scopes: CREDENTIALS_SCOPES,
3820
- ...policy !== void 0 && { policy }
3821
- },
3822
- { authRequired: true, requiredScopes: ["credentials"] }
3823
- );
3824
- return {
3825
- clientId: response.data.client_id,
3826
- clientSecret: response.data.client_secret
3827
- };
3855
+ var OauthAuthorizationDeniedError = class extends Error {
3856
+ constructor(reason) {
3857
+ super("OAuth authorization denied");
3858
+ this.reason = reason;
3859
+ this.name = "OauthAuthorizationDeniedError";
3860
+ }
3861
+ };
3862
+ function findAvailablePort() {
3863
+ return new Promise((resolve4, reject) => {
3864
+ let portIndex = 0;
3865
+ const tryPort = (port) => {
3866
+ const server = express__default.default().listen(port, OAUTH_LOOPBACK_HOST, () => {
3867
+ server.close();
3868
+ resolve4(port);
3869
+ });
3870
+ server.on("error", (err) => {
3871
+ if (err.code === "EADDRINUSE" && portIndex < LOGIN_PORTS.length) {
3872
+ tryPort(LOGIN_PORTS[portIndex++]);
3873
+ } else if (err.code === "EADDRINUSE") {
3874
+ reject(
3875
+ new Error(
3876
+ `All configured OAuth callback ports are busy: ${LOGIN_PORTS.join(", ")}. Please try again later or close applications using these ports.`
3877
+ )
3878
+ );
3879
+ } else {
3880
+ reject(err);
3881
+ }
3882
+ });
3883
+ };
3884
+ if (LOGIN_PORTS.length > 0) tryPort(LOGIN_PORTS[portIndex++]);
3885
+ else reject(new Error("No OAuth callback ports configured"));
3886
+ });
3828
3887
  }
3829
- async function deleteCredentialsOnServer(api2, clientId) {
3830
- await api2.delete(`/api/v0/client-credentials/${clientId}`, void 0, {
3831
- authRequired: true,
3832
- requiredScopes: ["credentials"]
3888
+ async function runLoginOauthFlow(options) {
3889
+ return runOauthFlowEntryPoint({
3890
+ ...options,
3891
+ entryPoint: "login",
3892
+ authAction: "log in",
3893
+ flowName: "Login"
3833
3894
  });
3834
3895
  }
3835
- async function setupClientCredentials({
3836
- api: api2,
3837
- name,
3838
- credentialsBaseUrl: credentialsBaseUrl2,
3839
- policy
3896
+ async function runSignupOauthFlow(options) {
3897
+ if (options.headless) {
3898
+ return runOauthFlowEntryPoint({
3899
+ ...options,
3900
+ entryPoint: "signup",
3901
+ authAction: "sign up",
3902
+ flowName: "Signup",
3903
+ headless: true
3904
+ });
3905
+ }
3906
+ return runOauthFlowEntryPoint({
3907
+ ...options,
3908
+ entryPoint: "signup",
3909
+ authAction: "sign up",
3910
+ flowName: "Signup"
3911
+ });
3912
+ }
3913
+ async function runOauthFlowEntryPoint({
3914
+ flowName,
3915
+ ...options
3840
3916
  }) {
3841
- const { clientId, clientSecret } = await createCredentialsOnServer(
3842
- api2,
3843
- name,
3844
- policy
3917
+ try {
3918
+ return options.headless ? await runHeadlessSignupOauthFlow(options) : await runOauthFlow(options);
3919
+ } catch (error) {
3920
+ if (error instanceof OauthFlowTimeoutError) {
3921
+ throw new Error(
3922
+ withRecoveryMessage(
3923
+ `${flowName} timed out after ${Math.round(error.timeoutMs / 1e3)} seconds.`,
3924
+ options.recoveryMessage
3925
+ )
3926
+ );
3927
+ }
3928
+ if (error instanceof OauthAuthorizationDeniedError) {
3929
+ throw new Error(
3930
+ withRecoveryMessage(
3931
+ `Authorization denied: ${error.reason}.`,
3932
+ options.recoveryMessage
3933
+ )
3934
+ );
3935
+ }
3936
+ if (error instanceof ZapierCliUserCancellationError && !options.silent) {
3937
+ log_default.info(`
3938
+ \u274C ${flowName} cancelled by user`);
3939
+ }
3940
+ throw error;
3941
+ }
3942
+ }
3943
+ function withRecoveryMessage(message, recoveryMessage) {
3944
+ return recoveryMessage ? `${message} ${recoveryMessage}` : message;
3945
+ }
3946
+ async function runOauthFlow({
3947
+ timeoutMs = LOGIN_TIMEOUT_MS,
3948
+ pkceCredentials,
3949
+ baseUrl: baseUrl2,
3950
+ entryPoint,
3951
+ authAction,
3952
+ silent = false,
3953
+ onProgress
3954
+ }) {
3955
+ const port = await findAvailablePort();
3956
+ if (!silent) log_default.info(`Using port ${port} for OAuth callback`);
3957
+ const transaction = await prepareOauthTransaction({
3958
+ pkceCredentials,
3959
+ baseUrl: baseUrl2,
3960
+ redirectUri: `http://${OAUTH_LOOPBACK_HOST}:${port}/oauth`,
3961
+ entryPoint
3962
+ });
3963
+ const code = await collectLocalCallbackCode({
3964
+ transaction,
3965
+ timeoutMs,
3966
+ authAction,
3967
+ silent,
3968
+ onProgress
3969
+ });
3970
+ onProgress?.({ type: "callback_accepted" });
3971
+ if (!silent) log_default.info("Exchanging authorization code for tokens...");
3972
+ onProgress?.({ type: "token_exchange_started" });
3973
+ const tokens = await exchangeOauthCode({ ...transaction, code });
3974
+ if (!silent) log_default.info("Token exchange completed successfully");
3975
+ onProgress?.({ type: "token_exchange_completed" });
3976
+ return tokens;
3977
+ }
3978
+ async function readHeadlessCallbackUrl({
3979
+ timeoutMs,
3980
+ interactive,
3981
+ recoveryMessage
3982
+ }) {
3983
+ const timeoutMessage = withRecoveryMessage(
3984
+ `Signup timed out after ${Math.round(timeoutMs / 1e3)} seconds.`,
3985
+ recoveryMessage
3845
3986
  );
3987
+ const missingCallbackUrlMessage = withRecoveryMessage(
3988
+ "Paste the final OAuth callback URL from your browser.",
3989
+ recoveryMessage
3990
+ );
3991
+ const rl = promises$1.createInterface({ input: process.stdin, output: process.stderr });
3992
+ const abortController = new AbortController();
3993
+ const timeoutTimer = setTimeout(() => abortController.abort(), timeoutMs);
3994
+ const readUrl = interactive ? rl.question("Paste the final OAuth callback URL: ", {
3995
+ signal: abortController.signal
3996
+ }) : new Promise((resolve4, reject) => {
3997
+ let settled = false;
3998
+ const settleResolve = (value) => {
3999
+ settled = true;
4000
+ resolve4(value);
4001
+ };
4002
+ const settleReject = (error) => {
4003
+ if (settled) return;
4004
+ settled = true;
4005
+ reject(error);
4006
+ };
4007
+ abortController.signal.addEventListener(
4008
+ "abort",
4009
+ () => settleReject(new Error(timeoutMessage)),
4010
+ { once: true }
4011
+ );
4012
+ rl.once("line", settleResolve);
4013
+ rl.once(
4014
+ "close",
4015
+ () => settleReject(new ZapierCliValidationError(missingCallbackUrlMessage))
4016
+ );
4017
+ rl.once("error", settleReject);
4018
+ });
3846
4019
  try {
3847
- await withRetry({
3848
- action: () => storeClientCredentials({
3849
- name,
3850
- clientId,
3851
- clientSecret,
3852
- scopes: [...CREDENTIALS_SCOPES],
3853
- baseUrl: credentialsBaseUrl2
3854
- })
4020
+ return await readUrl.catch((error) => {
4021
+ if (error instanceof Error && error.name === "AbortError") {
4022
+ throw new Error(timeoutMessage);
4023
+ }
4024
+ throw error;
3855
4025
  });
3856
- } catch (storeErr) {
3857
- try {
3858
- await withRetry({
3859
- action: () => deleteCredentialsOnServer(api2, clientId)
3860
- });
3861
- } catch {
3862
- console.error(
3863
- `Failed to roll back orphaned credential ${clientId}. Delete it manually with: zapier-sdk delete-client-credentials ${clientId}`
4026
+ } finally {
4027
+ clearTimeout(timeoutTimer);
4028
+ rl.close();
4029
+ }
4030
+ }
4031
+ async function runHeadlessSignupOauthFlow({
4032
+ timeoutMs = LOGIN_TIMEOUT_MS,
4033
+ pkceCredentials,
4034
+ baseUrl: baseUrl2,
4035
+ interactive = true,
4036
+ onProgress,
4037
+ recoveryMessage
4038
+ }) {
4039
+ const port = LOGIN_PORTS[0];
4040
+ const transaction = await prepareOauthTransaction({
4041
+ pkceCredentials,
4042
+ baseUrl: baseUrl2,
4043
+ redirectUri: `http://${OAUTH_LOOPBACK_HOST}:${port}/oauth`,
4044
+ entryPoint: "signup"
4045
+ });
4046
+ console.log(
4047
+ "Use this mode when signing up from a machine that has no browser."
4048
+ );
4049
+ console.log("Open this signup URL in a browser on another machine:");
4050
+ console.log(transaction.browserAuthUrl);
4051
+ console.log(
4052
+ `When the browser lands on ${transaction.redirectUri} and cannot connect, paste the full final URL back here.`
4053
+ );
4054
+ const callbackUrl = await readHeadlessCallbackUrl({
4055
+ timeoutMs,
4056
+ interactive,
4057
+ recoveryMessage
4058
+ });
4059
+ const code = getCallbackCode({
4060
+ callbackUrl,
4061
+ transaction,
4062
+ recoveryMessage
4063
+ });
4064
+ onProgress?.({ type: "callback_accepted" });
4065
+ console.log("Exchanging authorization code for tokens...");
4066
+ onProgress?.({ type: "token_exchange_started" });
4067
+ const tokens = await exchangeOauthCode({ ...transaction, code });
4068
+ onProgress?.({ type: "token_exchange_completed" });
4069
+ return tokens;
4070
+ }
4071
+ async function collectLocalCallbackCode({
4072
+ transaction,
4073
+ timeoutMs,
4074
+ authAction,
4075
+ silent,
4076
+ onProgress
4077
+ }) {
4078
+ const { promise, resolve: resolve4, reject } = getCallablePromise_default();
4079
+ const app = express__default.default();
4080
+ app.get("/oauth", (req, res) => {
4081
+ res.setHeader("Connection", "close");
4082
+ if (req.query.state !== transaction.state) {
4083
+ res.status(400).end("Invalid state. You can close this tab.");
4084
+ } else if (req.query.error) {
4085
+ reject(
4086
+ new OauthAuthorizationDeniedError(
4087
+ String(req.query.error_description ?? req.query.error)
4088
+ )
3864
4089
  );
4090
+ res.end("Authorization was denied. You can close this tab.");
4091
+ } else if (!req.query.code) {
4092
+ reject(new Error("No authorization code received"));
4093
+ res.end("No authorization code received. You can close this tab.");
4094
+ } else {
4095
+ resolve4(String(req.query.code));
4096
+ res.end("You can now close this tab and return to the CLI.");
3865
4097
  }
3866
- throw storeErr;
4098
+ });
4099
+ const server = app.listen(
4100
+ Number(new URL(transaction.redirectUri).port),
4101
+ OAUTH_LOOPBACK_HOST
4102
+ );
4103
+ const connections = /* @__PURE__ */ new Set();
4104
+ server.on("connection", (conn) => {
4105
+ connections.add(conn);
4106
+ conn.on("close", () => connections.delete(conn));
4107
+ });
4108
+ const cleanup = () => {
4109
+ server.close();
4110
+ reject(new ZapierCliUserCancellationError());
4111
+ };
4112
+ process.on("SIGINT", cleanup);
4113
+ process.on("SIGTERM", cleanup);
4114
+ let timeoutTimer;
4115
+ try {
4116
+ await waitForServerListening(server);
4117
+ await openBrowser({ transaction, authAction, silent, onProgress });
4118
+ const waitForCode = Promise.race([
4119
+ promise,
4120
+ new Promise((_resolve, rejectTimeout) => {
4121
+ timeoutTimer = setTimeout(() => {
4122
+ rejectTimeout(new OauthFlowTimeoutError(timeoutMs));
4123
+ }, timeoutMs);
4124
+ })
4125
+ ]);
4126
+ onProgress?.({ type: "callback_waiting" });
4127
+ return silent ? await waitForCode : await spinPromise(
4128
+ waitForCode,
4129
+ `Waiting for you to ${authAction} and authorize`
4130
+ );
4131
+ } finally {
4132
+ if (timeoutTimer) clearTimeout(timeoutTimer);
4133
+ process.off("SIGINT", cleanup);
4134
+ process.off("SIGTERM", cleanup);
4135
+ await closeServer({ server, connections, silent });
3867
4136
  }
3868
- return { clientId };
3869
4137
  }
3870
- function getBaseUrlFromResolvedCredentials(credentials2) {
3871
- if (credentials2 && zapierSdk.isCredentialsObject(credentials2)) {
3872
- return credentials2.baseUrl;
3873
- }
3874
- return void 0;
4138
+ async function waitForServerListening(server) {
4139
+ if (server.listening) return;
4140
+ await new Promise((resolve4, reject) => {
4141
+ const cleanup = () => {
4142
+ server.off("listening", handleListening);
4143
+ server.off("error", handleError);
4144
+ };
4145
+ const handleListening = () => {
4146
+ cleanup();
4147
+ resolve4();
4148
+ };
4149
+ const handleError = (error) => {
4150
+ cleanup();
4151
+ reject(error);
4152
+ };
4153
+ server.once("listening", handleListening);
4154
+ server.once("error", handleError);
4155
+ });
3875
4156
  }
3876
- function getBaseUrlFromOptionsCredentials(credentials2) {
3877
- if (credentials2 && typeof credentials2 === "object" && "baseUrl" in credentials2 && typeof credentials2.baseUrl === "string") {
3878
- return credentials2.baseUrl;
4157
+ async function openBrowser({
4158
+ transaction,
4159
+ authAction,
4160
+ silent,
4161
+ onProgress
4162
+ }) {
4163
+ if (!silent) {
4164
+ log_default.info(`Opening your browser to ${authAction}.`);
4165
+ log_default.info("If it doesn't open, visit:", transaction.browserAuthUrl);
4166
+ }
4167
+ onProgress?.({ type: "browser_opening", url: transaction.browserAuthUrl });
4168
+ try {
4169
+ await open__default.default(transaction.browserAuthUrl);
4170
+ onProgress?.({ type: "browser_opened", url: transaction.browserAuthUrl });
4171
+ } catch (err) {
4172
+ const reason = err instanceof Error ? err.message : String(err);
4173
+ if (!silent) {
4174
+ log_default.info(
4175
+ `Browser did not open automatically to ${authAction}: ${reason}`
4176
+ );
4177
+ log_default.info("Visit this URL manually:", transaction.browserAuthUrl);
4178
+ }
4179
+ onProgress?.({
4180
+ type: "browser_open_failed",
4181
+ url: transaction.browserAuthUrl,
4182
+ reason
4183
+ });
3879
4184
  }
3880
- return void 0;
3881
4185
  }
3882
- async function resolveCredentialsBaseUrl(context) {
3883
- const resolvedCredentials = "resolvedCredentials" in context ? context.resolvedCredentials : await context.resolveCredentials?.();
3884
- return getBaseUrlFromResolvedCredentials(resolvedCredentials) ?? getBaseUrlFromOptionsCredentials(context.options?.credentials) ?? context.options?.baseUrl;
4186
+ async function closeServer({
4187
+ server,
4188
+ connections,
4189
+ silent
4190
+ }) {
4191
+ await new Promise((resolve4) => {
4192
+ const timeout = setTimeout(() => {
4193
+ if (!silent)
4194
+ log_default.info("Server close timed out, forcing connection shutdown...");
4195
+ connections.forEach((conn) => conn.destroy());
4196
+ resolve4();
4197
+ }, 1e3);
4198
+ server.close(() => {
4199
+ clearTimeout(timeout);
4200
+ resolve4();
4201
+ });
4202
+ });
3885
4203
  }
3886
4204
 
3887
- // src/utils/non-interactive.ts
3888
- function resolveNonInteractive(options) {
3889
- return (options.nonInteractive ?? options.skipPrompts) === true || !process.stdin.isTTY || !process.stdout.isTTY;
4205
+ // src/utils/auth/oauth-errors.ts
4206
+ var SENSITIVE_OAUTH_FIELDS = [
4207
+ "access_token",
4208
+ "refresh_token",
4209
+ "id_token",
4210
+ "client_secret",
4211
+ "code_verifier",
4212
+ "code_challenge"
4213
+ ];
4214
+ function getErrorMessage(error) {
4215
+ return error instanceof Error ? error.message : String(error);
4216
+ }
4217
+ function toCamelCase(field) {
4218
+ return field.replace(
4219
+ /_([a-z])/g,
4220
+ (_match, letter) => letter.toUpperCase()
4221
+ );
4222
+ }
4223
+ function escapeRegExp(value) {
4224
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4225
+ }
4226
+ var sensitiveOauthFieldPattern = Array.from(
4227
+ new Set(
4228
+ SENSITIVE_OAUTH_FIELDS.flatMap((field) => [field, toCamelCase(field)])
4229
+ )
4230
+ ).map(escapeRegExp).join("|");
4231
+ var sensitiveQueryParamPattern = new RegExp(
4232
+ `([?&])(${sensitiveOauthFieldPattern})(=)[^&#\\s"'<>]*`,
4233
+ "gi"
4234
+ );
4235
+ function redactSensitiveOauthErrorMessage(message) {
4236
+ return message.replace(
4237
+ sensitiveQueryParamPattern,
4238
+ (_match, prefix, key, separator) => `${prefix}${key}${separator}[REDACTED]`
4239
+ ).replace(
4240
+ new RegExp(`"(${sensitiveOauthFieldPattern})"(\\s*:\\s*)"[^"]*"`, "g"),
4241
+ (_match, key, separator) => `"${key}"${separator}"[REDACTED]"`
4242
+ );
4243
+ }
4244
+ function toRedactedOauthError(error) {
4245
+ const message = redactSensitiveOauthErrorMessage(getErrorMessage(error));
4246
+ if (error instanceof ZapierCliValidationError) {
4247
+ return new ZapierCliValidationError(message);
4248
+ }
4249
+ if (error instanceof Error) {
4250
+ const redactedError = new Error(message);
4251
+ redactedError.name = error.name;
4252
+ return redactedError;
4253
+ }
4254
+ return new ZapierCliValidationError(message);
3890
4255
  }
3891
- var LoginSchema = zod.z.object({
3892
- timeout: zod.z.string().optional().describe("Login timeout in seconds (default: 300)"),
3893
- useApprovals: zod.z.boolean().optional().describe(
3894
- "Require approvals for actions performed with these credentials"
3895
- ),
3896
- nonInteractive: zod.z.boolean().optional().describe(
3897
- "Skip interactive prompts. Uses defaults where possible; errors instead of prompting when input is required. Useful in CI, piped output, or environments where TTY detection is unreliable."
3898
- ),
3899
- /** @deprecated Use `nonInteractive` instead. */
3900
- skipPrompts: zod.z.boolean().optional().meta({
3901
- deprecated: true,
3902
- deprecationMessage: "Use --non-interactive instead."
3903
- })
3904
- }).describe("Log in to Zapier to access your account");
3905
4256
 
3906
- // src/plugins/login/index.ts
4257
+ // src/utils/auth/account-auth.ts
4258
+ var LEGACY_JWT_UPGRADE_PROMPT = "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?";
4259
+ var SIGNUP_RECOVERY_MESSAGE = "Restart `zapier-sdk signup` to generate a fresh signup URL and try again.";
4260
+ var HEADLESS_SIGNUP_RECOVERY_MESSAGE = "Restart `zapier-sdk signup --headless` to generate a fresh signup URL and try again.";
4261
+ function getEntryPointLabel(entryPoint) {
4262
+ return entryPoint === "signup" ? "Signup" : "Login";
4263
+ }
4264
+ function getActiveCredentialsAction(entryPoint) {
4265
+ return entryPoint === "signup" ? "continue signup" : "log in again";
4266
+ }
4267
+ function getCredentialsPromptMessage(entryPoint) {
4268
+ return entryPoint === "signup" ? "Enter a name to identify these credentials:" : "Enter a name to identify them:";
4269
+ }
4270
+ function getProfileMessage(entryPoint, email) {
4271
+ return entryPoint === "signup" ? `\u{1F464} Authenticated as ${email}` : `\u{1F464} Logged in as ${email}`;
4272
+ }
4273
+ function defaultCredentialsName(email) {
4274
+ return `${email}@${os.hostname()}`;
4275
+ }
4276
+ function validateCredentialsName(name) {
4277
+ const trimmedName = name.trim();
4278
+ if (!trimmedName) throw new ZapierCliValidationError("Name cannot be empty");
4279
+ return trimmedName;
4280
+ }
4281
+ async function promptCredentialsName({
4282
+ email,
4283
+ promptMessage
4284
+ }) {
4285
+ const { credentialName } = await inquirer__default.default.prompt([
4286
+ {
4287
+ type: "input",
4288
+ name: "credentialName",
4289
+ message: promptMessage,
4290
+ default: defaultCredentialsName(email),
4291
+ validate: (input) => {
4292
+ try {
4293
+ validateCredentialsName(input);
4294
+ return true;
4295
+ } catch (err) {
4296
+ return err instanceof Error ? err.message : String(err);
4297
+ }
4298
+ }
4299
+ }
4300
+ ]);
4301
+ return validateCredentialsName(credentialName);
4302
+ }
4303
+ function resolveDefaultCredentialsName({
4304
+ email
4305
+ }) {
4306
+ return validateCredentialsName(defaultCredentialsName(email));
4307
+ }
3907
4308
  function toPkceCredentials(credentials2) {
3908
4309
  if (credentials2 && zapierSdk.isCredentialsObject(credentials2) && !("clientSecret" in credentials2)) {
3909
4310
  return {
@@ -3915,104 +4316,125 @@ function toPkceCredentials(credentials2) {
3915
4316
  }
3916
4317
  return void 0;
3917
4318
  }
3918
- async function confirmRevokeAndRelogin(activeCredentials, nonInteractive) {
3919
- if (nonInteractive) {
4319
+ function parseTimeoutSeconds(timeout) {
4320
+ if (timeout === void 0) return 300;
4321
+ const timeoutSeconds = Number(timeout);
4322
+ if (!Number.isInteger(timeoutSeconds) || timeoutSeconds <= 0) {
3920
4323
  throw new ZapierCliValidationError(
3921
- `Already logged in as "${activeCredentials.name}". Run \`logout\` first or use an interactive terminal to re-authenticate.`
4324
+ "Timeout must be a positive integer (seconds)."
3922
4325
  );
3923
4326
  }
4327
+ return timeoutSeconds;
4328
+ }
4329
+ async function promptConfirm2({
4330
+ message,
4331
+ defaultValue
4332
+ }) {
3924
4333
  const { confirmed } = await inquirer__default.default.prompt([
3925
- {
3926
- type: "confirm",
3927
- name: "confirmed",
4334
+ { type: "confirm", name: "confirmed", message, default: defaultValue }
4335
+ ]);
4336
+ return confirmed;
4337
+ }
4338
+ function promptlessCredentialResetError(credentials2) {
4339
+ throw new ZapierCliValidationError(
4340
+ `Already logged in as "${credentials2.name}". Run \`logout\` first or use an interactive terminal to re-authenticate.`
4341
+ );
4342
+ }
4343
+ function promptlessLegacyJwtUpgradeError() {
4344
+ throw new ZapierCliValidationError(
4345
+ "Legacy JWT login detected. Run `logout` first or use an interactive terminal to migrate to client credentials."
4346
+ );
4347
+ }
4348
+ async function clearExistingAuthState({
4349
+ sdk,
4350
+ baseUrl: baseUrl2,
4351
+ interactive,
4352
+ entryPoint
4353
+ }) {
4354
+ const activeCredentials = getActiveCredentials({ baseUrl: baseUrl2 });
4355
+ const flowLabel = getEntryPointLabel(entryPoint);
4356
+ if (activeCredentials) {
4357
+ const confirmed = interactive ? await promptConfirm2({
4358
+ defaultValue: false,
3928
4359
  message: `You are already logged in as "${activeCredentials.name}".
3929
4360
  Logging out will delete these credentials and may interrupt other Zapier SDK or CLI sessions using them.
3930
- Log out and log in again?`,
3931
- default: false
4361
+ Log out and ${getActiveCredentialsAction(entryPoint)}?`
4362
+ }) : promptlessCredentialResetError(activeCredentials);
4363
+ if (!confirmed) {
4364
+ console.log(`${flowLabel} cancelled.`);
4365
+ return false;
3932
4366
  }
3933
- ]);
3934
- if (!confirmed) {
3935
- console.log("Login cancelled.");
3936
- return false;
3937
- }
3938
- return true;
3939
- }
3940
- async function confirmJwtMigration(nonInteractive) {
3941
- if (nonInteractive) {
3942
- throw new ZapierCliValidationError(
3943
- "Legacy JWT login detected. Run `logout` first or use an interactive terminal to migrate to client credentials."
3944
- );
3945
- }
3946
- const { confirmed } = await inquirer__default.default.prompt([
3947
- {
3948
- type: "confirm",
3949
- name: "confirmed",
3950
- 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?",
3951
- default: true
4367
+ try {
4368
+ await revokeCredentials({
4369
+ api: sdk.context.api,
4370
+ credentials: activeCredentials
4371
+ });
4372
+ } catch {
4373
+ if (!interactive) {
4374
+ throw new ZapierCliValidationError(
4375
+ `${flowLabel} cleanup failed and cannot be reset without confirmation. Re-run with an interactive terminal.`
4376
+ );
4377
+ }
4378
+ const reset = await promptConfirm2({
4379
+ defaultValue: false,
4380
+ message: `${flowLabel} cleanup failed. Reset local session state and continue?`
4381
+ });
4382
+ if (!reset) {
4383
+ console.log(`${flowLabel} cancelled.`);
4384
+ return false;
4385
+ }
4386
+ await deleteStoredClientCredentials({
4387
+ name: activeCredentials.name,
4388
+ baseUrl: activeCredentials.baseUrl
4389
+ });
3952
4390
  }
3953
- ]);
3954
- if (!confirmed) {
3955
- console.log("Login cancelled.");
3956
- return false;
3957
- }
3958
- return true;
3959
- }
3960
- async function confirmLocalLoginReset(nonInteractive) {
3961
- if (nonInteractive) {
3962
- throw new ZapierCliValidationError(
3963
- "Login cleanup failed and cannot be reset without confirmation. Re-run with an interactive terminal."
3964
- );
3965
- }
3966
- const { confirmed } = await inquirer__default.default.prompt([
3967
- {
3968
- type: "confirm",
3969
- name: "confirmed",
3970
- message: "Login cleanup failed. Reset local session state and continue?",
3971
- default: false
4391
+ } else if (hasLegacyJwtConfig()) {
4392
+ const confirmed = interactive ? await promptConfirm2({
4393
+ defaultValue: true,
4394
+ message: LEGACY_JWT_UPGRADE_PROMPT
4395
+ }) : promptlessLegacyJwtUpgradeError();
4396
+ if (!confirmed) {
4397
+ console.log(`${flowLabel} cancelled.`);
4398
+ return false;
3972
4399
  }
3973
- ]);
3974
- if (!confirmed) {
3975
- console.log("Login cancelled.");
3976
- return false;
3977
4400
  }
3978
4401
  return true;
3979
4402
  }
3980
- function parseTimeoutSeconds(timeout) {
3981
- const timeoutSeconds = timeout ? parseInt(timeout, 10) : 300;
3982
- if (isNaN(timeoutSeconds) || timeoutSeconds <= 0) {
3983
- throw new Error("Timeout must be a positive number");
3984
- }
3985
- return timeoutSeconds;
4403
+ async function getProfile(api2) {
4404
+ return api2.get("/zapier/api/v4/profile/", {
4405
+ authRequired: true
4406
+ });
3986
4407
  }
3987
- async function promptCredentialsName(email, nonInteractive) {
3988
- const fallback = `${email}@${os.hostname()}`;
3989
- if (nonInteractive) {
3990
- return fallback;
4408
+ async function saveClientCredentials({
4409
+ api: api2,
4410
+ name,
4411
+ credentialsBaseUrl: credentialsBaseUrl2,
4412
+ useApprovals,
4413
+ cleanupLogPrefix
4414
+ }) {
4415
+ await setupClientCredentials({
4416
+ api: api2,
4417
+ name,
4418
+ credentialsBaseUrl: credentialsBaseUrl2,
4419
+ ...useApprovals && { policy: EMPTY_POLICY }
4420
+ });
4421
+ try {
4422
+ await clearLegacyJwtState();
4423
+ } catch (err) {
4424
+ console.error(
4425
+ `[${cleanupLogPrefix}] Best-effort legacy JWT cleanup failed:`,
4426
+ err
4427
+ );
3991
4428
  }
3992
- const { credentialName } = await inquirer__default.default.prompt([
3993
- {
3994
- type: "input",
3995
- name: "credentialName",
3996
- message: "Enter a name to identify them:",
3997
- default: fallback,
3998
- validate: (input) => {
3999
- if (!input.trim()) return "Name cannot be empty";
4000
- return true;
4001
- }
4002
- }
4003
- ]);
4004
- return credentialName;
4005
4429
  }
4006
- function emitLoginSuccess({
4430
+ function emitAccountAuthSuccess({
4007
4431
  sdk,
4008
4432
  profile
4009
4433
  }) {
4010
4434
  sdk.context.eventEmission.emit(
4011
4435
  "platform.sdk.ApplicationLifecycleEvent",
4012
4436
  zapierSdk.buildApplicationLifecycleEvent(
4013
- {
4014
- lifecycle_event_type: "login_success"
4015
- },
4437
+ { lifecycle_event_type: "login_success" },
4016
4438
  {
4017
4439
  customuser_id: profile.user_id,
4018
4440
  account_id: profile.roles[0]?.account_id ?? null
@@ -4020,18 +4442,128 @@ function emitLoginSuccess({
4020
4442
  )
4021
4443
  );
4022
4444
  }
4023
- async function getProfile(api2) {
4024
- return api2.get("/zapier/api/v4/profile/", {
4025
- authRequired: true
4026
- });
4445
+ function emitSignupSuccess({
4446
+ sdk
4447
+ }) {
4448
+ sdk.context.eventEmission.emit(
4449
+ "platform.sdk.ApplicationLifecycleEvent",
4450
+ zapierSdk.buildApplicationLifecycleEvent({ lifecycle_event_type: "signup_success" })
4451
+ );
4027
4452
  }
4028
- async function bestEffortClearLegacyJwtState() {
4453
+ async function runOauthWithRedaction(runOauth) {
4029
4454
  try {
4030
- await clearLegacyJwtState();
4031
- } catch (err) {
4032
- console.error("[login] Best-effort legacy JWT cleanup failed:", err);
4455
+ return await runOauth();
4456
+ } catch (error) {
4457
+ if (error instanceof ZapierCliUserCancellationError) throw error;
4458
+ throw toRedactedOauthError(error);
4459
+ }
4460
+ }
4461
+ async function runOauthForEntryPoint({
4462
+ sdk,
4463
+ entryPoint,
4464
+ timeoutMs,
4465
+ pkceCredentials,
4466
+ baseUrl: baseUrl2,
4467
+ headless,
4468
+ interactive
4469
+ }) {
4470
+ if (entryPoint === "signup") {
4471
+ return runOauthWithRedaction(
4472
+ () => runSignupOauthFlow({
4473
+ timeoutMs,
4474
+ pkceCredentials,
4475
+ baseUrl: baseUrl2,
4476
+ headless,
4477
+ interactive,
4478
+ recoveryMessage: headless ? HEADLESS_SIGNUP_RECOVERY_MESSAGE : SIGNUP_RECOVERY_MESSAGE,
4479
+ onProgress: (event) => {
4480
+ if (event.type === "callback_accepted") {
4481
+ emitSignupSuccess({ sdk });
4482
+ }
4483
+ }
4484
+ })
4485
+ );
4486
+ }
4487
+ return runOauthWithRedaction(
4488
+ () => runLoginOauthFlow({ timeoutMs, pkceCredentials, baseUrl: baseUrl2 })
4489
+ );
4490
+ }
4491
+ async function runAccountAuth({
4492
+ sdk,
4493
+ options,
4494
+ entryPoint
4495
+ }) {
4496
+ const timeoutSeconds = parseTimeoutSeconds(options.timeout);
4497
+ const interactive = !resolveNonInteractive(options);
4498
+ const resolvedCredentials = await sdk.context.resolveCredentials();
4499
+ const pkceCredentials = toPkceCredentials(resolvedCredentials);
4500
+ const credentialsBaseUrl2 = await resolveCredentialsBaseUrl({
4501
+ ...sdk.context,
4502
+ resolvedCredentials
4503
+ });
4504
+ if (!await clearExistingAuthState({
4505
+ sdk,
4506
+ baseUrl: credentialsBaseUrl2,
4507
+ interactive,
4508
+ entryPoint
4509
+ })) {
4510
+ return;
4511
+ }
4512
+ const { accessToken } = await runOauthForEntryPoint({
4513
+ sdk,
4514
+ entryPoint,
4515
+ timeoutMs: timeoutSeconds * 1e3,
4516
+ pkceCredentials,
4517
+ baseUrl: credentialsBaseUrl2,
4518
+ headless: options.headless === true,
4519
+ interactive
4520
+ });
4521
+ const scopedApi = zapierSdk.getOrCreateApiClient({
4522
+ credentials: accessToken,
4523
+ baseUrl: credentialsBaseUrl2
4524
+ });
4525
+ const profile = await getProfile(scopedApi);
4526
+ console.log(getProfileMessage(entryPoint, profile.email));
4527
+ console.log(
4528
+ "\nGenerating credentials so this machine can make authenticated requests on your behalf."
4529
+ );
4530
+ const resolveCredentialsName = interactive ? ({ email }) => promptCredentialsName({
4531
+ email,
4532
+ promptMessage: getCredentialsPromptMessage(entryPoint)
4533
+ }) : resolveDefaultCredentialsName;
4534
+ const credentialName = await resolveCredentialsName({ email: profile.email });
4535
+ const useApprovals = options.useApprovals === true;
4536
+ await saveClientCredentials({
4537
+ api: scopedApi,
4538
+ name: credentialName,
4539
+ credentialsBaseUrl: credentialsBaseUrl2,
4540
+ useApprovals,
4541
+ cleanupLogPrefix: entryPoint
4542
+ });
4543
+ console.log(
4544
+ `\u2705 Credentials "${credentialName}" created and set as default. You are ready to use the Zapier SDK.`
4545
+ );
4546
+ if (useApprovals) {
4547
+ console.log("\u{1F510} Approvals are enabled for these credentials.");
4033
4548
  }
4549
+ emitAccountAuthSuccess({ sdk, profile });
4034
4550
  }
4551
+ var LoginSchema = zod.z.object({
4552
+ timeout: zod.z.string().optional().describe("Login timeout in seconds (default: 300)"),
4553
+ useApprovals: zod.z.boolean().optional().describe(
4554
+ "Require approvals for actions performed with these credentials"
4555
+ ),
4556
+ nonInteractive: zod.z.boolean().optional().describe(
4557
+ "Skip interactive prompts. Uses defaults where possible; errors instead of prompting when input is required. Useful in CI, piped output, or environments where TTY detection is unreliable."
4558
+ ),
4559
+ /** @deprecated Use `nonInteractive` instead. */
4560
+ skipPrompts: zod.z.boolean().optional().meta({
4561
+ deprecated: true,
4562
+ deprecationMessage: "Use --non-interactive instead."
4563
+ })
4564
+ }).describe("Log in to Zapier to access your account");
4565
+
4566
+ // src/plugins/login/index.ts
4035
4567
  var loginPlugin = zapierSdk.definePlugin(
4036
4568
  (sdk) => zapierSdk.createPluginMethod(sdk, {
4037
4569
  name: "login",
@@ -4039,68 +4571,37 @@ var loginPlugin = zapierSdk.definePlugin(
4039
4571
  inputSchema: LoginSchema,
4040
4572
  supportsJsonOutput: false,
4041
4573
  handler: async ({ sdk: sdk2, options }) => {
4042
- const timeoutSeconds = parseTimeoutSeconds(options.timeout);
4043
- const nonInteractive = resolveNonInteractive(options);
4044
- const resolvedCredentials = await sdk2.context.resolveCredentials();
4045
- const pkceCredentials = toPkceCredentials(resolvedCredentials);
4046
- const credentialsBaseUrl2 = await resolveCredentialsBaseUrl({
4047
- ...sdk2.context,
4048
- resolvedCredentials
4049
- });
4050
- const activeCredentials = getActiveCredentials({
4051
- baseUrl: credentialsBaseUrl2
4052
- });
4053
- if (activeCredentials) {
4054
- if (!await confirmRevokeAndRelogin(activeCredentials, nonInteractive))
4055
- return;
4056
- try {
4057
- await revokeCredentials({
4058
- api: sdk2.context.api,
4059
- credentials: activeCredentials
4060
- });
4061
- } catch {
4062
- if (!await confirmLocalLoginReset(nonInteractive)) return;
4063
- await deleteStoredClientCredentials({
4064
- name: activeCredentials.name,
4065
- baseUrl: activeCredentials.baseUrl
4066
- });
4067
- }
4068
- } else if (hasLegacyJwtConfig()) {
4069
- if (!await confirmJwtMigration(nonInteractive)) return;
4070
- }
4071
- const { accessToken } = await runOauthFlow({
4072
- timeoutMs: timeoutSeconds * 1e3,
4073
- pkceCredentials,
4074
- baseUrl: credentialsBaseUrl2
4075
- });
4076
- const scopedApi = zapierSdk.getOrCreateApiClient({
4077
- credentials: accessToken,
4078
- baseUrl: credentialsBaseUrl2
4079
- });
4080
- const profile = await getProfile(scopedApi);
4081
- console.log(`\u{1F464} Logged in as ${profile.email}`);
4082
- console.log(
4083
- "\nGenerating credentials so this machine can make authenticated requests on your behalf."
4084
- );
4085
- const credentialName = await promptCredentialsName(
4086
- profile.email,
4087
- nonInteractive
4088
- );
4089
- const useApprovals = options.useApprovals === true;
4090
- await setupClientCredentials({
4091
- api: scopedApi,
4092
- name: credentialName,
4093
- credentialsBaseUrl: credentialsBaseUrl2,
4094
- ...useApprovals && { policy: EMPTY_POLICY }
4095
- });
4096
- await bestEffortClearLegacyJwtState();
4097
- console.log(
4098
- `\u2705 Credentials "${credentialName}" created and set as default. You are ready to use the Zapier SDK.`
4099
- );
4100
- if (useApprovals) {
4101
- console.log("\u{1F510} Approvals are enabled for these credentials.");
4102
- }
4103
- emitLoginSuccess({ sdk: sdk2, profile });
4574
+ await runAccountAuth({ sdk: sdk2, options, entryPoint: "login" });
4575
+ }
4576
+ })
4577
+ );
4578
+ var SignupSchema = zod.z.object({
4579
+ timeout: zod.z.string().optional().describe("Signup timeout in seconds (default: 300)"),
4580
+ useApprovals: zod.z.boolean().optional().describe(
4581
+ "Require approvals for actions performed with these credentials"
4582
+ ),
4583
+ nonInteractive: zod.z.boolean().optional().describe(
4584
+ "Skip interactive prompts. Uses defaults where possible; errors instead of prompting when input is required. Useful in CI, piped output, or environments where TTY detection is unreliable."
4585
+ ),
4586
+ /** @deprecated Use `nonInteractive` instead. */
4587
+ skipPrompts: zod.z.boolean().optional().meta({
4588
+ deprecated: true,
4589
+ deprecationMessage: "Use --non-interactive instead."
4590
+ }),
4591
+ headless: zod.z.boolean().optional().describe(
4592
+ "Use when signing up from a machine that has no browser. Prints a signup link to open elsewhere, then accepts the pasted loopback callback URL."
4593
+ )
4594
+ }).describe("Set up Zapier account access and SDK credentials");
4595
+
4596
+ // src/plugins/signup/index.ts
4597
+ var signupPlugin = zapierSdk.definePlugin(
4598
+ (sdk) => zapierSdk.createPluginMethod(sdk, {
4599
+ name: "signup",
4600
+ categories: ["account"],
4601
+ inputSchema: SignupSchema,
4602
+ supportsJsonOutput: false,
4603
+ handler: async ({ sdk: sdk2, options }) => {
4604
+ await runAccountAuth({ sdk: sdk2, options, entryPoint: "signup" });
4104
4605
  }
4105
4606
  })
4106
4607
  );
@@ -6649,7 +7150,7 @@ var watchTriggerInboxCliPlugin = zapierSdk.definePlugin(
6649
7150
  // package.json with { type: 'json' }
6650
7151
  var package_default2 = {
6651
7152
  name: "@zapier/zapier-sdk-cli",
6652
- version: "0.52.12"};
7153
+ version: "0.53.1"};
6653
7154
 
6654
7155
  // src/sdk.ts
6655
7156
  zapierSdk.injectCliLogin(login_exports);
@@ -6662,7 +7163,7 @@ function createZapierCliSdk(options = {}) {
6662
7163
  ...sdkOptions,
6663
7164
  eventEmission: { ...sdkOptions.eventEmission, callContext: "cli" },
6664
7165
  callerPackage: { name: package_default2.name, version: package_default2.version }
6665
- }).use(extensionsContextPlugin).use(generateAppTypesPlugin).use(buildManifestPlugin).use(bundleCodePlugin).use(getLoginConfigPathPlugin).use(addAppsPlugin).use(feedbackPlugin).use(curlPlugin).use(initPlugin).use(mcpPlugin).use(loginPlugin).use(logoutPlugin).use(cliOverridesPlugin, { override: true }).toSdk();
7166
+ }).use(extensionsContextPlugin).use(generateAppTypesPlugin).use(buildManifestPlugin).use(bundleCodePlugin).use(getLoginConfigPathPlugin).use(addAppsPlugin).use(feedbackPlugin).use(curlPlugin).use(initPlugin).use(mcpPlugin).use(loginPlugin).use(signupPlugin).use(logoutPlugin).use(cliOverridesPlugin, { override: true }).toSdk();
6666
7167
  for (const ext of extensions) {
6667
7168
  try {
6668
7169
  zapierSdk.addPlugin(sdk, ext);
@@ -6687,7 +7188,7 @@ function createZapierCliSdk2(options = {}) {
6687
7188
  ...sdkOptions,
6688
7189
  eventEmission: { ...sdkOptions.eventEmission, callContext: "cli" },
6689
7190
  callerPackage: { name: package_default2.name, version: package_default2.version }
6690
- }).use(extensionsContextPlugin).use(experimentalContextPlugin).use(generateAppTypesPlugin).use(buildManifestPlugin).use(bundleCodePlugin).use(getLoginConfigPathPlugin).use(addAppsPlugin).use(feedbackPlugin).use(curlPlugin).use(initPlugin).use(drainTriggerInboxCliPlugin, { override: true }).use(watchTriggerInboxCliPlugin, { override: true }).use(mcpPlugin).use(loginPlugin).use(logoutPlugin).use(cliOverridesPlugin, { override: true }).toSdk();
7191
+ }).use(extensionsContextPlugin).use(experimentalContextPlugin).use(generateAppTypesPlugin).use(buildManifestPlugin).use(bundleCodePlugin).use(getLoginConfigPathPlugin).use(addAppsPlugin).use(feedbackPlugin).use(curlPlugin).use(initPlugin).use(drainTriggerInboxCliPlugin, { override: true }).use(watchTriggerInboxCliPlugin, { override: true }).use(mcpPlugin).use(loginPlugin).use(signupPlugin).use(logoutPlugin).use(cliOverridesPlugin, { override: true }).toSdk();
6691
7192
  for (const ext of extensions) {
6692
7193
  try {
6693
7194
  experimental.addPlugin(sdk, ext);