@zapier/zapier-sdk-cli 0.60.0 → 0.61.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.
package/dist/cli.cjs CHANGED
@@ -102,12 +102,18 @@ var ZapierCliExitError = class extends ZapierCliError {
102
102
  this.exitCode = exitCode;
103
103
  }
104
104
  };
105
- var ZapierCliValidationError = class extends ZapierCliError {
106
- constructor(message) {
105
+ var ZapierCliValidationError = class _ZapierCliValidationError extends ZapierCliError {
106
+ constructor(message, options = {}) {
107
107
  super(message);
108
- this.name = "ZapierCliValidationError";
109
- this.code = "ZAPIER_CLI_VALIDATION_ERROR";
110
108
  this.exitCode = 1;
109
+ this.name = options.name ?? "ZapierCliValidationError";
110
+ this.code = options.code ?? "ZAPIER_CLI_VALIDATION_ERROR";
111
+ }
112
+ withMessage(message) {
113
+ return new _ZapierCliValidationError(message, {
114
+ name: this.name,
115
+ code: this.code
116
+ });
111
117
  }
112
118
  };
113
119
  var ZapierCliMissingParametersError = class extends ZapierCliError {
@@ -1548,6 +1554,148 @@ Optional fields${pathContext}:`));
1548
1554
  return constants;
1549
1555
  }
1550
1556
  };
1557
+ function offers(question, action) {
1558
+ return question.actions.some((a) => a.action === action);
1559
+ }
1560
+ function isActionRow(value) {
1561
+ return typeof value === "object" && value !== null && "action" in value;
1562
+ }
1563
+ async function promptText({
1564
+ message,
1565
+ password
1566
+ }) {
1567
+ const { value } = await inquirer__default.default.prompt([
1568
+ { type: password ? "password" : "input", name: "value", message }
1569
+ ]);
1570
+ return value;
1571
+ }
1572
+ async function answerSelect(question, field) {
1573
+ const display = (c) => c.hint ? `${c.label} ${chalk__default.default.dim(`(${c.hint})`)}` : c.label;
1574
+ if (question.multiple) {
1575
+ const choices = [
1576
+ ...question.choices.map((c) => ({ name: display(c), value: c.value })),
1577
+ ...offers(question, "more") ? [
1578
+ {
1579
+ name: chalk__default.default.dim("Load more\u2026"),
1580
+ value: { action: "more" }
1581
+ }
1582
+ ] : [],
1583
+ ...(question.notes ?? []).map((note) => ({
1584
+ name: chalk__default.default.dim(note),
1585
+ value: note,
1586
+ disabled: true
1587
+ }))
1588
+ ];
1589
+ const { values } = await inquirer__default.default.prompt([
1590
+ { type: "checkbox", name: "values", message: question.message, choices }
1591
+ ]);
1592
+ const selected = values;
1593
+ if (selected.some(isActionRow)) return { type: "more" };
1594
+ if (selected.length === 0 && offers(question, "skip")) {
1595
+ return { type: "skip" };
1596
+ }
1597
+ return { type: "choose", value: selected };
1598
+ }
1599
+ const row = (name, action) => ({
1600
+ name,
1601
+ value: { action }
1602
+ });
1603
+ const value = await search__default.default({
1604
+ message: question.message,
1605
+ source: (term) => {
1606
+ const t = (term ?? "").trim().toLowerCase();
1607
+ const matched = t ? question.choices.filter(
1608
+ (c) => `${c.label} ${c.hint ?? ""}`.toLowerCase().includes(t)
1609
+ ) : question.choices;
1610
+ const rows = matched.map((c) => ({
1611
+ name: display(c),
1612
+ value: c.value
1613
+ }));
1614
+ if (offers(question, "search"))
1615
+ rows.push(row(chalk__default.default.cyan("Search\u2026"), "search"));
1616
+ if (offers(question, "more"))
1617
+ rows.push(row(chalk__default.default.dim("Load more\u2026"), "more"));
1618
+ if (offers(question, "custom"))
1619
+ rows.push(row(chalk__default.default.dim("Enter a value manually\u2026"), "custom"));
1620
+ if (offers(question, "retry"))
1621
+ rows.push(row(chalk__default.default.yellow("Retry"), "retry"));
1622
+ if (offers(question, "skip"))
1623
+ rows.push(row(chalk__default.default.dim("Skip (optional)"), "skip"));
1624
+ if (offers(question, "cancel"))
1625
+ rows.push(row(chalk__default.default.dim("Cancel"), "cancel"));
1626
+ for (const note of question.notes ?? [])
1627
+ rows.push({ name: chalk__default.default.dim(note), value: note, disabled: true });
1628
+ return rows;
1629
+ }
1630
+ });
1631
+ if (!isActionRow(value)) {
1632
+ return { type: "choose", value };
1633
+ }
1634
+ switch (value.action) {
1635
+ case "search": {
1636
+ const term = await promptText({
1637
+ message: `Search ${field}:`,
1638
+ password: false
1639
+ });
1640
+ return { type: "search", term };
1641
+ }
1642
+ case "custom": {
1643
+ const custom = await promptText({
1644
+ message: `Enter ${field}:`,
1645
+ password: false
1646
+ });
1647
+ return { type: "custom", value: custom };
1648
+ }
1649
+ case "more":
1650
+ return { type: "more" };
1651
+ case "retry":
1652
+ return { type: "retry" };
1653
+ case "skip":
1654
+ return { type: "skip" };
1655
+ case "cancel":
1656
+ return { type: "cancel" };
1657
+ }
1658
+ }
1659
+ async function answerInput(question) {
1660
+ const value = await promptText({
1661
+ message: question.message,
1662
+ password: question.inputType === "password"
1663
+ });
1664
+ if (value === "" && offers(question, "skip")) {
1665
+ return { type: "skip" };
1666
+ }
1667
+ return { type: "custom", value };
1668
+ }
1669
+ async function answerCollection(question) {
1670
+ if (!offers(question, "done")) {
1671
+ return { type: "add" };
1672
+ }
1673
+ const { again } = await inquirer__default.default.prompt([
1674
+ {
1675
+ type: "confirm",
1676
+ name: "again",
1677
+ message: question.message,
1678
+ default: false
1679
+ }
1680
+ ]);
1681
+ return again ? { type: "add" } : { type: "done" };
1682
+ }
1683
+ var answerViaCli = ({ state, result }) => {
1684
+ if (result.error !== void 0) {
1685
+ const message = typeof result.error === "string" ? result.error : result.error.message;
1686
+ console.log(chalk__default.default.yellow(`! ${message}`));
1687
+ }
1688
+ const field = state.current?.join(".") ?? "value";
1689
+ const question = result.question;
1690
+ switch (question.type) {
1691
+ case "select":
1692
+ return answerSelect(question, field);
1693
+ case "input":
1694
+ return answerInput(question);
1695
+ case "collection":
1696
+ return answerCollection(question);
1697
+ }
1698
+ };
1551
1699
 
1552
1700
  // src/utils/cli-options.ts
1553
1701
  var RESERVED_CLI_OPTIONS = [
@@ -1579,7 +1727,7 @@ var SHARED_COMMAND_CLI_OPTIONS = [
1579
1727
 
1580
1728
  // package.json
1581
1729
  var package_default = {
1582
- version: "0.60.0"};
1730
+ version: "0.61.1"};
1583
1731
 
1584
1732
  // src/telemetry/builders.ts
1585
1733
  function createCliBaseEvent(context = {}) {
@@ -1654,7 +1802,7 @@ async function formatItemsFromSchema(_functionInfo, items, startingNumber = 0, o
1654
1802
  if (options?.formatter) {
1655
1803
  const formatter = options.formatter;
1656
1804
  const input = options.input ?? {};
1657
- const context = formatter.fetchContext ? await formatter.fetchContext({ items, input }) : void 0;
1805
+ const context = formatter.getContext ? await formatter.getContext({ items, input }) : void 0;
1658
1806
  items.forEach((item, index) => {
1659
1807
  const formatted = formatter.format({ item, input, context });
1660
1808
  formatSingleItem(formatted, startingNumber + index);
@@ -2249,7 +2397,7 @@ function analyzeZodField(name, schema, functionInfo) {
2249
2397
  paramType = "object";
2250
2398
  }
2251
2399
  let paramHasResolver = false;
2252
- if (functionInfo?.resolvers?.[name]) {
2400
+ if (functionInfo?.resolvers?.[name] || functionInfo?.boundResolvers?.[name]) {
2253
2401
  paramHasResolver = true;
2254
2402
  }
2255
2403
  return {
@@ -2408,6 +2556,9 @@ function createCommandConfig(cliCommandName, functionInfo, sdk) {
2408
2556
  const usesInputParameters = !functionInfo.inputSchema && !!functionInfo.inputParameters;
2409
2557
  const schema = functionInfo.inputSchema;
2410
2558
  const parameters = usesInputParameters ? analyzeInputParameters(functionInfo.inputParameters, functionInfo) : analyzeZodSchema(schema, functionInfo);
2559
+ if (functionInfo.boundResolvers && Object.keys(functionInfo.boundResolvers).length > 0) {
2560
+ for (const param of parameters) param.hasResolver = true;
2561
+ }
2411
2562
  const schemaAliases = getSchemaAliases(schema);
2412
2563
  if (schemaAliases) {
2413
2564
  const aliasedNames = new Set(Object.keys(schemaAliases));
@@ -2460,7 +2611,35 @@ function createCommandConfig(cliCommandName, functionInfo, sdk) {
2460
2611
  }
2461
2612
  }
2462
2613
  }
2463
- if (schema && !usesInputParameters) {
2614
+ const boundResolvers = functionInfo.boundResolvers;
2615
+ if (boundResolvers && Object.keys(boundResolvers).length > 0) {
2616
+ const seedInput = Object.fromEntries(
2617
+ Object.entries(rawParams).filter(([, v]) => v !== void 0)
2618
+ );
2619
+ const controller = zapierSdk.createController(sdk);
2620
+ let resolved;
2621
+ try {
2622
+ resolved = await controller.resolve({
2623
+ method: functionInfo.name,
2624
+ input: seedInput,
2625
+ answer: interactiveMode ? answerViaCli : ({ state }) => {
2626
+ throw new ZapierCliMissingParametersError([
2627
+ {
2628
+ name: state.current?.join(".") ?? "value",
2629
+ isPositional: false
2630
+ }
2631
+ ]);
2632
+ },
2633
+ interactive: interactiveMode
2634
+ });
2635
+ } catch (err) {
2636
+ if (err instanceof zapierSdk.CoreCancelledSignal) {
2637
+ throw new ZapierCliUserCancellationError();
2638
+ }
2639
+ throw err;
2640
+ }
2641
+ Object.assign(resolvedParams, resolved);
2642
+ } else if (schema && !usesInputParameters) {
2464
2643
  const resolver = new SchemaParameterResolver();
2465
2644
  const resolved = await resolver.resolveParameters(
2466
2645
  schema,
@@ -2553,7 +2732,7 @@ ${confirmMessageAfter}`));
2553
2732
  }
