@better-webhook/cli 3.6.0 → 3.7.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/README.md CHANGED
@@ -165,10 +165,12 @@ Start a server to capture incoming webhooks. All captured webhooks are saved to
165
165
  better-webhook capture [options]
166
166
  ```
167
167
 
168
- | Option | Description | Default |
169
- | ------------------- | ----------------- | --------- |
170
- | `-p, --port <port>` | Port to listen on | `3001` |
171
- | `-h, --host <host>` | Host to bind to | `0.0.0.0` |
168
+ | Option | Description | Default |
169
+ | ------------------- | ------------------------ | --------- |
170
+ | `-p, --port <port>` | Port to listen on | `3001` |
171
+ | `-h, --host <host>` | Host to bind to | `0.0.0.0` |
172
+ | `-v, --verbose` | Show raw request details | |
173
+ | `--debug` | Alias for `--verbose` | |
172
174
 
173
175
  **Features:**
174
176
 
@@ -176,11 +178,15 @@ better-webhook capture [options]
176
178
  - Saves full request including headers, body, query params
177
179
  - WebSocket server for real-time notifications
178
180
  - Returns capture ID in response for easy reference
181
+ - `--verbose/--debug` prints raw request data; use with care since it may include sensitive payloads
179
182
 
180
183
  **Example:**
181
184
 
182
185
  ```bash
183
186
  better-webhook capture --port 4000 --host localhost
187
+
188
+ # Show request headers + raw body
189
+ better-webhook capture --verbose
184
190
  ```
185
191
 
186
192
  ---
package/dist/index.cjs CHANGED
@@ -1297,11 +1297,13 @@ var CaptureServer = class {
1297
1297
  captureCount = 0;
1298
1298
  enableWebSocket;
1299
1299
  onCapture;
1300
+ verbose;
1300
1301
  constructor(options) {
1301
1302
  const capturesDir = typeof options === "string" ? options : options?.capturesDir;
1302
1303
  this.capturesDir = capturesDir || (0, import_path2.join)((0, import_os2.homedir)(), ".better-webhook", "captures");
1303
1304
  this.enableWebSocket = typeof options === "object" ? options?.enableWebSocket !== false : true;
1304
1305
  this.onCapture = typeof options === "object" ? options?.onCapture : void 0;
1306
+ this.verbose = typeof options === "object" ? options?.verbose === true : false;
1305
1307
  if (!(0, import_fs2.existsSync)(this.capturesDir)) {
1306
1308
  (0, import_fs2.mkdirSync)(this.capturesDir, { recursive: true });
1307
1309
  }
@@ -1399,7 +1401,16 @@ var CaptureServer = class {
1399
1401
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
1400
1402
  const id = (0, import_crypto2.randomUUID)();
1401
1403
  const url = req.url || "/";
1402
- const urlParts = new URL(url, `http://${req.headers.host || "localhost"}`);
1404
+ const hostHeader = req.headers.host;
1405
+ const hostValue = typeof hostHeader === "string" ? hostHeader : "";
1406
+ const isHostSafe = /^[a-z0-9.-]+(:\d+)?$/i.test(hostValue);
1407
+ const baseUrl = isHostSafe ? `http://${hostValue}` : "http://localhost";
1408
+ let urlParts;
1409
+ try {
1410
+ urlParts = new URL(url, baseUrl);
1411
+ } catch {
1412
+ urlParts = new URL(url, "http://localhost");
1413
+ }
1403
1414
  const query = {};
