@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/index.mjs CHANGED
@@ -307,12 +307,18 @@ var ZapierCliExitError = class extends ZapierCliError {
307
307
  this.exitCode = exitCode;
308
308
  }
309
309
  };
310
- var ZapierCliValidationError = class extends ZapierCliError {
311
- constructor(message) {
310
+ var ZapierCliValidationError = class _ZapierCliValidationError extends ZapierCliError {
311
+ constructor(message, options = {}) {
312
312
  super(message);
313
- this.name = "ZapierCliValidationError";
314
- this.code = "ZAPIER_CLI_VALIDATION_ERROR";
315
313
  this.exitCode = 1;
314
+ this.name = options.name ?? "ZapierCliValidationError";
315
+ this.code = options.code ?? "ZAPIER_CLI_VALIDATION_ERROR";
316
+ }
317
+ withMessage(message) {
318
+ return new _ZapierCliValidationError(message, {
319
+ name: this.name,
320
+ code: this.code
321
+ });
316
322
  }
317
323
  };
318
324
 
@@ -1073,41 +1079,165 @@ var spinPromise = async (promise, text) => {
1073
1079
  }
1074
1080
  };
1075
1081
 
1082
+ // src/utils/auth/oauth-errors.ts
1083
+ var OauthFlowTimeoutError = class _OauthFlowTimeoutError extends ZapierCliValidationError {
1084
+ constructor({
1085
+ timeoutMs,
1086
+ message = "OAuth flow timed out"
1087
+ }) {
1088
+ super(message, {
1089
+ name: "OauthFlowTimeoutError",
1090
+ code: "ZAPIER_OAUTH_FLOW_TIMEOUT"
1091
+ });
1092
+ this.timeoutMs = timeoutMs;
1093
+ }
1094
+ withMessage(message) {
1095
+ return new _OauthFlowTimeoutError({ timeoutMs: this.timeoutMs, message });
1096
+ }
1097
+ };
1098
+ var OauthAuthorizationDeniedError = class _OauthAuthorizationDeniedError extends ZapierCliValidationError {
1099
+ constructor({
1100
+ reason,
1101
+ message = "OAuth authorization denied"
1102
+ }) {
1103
+ super(message, {
1104
+ name: "OauthAuthorizationDeniedError",
1105
+ code: "ZAPIER_OAUTH_AUTHORIZATION_DENIED"
1106
+ });
1107
+ this.reason = reason;
1108
+ }
1109
+ withMessage(message) {
1110
+ return new _OauthAuthorizationDeniedError({
1111
+ reason: this.reason,
1112
+ message
1113
+ });
1114
+ }
1115
+ };
1116
+ var OauthFlowError = class _OauthFlowError extends ZapierCliValidationError {
1117
+ constructor({ message }) {
1118
+ super(message, {
1119
+ name: "OauthFlowError",
1120
+ code: "ZAPIER_OAUTH_FLOW"
1121
+ });
1122
+ }
1123
+ withMessage(message) {
1124
+ return new _OauthFlowError({ message });
1125
+ }
1126
+ };
1127
+ var OauthCallbackError = class _OauthCallbackError extends ZapierCliValidationError {
1128
+ constructor({ kind, message }) {
1129
+ super(message, {
1130
+ name: "OauthCallbackError",
1131
+ code: "ZAPIER_OAUTH_CALLBACK"
1132
+ });
1133
+ this.kind = kind;
1134
+ }
1135
+ withMessage(message) {
1136
+ return new _OauthCallbackError({ kind: this.kind, message });
1137
+ }
1138
+ };
1139
+ var OauthTokenExchangeError = class _OauthTokenExchangeError extends ZapierCliValidationError {
1140
+ constructor({ message }) {
1141
+ super(message, {
1142
+ name: "OauthTokenExchangeError",
1143
+ code: "ZAPIER_OAUTH_TOKEN_EXCHANGE"
1144
+ });
1145
+ }
1146
+ withMessage(message) {
1147
+ return new _OauthTokenExchangeError({ message });
1148
+ }
1149
+ };
1150
+ var SENSITIVE_OAUTH_FIELDS = [
1151
+ "access_token",
1152
+ "refresh_token",
1153
+ "id_token",
1154
+ "client_secret",
1155
+ "code_verifier",
1156
+ "code_challenge"
1157
+ ];
1158
+ function getErrorMessage(error) {
1159
+ return error instanceof Error ? error.message : String(error);
1160
+ }
1161
+ function toCamelCase(field) {
1162
+ return field.replace(
1163
+ /_([a-z])/g,
1164
+ (_match, letter) => letter.toUpperCase()
1165
+ );
1166
+ }
1167
+ function escapeRegExp(value) {
1168
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1169
+ }
1170
+ var sensitiveOauthFieldPattern = Array.from(
1171
+ new Set(
1172
+ SENSITIVE_OAUTH_FIELDS.flatMap((field) => [field, toCamelCase(field)])
1173
+ )
1174
+ ).map(escapeRegExp).join("|");
1175
+ var sensitiveQueryParamPattern = new RegExp(
1176
+ `([?&])(${sensitiveOauthFieldPattern})(=)[^&#\\s"'<>]*`,
1177
+ "gi"
1178
+ );
1179
+ function redactSensitiveOauthErrorMessage(message) {
1180
+ return message.replace(
1181
+ sensitiveQueryParamPattern,
1182
+ (_match, prefix, key, separator) => `${prefix}${key}${separator}[REDACTED]`
1183
+ ).replace(
1184
+ new RegExp(`"(${sensitiveOauthFieldPattern})"(\\s*:\\s*)"[^"]*"`, "g"),
1185
+ (_match, key, separator) => `"${key}"${separator}"[REDACTED]"`
1186
+ );
1187
+ }
1188
+ function toRedactedOauthError(error) {
1189
+ const message = redactSensitiveOauthErrorMessage(getErrorMessage(error));
1190
+ if (error instanceof ZapierCliValidationError) {
1191
+ return error.withMessage(message);
1192
+ }
1193
+ return new OauthFlowError({ message });
1194
+ }
1195
+ function toOauthTokenExchangeError(error) {
1196
+ return new OauthTokenExchangeError({
1197
+ message: redactSensitiveOauthErrorMessage(getErrorMessage(error))
1198
+ });
1199
+ }
1200
+
1076
1201
  // src/utils/auth/oauth-callback.ts
1077
1202
  function getCallbackCode({
1078
1203
  callbackUrl,
1079
- transaction,
1080
- recoveryMessage
1204
+ transaction
1081
1205
  }) {
1082
1206
  let parsed;
1083
1207
  try {
1084
1208
  parsed = new URL(callbackUrl.trim());
1085
1209
  } catch {
1086
- throw new ZapierCliValidationError(
1087
- "Paste the final OAuth callback URL from your browser."
1088
- );
1210
+ throw new OauthCallbackError({
1211
+ kind: "invalid_url",
1212
+ message: "Paste the final OAuth callback URL from your browser."
1213
+ });
1089
1214
  }
1090
1215
  const expected = new URL(transaction.redirectUri);
1091
1216
  if (parsed.protocol !== "http:" || parsed.hostname !== expected.hostname || parsed.pathname !== expected.pathname || parsed.port !== expected.port) {
1092
- throw new ZapierCliValidationError(
1093
- `Expected the final OAuth callback URL to start with ${transaction.redirectUri}.`
1094
- );
1217
+ throw new OauthCallbackError({
1218
+ kind: "redirect_mismatch",
1219
+ message: `Expected the final OAuth callback URL to start with ${transaction.redirectUri}.`
1220
+ });
1095
1221
  }
1096
1222
  if (parsed.searchParams.get("state") !== transaction.state) {
1097
- throw new ZapierCliValidationError(
1098
- `OAuth state mismatch.${recoveryMessage ? ` ${recoveryMessage}` : ""}`
1099
- );
1223
+ throw new OauthCallbackError({
1224
+ kind: "state_mismatch",
1225
+ message: "OAuth state mismatch."
1226
+ });
1100
1227
  }
1101
1228
  if (parsed.searchParams.has("error")) {
1102
- throw new ZapierCliValidationError(
1103
- `Authorization denied: ${parsed.searchParams.get("error_description") ?? parsed.searchParams.get("error")}.${recoveryMessage ? ` ${recoveryMessage}` : ""}`
1104
- );
1229
+ throw new OauthAuthorizationDeniedError({
1230
+ reason: String(
1231
+ parsed.searchParams.get("error_description") ?? parsed.searchParams.get("error")
1232
+ )
1233
+ });
1105
1234
  }
1106
1235
  const code = parsed.searchParams.get("code");
1107
1236
  if (!code) {
1108
- throw new ZapierCliValidationError(
1109
- "No authorization code found in the pasted callback URL."
1110
- );
1237
+ throw new OauthCallbackError({
1238
+ kind: "missing_code",
1239
+ message: "No authorization code found in the pasted callback URL."
1240
+ });
1111
1241
  }
1112
1242
  return code;
1113
1243
  }
@@ -1222,7 +1352,9 @@ async function exchangeOauthCode({
1222
1352
  "Content-Type": "application/x-www-form-urlencoded"
1223
1353
  }
1224
1354
  }
1225
- );
1355
+ ).catch((error) => {
1356
+ throw toOauthTokenExchangeError(error);
1357
+ });
1226
1358
  return {
1227
1359
  accessToken: data.access_token,
1228
1360
  refreshToken: data.refresh_token,
@@ -1231,19 +1363,15 @@ async function exchangeOauthCode({
1231
1363
  }
1232
1364
 
1233
1365
  // src/utils/auth/oauth-flow.ts
1234
- var OauthFlowTimeoutError = class extends Error {
1235
- constructor(timeoutMs) {
1236
- super("OAuth flow timed out");
1237
- this.timeoutMs = timeoutMs;
1238
- this.name = "OauthFlowTimeoutError";
1239
- }
1366
+ var LOGIN_OAUTH_COPY = {
1367
+ flowName: "Login",
1368
+ action: "log in",
1369
+ urlLabel: "login URL"
1240
1370
  };
1241
- var OauthAuthorizationDeniedError = class extends Error {
1242
- constructor(reason) {
1243
- super("OAuth authorization denied");
1244
- this.reason = reason;
1245
- this.name = "OauthAuthorizationDeniedError";
1246
- }
1371
+ var SIGNUP_OAUTH_COPY = {
1372
+ flowName: "Signup",
1373
+ action: "sign up",
1374
+ urlLabel: "signup URL"
1247
1375
  };
1248
1376
  function findAvailablePort() {
1249
1377
  return new Promise((resolve4, reject) => {
@@ -1258,70 +1386,86 @@ function findAvailablePort() {
1258
1386
  tryPort(LOGIN_PORTS[portIndex++]);
1259
1387
  } else if (err.code === "EADDRINUSE") {
1260
1388
  reject(
1261
- new Error(
1262
- `All configured OAuth callback ports are busy: ${LOGIN_PORTS.join(", ")}. Please try again later or close applications using these ports.`
1263
- )
1389
+ new OauthFlowError({
1390
+ message: `All configured OAuth callback ports are busy: ${LOGIN_PORTS.join(", ")}. Please try again later or close applications using these ports.`
1391
+ })
1264
1392
  );
1265
1393
  } else {
1266
1394
  reject(err);
1267
1395
  }
1268
1396
  });
1269
1397
  };
1270
- if (LOGIN_PORTS.length > 0) tryPort(LOGIN_PORTS[portIndex++]);
1271
- else reject(new Error("No OAuth callback ports configured"));
1398
+ if (LOGIN_PORTS.length > 0) {
1399
+ tryPort(LOGIN_PORTS[portIndex++]);
1400
+ return;
1401
+ }
1402
+ reject(
1403
+ new OauthFlowError({ message: "No OAuth callback ports configured" })
1404
+ );
1272
1405
  });
1273
1406
  }
1274
1407
  async function runLoginOauthFlow(options) {
1275
- return runOauthFlowEntryPoint({
1276
- ...options,
1408
+ return runOauthFlowForEntryPoint({
1409
+ options,
1277
1410
  entryPoint: "login",
1278
- authAction: "log in",
1279
- flowName: "Login"
1411
+ copy: LOGIN_OAUTH_COPY
1280
1412
  });
1281
1413
  }
1282
1414
  async function runSignupOauthFlow(options) {
1415
+ return runOauthFlowForEntryPoint({
1416
+ options,
1417
+ entryPoint: "signup",
1418
+ copy: SIGNUP_OAUTH_COPY
1419
+ });
1420
+ }
1421
+ function runOauthFlowForEntryPoint({
1422
+ options,
1423
+ entryPoint,
1424
+ copy
1425
+ }) {
1283
1426
  if (options.headless) {
1284
1427
  return runOauthFlowEntryPoint({
1285
1428
  ...options,
1286
- entryPoint: "signup",
1287
- authAction: "sign up",
1288
- flowName: "Signup",
1289
- headless: true
1429
+ entryPoint,
1430
+ headless: true,
1431
+ copy
1290
1432
  });
1291
1433
  }
1292
1434
  return runOauthFlowEntryPoint({
1293
1435
  ...options,
1294
- entryPoint: "signup",
1295
- authAction: "sign up",
1296
- flowName: "Signup"
1436
+ entryPoint,
1437
+ copy,
1438
+ headless: false
1297
1439
  });
1298
1440
  }
1299
- async function runOauthFlowEntryPoint({
1300
- flowName,
1301
- ...options
1302
- }) {
1441
+ async function runOauthFlowEntryPoint(options) {
1303
1442
  try {
1304
- return options.headless ? await runHeadlessSignupOauthFlow(options) : await runOauthFlow(options);
1443
+ return options.headless ? await runHeadlessOauthFlow(options) : await runOauthFlow(options);
1305
1444
  } catch (error) {
1306
1445
  if (error instanceof OauthFlowTimeoutError) {
1307
- throw new Error(
1446
+ throw error.withMessage(
1308
1447
  withRecoveryMessage(
1309
- `${flowName} timed out after ${Math.round(error.timeoutMs / 1e3)} seconds.`,
1448
+ `${options.copy.flowName} timed out after ${Math.round(error.timeoutMs / 1e3)} seconds.`,
1310
1449
  options.recoveryMessage
1311
1450
  )
1312
1451
  );
1313
1452
  }
1314
1453
  if (error instanceof OauthAuthorizationDeniedError) {
1315
- throw new Error(
1454
+ throw error.withMessage(
1316
1455
  withRecoveryMessage(
1317
1456
  `Authorization denied: ${error.reason}.`,
1318
1457
  options.recoveryMessage
1319
1458
  )
1320
1459
  );
1321
1460
  }
1461
+ if (error instanceof OauthCallbackError && options.recoveryMessage) {
1462
+ throw error.withMessage(
1463
+ withRecoveryMessage(error.message, options.recoveryMessage)
1464
+ );
1465
+ }
1322
1466
  if (error instanceof ZapierCliUserCancellationError && !options.silent) {
1323
1467
  log_default.info(`
1324
- \u274C ${flowName} cancelled by user`);
1468
+ \u274C ${options.copy.flowName} cancelled by user`);
1325
1469
  }
1326
1470
  throw error;
1327
1471
  }
@@ -1329,12 +1473,22 @@ async function runOauthFlowEntryPoint({
1329
1473
  function withRecoveryMessage(message, recoveryMessage) {
1330
1474
  return recoveryMessage ? `${message} ${recoveryMessage}` : message;
1331
1475
  }
1476
+ function createMissingHeadlessCallbackUrlError() {
1477
+ return new OauthCallbackError({
1478
+ kind: "missing_callback_url",
1479
+ message: "Paste the final OAuth callback URL from your browser."
1480
+ });
1481
+ }
1482
+ function writeHeadlessOauthStatus(message) {
1483
+ process.stderr.write(`${message}
1484
+ `);
1485
+ }
1332
1486
  async function runOauthFlow({
1333
1487
  timeoutMs = LOGIN_TIMEOUT_MS,
1334
1488
  pkceCredentials,
1335
1489
  baseUrl,
1336
1490
  entryPoint,
1337
- authAction,
1491
+ copy,
1338
1492
  silent = false,
1339
1493
  onProgress
1340
1494
  }) {
@@ -1349,7 +1503,7 @@ async function runOauthFlow({
1349
1503
  const code = await collectLocalCallbackCode({
1350
1504
  transaction,
1351
1505
  timeoutMs,
1352
- authAction,
1506
+ action: copy.action,
1353
1507
  silent,
1354
1508
  onProgress
1355
1509
  });
@@ -1363,92 +1517,104 @@ async function runOauthFlow({
1363
1517
  }
1364
1518
  async function readHeadlessCallbackUrl({
1365
1519
  timeoutMs,
1366
- interactive,
1367
- recoveryMessage
1520
+ interactive
1368
1521
  }) {
1369
- const timeoutMessage = withRecoveryMessage(
1370
- `Signup timed out after ${Math.round(timeoutMs / 1e3)} seconds.`,
1371
- recoveryMessage
1372
- );
1373
- const missingCallbackUrlMessage = withRecoveryMessage(
1374
- "Paste the final OAuth callback URL from your browser.",
1375
- recoveryMessage
1376
- );
1377
1522
  const rl = createInterface({ input: process.stdin, output: process.stderr });
1378
1523
  const abortController = new AbortController();
1379
1524
  const timeoutTimer = setTimeout(() => abortController.abort(), timeoutMs);
1380
- const readUrl = interactive ? rl.question("Paste the final OAuth callback URL: ", {
1381
- signal: abortController.signal
1382
- }) : new Promise((resolve4, reject) => {
1525
+ const readUrl = new Promise((resolve4, reject) => {
1383
1526
  let settled = false;
1384
- const settleResolve = (value) => {
1527
+ const handleLine = (value) => settleResolve(value);
1528
+ const handleAbort = () => settleReject(new OauthFlowTimeoutError({ timeoutMs }));
1529
+ const handleClose = () => settleReject(createMissingHeadlessCallbackUrlError());
1530
+ const handleError = (error) => settleReject(error);
1531
+ const handleCancellation = () => settleReject(new ZapierCliUserCancellationError());
1532
+ const cleanupListeners = () => {
1533
+ abortController.signal.removeEventListener("abort", handleAbort);
1534
+ rl.off("line", handleLine);
1535
+ rl.off("close", handleClose);
1536
+ rl.off("error", handleError);
1537
+ rl.off("SIGINT", handleCancellation);
1538
+ process.off("SIGINT", handleCancellation);
1539
+ process.off("SIGTERM", handleCancellation);
1540
+ };
1541
+ function settleResolve(value) {
1542
+ if (settled) return;
1385
1543
  settled = true;
1544
+ cleanupListeners();
1386
1545
  resolve4(value);
1387
- };
1388
- const settleReject = (error) => {
1546
+ }
1547
+ function settleReject(error) {
1389
1548
  if (settled) return;
1390
1549
  settled = true;
1550
+ cleanupListeners();
1391
1551
  reject(error);
1392
- };
1393
- abortController.signal.addEventListener(
1394
- "abort",
1395
- () => settleReject(new Error(timeoutMessage)),
1396
- { once: true }
1397
- );
1398
- rl.once("line", settleResolve);
1399
- rl.once(
1400
- "close",
1401
- () => settleReject(new ZapierCliValidationError(missingCallbackUrlMessage))
1402
- );
1403
- rl.once("error", settleReject);
1552
+ }
1553
+ abortController.signal.addEventListener("abort", handleAbort, {
1554
+ once: true
1555
+ });
1556
+ rl.once("close", handleClose);
1557
+ rl.once("error", handleError);
1558
+ rl.once("SIGINT", handleCancellation);
1559
+ process.once("SIGINT", handleCancellation);
1560
+ process.once("SIGTERM", handleCancellation);
1561
+ if (interactive) {
1562
+ void rl.question("Paste the final OAuth callback URL: ", {
1563
+ signal: abortController.signal
1564
+ }).then(settleResolve).catch((error) => {
1565
+ if (error instanceof Error && error.name === "AbortError") {
1566
+ settleReject(new OauthFlowTimeoutError({ timeoutMs }));
1567
+ return;
1568
+ }
1569
+ settleReject(error);
1570
+ });
1571
+ return;
1572
+ }
1573
+ rl.once("line", handleLine);
1404
1574
  });
1405
1575
  try {
1406
- return await readUrl.catch((error) => {
1407
- if (error instanceof Error && error.name === "AbortError") {
1408
- throw new Error(timeoutMessage);
1409
- }
1410
- throw error;
1411
- });
1576
+ return await readUrl;
1412
1577
  } finally {
1413
1578
  clearTimeout(timeoutTimer);
1414
1579
  rl.close();
1415
1580
  }
1416
1581
  }
1417
- async function runHeadlessSignupOauthFlow({
1582
+ async function runHeadlessOauthFlow({
1418
1583
  timeoutMs = LOGIN_TIMEOUT_MS,
1419
1584
  pkceCredentials,
1420
1585
  baseUrl,
1586
+ entryPoint,
1421
1587
  interactive = true,
1422
1588
  onProgress,
1423
- recoveryMessage
1589
+ copy
1424
1590
  }) {
1425
1591
  const port = LOGIN_PORTS[0];
1426
1592
  const transaction = await prepareOauthTransaction({
1427
1593
  pkceCredentials,
1428
1594
  baseUrl,
1429
1595
  redirectUri: `http://${OAUTH_LOOPBACK_HOST}:${port}/oauth`,
1430
- entryPoint: "signup"
1596
+ entryPoint
1431
1597
  });
1432
- console.log(
1433
- "Use this mode when signing up from a machine that has no browser."
1598
+ writeHeadlessOauthStatus(
1599
+ `Use this mode to ${copy.action} from a machine that has no browser.`
1600
+ );
1601
+ writeHeadlessOauthStatus(
1602
+ `Open this ${copy.urlLabel} in a browser on another machine:`
1434
1603
  );
1435
- console.log("Open this signup URL in a browser on another machine:");
1436
1604
  console.log(transaction.browserAuthUrl);
1437
- console.log(
1605
+ writeHeadlessOauthStatus(
1438
1606
  `When the browser lands on ${transaction.redirectUri} and cannot connect, paste the full final URL back here.`
1439
1607
  );
1440
1608
  const callbackUrl = await readHeadlessCallbackUrl({
1441
1609
  timeoutMs,
1442
- interactive,
1443
- recoveryMessage
1610
+ interactive
1444
1611
  });
1445
1612
  const code = getCallbackCode({
1446
1613
  callbackUrl,
1447
- transaction,
1448
- recoveryMessage
1614
+ transaction
1449
1615
  });
1450
1616
  onProgress?.({ type: "callback_accepted" });
1451
- console.log("Exchanging authorization code for tokens...");
1617
+ writeHeadlessOauthStatus("Exchanging authorization code for tokens...");
1452
1618
  onProgress?.({ type: "token_exchange_started" });
1453
1619
  const tokens = await exchangeOauthCode({ ...transaction, code });
1454
1620
  onProgress?.({ type: "token_exchange_completed" });
@@ -1457,7 +1623,7 @@ async function runHeadlessSignupOauthFlow({
1457
1623
  async function collectLocalCallbackCode({
1458
1624
  transaction,
1459
1625
  timeoutMs,
1460
- authAction,
1626
+ action,
1461
1627
  silent,
1462
1628
  onProgress
1463
1629
  }) {
@@ -1465,21 +1631,26 @@ async function collectLocalCallbackCode({
1465
1631
  const app = express();
1466
1632
  app.get("/oauth", (req, res) => {
1467
1633
  res.setHeader("Connection", "close");
1468
- if (req.query.state !== transaction.state) {
1469
- res.status(400).end("Invalid state. You can close this tab.");
1470
- } else if (req.query.error) {
1471
- reject(
1472
- new OauthAuthorizationDeniedError(
1473
- String(req.query.error_description ?? req.query.error)
1474
- )
1475
- );
1476
- res.end("Authorization was denied. You can close this tab.");
1477
- } else if (!req.query.code) {
1478
- reject(new Error("No authorization code received"));
1479
- res.end("No authorization code received. You can close this tab.");
1480
- } else {
1481
- resolve4(String(req.query.code));
1634
+ try {
1635
+ const code = getCallbackCode({
1636
+ callbackUrl: new URL(
1637
+ req.originalUrl,
1638
+ transaction.redirectUri
1639
+ ).toString(),
1640
+ transaction
1641
+ });
1642
+ resolve4(code);
1482
1643
  res.end("You can now close this tab and return to the CLI.");
1644
+ } catch (error) {
1645
+ if (error instanceof OauthCallbackError && error.kind === "state_mismatch") {
1646
+ res.status(400).end("Invalid state. You can close this tab.");
1647
+ } else if (error instanceof OauthAuthorizationDeniedError) {
1648
+ reject(error);
1649
+ res.end("Authorization was denied. You can close this tab.");
1650
+ } else {
1651
+ reject(error);
1652
+ res.end("No authorization code received. You can close this tab.");
1653
+ }
1483
1654
  }
1484
1655
  });
1485
1656
  const server = app.listen(
@@ -1500,19 +1671,19 @@ async function collectLocalCallbackCode({
1500
1671
  let timeoutTimer;
1501
1672
  try {
1502
1673
  await waitForServerListening(server);
1503
- await openBrowser({ transaction, authAction, silent, onProgress });
1674
+ await openBrowser({ transaction, action, silent, onProgress });
1504
1675
  const waitForCode = Promise.race([
1505
1676
  promise,
1506
1677
  new Promise((_resolve, rejectTimeout) => {
1507
1678
  timeoutTimer = setTimeout(() => {
1508
- rejectTimeout(new OauthFlowTimeoutError(timeoutMs));
1679
+ rejectTimeout(new OauthFlowTimeoutError({ timeoutMs }));
1509
1680
  }, timeoutMs);
1510
1681
  })
1511
1682
  ]);
1512
1683
  onProgress?.({ type: "callback_waiting" });
1513
1684
  return silent ? await waitForCode : await spinPromise(
1514
1685
  waitForCode,
1515
- `Waiting for you to ${authAction} and authorize`
1686
+ `Waiting for you to ${action} and authorize`
1516
1687
  );
1517
1688
  } finally {
1518
1689
  if (timeoutTimer) clearTimeout(timeoutTimer);
@@ -1542,12 +1713,12 @@ async function waitForServerListening(server) {
1542
1713
  }
1543
1714
  async function openBrowser({
1544
1715
  transaction,
1545
- authAction,
1716
+ action,
1546
1717
  silent,
1547
1718
  onProgress
1548
1719
  }) {
1549
1720
  if (!silent) {
1550
- log_default.info(`Opening your browser to ${authAction}.`);
1721
+ log_default.info(`Opening your browser to ${action}.`);
1551
1722
  log_default.info("If it doesn't open, visit:", transaction.browserAuthUrl);
1552
1723
  }
1553
1724
  onProgress?.({ type: "browser_opening", url: transaction.browserAuthUrl });
@@ -1557,9 +1728,7 @@ async function openBrowser({
1557
1728
  } catch (err) {
1558
1729
  const reason = err instanceof Error ? err.message : String(err);
1559
1730
  if (!silent) {
1560
- log_default.info(
1561
- `Browser did not open automatically to ${authAction}: ${reason}`
1562
- );
1731
+ log_default.info(`Browser did not open automatically to ${action}: ${reason}`);
1563
1732
  log_default.info("Visit this URL manually:", transaction.browserAuthUrl);
1564
1733
  }
1565
1734
  onProgress?.({
@@ -1588,61 +1757,10 @@ async function closeServer({
1588
1757
  });
1589
1758
  }
1590
1759
 
1591
- // src/utils/auth/oauth-errors.ts
1592
- var SENSITIVE_OAUTH_FIELDS = [
1593
- "access_token",
1594
- "refresh_token",
1595
- "id_token",
1596
- "client_secret",
1597
- "code_verifier",
1598
- "code_challenge"
1599
- ];
1600
- function getErrorMessage(error) {
1601
- return error instanceof Error ? error.message : String(error);
1602
- }
1603
- function toCamelCase(field) {
1604
- return field.replace(
1605
- /_([a-z])/g,
1606
- (_match, letter) => letter.toUpperCase()
1607
- );
1608
- }
1609
- function escapeRegExp(value) {
1610
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1611
- }
1612
- var sensitiveOauthFieldPattern = Array.from(
1613
- new Set(
1614
- SENSITIVE_OAUTH_FIELDS.flatMap((field) => [field, toCamelCase(field)])
1615
- )
1616
- ).map(escapeRegExp).join("|");
1617
- var sensitiveQueryParamPattern = new RegExp(
1618
- `([?&])(${sensitiveOauthFieldPattern})(=)[^&#\\s"'<>]*`,
1619
- "gi"
1620
- );
1621
- function redactSensitiveOauthErrorMessage(message) {
1622
- return message.replace(
1623
- sensitiveQueryParamPattern,
1624
- (_match, prefix, key, separator) => `${prefix}${key}${separator}[REDACTED]`
1625
- ).replace(
1626
- new RegExp(`"(${sensitiveOauthFieldPattern})"(\\s*:\\s*)"[^"]*"`, "g"),
1627
- (_match, key, separator) => `"${key}"${separator}"[REDACTED]"`
1628
- );
1629
- }
1630
- function toRedactedOauthError(error) {
1631
- const message = redactSensitiveOauthErrorMessage(getErrorMessage(error));
1632
- if (error instanceof ZapierCliValidationError) {
1633
- return new ZapierCliValidationError(message);
1634
- }
1635
- if (error instanceof Error) {
1636
- const redactedError = new Error(message);
1637
- redactedError.name = error.name;
1638
- return redactedError;
1639
- }
1640
- return new ZapierCliValidationError(message);
1641
- }
1642
-
1643
1760
  // src/utils/auth/account-auth.ts
1644
1761
  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?";
1645
1762
  var SIGNUP_RECOVERY_MESSAGE = "Restart `zapier-sdk signup` to generate a fresh signup URL and try again.";
1763
+ var HEADLESS_LOGIN_RECOVERY_MESSAGE = "Restart `zapier-sdk login --headless` to generate a fresh login URL and try again.";
1646
1764
  var HEADLESS_SIGNUP_RECOVERY_MESSAGE = "Restart `zapier-sdk signup --headless` to generate a fresh signup URL and try again.";
1647
1765
  function getEntryPointLabel(entryPoint) {
1648
1766
  return entryPoint === "signup" ? "Signup" : "Login";
@@ -1762,7 +1880,8 @@ Logging out will delete these credentials and may interrupt other Zapier SDK or
1762
1880
  Log out and ${getActiveCredentialsAction(entryPoint)}?`
1763
1881
  }) : promptlessCredentialResetError(activeCredentials);
1764
1882
  if (!confirmed) {
1765
- console.log(`${flowLabel} cancelled.`);
1883
+ process.stderr.write(`${flowLabel} cancelled.
1884
+ `);
1766
1885
  return false;
1767
1886
  }
1768
1887
  try {
@@ -1781,7 +1900,8 @@ Log out and ${getActiveCredentialsAction(entryPoint)}?`
1781
1900
  message: `${flowLabel} cleanup failed. Reset local session state and continue?`
1782
1901
  });
1783
1902
  if (!reset) {
1784
- console.log(`${flowLabel} cancelled.`);
1903
+ process.stderr.write(`${flowLabel} cancelled.
1904
+ `);
1785
1905
  return false;
1786
1906
  }
1787
1907
  await deleteStoredClientCredentials({
@@ -1795,7 +1915,8 @@ Log out and ${getActiveCredentialsAction(entryPoint)}?`
1795
1915
  message: LEGACY_JWT_UPGRADE_PROMPT
1796
1916
  }) : promptlessLegacyJwtUpgradeError();
1797
1917
  if (!confirmed) {
1798
- console.log(`${flowLabel} cancelled.`);
1918
+ process.stderr.write(`${flowLabel} cancelled.
1919
+ `);
1799
1920
  return false;
1800
1921
  }
1801
1922
  }
@@ -1890,7 +2011,14 @@ async function runOauthForEntryPoint({
1890
2011
  );
1891
2012
  }
1892
2013
  return runOauthWithRedaction(
1893
- () => runLoginOauthFlow({ timeoutMs, pkceCredentials, baseUrl })
2014
+ () => runLoginOauthFlow({
2015
+ timeoutMs,
2016
+ pkceCredentials,
2017
+ baseUrl,
2018
+ headless,
2019
+ interactive,
2020
+ recoveryMessage: headless ? HEADLESS_LOGIN_RECOVERY_MESSAGE : void 0
2021
+ })
1894
2022
  );
1895
2023
  }
1896
2024
  async function runAccountAuth({
@@ -1902,6 +2030,7 @@ async function runAccountAuth({
1902
2030
  const interactive = !resolveNonInteractive(options);
1903
2031
  const resolvedCredentials = await sdk.context.resolveCredentials();
1904
2032
  const pkceCredentials = toPkceCredentials(resolvedCredentials);
2033
+ const headless = options.headless === true;
1905
2034
  const credentialsBaseUrl = await resolveCredentialsBaseUrl({
1906
2035
  ...sdk.context,
1907
2036
  resolvedCredentials
@@ -1931,7 +2060,7 @@ async function runAccountAuth({
1931
2060
  timeoutMs: timeoutSeconds * 1e3,
1932
2061
  pkceCredentials,
1933
2062
  baseUrl: credentialsBaseUrl,
1934
- headless: options.headless === true,
2063
+ headless,
1935
2064
  interactive
1936
2065
  });
1937
2066
  const scopedApi = getOrCreateApiClient({
@@ -1939,9 +2068,10 @@ async function runAccountAuth({
1939
2068
  baseUrl: credentialsBaseUrl
1940
2069
  });
1941
2070
  const profile = await getProfile(scopedApi);
1942
- console.log(getProfileMessage(entryPoint, profile.email));
1943
- console.log(
1944
- "\nGenerating credentials so this machine can make authenticated requests on your behalf."
2071
+ process.stderr.write(`${getProfileMessage(entryPoint, profile.email)}
2072
+ `);
2073
+ process.stderr.write(
2074
+ "\nGenerating credentials so this machine can make authenticated requests on your behalf.\n"
1945
2075
  );
1946
2076
  const credentialName = providedName ?? await resolveCredentialName({
1947
2077
  email: profile.email,
@@ -1957,11 +2087,12 @@ async function runAccountAuth({
1957
2087
  useApprovals,
1958
2088
  cleanupLogPrefix: entryPoint
1959
2089
  });
1960
- console.log(
1961
- `\u2705 Credentials "${credentialName}" created and set as default. You are ready to use the Zapier SDK.`
2090
+ process.stderr.write(
2091
+ `\u2705 Credentials "${credentialName}" created and set as default. You are ready to use the Zapier SDK.
2092
+ `
1962
2093
  );
1963
2094
  if (useApprovals) {
1964
- console.log("\u{1F510} Approvals are enabled for these credentials.");
2095
+ process.stderr.write("\u{1F510} Approvals are enabled for these credentials.\n");
1965
2096
  }
1966
2097
  emitAccountAuthSuccess({ sdk, profile, clientId });
1967
2098
  }
@@ -1980,7 +2111,10 @@ var LoginSchema = z.object({
1980
2111
  skipPrompts: z.boolean().optional().meta({
1981
2112
  deprecated: true,
1982
2113
  deprecationMessage: "Use --non-interactive instead."
1983
- })
2114
+ }),
2115
+ headless: z.boolean().optional().describe(
2116
+ "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."
2117
+ )
1984
2118
  }).describe("Log in to Zapier to access your account");
1985
2119
 
1986
2120
  // src/plugins/login/index.ts
@@ -4569,7 +4703,7 @@ function collectDeprecationNoticeAndForward(event, onEvent) {
4569
4703
  // package.json with { type: 'json' }
4570
4704
  var package_default = {
4571
4705
  name: "@zapier/zapier-sdk-cli",
4572
- version: "0.60.0"};
4706
+ version: "0.61.1"};
4573
4707
 
4574
4708
  // src/sdk.ts
4575
4709
  injectCliLogin(login_exports);
@@ -4609,7 +4743,7 @@ function createZapierCliSdk(options = {}) {
4609
4743
 
4610
4744
  // package.json
4611
4745
  var package_default2 = {
4612
- version: "0.60.0"};
4746
+ version: "0.61.1"};
4613
4747
 
4614
4748
  // src/telemetry/builders.ts
4615
4749
  function createCliBaseEvent(context = {}) {