2554
2733
  }
2555
2734
  } catch (error) {
2556
- success = false;
2735
+ success = error instanceof ZapierCliError ? error.exitCode === 0 : false;
2557
2736
  errorMessage = error instanceof Error ? error.message : String(error);
2558
2737
  if (error instanceof ZapierCliMissingParametersError) {
2559
2738
  renderer.renderError(error);
@@ -3765,41 +3944,165 @@ var spinPromise = async (promise, text) => {
3765
3944
  }
3766
3945
  };
3767
3946
 
3947
+ // src/utils/auth/oauth-errors.ts
3948
+ var OauthFlowTimeoutError = class _OauthFlowTimeoutError extends ZapierCliValidationError {
3949
+ constructor({
3950
+ timeoutMs,
3951
+ message = "OAuth flow timed out"
3952
+ }) {
3953
+ super(message, {
3954
+ name: "OauthFlowTimeoutError",
3955
+ code: "ZAPIER_OAUTH_FLOW_TIMEOUT"
3956
+ });
3957
+ this.timeoutMs = timeoutMs;
3958
+ }
3959
+ withMessage(message) {
3960
+ return new _OauthFlowTimeoutError({ timeoutMs: this.timeoutMs, message });
3961
+ }
3962
+ };
3963
+ var OauthAuthorizationDeniedError = class _OauthAuthorizationDeniedError extends ZapierCliValidationError {
3964
+ constructor({
3965
+ reason,
3966
+ message = "OAuth authorization denied"
3967
+ }) {
3968
+ super(message, {
3969
+ name: "OauthAuthorizationDeniedError",
3970
+ code: "ZAPIER_OAUTH_AUTHORIZATION_DENIED"
3971
+ });
3972
+ this.reason = reason;
3973
+ }
3974
+ withMessage(message) {
3975
+ return new _OauthAuthorizationDeniedError({
3976
+ reason: this.reason,
3977
+ message
3978
+ });
3979
+ }
3980
+ };
3981
+ var OauthFlowError = class _OauthFlowError extends ZapierCliValidationError {
3982
+ constructor({ message }) {
3983
+ super(message, {
3984
+ name: "OauthFlowError",
3985
+ code: "ZAPIER_OAUTH_FLOW"
3986
+ });
3987
+ }
3988
+ withMessage(message) {
3989
+ return new _OauthFlowError({ message });
3990
+ }
3991
+ };
3992
+ var OauthCallbackError = class _OauthCallbackError extends ZapierCliValidationError {
3993
+ constructor({ kind, message }) {
3994
+ super(message, {
3995
+ name: "OauthCallbackError",
3996
+ code: "ZAPIER_OAUTH_CALLBACK"
3997
+ });
3998
+ this.kind = kind;
3999
+ }
4000
+ withMessage(message) {
4001
+ return new _OauthCallbackError({ kind: this.kind, message });
4002
+ }
4003
+ };
4004
+ var OauthTokenExchangeError = class _OauthTokenExchangeError extends ZapierCliValidationError {
4005
+ constructor({ message }) {
4006
+ super(message, {
4007
+ name: "OauthTokenExchangeError",
4008
+ code: "ZAPIER_OAUTH_TOKEN_EXCHANGE"
4009
+ });
4010
+ }
4011
+ withMessage(message) {
4012
+ return new _OauthTokenExchangeError({ message });
4013
+ }
4014
+ };
4015
+ var SENSITIVE_OAUTH_FIELDS = [
4016
+ "access_token",
4017
+ "refresh_token",
4018
+ "id_token",
4019
+ "client_secret",
4020
+ "code_verifier",
4021
+ "code_challenge"
4022
+ ];
4023
+ function getErrorMessage(error) {
4024
+ return error instanceof Error ? error.message : String(error);
4025
+ }
4026
+ function toCamelCase(field) {
4027
+ return field.replace(
4028
+ /_([a-z])/g,
4029
+ (_match, letter) => letter.toUpperCase()
4030
+ );
4031
+ }
4032
+ function escapeRegExp(value) {
4033
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4034
+ }
4035
+ var sensitiveOauthFieldPattern = Array.from(
4036
+ new Set(
4037
+ SENSITIVE_OAUTH_FIELDS.flatMap((field) => [field, toCamelCase(field)])
4038
+ )
4039
+ ).map(escapeRegExp).join("|");
4040
+ var sensitiveQueryParamPattern = new RegExp(
4041
+ `([?&])(${sensitiveOauthFieldPattern})(=)[^&#\\s"'<>]*`,
4042
+ "gi"
4043
+ );
4044
+ function redactSensitiveOauthErrorMessage(message) {
4045
+ return message.replace(
4046
+ sensitiveQueryParamPattern,
4047
+ (_match, prefix, key, separator) => `${prefix}${key}${separator}[REDACTED]`
4048
+ ).replace(
4049
+ new RegExp(`"(${sensitiveOauthFieldPattern})"(\\s*:\\s*)"[^"]*"`, "g"),
4050
+ (_match, key, separator) => `"${key}"${separator}"[REDACTED]"`
4051
+ );
4052
+ }
4053
+ function toRedactedOauthError(error) {
4054
+ const message = redactSensitiveOauthErrorMessage(getErrorMessage(error));
4055
+ if (error instanceof ZapierCliValidationError) {
4056
+ return error.withMessage(message);
4057
+ }
4058
+ return new OauthFlowError({ message });
4059
+ }
4060
+ function toOauthTokenExchangeError(error) {
4061
+ return new OauthTokenExchangeError({
4062
+ message: redactSensitiveOauthErrorMessage(getErrorMessage(error))
4063
+ });
4064
+ }
4065
+
3768
4066
  // src/utils/auth/oauth-callback.ts
3769
4067
  function getCallbackCode({
3770
4068
  callbackUrl,
3771
- transaction,
3772
- recoveryMessage
4069
+ transaction
3773
4070
  }) {
3774
4071
  let parsed;
3775
4072
  try {
3776
4073
  parsed = new URL(callbackUrl.trim());
3777
4074
  } catch {
3778
- throw new ZapierCliValidationError(
3779
- "Paste the final OAuth callback URL from your browser."
3780
- );
4075
+ throw new OauthCallbackError({
4076
+ kind: "invalid_url",
4077
+ message: "Paste the final OAuth callback URL from your browser."
4078
+ });
3781
4079
  }
3782
4080
  const expected = new URL(transaction.redirectUri);
3783
4081
  if (parsed.protocol !== "http:" || parsed.hostname !== expected.hostname || parsed.pathname !== expected.pathname || parsed.port !== expected.port) {
3784
- throw new ZapierCliValidationError(
3785
- `Expected the final OAuth callback URL to start with ${transaction.redirectUri}.`
3786
- );
4082
+ throw new OauthCallbackError({
4083
+ kind: "redirect_mismatch",
4084
+ message: `Expected the final OAuth callback URL to start with ${transaction.redirectUri}.`
4085
+ });
3787
4086
  }
3788
4087
  if (parsed.searchParams.get("state") !== transaction.state) {
3789
- throw new ZapierCliValidationError(
3790
- `OAuth state mismatch.${recoveryMessage ? ` ${recoveryMessage}` : ""}`
3791
- );
4088
+ throw new OauthCallbackError({
4089
+ kind: "state_mismatch",
4090
+ message: "OAuth state mismatch."
4091
+ });
3792
4092
  }
3793
4093
  if (parsed.searchParams.has("error")) {
3794
- throw new ZapierCliValidationError(
3795
- `Authorization denied: ${parsed.searchParams.get("error_description") ?? parsed.searchParams.get("error")}.${recoveryMessage ? ` ${recoveryMessage}` : ""}`
3796
- );
4094
+ throw new OauthAuthorizationDeniedError({
4095
+ reason: String(
4096
+ parsed.searchParams.get("error_description") ?? parsed.searchParams.get("error")
4097
+ )
4098
+ });
3797
4099
  }
3798
4100
  const code = parsed.searchParams.get("code");
3799
4101
  if (!code) {
3800
- throw new ZapierCliValidationError(
3801
- "No authorization code found in the pasted callback URL."
3802
- );
4102
+ throw new OauthCallbackError({
4103
+ kind: "missing_code",
4104
+ message: "No authorization code found in the pasted callback URL."
4105
+ });
3803
4106
  }
3804
4107
  return code;
3805
4108
  }
@@ -3914,7 +4217,9 @@ async function exchangeOauthCode({
3914
4217
  "Content-Type": "application/x-www-form-urlencoded"
3915
4218
  }
3916
4219
  }
3917
- );
4220
+ ).catch((error) => {
4221
+ throw toOauthTokenExchangeError(error);
4222
+ });
3918
4223
  return {
3919
4224
  accessToken: data.access_token,
3920
4225
  refreshToken: data.refresh_token,
@@ -3923,19 +4228,15 @@ async function exchangeOauthCode({
3923
4228
  }
3924
4229
 
3925
4230
  // src/utils/auth/oauth-flow.ts
3926
- var OauthFlowTimeoutError = class extends Error {
3927
- constructor(timeoutMs) {
3928
- super("OAuth flow timed out");
3929
- this.timeoutMs = timeoutMs;
3930
- this.name = "OauthFlowTimeoutError";
3931
- }
4231
+ var LOGIN_OAUTH_COPY = {
4232
+ flowName: "Login",
4233
+ action: "log in",
4234
+ urlLabel: "login URL"
3932
4235
  };
3933
- var OauthAuthorizationDeniedError = class extends Error {
3934
- constructor(reason) {
3935
- super("OAuth authorization denied");
3936
- this.reason = reason;
3937
- this.name = "OauthAuthorizationDeniedError";
3938
- }
4236
+ var SIGNUP_OAUTH_COPY = {
4237
+ flowName: "Signup",
4238
+ action: "sign up",
4239
+ urlLabel: "signup URL"
3939
4240
  };
3940
4241
  function findAvailablePort() {
3941
4242
  return new Promise((resolve4, reject) => {
@@ -3950,70 +4251,86 @@ function findAvailablePort() {
3950
4251
  tryPort(LOGIN_PORTS[portIndex++]);
3951
4252
  } else if (err.code === "EADDRINUSE") {
3952
4253
  reject(
3953
- new Error(
3954
- `All configured OAuth callback ports are busy: ${LOGIN_PORTS.join(", ")}. Please try again later or close applications using these ports.`
3955
- )
4254
+ new OauthFlowError({
4255
+ message: `All configured OAuth callback ports are busy: ${LOGIN_PORTS.join(", ")}. Please try again later or close applications using these ports.`
4256
+ })
3956
4257
  );
3957
4258
  } else {
3958
4259
  reject(err);
3959
4260
  }
3960
4261
  });
3961
4262
  };
3962
- if (LOGIN_PORTS.length > 0) tryPort(LOGIN_PORTS[portIndex++]);
3963
- else reject(new Error("No OAuth callback ports configured"));
4263
+ if (LOGIN_PORTS.length > 0) {
4264
+ tryPort(LOGIN_PORTS[portIndex++]);
4265
+ return;
4266
+ }
4267
+ reject(
4268
+ new OauthFlowError({ message: "No OAuth callback ports configured" })
4269
+ );
3964
4270
  });
3965
4271
  }
3966
4272
  async function runLoginOauthFlow(options) {
3967
- return runOauthFlowEntryPoint({
3968
- ...options,
4273
+ return runOauthFlowForEntryPoint({
4274
+ options,
3969
4275
  entryPoint: "login",
3970
- authAction: "log in",
3971
- flowName: "Login"
4276
+ copy: LOGIN_OAUTH_COPY
3972
4277
  });
3973
4278
  }
3974
4279
  async function runSignupOauthFlow(options) {
4280
+ return runOauthFlowForEntryPoint({
4281
+ options,
4282
+ entryPoint: "signup",
4283
+ copy: SIGNUP_OAUTH_COPY
4284
+ });
4285
+ }
4286
+ function runOauthFlowForEntryPoint({
4287
+ options,
4288
+ entryPoint,
4289
+ copy
4290
+ }) {
3975
4291
  if (options.headless) {
3976
4292
  return runOauthFlowEntryPoint({
3977
4293
  ...options,
3978
- entryPoint: "signup",
3979
- authAction: "sign up",
3980
- flowName: "Signup",
3981
- headless: true
4294
+ entryPoint,
4295
+ headless: true,
4296
+ copy
3982
4297
  });
3983
4298
  }
3984
4299
  return runOauthFlowEntryPoint({
3985
4300
  ...options,
3986
- entryPoint: "signup",
3987
- authAction: "sign up",
3988
- flowName: "Signup"
4301
+ entryPoint,
4302
+ copy,
4303
+ headless: false
3989
4304
  });
3990
4305
  }
3991
- async function runOauthFlowEntryPoint({
3992
- flowName,
3993
- ...options
3994
- }) {
4306
+ async function runOauthFlowEntryPoint(options) {
3995
4307
  try {
3996
- return options.headless ? await runHeadlessSignupOauthFlow(options) : await runOauthFlow(options);
4308
+ return options.headless ? await runHeadlessOauthFlow(options) : await runOauthFlow(options);
3997
4309
  } catch (error) {
3998
4310
  if (error instanceof OauthFlowTimeoutError) {
3999
- throw new Error(
4311
+ throw error.withMessage(
4000
4312
  withRecoveryMessage(
4001
- `${flowName} timed out after ${Math.round(error.timeoutMs / 1e3)} seconds.`,
4313
+ `${options.copy.flowName} timed out after ${Math.round(error.timeoutMs / 1e3)} seconds.`,
4002
4314
  options.recoveryMessage
4003
4315
  )
4004
4316
  );
4005
4317
  }
4006
4318
  if (error instanceof OauthAuthorizationDeniedError) {
4007
- throw new Error(
4319
+ throw error.withMessage(
4008
4320
  withRecoveryMessage(
4009
4321
  `Authorization denied: ${error.reason}.`,
4010
4322
  options.recoveryMessage
4011
4323
  )
4012
4324
  );
4013
4325
  }
4326
+ if (error instanceof OauthCallbackError && options.recoveryMessage) {
4327
+ throw error.withMessage(
4328
+ withRecoveryMessage(error.message, options.recoveryMessage)
4329
+ );
4330
+ }
4014
4331
  if (error instanceof ZapierCliUserCancellationError && !options.silent) {
4015
4332
  log_default.info(`
4016
- \u274C ${flowName} cancelled by user`);
4333
+ \u274C ${options.copy.flowName} cancelled by user`);
4017
4334
  }
4018
4335
  throw error;
4019
4336
  }
@@ -4021,12 +4338,22 @@ async function runOauthFlowEntryPoint({
4021
4338
  function withRecoveryMessage(message, recoveryMessage) {
4022
4339
  return recoveryMessage ? `${message} ${recoveryMessage}` : message;
4023
4340
  }
4341
+ function createMissingHeadlessCallbackUrlError() {
4342
+ return new OauthCallbackError({
4343
+ kind: "missing_callback_url",
4344
+ message: "Paste the final OAuth callback URL from your browser."
4345
+ });
4346
+ }
4347
+ function writeHeadlessOauthStatus(message) {
4348
+ process.stderr.write(`${message}
4349
+ `);
4350
+ }
4024
4351
  async function runOauthFlow({
4025
4352
  timeoutMs = LOGIN_TIMEOUT_MS,
4026
4353
  pkceCredentials,
4027
4354
  baseUrl: baseUrl2,
4028
4355
  entryPoint,
4029
- authAction,
4356
+ copy,
4030
4357
  silent = false,
4031
4358
  onProgress
4032
4359
  }) {
@@ -4041,7 +4368,7 @@ async function runOauthFlow({
4041
4368
  const code = await collectLocalCallbackCode({
4042
4369
  transaction,
4043
4370
  timeoutMs,
4044
- authAction,
4371
+ action: copy.action,
4045
4372
  silent,
4046
4373
  onProgress
4047
4374
  });
@@ -4055,92 +4382,104 @@ async function runOauthFlow({
4055
4382
  }
4056
4383
  async function readHeadlessCallbackUrl({
4057
4384
  timeoutMs,
4058
- interactive,
4059
- recoveryMessage
4385
+ interactive
4060
4386
  }) {
4061
- const timeoutMessage = withRecoveryMessage(
4062
- `Signup timed out after ${Math.round(timeoutMs / 1e3)} seconds.`,
4063
- recoveryMessage
4064
- );
4065
- const missingCallbackUrlMessage = withRecoveryMessage(
4066
- "Paste the final OAuth callback URL from your browser.",
4067
- recoveryMessage
4068
- );
4069
4387
  const rl = promises$1.createInterface({ input: process.stdin, output: process.stderr });
4070
4388
  const abortController = new AbortController();
4071
4389
  const timeoutTimer = setTimeout(() => abortController.abort(), timeoutMs);
4072
- const readUrl = interactive ? rl.question("Paste the final OAuth callback URL: ", {
4073
- signal: abortController.signal
4074
- }) : new Promise((resolve4, reject) => {
4390
+ const readUrl = new Promise((resolve4, reject) => {
4075
4391
  let settled = false;
4076
- const settleResolve = (value) => {
4392
+ const handleLine = (value) => settleResolve(value);
4393
+ const handleAbort = () => settleReject(new OauthFlowTimeoutError({ timeoutMs }));
4394
+ const handleClose = () => settleReject(createMissingHeadlessCallbackUrlError());
4395
+ const handleError = (error) => settleReject(error);
4396
+ const handleCancellation = () => settleReject(new ZapierCliUserCancellationError());
4397
+ const cleanupListeners = () => {
4398
+ abortController.signal.removeEventListener("abort", handleAbort);
4399
+ rl.off("line", handleLine);
4400
+ rl.off("close", handleClose);
4401
+ rl.off("error", handleError);
4402
+ rl.off("SIGINT", handleCancellation);
4403
+ process.off("SIGINT", handleCancellation);
4404
+ process.off("SIGTERM", handleCancellation);
4405
+ };
4406
+ function settleResolve(value) {
4407
+ if (settled) return;
4077
4408
  settled = true;
4409
+ cleanupListeners();
4078
4410
  resolve4(value);
4079
- };
4080
- const settleReject = (error) => {
4411
+ }
4412
+ function settleReject(error) {
4081
4413
  if (settled) return;
4082
4414
  settled = true;
4415
+ cleanupListeners();
4083
4416
  reject(error);
4084
- };
4085
- abortController.signal.addEventListener(
4086
- "abort",
4087
- () => settleReject(new Error(timeoutMessage)),
4088
- { once: true }
4089
- );
4090
- rl.once("line", settleResolve);
4091
- rl.once(
4092
- "close",
4093
- () => settleReject(new ZapierCliValidationError(missingCallbackUrlMessage))
4094
- );
4095
- rl.once("error", settleReject);
4417
+ }
4418
+ abortController.signal.addEventListener("abort", handleAbort, {
4419
+ once: true
4420
+ });
4421
+ rl.once("close", handleClose);
4422
+ rl.once("error", handleError);
4423
+ rl.once("SIGINT", handleCancellation);
4424
+ process.once("SIGINT", handleCancellation);
4425
+ process.once("SIGTERM", handleCancellation);
4426
+ if (interactive) {
4427
+ void rl.question("Paste the final OAuth callback URL: ", {
4428
+ signal: abortController.signal
4429
+ }).then(settleResolve).catch((error) => {
4430
+ if (error instanceof Error && error.name === "AbortError") {
4431
+ settleReject(new OauthFlowTimeoutError({ timeoutMs }));
4432
+ return;
4433
+ }
4434
+ settleReject(error);
4435
+ });
4436
+ return;
4437
+ }
4438
+ rl.once("line", handleLine);
4096
4439
  });
4097
4440
  try {
4098
- return await readUrl.catch((error) => {
4099
- if (error instanceof Error && error.name === "AbortError") {
4100
- throw new Error(timeoutMessage);
4101
- }
4102
- throw error;
4103
- });
4441
+ return await readUrl;
4104
4442
  } finally {
4105
4443
  clearTimeout(timeoutTimer);
4106
4444
  rl.close();
4107
4445
  }
4108
4446
  }
4109
- async function runHeadlessSignupOauthFlow({
4447
+ async function runHeadlessOauthFlow({
4110
4448
  timeoutMs = LOGIN_TIMEOUT_MS,
4111
4449
  pkceCredentials,
4112
4450
  baseUrl: baseUrl2,
4451
+ entryPoint,
4113
4452
  interactive = true,
4114
4453
  onProgress,
4115
- recoveryMessage
4454
+ copy
4116
4455
  }) {
4117
4456
  const port = LOGIN_PORTS[0];
4118
4457
  const transaction = await prepareOauthTransaction({
4119
4458
  pkceCredentials,
4120
4459
  baseUrl: baseUrl2,
4121
4460
  redirectUri: `http://${OAUTH_LOOPBACK_HOST}:${port}/oauth`,
4122
- entryPoint: "signup"
4461
+ entryPoint
4123
4462
  });
4124
- console.log(
4125
- "Use this mode when signing up from a machine that has no browser."
4463
+ writeHeadlessOauthStatus(
4464
+ `Use this mode to ${copy.action} from a machine that has no browser.`
4465
+ );
4466
+ writeHeadlessOauthStatus(
4467
+ `Open this ${copy.urlLabel} in a browser on another machine:`
4126
4468
  );
4127
- console.log("Open this signup URL in a browser on another machine:");
4128
4469
  console.log(transaction.browserAuthUrl);
4129
- console.log(
4470
+ writeHeadlessOauthStatus(
4130
4471
  `When the browser lands on ${transaction.redirectUri} and cannot connect, paste the full final URL back here.`
4131
4472
  );
4132
4473
  const callbackUrl = await readHeadlessCallbackUrl({
4133
4474
  timeoutMs,
4134
- interactive,
4135
- recoveryMessage
4475
+ interactive
4136
4476
  });
4137
4477
  const code = getCallbackCode({
4138
4478
  callbackUrl,
4139
- transaction,
4140
- recoveryMessage
4479
+ transaction
4141
4480
  });
4142
4481
  onProgress?.({ type: "callback_accepted" });
4143
- console.log("Exchanging authorization code for tokens...");
4482
+ writeHeadlessOauthStatus("Exchanging authorization code for tokens...");
4144
4483
  onProgress?.({ type: "token_exchange_started" });
4145
4484
  const tokens = await exchangeOauthCode({ ...transaction, code });
4146
4485
  onProgress?.({ type: "token_exchange_completed" });
@@ -4149,7 +4488,7 @@ async function runHeadlessSignupOauthFlow({
4149
4488
  async function collectLocalCallbackCode({
4150
4489
  transaction,
4151
4490
  timeoutMs,
4152
- authAction,
4491
+ action,
4153
4492
  silent,
4154
4493
  onProgress
4155
4494
  }) {
@@ -4157,21 +4496,26 @@ async function collectLocalCallbackCode({
4157
4496
  const app = express__default.default();
4158
4497
  app.get("/oauth", (req, res) => {
4159
4498
  res.setHeader("Connection", "close");
4160
- if (req.query.state !== transaction.state) {
4161
- res.status(400).end("Invalid state. You can close this tab.");
4162
- } else if (req.query.error) {
4163
- reject(
4164
- new OauthAuthorizationDeniedError(
4165
- String(req.query.error_description ?? req.query.error)
4166
- )
4167
- );
4168
- res.end("Authorization was denied. You can close this tab.");
4169
- } else if (!req.query.code) {
4170
- reject(new Error("No authorization code received"));
4171
- res.end("No authorization code received. You can close this tab.");
4172
- } else {
4173
- resolve4(String(req.query.code));
4499
+ try {
4500
+ const code = getCallbackCode({
4501
+ callbackUrl: new URL(
4502
+ req.originalUrl,
4503
+ transaction.redirectUri
4504
+ ).toString(),
4505
+ transaction
4506
+ });
4507
+ resolve4(code);
4174
4508
  res.end("You can now close this tab and return to the CLI.");
4509
+ } catch (error) {
4510
+ if (error instanceof OauthCallbackError && error.kind === "state_mismatch") {
4511
+ res.status(400).end("Invalid state. You can close this tab.");
4512
+ } else if (error instanceof OauthAuthorizationDeniedError) {
4513
+ reject(error);
4514
+ res.end("Authorization was denied. You can close this tab.");
4515
+ } else {
4516
+ reject(error);
4517
+ res.end("No authorization code received. You can close this tab.");
4518
+ }
4175
4519
  }
4176
4520
  });
4177
4521
  const server = app.listen(
@@ -4192,19 +4536,19 @@ async function collectLocalCallbackCode({
4192
4536
  let timeoutTimer;
4193
4537
  try {
4194
4538
  await waitForServerListening(server);
4195
- await openBrowser({ transaction, authAction, silent, onProgress });
4539
+ await openBrowser({ transaction, action, silent, onProgress });
4196
4540
  const waitForCode = Promise.race([
4197
4541
  promise,
4198
4542
  new Promise((_resolve, rejectTimeout) => {
4199
4543
  timeoutTimer = setTimeout(() => {
4200
- rejectTimeout(new OauthFlowTimeoutError(timeoutMs));
4544
+ rejectTimeout(new OauthFlowTimeoutError({ timeoutMs }));
4201
4545
  }, timeoutMs);
4202
4546
  })
4203
4547
  ]);
4204
4548
  onProgress?.({ type: "callback_waiting" });
4205
4549
  return silent ? await waitForCode : await spinPromise(
4206
4550
  waitForCode,
4207
- `Waiting for you to ${authAction} and authorize`
4551
+ `Waiting for you to ${action} and authorize`
4208
4552
  );
4209
4553
  } finally {
4210
4554
  if (timeoutTimer) clearTimeout(timeoutTimer);
@@ -4234,12 +4578,12 @@ async function waitForServerListening(server) {
4234
4578
  }
4235
4579
  async function openBrowser({
4236
4580
  transaction,
4237
- authAction,
4581
+ action,
4238
4582
  silent,
4239
4583
  onProgress
4240
4584
  }) {
4241
4585
  if (!silent) {
4242
- log_default.info(`Opening your browser to ${authAction}.`);
4586
+ log_default.info(`Opening your browser to ${action}.`);
4243
4587
  log_default.info("If it doesn't open, visit:", transaction.browserAuthUrl);
4244
4588
  }
4245
4589
  onProgress?.({ type: "browser_opening", url: transaction.browserAuthUrl });
@@ -4249,9 +4593,7 @@ async function openBrowser({
4249
4593
  } catch (err) {
4250
4594
  const reason = err instanceof Error ? err.message : String(err);
4251
4595
  if (!silent) {
4252
- log_default.info(
4253
- `Browser did not open automatically to ${authAction}: ${reason}`
4254
- );
4596
+ log_default.info(`Browser did not open automatically to ${action}: ${reason}`);
4255
4597
  log_default.info("Visit this URL manually:", transaction.browserAuthUrl);
4256
4598
  }
4257
4599
  onProgress?.({
@@ -4280,61 +4622,10 @@ async function closeServer({
4280
4622
  });
4281
4623
  }
4282
4624
 
4283
- // src/utils/auth/oauth-errors.ts
4284
- var SENSITIVE_OAUTH_FIELDS = [
4285
- "access_token",
4286
- "refresh_token",
4287
- "id_token",
4288
- "client_secret",
4289
- "code_verifier",
4290
- "code_challenge"
4291
- ];
4292
- function getErrorMessage(error) {
4293
- return error instanceof Error ? error.message : String(error);
4294
- }
4295
- function toCamelCase(field) {
4296
- return field.replace(
4297
- /_([a-z])/g,
4298
- (_match, letter) => letter.toUpperCase()
4299
- );
4300
- }
4301
- function escapeRegExp(value) {
4302
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4303
- }
4304
- var sensitiveOauthFieldPattern = Array.from(
4305
- new Set(
4306
- SENSITIVE_OAUTH_FIELDS.flatMap((field) => [field, toCamelCase(field)])
4307
- )
4308
- ).map(escapeRegExp).join("|");
4309
- var sensitiveQueryParamPattern = new RegExp(
4310
- `([?&])(${sensitiveOauthFieldPattern})(=)[^&#\\s"'<>]*`,
4311
- "gi"
4312
- );
4313
- function redactSensitiveOauthErrorMessage(message) {
4314
- return message.replace(
4315
- sensitiveQueryParamPattern,
4316
- (_match, prefix, key, separator) => `${prefix}${key}${separator}[REDACTED]`
4317
- ).replace(
4318
- new RegExp(`"(${sensitiveOauthFieldPattern})"(\\s*:\\s*)"[^"]*"`, "g"),
4319
- (_match, key, separator) => `"${key}"${separator}"[REDACTED]"`
4320
- );
4321
- }
4322
- function toRedactedOauthError(error) {
4323
- const message = redactSensitiveOauthErrorMessage(getErrorMessage(error));
4324
- if (error instanceof ZapierCliValidationError) {
4325
- return new ZapierCliValidationError(message);
4326
- }
4327
- if (error instanceof Error) {
4328
- const redactedError = new Error(message);
4329
- redactedError.name = error.name;
4330
- return redactedError;
4331
- }
4332
- return new ZapierCliValidationError(message);
4333
- }
4334
-
4335
4625
  // src/utils/auth/account-auth.ts
4336
4626
  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?";
4337
4627
  var SIGNUP_RECOVERY_MESSAGE = "Restart `zapier-sdk signup` to generate a fresh signup URL and try again.";
4628
+ var HEADLESS_LOGIN_RECOVERY_MESSAGE = "Restart `zapier-sdk login --headless` to generate a fresh login URL and try again.";
4338
4629
  var HEADLESS_SIGNUP_RECOVERY_MESSAGE = "Restart `zapier-sdk signup --headless` to generate a fresh signup URL and try again.";
4339
4630
  function getEntryPointLabel(entryPoint) {
4340
4631
  return entryPoint === "signup" ? "Signup" : "Login";
@@ -4454,7 +4745,8 @@ Logging out will delete these credentials and may interrupt other Zapier SDK or
4454
4745
  Log out and ${getActiveCredentialsAction(entryPoint)}?`
4455
4746
  }) : promptlessCredentialResetError(activeCredentials);
4456
4747
  if (!confirmed) {
4457
- console.log(`${flowLabel} cancelled.`);
4748
+ process.stderr.write(`${flowLabel} cancelled.
4749
+ `);
4458
4750
  return false;
4459
4751
  }
4460
4752
  try {
@@ -4473,7 +4765,8 @@ Log out and ${getActiveCredentialsAction(entryPoint)}?`
4473
4765
  message: `${flowLabel} cleanup failed. Reset local session state and continue?`
4474
4766
  });
4475
4767
  if (!reset) {
4476
- console.log(`${flowLabel} cancelled.`);
4768
+ process.stderr.write(`${flowLabel} cancelled.
4769
+ `);
4477
4770
  return false;
4478
4771
  }
4479
4772
  await deleteStoredClientCredentials({
@@ -4487,7 +4780,8 @@ Log out and ${getActiveCredentialsAction(entryPoint)}?`
4487
4780
  message: LEGACY_JWT_UPGRADE_PROMPT
4488
4781
  }) : promptlessLegacyJwtUpgradeError();
4489
4782
  if (!confirmed) {
4490
- console.log(`${flowLabel} cancelled.`);
4783
+ process.stderr.write(`${flowLabel} cancelled.
4784
+ `);
4491
4785
  return false;
4492
4786
  }
4493
4787
  }
@@ -4582,7 +4876,14 @@ async function runOauthForEntryPoint({
4582
4876
  );
4583
4877
  }
4584
4878
  return runOauthWithRedaction(
4585
- () => runLoginOauthFlow({ timeoutMs, pkceCredentials, baseUrl: baseUrl2 })
4879
+ () => runLoginOauthFlow({
4880
+ timeoutMs,
4881
+ pkceCredentials,
4882
+ baseUrl: baseUrl2,
4883
+ headless,
4884
+ interactive,
4885
+ recoveryMessage: headless ? HEADLESS_LOGIN_RECOVERY_MESSAGE : void 0
4886
+ })
4586
4887
  );
4587
4888
  }
4588
4889
  async function runAccountAuth({
@@ -4594,6 +4895,7 @@ async function runAccountAuth({
4594
4895
  const interactive = !resolveNonInteractive(options);
4595
4896
  const resolvedCredentials = await sdk.context.resolveCredentials();
4596
4897
  const pkceCredentials = toPkceCredentials(resolvedCredentials);
4898
+ const headless = options.headless === true;
4597
4899
  const credentialsBaseUrl2 = await resolveCredentialsBaseUrl({
4598
4900
  ...sdk.context,
4599
4901
  resolvedCredentials
@@ -4623,7 +4925,7 @@ async function runAccountAuth({
4623
4925
  timeoutMs: timeoutSeconds * 1e3,
4624
4926
  pkceCredentials,
4625
4927
  baseUrl: credentialsBaseUrl2,
4626
- headless: options.headless === true,
4928
+ headless,
4627
4929
  interactive
4628
4930
  });
4629
4931
  const scopedApi = zapierSdk.getOrCreateApiClient({
@@ -4631,9 +4933,10 @@ async function runAccountAuth({
4631
4933
  baseUrl: credentialsBaseUrl2
4632
4934
  });
4633
4935
  const profile = await getProfile(scopedApi);
4634
- console.log(getProfileMessage(entryPoint, profile.email));
4635
- console.log(
4636
- "\nGenerating credentials so this machine can make authenticated requests on your behalf."
4936
+ process.stderr.write(`${getProfileMessage(entryPoint, profile.email)}
4937
+ `);
4938
+ process.stderr.write(
4939
+ "\nGenerating credentials so this machine can make authenticated requests on your behalf.\n"
4637
4940
  );
4638
4941
  const credentialName = providedName ?? await resolveCredentialName({
4639
4942
  email: profile.email,
@@ -4649,11 +4952,12 @@ async function runAccountAuth({
4649
4952
  useApprovals,
4650
4953
  cleanupLogPrefix: entryPoint
4651
4954
  });
4652
- console.log(
4653
- `\u2705 Credentials "${credentialName}" created and set as default. You are ready to use the Zapier SDK.`
4955
+ process.stderr.write(
4956
+ `\u2705 Credentials "${credentialName}" created and set as default. You are ready to use the Zapier SDK.
4957
+ `
4654
4958
  );
4655
4959
  if (useApprovals) {
4656
- console.log("\u{1F510} Approvals are enabled for these credentials.");
4960
+ process.stderr.write("\u{1F510} Approvals are enabled for these credentials.\n");
4657
4961
  }
4658
4962
  emitAccountAuthSuccess({ sdk, profile, clientId });
4659
4963
  }
@@ -4672,7 +4976,10 @@ var LoginSchema = zod.z.object({
4672
4976
  skipPrompts: zod.z.boolean().optional().meta({
4673
4977
  deprecated: true,
4674
4978
  deprecationMessage: "Use --non-interactive instead."
4675
- })
4979
+ }),
4980
+ headless: zod.z.boolean().optional().describe(
4981
+ "Use when logging in from a machine that has no browser. Prints a login link to open elsewhere, then accepts the pasted loopback callback URL."
4982
+ )
4676
4983
  }).describe("Log in to Zapier to access your account");
4677
4984
 
4678
4985
  // src/plugins/login/index.ts
@@ -7304,7 +7611,7 @@ function buildBoxLines(message) {
7304
7611
  // package.json with { type: 'json' }
7305
7612
  var package_default2 = {
7306
7613
  name: "@zapier/zapier-sdk-cli",
7307
- version: "0.60.0"};
7614
+ version: "0.61.1"};
7308
7615
 
7309
7616
  // src/sdk.ts
7310
7617
  zapierSdk.injectCliLogin(login_exports);
@@ -7381,6 +7688,9 @@ function createZapierCliSdk2(options = {}) {
7381
7688
 
7382
7689
  // src/utils/extensions.ts
7383
7690
  var ENV_VAR = "ZAPIER_SDK_EXTENSIONS";
7691
+ function isModelPlugin(value) {
7692
+ return typeof value === "object" && value !== null && "pluginType" in value && typeof value.pluginType === "string";
7693
+ }
7384
7694
  async function resolveExtensions() {
7385
7695
  const seen = /* @__PURE__ */ new Set();
7386
7696
  const specs = readEnvSpecs().filter((spec) => {
@@ -7413,8 +7723,11 @@ function normalizeExtension(exported) {
7413
7723
  if (typeof exported === "function") {
7414
7724
  return [exported];
7415
7725
  }
7726
+ if (isModelPlugin(exported)) {
7727
+ return [exported];
7728
+ }
7416
7729
  if (Array.isArray(exported)) {
7417
- if (exported.every((e) => typeof e === "function")) {
7730
+ if (exported.every((e) => typeof e === "function" || isModelPlugin(e))) {
7418
7731
  return exported;
7419
7732
  }
7420
7733
  return null;