1404
1415
  for (const [key, value] of urlParts.searchParams.entries()) {
1405
1416
  if (query[key]) {
@@ -1459,6 +1470,34 @@ var CaptureServer = class {
1459
1470
  console.log(
1460
1471
  `\u{1F4E6} ${req.method} ${urlParts.pathname}${providerStr} -> ${filename}`
1461
1472
  );
1473
+ if (this.verbose) {
1474
+ const headerEntries = Object.entries(req.headers).map(([key, value]) => {
1475
+ if (Array.isArray(value)) {
1476
+ return `${key}: ${value.join(", ")}`;
1477
+ }
1478
+ if (value === void 0) {
1479
+ return `${key}:`;
1480
+ }
1481
+ return `${key}: ${value}`;
1482
+ }).join("\n");
1483
+ const method = req.method || "GET";
1484
+ const providerLabel = provider || "unknown";
1485
+ const contentTypeLabel = contentType || "(none)";
1486
+ console.log(
1487
+ [
1488
+ "[debug] request",
1489
+ `method: ${method}`,
1490
+ `path: ${urlParts.pathname}`,
1491
+ `provider: ${providerLabel}`,
1492
+ `content-type: ${contentTypeLabel}`,
1493
+ `body-length: ${rawBody.length}`,
1494
+ "headers:",
1495
+ headerEntries || "(none)",
1496
+ "raw-body:",
1497
+ rawBody
1498
+ ].join("\n")
1499
+ );
1500
+ }
1462
1501
  this.onCapture?.({ file: filename, capture: captured });
1463
1502
  if (this.enableWebSocket) {
1464
1503
  this.broadcast({
@@ -1594,27 +1633,30 @@ var CaptureServer = class {
1594
1633
  };
1595
1634
 
1596
1635
  // src/commands/capture.ts
1597
- var capture = new import_commander3.Command().name("capture").description("Start a server to capture incoming webhooks").option("-p, --port <port>", "Port to listen on", "3001").option("-h, --host <host>", "Host to bind to", "0.0.0.0").action(async (options) => {
1598
- const port = parseInt(options.port, 10);
1599
- if (isNaN(port) || port < 0 || port > 65535) {
1600
- console.error(import_chalk3.default.red("Invalid port number"));
1601
- process.exitCode = 1;
1602
- return;
1603
- }
1604
- const server = new CaptureServer();
1605
- try {
1606
- await server.start(port, options.host);
1607
- const shutdown = async () => {
1608
- await server.stop();
1609
- process.exit(0);
1610
- };
1611
- process.on("SIGINT", shutdown);
1612
- process.on("SIGTERM", shutdown);
1613
- } catch (error) {
1614
- console.error(import_chalk3.default.red(`Failed to start server: ${error.message}`));
1615
- process.exitCode = 1;
1636
+ var capture = new import_commander3.Command().name("capture").description("Start a server to capture incoming webhooks").option("-p, --port <port>", "Port to listen on", "3001").option("-h, --host <host>", "Host to bind to", "0.0.0.0").option("-v, --verbose", "Show raw request details").option("--debug", "Alias for --verbose").action(
1637
+ async (options) => {
1638
+ const port = parseInt(options.port, 10);
1639
+ if (isNaN(port) || port < 0 || port > 65535) {
1640
+ console.error(import_chalk3.default.red("Invalid port number"));
1641
+ process.exitCode = 1;
1642
+ return;
1643
+ }
1644
+ const verbose = Boolean(options.verbose || options.debug);
1645
+ const server = new CaptureServer({ verbose });
1646
+ try {
1647
+ await server.start(port, options.host);
1648
+ const shutdown = async () => {
1649
+ await server.stop();
1650
+ process.exit(0);
1651
+ };
1652
+ process.on("SIGINT", shutdown);
1653
+ process.on("SIGTERM", shutdown);
1654
+ } catch (error) {
1655
+ console.error(import_chalk3.default.red(`Failed to start server: ${error.message}`));
1656
+ process.exitCode = 1;
1657
+ }
1616
1658
  }
1617
- });
1659
+ );
1618
1660
 
1619
1661
  // src/commands/captures.ts
1620
1662
  var import_commander4 = require("commander");
package/dist/index.js CHANGED
@@ -1291,11 +1291,13 @@ var CaptureServer = class {
1291
1291
  captureCount = 0;
1292
1292
  enableWebSocket;
1293
1293
  onCapture;
1294
+ verbose;
1294
1295
  constructor(options) {
1295
1296
  const capturesDir = typeof options === "string" ? options : options?.capturesDir;
1296
1297
  this.capturesDir = capturesDir || join2(homedir2(), ".better-webhook", "captures");
1297
1298
  this.enableWebSocket = typeof options === "object" ? options?.enableWebSocket !== false : true;
1298
1299
  this.onCapture = typeof options === "object" ? options?.onCapture : void 0;
1300
+ this.verbose = typeof options === "object" ? options?.verbose === true : false;
1299
1301
  if (!existsSync2(this.capturesDir)) {
1300
1302
  mkdirSync2(this.capturesDir, { recursive: true });
1301
1303
  }
@@ -1393,7 +1395,16 @@ var CaptureServer = class {
1393
1395
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
1394
1396
  const id = randomUUID();
1395
1397
  const url = req.url || "/";
1396
- const urlParts = new URL(url, `http://${req.headers.host || "localhost"}`);
1398
+ const hostHeader = req.headers.host;
1399
+ const hostValue = typeof hostHeader === "string" ? hostHeader : "";
1400
+ const isHostSafe = /^[a-z0-9.-]+(:\d+)?$/i.test(hostValue);
1401
+ const baseUrl = isHostSafe ? `http://${hostValue}` : "http://localhost";
1402
+ let urlParts;
1403
+ try {
1404
+ urlParts = new URL(url, baseUrl);
1405
+ } catch {
1406
+ urlParts = new URL(url, "http://localhost");
1407
+ }
1397
1408
  const query = {};
1398
1409
  for (const [key, value] of urlParts.searchParams.entries()) {
1399
1410
  if (query[key]) {
@@ -1453,6 +1464,34 @@ var CaptureServer = class {
1453
1464
  console.log(
1454
1465
  `\u{1F4E6} ${req.method} ${urlParts.pathname}${providerStr} -> ${filename}`
1455
1466
  );
1467
+ if (this.verbose) {
1468
+ const headerEntries = Object.entries(req.headers).map(([key, value]) => {
1469
+ if (Array.isArray(value)) {
1470
+ return `${key}: ${value.join(", ")}`;
1471
+ }
1472
+ if (value === void 0) {
1473
+ return `${key}:`;
1474
+ }
1475
+ return `${key}: ${value}`;
1476
+ }).join("\n");
1477
+ const method = req.method || "GET";
1478
+ const providerLabel = provider || "unknown";
1479
+ const contentTypeLabel = contentType || "(none)";
1480
+ console.log(
1481
+ [
1482
+ "[debug] request",
1483
+ `method: ${method}`,
1484
+ `path: ${urlParts.pathname}`,
1485
+ `provider: ${providerLabel}`,
1486
+ `content-type: ${contentTypeLabel}`,
1487
+ `body-length: ${rawBody.length}`,
1488
+ "headers:",
1489
+ headerEntries || "(none)",
1490
+ "raw-body:",
1491
+ rawBody
1492
+ ].join("\n")
1493
+ );
1494
+ }
1456
1495
  this.onCapture?.({ file: filename, capture: captured });
1457
1496
  if (this.enableWebSocket) {
1458
1497
  this.broadcast({
@@ -1588,27 +1627,30 @@ var CaptureServer = class {
1588
1627
  };
1589
1628
 
1590
1629
  // src/commands/capture.ts
1591
- var capture = new Command3().name("capture").description("Start a server to capture incoming webhooks").option("-p, --port <port>", "Port to listen on", "3001").option("-h, --host <host>", "Host to bind to", "0.0.0.0").action(async (options) => {
1592
- const port = parseInt(options.port, 10);
1593
- if (isNaN(port) || port < 0 || port > 65535) {
1594
- console.error(chalk3.red("Invalid port number"));
1595
- process.exitCode = 1;
1596
- return;
1597
- }
1598
- const server = new CaptureServer();
1599
- try {
1600
- await server.start(port, options.host);
1601
- const shutdown = async () => {
1602
- await server.stop();
1603
- process.exit(0);
1604
- };
1605
- process.on("SIGINT", shutdown);
1606
- process.on("SIGTERM", shutdown);
1607
- } catch (error) {
1608
- console.error(chalk3.red(`Failed to start server: ${error.message}`));
1609
- process.exitCode = 1;
1630
+ var capture = new Command3().name("capture").description("Start a server to capture incoming webhooks").option("-p, --port <port>", "Port to listen on", "3001").option("-h, --host <host>", "Host to bind to", "0.0.0.0").option("-v, --verbose", "Show raw request details").option("--debug", "Alias for --verbose").action(
1631
+ async (options) => {
1632
+ const port = parseInt(options.port, 10);
1633
+ if (isNaN(port) || port < 0 || port > 65535) {
1634
+ console.error(chalk3.red("Invalid port number"));
1635
+ process.exitCode = 1;
1636
+ return;
1637
+ }
1638
+ const verbose = Boolean(options.verbose || options.debug);
1639
+ const server = new CaptureServer({ verbose });
1640
+ try {
1641
+ await server.start(port, options.host);
1642
+ const shutdown = async () => {
1643
+ await server.stop();
1644
+ process.exit(0);
1645
+ };
1646
+ process.on("SIGINT", shutdown);
1647
+ process.on("SIGTERM", shutdown);
1648
+ } catch (error) {
1649
+ console.error(chalk3.red(`Failed to start server: ${error.message}`));
1650
+ process.exitCode = 1;
1651
+ }
1610
1652
  }
1611
- });
1653
+ );
1612
1654
 
1613
1655
  // src/commands/captures.ts
1614
1656
  import { Command as Command4 } from "commander";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-webhook/cli",
3
- "version": "3.6.0",
3
+ "version": "3.7.0",
4
4
  "description": "Modern CLI for developing, capturing, and replaying webhooks locally with dashboard UI.",
5
5
  "type": "module",
6
6
  "bin": {