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