@zapier/zapier-sdk-cli 0.60.0 → 0.61.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -344,12 +344,18 @@ var ZapierCliExitError = class extends ZapierCliError {
344
344
  this.exitCode = exitCode;
345
345
  }
346
346
  };
347
- var ZapierCliValidationError = class extends ZapierCliError {
348
- constructor(message) {
347
+ var ZapierCliValidationError = class _ZapierCliValidationError extends ZapierCliError {
348
+ constructor(message, options = {}) {
349
349
  super(message);
350
- this.name = "ZapierCliValidationError";
351
- this.code = "ZAPIER_CLI_VALIDATION_ERROR";
352
350
  this.exitCode = 1;
351
+ this.name = options.name ?? "ZapierCliValidationError";
352
+ this.code = options.code ?? "ZAPIER_CLI_VALIDATION_ERROR";
353
+ }
354
+ withMessage(message) {
355
+ return new _ZapierCliValidationError(message, {
356
+ name: this.name,
357
+ code: this.code
358
+ });
353
359
  }
354
360
  };
355
361
 
@@ -1110,41 +1116,165 @@ var spinPromise = async (promise, text) => {
1110
1116
  }
1111
1117
  };
1112
1118
 
1119
+ // src/utils/auth/oauth-errors.ts
1120
+ var OauthFlowTimeoutError = class _OauthFlowTimeoutError extends ZapierCliValidationError {
1121
+ constructor({
1122
+ timeoutMs,
1123
+ message = "OAuth flow timed out"
1124
+ }) {
1125
+ super(message, {
1126
+ name: "OauthFlowTimeoutError",
1127
+ code: "ZAPIER_OAUTH_FLOW_TIMEOUT"
1128
+ });
1129
+ this.timeoutMs = timeoutMs;
1130
+ }
1131
+ withMessage(message) {
1132
+ return new _OauthFlowTimeoutError({ timeoutMs: this.timeoutMs, message });
1133
+ }
1134
+ };
1135
+ var OauthAuthorizationDeniedError = class _OauthAuthorizationDeniedError extends ZapierCliValidationError {
1136
+ constructor({
1137
+ reason,
1138
+ message = "OAuth authorization denied"
1139
+ }) {
1140
+ super(message, {
1141
+ name: "OauthAuthorizationDeniedError",
1142
+ code: "ZAPIER_OAUTH_AUTHORIZATION_DENIED"
1143
+ });
1144
+ this.reason = reason;
1145
+ }
1146
+ withMessage(message) {
1147
+ return new _OauthAuthorizationDeniedError({
1148
+ reason: this.reason,
1149
+ message
1150
+ });
1151
+ }
1152
+ };
1153
+ var OauthFlowError = class _OauthFlowError extends ZapierCliValidationError {
1154
+ constructor({ message }) {
1155
+ super(message, {
1156
+ name: "OauthFlowError",
1157
+ code: "ZAPIER_OAUTH_FLOW"
1158
+ });
1159
+ }
1160
+ withMessage(message) {
1161
+ return new _OauthFlowError({ message });
1162
+ }
1163
+ };
1164
+ var OauthCallbackError = class _OauthCallbackError extends ZapierCliValidationError {
1165
+ constructor({ kind, message }) {
1166
+ super(message, {
1167
+ name: "OauthCallbackError",
1168
+ code: "ZAPIER_OAUTH_CALLBACK"
1169
+ });
1170
+ this.kind = kind;
1171
+ }
1172
+ withMessage(message) {
1173
+ return new _OauthCallbackError({ kind: this.kind, message });
1174
+ }
1175
+ };
1176
+ var OauthTokenExchangeError = class _OauthTokenExchangeError extends ZapierCliValidationError {
1177
+ constructor({ message }) {
1178
+ super(message, {
1179
+ name: "OauthTokenExchangeError",
1180
+ code: "ZAPIER_OAUTH_TOKEN_EXCHANGE"
1181
+ });
1182
+ }
1183
+ withMessage(message) {
1184
+ return new _OauthTokenExchangeError({ message });
1185
+ }
1186
+ };
1187
+ var SENSITIVE_OAUTH_FIELDS = [
1188
+ "access_token",
1189
+ "refresh_token",
1190
+ "id_token",
1191
+ "client_secret",
1192
+ "code_verifier",
1193
+ "code_challenge"
1194
+ ];
1195
+ function getErrorMessage(error) {
1196
+ return error instanceof Error ? error.message : String(error);
1197
+ }
1198
+ function toCamelCase(field) {
1199
+ return field.replace(
1200
+ /_([a-z])/g,
1201
+ (_match, letter) => letter.toUpperCase()
1202
+ );
1203
+ }
1204
+ function escapeRegExp(value) {
1205
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1206
+ }
1207
+ var sensitiveOauthFieldPattern = Array.from(
1208
+ new Set(
1209
+ SENSITIVE_OAUTH_FIELDS.flatMap((field) => [field, toCamelCase(field)])
1210
+ )
1211
+ ).map(escapeRegExp).join("|");
1212
+ var sensitiveQueryParamPattern = new RegExp(
1213
+ `([?&])(${sensitiveOauthFieldPattern})(=)[^&#\\s"'<>]*`,
1214
+ "gi"
1215
+ );
1216
+ function redactSensitiveOauthErrorMessage(message) {
1217
+ return message.replace(
1218
+ sensitiveQueryParamPattern,
1219
+ (_match, prefix, key, separator) => `${prefix}${key}${separator}[REDACTED]`
1220
+ ).replace(
1221
+ new RegExp(`"(${sensitiveOauthFieldPattern})"(\\s*:\\s*)"[^"]*"`, "g"),
1222
+ (_match, key, separator) => `"${key}"${separator}"[REDACTED]"`
1223
+ );
1224
+ }
1225
+ function toRedactedOauthError(error) {
1226
+ const message = redactSensitiveOauthErrorMessage(getErrorMessage(error));
1227
+ if (error instanceof ZapierCliValidationError) {
1228
+ return error.withMessage(message);
1229
+ }
1230
+ return new OauthFlowError({ message });
1231
+ }
1232
+ function toOauthTokenExchangeError(error) {
1233
+ return new OauthTokenExchangeError({
1234
+ message: redactSensitiveOauthErrorMessage(getErrorMessage(error))
1235
+ });
1236
+ }
1237
+
1113
1238
  // src/utils/auth/oauth-callback.ts
1114
1239
  function getCallbackCode({
1115
1240
  callbackUrl,
1116
- transaction,
1117
- recoveryMessage
1241
+ transaction
1118
1242
  }) {
1119
1243
  let parsed;
1120
1244
  try {
1121
1245
  parsed = new URL(callbackUrl.trim());
1122
1246
  } catch {
1123
- throw new ZapierCliValidationError(
1124
- "Paste the final OAuth callback URL from your browser."
1125
- );
1247
+ throw new OauthCallbackError({
1248
+ kind: "invalid_url",
1249
+ message: "Paste the final OAuth callback URL from your browser."
1250
+ });
1126
1251
  }
1127
1252
  const expected = new URL(transaction.redirectUri);
1128
1253
  if (parsed.protocol !== "http:" || parsed.hostname !== expected.hostname || parsed.pathname !== expected.pathname || parsed.port !== expected.port) {
1129
- throw new ZapierCliValidationError(
1130
- `Expected the final OAuth callback URL to start with ${transaction.redirectUri}.`
1131
- );
1254
+ throw new OauthCallbackError({
1255
+ kind: "redirect_mismatch",
1256
+ message: `Expected the final OAuth callback URL to start with ${transaction.redirectUri}.`
1257
+ });
1132
1258
  }
1133
1259
  if (parsed.searchParams.get("state") !== transaction.state) {
1134
- throw new ZapierCliValidationError(
1135
- `OAuth state mismatch.${recoveryMessage ? ` ${recoveryMessage}` : ""}`
1136
- );
1260
+ throw new OauthCallbackError({
1261
+ kind: "state_mismatch",
1262
+ message: "OAuth state mismatch."
1263
+ });
1137
1264
  }
1138
1265
  if (parsed.searchParams.has("error")) {
1139
- throw new ZapierCliValidationError(
1140
- `Authorization denied: ${parsed.searchParams.get("error_description") ?? parsed.searchParams.get("error")}.${recoveryMessage ? ` ${recoveryMessage}` : ""}`
1141
- );
1266
+ throw new OauthAuthorizationDeniedError({
1267
+ reason: String(
1268
+ parsed.searchParams.get("error_description") ?? parsed.searchParams.get("error")
1269
+ )
1270
+ });
1142
1271
  }
1143
1272
  const code = parsed.searchParams.get("code");
1144
1273
  if (!code) {
1145
- throw new ZapierCliValidationError(
1146
- "No authorization code found in the pasted callback URL."
1147
- );
1274
+ throw new OauthCallbackError({
1275
+ kind: "missing_code",
1276
+ message: "No authorization code found in the pasted callback URL."
1277
+ });
1148
1278
  }
1149
1279
  return code;
1150
1280
  }
@@ -1259,7 +1389,9 @@ async function exchangeOauthCode({
1259
1389
  "Content-Type": "application/x-www-form-urlencoded"
1260
1390
  }
1261
1391
  }
1262
- );
1392
+ ).catch((error) => {
1393
+ throw toOauthTokenExchangeError(error);
1394
+ });
1263
1395
  return {
1264
1396
  accessToken: data.access_token,
1265
1397
  refreshToken: data.refresh_token,
@@ -1268,19 +1400,15 @@ async function exchangeOauthCode({
1268
1400
  }
1269
1401
 
1270
1402
  // src/utils/auth/oauth-flow.ts
1271
- var OauthFlowTimeoutError = class extends Error {
1272
- constructor(timeoutMs) {
1273
- super("OAuth flow timed out");
1274
- this.timeoutMs = timeoutMs;
1275
- this.name = "OauthFlowTimeoutError";
1276
- }
1403
+ var LOGIN_OAUTH_COPY = {
1404
+ flowName: "Login",
1405
+ action: "log in",
1406
+ urlLabel: "login URL"
1277
1407
  };
1278
- var OauthAuthorizationDeniedError = class extends Error {
1279
- constructor(reason) {
1280
- super("OAuth authorization denied");
1281
- this.reason = reason;
1282
- this.name = "OauthAuthorizationDeniedError";
1283
- }
1408
+ var SIGNUP_OAUTH_COPY = {
1409
+ flowName: "Signup",
1410
+ action: "sign up",
1411
+ urlLabel: "signup URL"
1284
1412
  };
1285
1413
  function findAvailablePort() {
1286
1414
  return new Promise((resolve4, reject) => {
@@ -1295,70 +1423,86 @@ function findAvailablePort() {
1295
1423
  tryPort(LOGIN_PORTS[portIndex++]);
1296
1424
  } else if (err.code === "EADDRINUSE") {
1297
1425
  reject(
1298
- new Error(
1299
- `All configured OAuth callback ports are busy: ${LOGIN_PORTS.join(", ")}. Please try again later or close applications using these ports.`
1300
- )
1426
+ new OauthFlowError({
1427
+ message: `All configured OAuth callback ports are busy: ${LOGIN_PORTS.join(", ")}. Please try again later or close applications using these ports.`
1428
+ })
1301
1429
  );
1302
1430
  } else {
1303
1431
  reject(err);
1304
1432
  }
1305
1433
  });
1306
1434
  };
1307
- if (LOGIN_PORTS.length > 0) tryPort(LOGIN_PORTS[portIndex++]);
1308
- else reject(new Error("No OAuth callback ports configured"));
1435
+ if (LOGIN_PORTS.length > 0) {
1436
+ tryPort(LOGIN_PORTS[portIndex++]);
1437
+ return;
1438
+ }
1439
+ reject(
1440
+ new OauthFlowError({ message: "No OAuth callback ports configured" })
1441
+ );
1309
1442
  });
1310
1443
  }
1311
1444
  async function runLoginOauthFlow(options) {
1312
- return runOauthFlowEntryPoint({
1313
- ...options,
1445
+ return runOauthFlowForEntryPoint({
1446
+ options,
1314
1447
  entryPoint: "login",
1315
- authAction: "log in",
1316
- flowName: "Login"
1448
+ copy: LOGIN_OAUTH_COPY
1317
1449
  });
1318
1450
  }
1319
1451
  async function runSignupOauthFlow(options) {
1452
+ return runOauthFlowForEntryPoint({
1453
+ options,
1454
+ entryPoint: "signup",
1455
+ copy: SIGNUP_OAUTH_COPY
1456
+ });
1457
+ }
1458
+ function runOauthFlowForEntryPoint({
1459
+ options,
1460
+ entryPoint,
1461
+ copy
1462
+ }) {
1320
1463
  if (options.headless) {
1321
1464
  return runOauthFlowEntryPoint({
1322
1465
  ...options,
1323
- entryPoint: "signup",
1324
- authAction: "sign up",
1325
- flowName: "Signup",
1326
- headless: true
1466
+ entryPoint,
1467
+ headless: true,
1468
+ copy
1327
1469
  });
1328
1470
  }
1329
1471
  return runOauthFlowEntryPoint({
1330
1472
  ...options,
1331
- entryPoint: "signup",
1332
- authAction: "sign up",
1333
- flowName: "Signup"
1473
+ entryPoint,
1474
+ copy,
1475
+ headless: false
1334
1476
  });
1335
1477
  }
1336
- async function runOauthFlowEntryPoint({
1337
- flowName,
1338
- ...options
1339
- }) {
1478
+ async function runOauthFlowEntryPoint(options) {
1340
1479
  try {
1341
- return options.headless ? await runHeadlessSignupOauthFlow(options) : await runOauthFlow(options);
1480
+ return options.headless ? await runHeadlessOauthFlow(options) : await runOauthFlow(options);
1342
1481
  } catch (error) {
1343
1482
  if (error instanceof OauthFlowTimeoutError) {
1344
- throw new Error(
1483
+ throw error.withMessage(
1345
1484
  withRecoveryMessage(
1346
- `${flowName} timed out after ${Math.round(error.timeoutMs / 1e3)} seconds.`,
1485
+ `${options.copy.flowName} timed out after ${Math.round(error.timeoutMs / 1e3)} seconds.`,
1347
1486
  options.recoveryMessage
1348
1487
  )
1349
1488
  );
1350
1489
  }
1351
1490
  if (error instanceof OauthAuthorizationDeniedError) {
1352
- throw new Error(
1491
+ throw error.withMessage(
1353
1492
  withRecoveryMessage(
1354
1493
  `Authorization denied: ${error.reason}.`,
1355
1494
  options.recoveryMessage
1356
1495
  )
1357
1496
  );
1358
1497
  }
1498
+ if (error instanceof OauthCallbackError && options.recoveryMessage) {
1499
+ throw error.withMessage(
1500
+ withRecoveryMessage(error.message, options.recoveryMessage)
1501
+ );
1502
+ }
1359
1503
  if (error instanceof ZapierCliUserCancellationError && !options.silent) {
1360
1504
  log_default.info(`
1361
- \u274C ${flowName} cancelled by user`);
1505
+ \u274C ${options.copy.flowName} cancelled by user`);
1362
1506
  }
1363
1507
  throw error;
1364
1508
  }
@@ -1366,12 +1510,22 @@ async function runOauthFlowEntryPoint({
1366
1510
  function withRecoveryMessage(message, recoveryMessage) {
1367
1511
  return recoveryMessage ? `${message} ${recoveryMessage}` : message;
1368
1512
  }
1513
+ function createMissingHeadlessCallbackUrlError() {
1514
+ return new OauthCallbackError({
1515
+ kind: "missing_callback_url",
1516
+ message: "Paste the final OAuth callback URL from your browser."
1517
+ });
1518
+ }
1519
+ function writeHeadlessOauthStatus(message) {
1520
+ process.stderr.write(`${message}
1521
+ `);
1522
+ }
1369
1523
  async function runOauthFlow({
1370
1524
  timeoutMs = LOGIN_TIMEOUT_MS,
1371
1525
  pkceCredentials,
1372
1526
  baseUrl,
1373
1527
  entryPoint,
1374
- authAction,
1528
+ copy,
1375
1529
  silent = false,
1376
1530
  onProgress
1377
1531
  }) {
@@ -1386,7 +1540,7 @@ async function runOauthFlow({
1386
1540
  const code = await collectLocalCallbackCode({
1387
1541
  transaction,
1388
1542
  timeoutMs,
1389
- authAction,
1543
+ action: copy.action,
1390
1544
  silent,
1391
1545
  onProgress
1392
1546
  });
@@ -1400,92 +1554,104 @@ async function runOauthFlow({
1400
1554
  }
1401
1555
  async function readHeadlessCallbackUrl({
1402
1556
  timeoutMs,
1403
- interactive,
1404
- recoveryMessage
1557
+ interactive
1405
1558
  }) {
1406
- const timeoutMessage = withRecoveryMessage(
1407
- `Signup timed out after ${Math.round(timeoutMs / 1e3)} seconds.`,
1408
- recoveryMessage
1409
- );
1410
- const missingCallbackUrlMessage = withRecoveryMessage(
1411
- "Paste the final OAuth callback URL from your browser.",
1412
- recoveryMessage
1413
- );
1414
1559
  const rl = promises$1.createInterface({ input: process.stdin, output: process.stderr });
1415
1560
  const abortController = new AbortController();
1416
1561
  const timeoutTimer = setTimeout(() => abortController.abort(), timeoutMs);
1417
- const readUrl = interactive ? rl.question("Paste the final OAuth callback URL: ", {
1418
- signal: abortController.signal
1419
- }) : new Promise((resolve4, reject) => {
1562
+ const readUrl = new Promise((resolve4, reject) => {
1420
1563
  let settled = false;
1421
- const settleResolve = (value) => {
1564
+ const handleLine = (value) => settleResolve(value);
1565
+ const handleAbort = () => settleReject(new OauthFlowTimeoutError({ timeoutMs }));
1566
+ const handleClose = () => settleReject(createMissingHeadlessCallbackUrlError());
1567
+ const handleError = (error) => settleReject(error);
1568
+ const handleCancellation = () => settleReject(new ZapierCliUserCancellationError());
1569
+ const cleanupListeners = () => {
1570
+ abortController.signal.removeEventListener("abort", handleAbort);
1571
+ rl.off("line", handleLine);
1572
+ rl.off("close", handleClose);
1573
+ rl.off("error", handleError);
1574
+ rl.off("SIGINT", handleCancellation);
1575
+ process.off("SIGINT", handleCancellation);
1576
+ process.off("SIGTERM", handleCancellation);
1577
+ };
1578
+ function settleResolve(value) {
1579
+ if (settled) return;
1422
1580
  settled = true;
1581
+ cleanupListeners();
1423
1582
  resolve4(value);
1424
- };
1425
- const settleReject = (error) => {
1583
+ }
1584
+ function settleReject(error) {
1426
1585
  if (settled) return;
1427
1586
  settled = true;
1587
+ cleanupListeners();
1428
1588
  reject(error);
1429
- };
1430
- abortController.signal.addEventListener(
1431
- "abort",
1432
- () => settleReject(new Error(timeoutMessage)),
1433
- { once: true }
1434
- );
1435
- rl.once("line", settleResolve);
1436
- rl.once(
1437
- "close",
1438
- () => settleReject(new ZapierCliValidationError(missingCallbackUrlMessage))
1439
- );
1440
- rl.once("error", settleReject);
1589
+ }
1590
+ abortController.signal.addEventListener("abort", handleAbort, {
1591
+ once: true
1592
+ });
1593
+ rl.once("close", handleClose);
1594
+ rl.once("error", handleError);
1595
+ rl.once("SIGINT", handleCancellation);
1596
+ process.once("SIGINT", handleCancellation);
1597
+ process.once("SIGTERM", handleCancellation);
1598
+ if (interactive) {
1599
+ void rl.question("Paste the final OAuth callback URL: ", {
1600
+ signal: abortController.signal
1601
+ }).then(settleResolve).catch((error) => {
1602
+ if (error instanceof Error && error.name === "AbortError") {
1603
+ settleReject(new OauthFlowTimeoutError({ timeoutMs }));
1604
+ return;
1605
+ }
1606
+ settleReject(error);
1607
+ });
1608
+ return;
1609
+ }
1610
+ rl.once("line", handleLine);
1441
1611
  });
1442
1612
  try {
1443
- return await readUrl.catch((error) => {
1444
- if (error instanceof Error && error.name === "AbortError") {
1445
- throw new Error(timeoutMessage);
1446
- }
1447
- throw error;
1448
- });
1613
+ return await readUrl;
1449
1614
  } finally {
1450
1615
  clearTimeout(timeoutTimer);
1451
1616
  rl.close();
1452
1617
  }
1453
1618
  }
1454
- async function runHeadlessSignupOauthFlow({
1619
+ async function runHeadlessOauthFlow({
1455
1620
  timeoutMs = LOGIN_TIMEOUT_MS,
1456
1621
  pkceCredentials,
1457
1622
  baseUrl,
1623
+ entryPoint,
1458
1624
  interactive = true,
1459
1625
  onProgress,
1460
- recoveryMessage
1626
+ copy
1461
1627
  }) {
1462
1628
  const port = LOGIN_PORTS[0];
1463
1629
  const transaction = await prepareOauthTransaction({
1464
1630
  pkceCredentials,
1465
1631
  baseUrl,
1466
1632
  redirectUri: `http://${OAUTH_LOOPBACK_HOST}:${port}/oauth`,
1467
- entryPoint: "signup"
1633
+ entryPoint
1468
1634
  });
1469
- console.log(
1470
- "Use this mode when signing up from a machine that has no browser."
1635
+ writeHeadlessOauthStatus(
1636
+ `Use this mode to ${copy.action} from a machine that has no browser.`
1637
+ );
1638
+ writeHeadlessOauthStatus(
1639
+ `Open this ${copy.urlLabel} in a browser on another machine:`
1471
1640
  );
1472
- console.log("Open this signup URL in a browser on another machine:");
1473
1641
  console.log(transaction.browserAuthUrl);
1474
- console.log(
1642
+ writeHeadlessOauthStatus(
1475
1643
  `When the browser lands on ${transaction.redirectUri} and cannot connect, paste the full final URL back here.`
1476
1644
  );
1477
1645
  const callbackUrl = await readHeadlessCallbackUrl({
1478
1646
  timeoutMs,
1479
- interactive,
1480
- recoveryMessage
1647
+ interactive
1481
1648
  });
1482
1649
  const code = getCallbackCode({
1483
1650
  callbackUrl,
1484
- transaction,
1485
- recoveryMessage
1651
+ transaction
1486
1652
  });
1487
1653
  onProgress?.({ type: "callback_accepted" });
1488
- console.log("Exchanging authorization code for tokens...");
1654
+ writeHeadlessOauthStatus("Exchanging authorization code for tokens...");
1489
1655
  onProgress?.({ type: "token_exchange_started" });
1490
1656
  const tokens = await exchangeOauthCode({ ...transaction, code });
1491
1657
  onProgress?.({ type: "token_exchange_completed" });
@@ -1494,7 +1660,7 @@ async function runHeadlessSignupOauthFlow({
1494
1660
  async function collectLocalCallbackCode({
1495
1661
  transaction,
1496
1662
  timeoutMs,
1497
- authAction,
1663
+ action,
1498
1664
  silent,
1499
1665
  onProgress
1500
1666
  }) {
@@ -1502,21 +1668,26 @@ async function collectLocalCallbackCode({
1502
1668
  const app = express__default.default();
1503
1669
  app.get("/oauth", (req, res) => {
1504
1670
  res.setHeader("Connection", "close");
1505
- if (req.query.state !== transaction.state) {
1506
- res.status(400).end("Invalid state. You can close this tab.");
1507
- } else if (req.query.error) {
1508
- reject(
1509
- new OauthAuthorizationDeniedError(
1510
- String(req.query.error_description ?? req.query.error)
1511
- )
1512
- );
1513
- res.end("Authorization was denied. You can close this tab.");
1514
- } else if (!req.query.code) {
1515
- reject(new Error("No authorization code received"));
1516
- res.end("No authorization code received. You can close this tab.");
1517
- } else {
1518
- resolve4(String(req.query.code));
1671
+ try {
1672
+ const code = getCallbackCode({
1673
+ callbackUrl: new URL(
1674
+ req.originalUrl,
1675
+ transaction.redirectUri
1676
+ ).toString(),
1677
+ transaction
1678
+ });
1679
+ resolve4(code);
1519
1680
  res.end("You can now close this tab and return to the CLI.");
1681
+ } catch (error) {
1682
+ if (error instanceof OauthCallbackError && error.kind === "state_mismatch") {
1683
+ res.status(400).end("Invalid state. You can close this tab.");
1684
+ } else if (error instanceof OauthAuthorizationDeniedError) {
1685
+ reject(error);
1686
+ res.end("Authorization was denied. You can close this tab.");
1687
+ } else {
1688
+ reject(error);
1689
+ res.end("No authorization code received. You can close this tab.");
1690
+ }
1520
1691
  }
1521
1692
  });
1522
1693
  const server = app.listen(
@@ -1537,19 +1708,19 @@ async function collectLocalCallbackCode({
1537
1708
  let timeoutTimer;
1538
1709
  try {
1539
1710
  await waitForServerListening(server);
1540
- await openBrowser({ transaction, authAction, silent, onProgress });
1711
+ await openBrowser({ transaction, action, silent, onProgress });
1541
1712
  const waitForCode = Promise.race([
1542
1713
  promise,
1543
1714
  new Promise((_resolve, rejectTimeout) => {
1544
1715
  timeoutTimer = setTimeout(() => {
1545
- rejectTimeout(new OauthFlowTimeoutError(timeoutMs));
1716
+ rejectTimeout(new OauthFlowTimeoutError({ timeoutMs }));
1546
1717
  }, timeoutMs);
1547
1718
  })
1548
1719
  ]);
1549
1720
  onProgress?.({ type: "callback_waiting" });
1550
1721
  return silent ? await waitForCode : await spinPromise(
1551
1722
  waitForCode,
1552
- `Waiting for you to ${authAction} and authorize`
1723
+ `Waiting for you to ${action} and authorize`
1553
1724
  );
1554
1725
  } finally {
1555
1726
  if (timeoutTimer) clearTimeout(timeoutTimer);
@@ -1579,12 +1750,12 @@ async function waitForServerListening(server) {
1579
1750
  }
1580
1751
  async function openBrowser({
1581
1752
  transaction,
1582
- authAction,
1753
+ action,
1583
1754
  silent,
1584
1755
  onProgress
1585
1756
  }) {
1586
1757
  if (!silent) {
1587
- log_default.info(`Opening your browser to ${authAction}.`);
1758
+ log_default.info(`Opening your browser to ${action}.`);
1588
1759
  log_default.info("If it doesn't open, visit:", transaction.browserAuthUrl);
1589
1760
  }
1590
1761
  onProgress?.({ type: "browser_opening", url: transaction.browserAuthUrl });
@@ -1594,9 +1765,7 @@ async function openBrowser({
1594
1765
  } catch (err) {
1595
1766
  const reason = err instanceof Error ? err.message : String(err);
1596
1767
  if (!silent) {
1597
- log_default.info(
1598
- `Browser did not open automatically to ${authAction}: ${reason}`
1599
- );
1768
+ log_default.info(`Browser did not open automatically to ${action}: ${reason}`);
1600
1769
  log_default.info("Visit this URL manually:", transaction.browserAuthUrl);
1601
1770
  }
1602
1771
  onProgress?.({
@@ -1625,61 +1794,10 @@ async function closeServer({
1625
1794
  });
1626
1795
  }
1627
1796
 
1628
- // src/utils/auth/oauth-errors.ts
1629
- var SENSITIVE_OAUTH_FIELDS = [
1630
- "access_token",
1631
- "refresh_token",
1632
- "id_token",
1633
- "client_secret",
1634
- "code_verifier",
1635
- "code_challenge"
1636
- ];
1637
- function getErrorMessage(error) {
1638
- return error instanceof Error ? error.message : String(error);
1639
- }
1640
- function toCamelCase(field) {
1641
- return field.replace(
1642
- /_([a-z])/g,
1643
- (_match, letter) => letter.toUpperCase()
1644
- );
1645
- }
1646
- function escapeRegExp(value) {
1647
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1648
- }
1649
- var sensitiveOauthFieldPattern = Array.from(
1650
- new Set(
1651
- SENSITIVE_OAUTH_FIELDS.flatMap((field) => [field, toCamelCase(field)])
1652
- )
1653
- ).map(escapeRegExp).join("|");
1654
- var sensitiveQueryParamPattern = new RegExp(
1655
- `([?&])(${sensitiveOauthFieldPattern})(=)[^&#\\s"'<>]*`,
1656
- "gi"
1657
- );
1658
- function redactSensitiveOauthErrorMessage(message) {
1659
- return message.replace(
1660
- sensitiveQueryParamPattern,
1661
- (_match, prefix, key, separator) => `${prefix}${key}${separator}[REDACTED]`
1662
- ).replace(
1663
- new RegExp(`"(${sensitiveOauthFieldPattern})"(\\s*:\\s*)"[^"]*"`, "g"),
1664
- (_match, key, separator) => `"${key}"${separator}"[REDACTED]"`
1665
- );
1666
- }
1667
- function toRedactedOauthError(error) {
1668
- const message = redactSensitiveOauthErrorMessage(getErrorMessage(error));
1669
- if (error instanceof ZapierCliValidationError) {
1670
- return new ZapierCliValidationError(message);
1671
- }
1672
- if (error instanceof Error) {
1673
- const redactedError = new Error(message);
1674
- redactedError.name = error.name;
1675
- return redactedError;
1676
- }
1677
- return new ZapierCliValidationError(message);
1678
- }
1679
-
1680
1797
  // src/utils/auth/account-auth.ts
1681
1798
  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?";
1682
1799
  var SIGNUP_RECOVERY_MESSAGE = "Restart `zapier-sdk signup` to generate a fresh signup URL and try again.";
1800
+ var HEADLESS_LOGIN_RECOVERY_MESSAGE = "Restart `zapier-sdk login --headless` to generate a fresh login URL and try again.";
1683
1801
  var HEADLESS_SIGNUP_RECOVERY_MESSAGE = "Restart `zapier-sdk signup --headless` to generate a fresh signup URL and try again.";
1684
1802
  function getEntryPointLabel(entryPoint) {
1685
1803
  return entryPoint === "signup" ? "Signup" : "Login";
@@ -1799,7 +1917,8 @@ Logging out will delete these credentials and may interrupt other Zapier SDK or
1799
1917
  Log out and ${getActiveCredentialsAction(entryPoint)}?`
1800
1918
  }) : promptlessCredentialResetError(activeCredentials);
1801
1919
  if (!confirmed) {
1802
- console.log(`${flowLabel} cancelled.`);
1920
+ process.stderr.write(`${flowLabel} cancelled.
1921
+ `);
1803
1922
  return false;
1804
1923
  }
1805
1924
  try {
@@ -1818,7 +1937,8 @@ Log out and ${getActiveCredentialsAction(entryPoint)}?`
1818
1937
  message: `${flowLabel} cleanup failed. Reset local session state and continue?`
1819
1938
  });
1820
1939
  if (!reset) {
1821
- console.log(`${flowLabel} cancelled.`);
1940
+ process.stderr.write(`${flowLabel} cancelled.
1941
+ `);
1822
1942
  return false;
1823
1943
  }
1824
1944
  await deleteStoredClientCredentials({
@@ -1832,7 +1952,8 @@ Log out and ${getActiveCredentialsAction(entryPoint)}?`
1832
1952
  message: LEGACY_JWT_UPGRADE_PROMPT
1833
1953
  }) : promptlessLegacyJwtUpgradeError();
1834
1954
  if (!confirmed) {
1835
- console.log(`${flowLabel} cancelled.`);
1955
+ process.stderr.write(`${flowLabel} cancelled.
1956
+ `);
1836
1957
  return false;
1837
1958
  }
1838
1959
  }
@@ -1927,7 +2048,14 @@ async function runOauthForEntryPoint({
1927
2048
  );
1928
2049
  }
1929
2050
  return runOauthWithRedaction(
1930
- () => runLoginOauthFlow({ timeoutMs, pkceCredentials, baseUrl })
2051
+ () => runLoginOauthFlow({
2052
+ timeoutMs,
2053
+ pkceCredentials,
2054
+ baseUrl,
2055
+ headless,
2056
+ interactive,
2057
+ recoveryMessage: headless ? HEADLESS_LOGIN_RECOVERY_MESSAGE : void 0
2058
+ })
1931
2059
  );
1932
2060
  }
1933
2061
  async function runAccountAuth({
@@ -1939,6 +2067,7 @@ async function runAccountAuth({
1939
2067
  const interactive = !resolveNonInteractive(options);
1940
2068
  const resolvedCredentials = await sdk.context.resolveCredentials();
1941
2069
  const pkceCredentials = toPkceCredentials(resolvedCredentials);
2070
+ const headless = options.headless === true;
1942
2071
  const credentialsBaseUrl = await resolveCredentialsBaseUrl({
1943
2072
  ...sdk.context,
1944
2073
  resolvedCredentials
@@ -1968,7 +2097,7 @@ async function runAccountAuth({
1968
2097
  timeoutMs: timeoutSeconds * 1e3,
1969
2098
  pkceCredentials,
1970
2099
  baseUrl: credentialsBaseUrl,
1971
- headless: options.headless === true,
2100
+ headless,
1972
2101
  interactive
1973
2102
  });
1974
2103
  const scopedApi = zapierSdk.getOrCreateApiClient({
@@ -1976,9 +2105,10 @@ async function runAccountAuth({
1976
2105
  baseUrl: credentialsBaseUrl
1977
2106
  });
1978
2107
  const profile = await getProfile(scopedApi);
1979
- console.log(getProfileMessage(entryPoint, profile.email));
1980
- console.log(
1981
- "\nGenerating credentials so this machine can make authenticated requests on your behalf."
2108
+ process.stderr.write(`${getProfileMessage(entryPoint, profile.email)}
2109
+ `);
2110
+ process.stderr.write(
2111
+ "\nGenerating credentials so this machine can make authenticated requests on your behalf.\n"
1982
2112
  );
1983
2113
  const credentialName = providedName ?? await resolveCredentialName({
1984
2114
  email: profile.email,
@@ -1994,11 +2124,12 @@ async function runAccountAuth({
1994
2124
  useApprovals,
1995
2125
  cleanupLogPrefix: entryPoint
1996
2126
  });
1997
- console.log(
1998
- `\u2705 Credentials "${credentialName}" created and set as default. You are ready to use the Zapier SDK.`
2127
+ process.stderr.write(
2128
+ `\u2705 Credentials "${credentialName}" created and set as default. You are ready to use the Zapier SDK.
2129
+ `
1999
2130
  );
2000
2131
  if (useApprovals) {
2001
- console.log("\u{1F510} Approvals are enabled for these credentials.");
2132
+ process.stderr.write("\u{1F510} Approvals are enabled for these credentials.\n");
2002
2133
  }
2003
2134
  emitAccountAuthSuccess({ sdk, profile, clientId });
2004
2135
  }
@@ -2017,7 +2148,10 @@ var LoginSchema = zod.z.object({
2017
2148
  skipPrompts: zod.z.boolean().optional().meta({
2018
2149
  deprecated: true,
2019
2150
  deprecationMessage: "Use --non-interactive instead."
2020
- })
2151
+ }),
2152
+ headless: zod.z.boolean().optional().describe(
2153
+ "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."
2154
+ )
2021
2155
  }).describe("Log in to Zapier to access your account");
2022
2156
 
2023
2157
  // src/plugins/login/index.ts
@@ -4606,7 +4740,7 @@ function collectDeprecationNoticeAndForward(event, onEvent) {
4606
4740
  // package.json with { type: 'json' }
4607
4741
  var package_default = {
4608
4742
  name: "@zapier/zapier-sdk-cli",
4609
- version: "0.60.0"};
4743
+ version: "0.61.0"};
4610
4744
 
4611
4745
  // src/experimental.ts
4612
4746
  experimental.injectCliLogin(login_exports);