hooklens 0.1.0 → 0.2.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
@@ -10,7 +10,7 @@
10
10
  </div>
11
11
 
12
12
  > [!WARNING]
13
- > HookLens is under active development and not yet published to npm. Star or watch the repo to get notified when the first release drops.
13
+ > HookLens is under active development. Expect changes as more providers and debugging workflows are added.
14
14
 
15
15
  ---
16
16
 
@@ -63,11 +63,16 @@ HookLens intercepts the request at the HTTP level using `node:http` directly, be
63
63
 
64
64
  ## Install
65
65
 
66
- > [!NOTE]
67
- > Not on npm yet. For now, you can clone and build locally.
68
-
69
66
  **Requires Node.js 24 or newer** (HookLens uses the built-in `node:sqlite` module).
70
67
 
68
+ ```bash
69
+ npm install -g hooklens
70
+ ```
71
+
72
+ ## Local development
73
+
74
+ If you want to run HookLens from source or contribute to it:
75
+
71
76
  ```bash
72
77
  git clone https://github.com/Ilia01/hooklens.git
73
78
  cd hooklens
package/dist/index.js CHANGED
@@ -3,6 +3,14 @@
3
3
  // src/cli/index.ts
4
4
  import { Command as Command4 } from "commander";
5
5
 
6
+ // src/errors.ts
7
+ function toError(value) {
8
+ return value instanceof Error ? value : new Error(String(value));
9
+ }
10
+ function errorMessage(value) {
11
+ return value instanceof Error ? value.message : String(value);
12
+ }
13
+
6
14
  // src/cli/listen.ts
7
15
  import { Command } from "commander";
8
16
 
@@ -180,7 +188,10 @@ async function forwardEvent(targetUrl, event, timeoutMs = DEFAULT_FORWARD_TIMEOU
180
188
  if (isAbortError(error)) {
181
189
  throw new Error(`forward timed out after ${timeoutMs}ms`);
182
190
  }
183
- throw error;
191
+ const cause = error instanceof Error ? error.cause : void 0;
192
+ const code = cause instanceof Error ? cause.code : void 0;
193
+ const message = cause instanceof Error && cause.message ? cause.message : code;
194
+ throw new Error(message ?? errorMessage(error));
184
195
  } finally {
185
196
  clearTimeout(timeout);
186
197
  }
@@ -213,9 +224,14 @@ function createServer(opts) {
213
224
  const forwarded = await forwardEvent(opts.forwardTo, event, forwardTimeoutMs);
214
225
  res.statusCode = forwarded.status;
215
226
  res.end(forwarded.body);
216
- } catch {
227
+ } catch (error) {
228
+ const err = toError(error);
229
+ try {
230
+ opts.onForwardError?.(event, err);
231
+ } catch {
232
+ }
217
233
  res.statusCode = 502;
218
- res.end("bad gateway");
234
+ res.end(`bad gateway: ${err.message}`);
219
235
  }
220
236
  };
221
237
  return {
@@ -235,7 +251,7 @@ function createServer(opts) {
235
251
  return;
236
252
  }
237
253
  res.statusCode = 500;
238
- res.end(err instanceof Error ? err.message : String(err));
254
+ res.end(errorMessage(err));
239
255
  });
240
256
  });
241
257
  httpServer = server;
@@ -428,6 +444,9 @@ function createTerminal(stdout = process.stdout, stderr = process.stderr) {
428
444
  }
429
445
  writeLine(stdout, `${summary} ${result.message}`);
430
446
  },
447
+ printForwardError(eventId, reason) {
448
+ writeLine(stdout, `${chalk.red("FWD")} ${chalk.bold(eventId)} ${reason}`);
449
+ },
431
450
  printEventList(events) {
432
451
  if (!events.length) {
433
452
  writeLine(stdout, chalk.dim("No stored events."));
@@ -455,7 +474,7 @@ function createTerminal(stdout = process.stdout, stderr = process.stderr) {
455
474
  };
456
475
  }
457
476
 
458
- // src/verify/stripe.ts
477
+ // src/verify/github.ts
459
478
  import crypto2 from "crypto";
460
479
 
461
480
  // src/verify/headers.ts
@@ -466,10 +485,86 @@ function getHeaderCaseInsensitive(headers, name) {
466
485
  }
467
486
  return void 0;
468
487
  }
488
+ function tryCanonicalForm(payload) {
489
+ try {
490
+ const canonical = JSON.stringify(JSON.parse(payload));
491
+ return canonical === payload ? null : canonical;
492
+ } catch {
493
+ return null;
494
+ }
495
+ }
496
+
497
+ // src/verify/github.ts
498
+ var PROVIDER = "github";
499
+ var PREFIX = "sha256=";
500
+ var SHA256_HEX = /^[0-9a-fA-F]{64}$/;
501
+ function computeHmac(secret, payload) {
502
+ return crypto2.createHmac("sha256", secret).update(payload).digest("hex");
503
+ }
504
+ function constantTimeMatch(expected, actual) {
505
+ if (expected.length !== actual.length) return false;
506
+ const expectedBuf = Buffer.from(expected, "utf8");
507
+ const actualBuf = Buffer.from(actual, "utf8");
508
+ return crypto2.timingSafeEqual(expectedBuf, actualBuf);
509
+ }
510
+ function success(message) {
511
+ return { valid: true, provider: PROVIDER, code: "valid", message };
512
+ }
513
+ function failure(code, message) {
514
+ return { valid: false, provider: PROVIDER, code, message };
515
+ }
516
+ function verifyGitHubSignature(opts) {
517
+ if (!opts.header) {
518
+ return failure(
519
+ "missing_header",
520
+ "x-hub-signature-256 header not found. Is this actually from GitHub?"
521
+ );
522
+ }
523
+ if (!opts.header.startsWith(PREFIX)) {
524
+ return failure("malformed_header", "x-hub-signature-256 header must start with sha256=");
525
+ }
526
+ const signature = opts.header.slice(PREFIX.length);
527
+ if (signature.length === 0) {
528
+ return failure("malformed_header", "x-hub-signature-256 header has no signature after sha256=");
529
+ }
530
+ if (!SHA256_HEX.test(signature)) {
531
+ return failure("malformed_header", "x-hub-signature-256 header has invalid sha256 hex digest");
532
+ }
533
+ const normalizedSignature = signature.toLowerCase();
534
+ const expected = computeHmac(opts.secret, opts.payload);
535
+ if (constantTimeMatch(expected, normalizedSignature)) {
536
+ return success("Signature verified.");
537
+ }
538
+ const canonical = tryCanonicalForm(opts.payload);
539
+ if (canonical !== null) {
540
+ const expectedCanonical = computeHmac(opts.secret, canonical);
541
+ if (constantTimeMatch(expectedCanonical, normalizedSignature)) {
542
+ return failure(
543
+ "body_mutated",
544
+ "Signature mismatch with correct secret. Body was likely parsed and re-serialized by your framework."
545
+ );
546
+ }
547
+ }
548
+ return failure(
549
+ "signature_mismatch",
550
+ "Signature mismatch. Check your webhook secret matches the GitHub settings."
551
+ );
552
+ }
553
+ function createGitHubVerifier(opts) {
554
+ return {
555
+ provider: PROVIDER,
556
+ verify: (event) => verifyGitHubSignature({
557
+ payload: event.body,
558
+ header: getHeaderCaseInsensitive(event.headers, "x-hub-signature-256"),
559
+ secret: opts.secret
560
+ })
561
+ };
562
+ }
469
563
 
470
564
  // src/verify/stripe.ts
565
+ import crypto3 from "crypto";
471
566
  var DEFAULT_TOLERANCE_SECONDS = 300;
472
- var PROVIDER = "stripe";
567
+ var PROVIDER2 = "stripe";
473
568
  function parseHeader(header) {
474
569
  let timestamp = null;
475
570
  const signatures = [];
@@ -489,42 +584,34 @@ function parseHeader(header) {
489
584
  if (timestamp === null || signatures.length === 0) return null;
490
585
  return { timestamp, signatures };
491
586
  }
492
- function computeHmac(secret, signedPayload) {
493
- return crypto2.createHmac("sha256", secret).update(signedPayload).digest("hex");
587
+ function computeHmac2(secret, signedPayload) {
588
+ return crypto3.createHmac("sha256", secret).update(signedPayload).digest("hex");
494
589
  }
495
- function constantTimeMatch(expected, candidates) {
590
+ function constantTimeMatch2(expected, candidates) {
496
591
  const expectedBuf = Buffer.from(expected, "utf8");
497
592
  for (const candidate of candidates) {
498
593
  if (candidate.length !== expected.length) continue;
499
594
  const candidateBuf = Buffer.from(candidate, "utf8");
500
- if (crypto2.timingSafeEqual(expectedBuf, candidateBuf)) return true;
595
+ if (crypto3.timingSafeEqual(expectedBuf, candidateBuf)) return true;
501
596
  }
502
597
  return false;
503
598
  }
504
- function tryCanonicalForm(payload) {
505
- try {
506
- const canonical = JSON.stringify(JSON.parse(payload));
507
- return canonical === payload ? null : canonical;
508
- } catch {
509
- return null;
510
- }
511
- }
512
- function success(message) {
513
- return { valid: true, provider: PROVIDER, code: "valid", message };
599
+ function success2(message) {
600
+ return { valid: true, provider: PROVIDER2, code: "valid", message };
514
601
  }
515
- function failure(code, message) {
516
- return { valid: false, provider: PROVIDER, code, message };
602
+ function failure2(code, message) {
603
+ return { valid: false, provider: PROVIDER2, code, message };
517
604
  }
518
605
  function verifyStripeSignature(opts) {
519
606
  if (!opts.header) {
520
- return failure(
607
+ return failure2(
521
608
  "missing_header",
522
609
  "stripe-signature header not found. Is this actually from Stripe?"
523
610
  );
524
611
  }
525
612
  const parsed = parseHeader(opts.header);
526
613
  if (!parsed) {
527
- return failure(
614
+ return failure2(
528
615
  "malformed_header",
529
616
  "stripe-signature header is malformed. Expected format: t=timestamp,v1=signature"
530
617
  );
@@ -534,34 +621,34 @@ function verifyStripeSignature(opts) {
534
621
  const ageSeconds = Math.floor(nowMs / 1e3) - parsed.timestamp;
535
622
  if (ageSeconds > tolerance) {
536
623
  const minutes = Math.floor(ageSeconds / 60);
537
- return failure(
624
+ return failure2(
538
625
  "expired_timestamp",
539
626
  `Timestamp is ${minutes} minutes old. Event expired or your clock is drifting.`
540
627
  );
541
628
  }
542
629
  const signedPayload = `${parsed.timestamp}.${opts.payload}`;
543
- const expected = computeHmac(opts.secret, signedPayload);
544
- if (constantTimeMatch(expected, parsed.signatures)) {
545
- return success("Signature verified.");
630
+ const expected = computeHmac2(opts.secret, signedPayload);
631
+ if (constantTimeMatch2(expected, parsed.signatures)) {
632
+ return success2("Signature verified.");
546
633
  }
547
634
  const canonical = tryCanonicalForm(opts.payload);
548
635
  if (canonical !== null) {
549
- const expectedCanonical = computeHmac(opts.secret, `${parsed.timestamp}.${canonical}`);
550
- if (constantTimeMatch(expectedCanonical, parsed.signatures)) {
551
- return failure(
636
+ const expectedCanonical = computeHmac2(opts.secret, `${parsed.timestamp}.${canonical}`);
637
+ if (constantTimeMatch2(expectedCanonical, parsed.signatures)) {
638
+ return failure2(
552
639
  "body_mutated",
553
640
  "Signature mismatch with correct secret. Body was likely parsed and re-serialized by your framework."
554
641
  );
555
642
  }
556
643
  }
557
- return failure(
644
+ return failure2(
558
645
  "signature_mismatch",
559
646
  "Signature mismatch. Check your webhook secret matches the Stripe dashboard."
560
647
  );
561
648
  }
562
649
  function createStripeVerifier(opts) {
563
650
  return {
564
- provider: PROVIDER,
651
+ provider: PROVIDER2,
565
652
  verify: (event) => verifyStripeSignature({
566
653
  payload: event.body,
567
654
  header: getHeaderCaseInsensitive(event.headers, "stripe-signature"),
@@ -589,8 +676,14 @@ function buildVerifier(flags) {
589
676
  }
590
677
  return createStripeVerifier({ secret: flags.secret });
591
678
  }
679
+ case "github": {
680
+ if (!flags.secret) {
681
+ throw new Error("--secret is required when --verify github is set");
682
+ }
683
+ return createGitHubVerifier({ secret: flags.secret });
684
+ }
592
685
  default:
593
- throw new Error(`Unknown --verify provider "${flags.verify}". Supported: stripe`);
686
+ throw new Error(`Unknown --verify provider "${flags.verify}". Supported: stripe, github`);
594
687
  }
595
688
  }
596
689
  async function stopServer(server) {
@@ -601,8 +694,7 @@ function printEventCapturedBestEffort(terminal, event, result) {
601
694
  try {
602
695
  terminal.printEventCaptured(event, result);
603
696
  } catch (error) {
604
- const message = error instanceof Error ? error.message : String(error);
605
- console.error(`Failed to print captured event: ${message}`);
697
+ console.error(`Failed to print captured event: ${errorMessage(error)}`);
606
698
  }
607
699
  }
608
700
  async function runListen(flags, deps = {}) {
@@ -656,7 +748,8 @@ async function runListen(flags, deps = {}) {
656
748
  storage,
657
749
  verifier,
658
750
  forwardTo: flags.forwardTo,
659
- onEvent: (event, result) => printEventCapturedBestEffort(terminal, event, result)
751
+ onEvent: (event, result) => printEventCapturedBestEffort(terminal, event, result),
752
+ onForwardError: (event, error) => terminal.printForwardError(event.id, error.message)
660
753
  });
661
754
  signals.on("SIGINT", onSignal);
662
755
  signals.on("SIGTERM", onSignal);
@@ -680,12 +773,20 @@ async function runListen(flags, deps = {}) {
680
773
  throw error;
681
774
  }
682
775
  }
683
- var listenCommand = new Command("listen").description("Start receiving webhooks").option("-p, --port <port>", "Port to listen on", "4400").option("--verify <provider>", "Verify signatures (stripe)").option("--secret <secret>", "Webhook signing secret").option("--forward-to <url>", "Forward received webhooks to this URL").action(async (options) => {
776
+ var listenCommand = new Command("listen").description("Start receiving webhooks").option("-p, --port <port>", "Port to listen on", "4400").option("--verify <provider>", "Verify signatures (stripe, github)").option("--secret <secret>", "Webhook signing secret").option("--forward-to <url>", "Forward received webhooks to this URL").addHelpText(
777
+ "after",
778
+ `
779
+ Examples:
780
+ hooklens listen
781
+ hooklens listen -p 8080 --forward-to http://localhost:3000/webhook
782
+ hooklens listen --verify stripe --secret whsec_xxx
783
+ hooklens listen --verify github --secret ghsecret_xxx`
784
+ ).action(async (options) => {
684
785
  const terminal = createTerminal();
685
786
  try {
686
787
  await runListen(options, { terminal });
687
788
  } catch (error) {
688
- terminal.printError(error instanceof Error ? error.message : String(error));
789
+ terminal.printError(errorMessage(error));
689
790
  process.exitCode = 1;
690
791
  }
691
792
  });
@@ -711,12 +812,18 @@ async function runList(flags, deps = {}) {
711
812
  storage.close();
712
813
  }
713
814
  }
714
- var listCommand = new Command2("list").description("Show received webhook events").option("-n, --limit <count>", "Number of events to show", "20").action(async (options) => {
815
+ var listCommand = new Command2("list").description("Show received webhook events").option("-n, --limit <count>", "Number of events to show", "20").addHelpText(
816
+ "after",
817
+ `
818
+ Examples:
819
+ hooklens list
820
+ hooklens list -n 5`
821
+ ).action(async (options) => {
715
822
  const terminal = createTerminal();
716
823
  try {
717
824
  await runList(options, { terminal });
718
825
  } catch (error) {
719
- terminal.printError(error instanceof Error ? error.message : String(error));
826
+ terminal.printError(errorMessage(error));
720
827
  process.exitCode = 1;
721
828
  }
722
829
  });
@@ -749,19 +856,24 @@ async function runReplay(eventId, flags, deps = {}) {
749
856
  body
750
857
  });
751
858
  } catch (error) {
752
- const message = error instanceof Error ? error.message : String(error);
753
- throw new Error(`Failed to replay "${eventId}" to ${targetUrl}: ${message}`);
859
+ throw new Error(`Failed to replay "${eventId}" to ${targetUrl}: ${errorMessage(error)}`);
754
860
  }
755
861
  } finally {
756
862
  storage.close();
757
863
  }
758
864
  }
759
- var replayCommand = new Command3("replay").description("Replay a stored webhook event").argument("<event-id>", "ID of the event to replay").option("--to <url>", "Target URL to send the event to", DEFAULT_REPLAY_TARGET_URL).action(async (eventId, options) => {
865
+ var replayCommand = new Command3("replay").description("Replay a stored webhook event").argument("<event-id>", "ID of the event to replay").option("--to <url>", "Target URL to send the event to", DEFAULT_REPLAY_TARGET_URL).addHelpText(
866
+ "after",
867
+ `
868
+ Examples:
869
+ hooklens replay evt_abc123
870
+ hooklens replay evt_abc123 --to http://localhost:8080/hook`
871
+ ).action(async (eventId, options) => {
760
872
  const terminal = createTerminal();
761
873
  try {
762
874
  await runReplay(eventId, options, { terminal });
763
875
  } catch (error) {
764
- terminal.printError(error instanceof Error ? error.message : String(error));
876
+ terminal.printError(errorMessage(error));
765
877
  process.exitCode = 1;
766
878
  }
767
879
  });
@@ -775,8 +887,7 @@ program.addCommand(replayCommand);
775
887
  try {
776
888
  await program.parseAsync(process.argv);
777
889
  } catch (error) {
778
- const message = error instanceof Error ? error.message : String(error);
779
- console.error(message);
890
+ console.error(errorMessage(error));
780
891
  process.exitCode = 1;
781
892
  }
782
893
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli/index.ts","../src/cli/listen.ts","../src/server/index.ts","../src/storage/index.ts","../src/types.ts","../src/ui/terminal.ts","../src/verify/stripe.ts","../src/verify/headers.ts","../src/cli/list.ts","../src/cli/replay.ts"],"sourcesContent":["import { Command } from 'commander'\nimport { listenCommand } from './listen.js'\nimport { listCommand } from './list.js'\nimport { replayCommand } from './replay.js'\n\nconst program = new Command()\n\nprogram\n .name('hooklens')\n .description('Inspect, verify, and replay webhooks from your terminal')\n .version('0.1.0')\n\nprogram.addCommand(listenCommand)\nprogram.addCommand(listCommand)\nprogram.addCommand(replayCommand)\n\ntry {\n await program.parseAsync(process.argv)\n} catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n console.error(message)\n process.exitCode = 1\n}\n","import { Command } from 'commander'\nimport { createServer, type Server } from '../server/index.js'\nimport { createStorage, defaultDbPath } from '../storage/index.js'\nimport type { VerificationResult, Verifier, WebhookEvent } from '../types.js'\nimport { createTerminal, type TerminalUI } from '../ui/terminal.js'\nimport { createStripeVerifier } from '../verify/stripe.js'\n\nexport interface ListenFlags {\n port?: string | number\n verify?: string\n secret?: string\n forwardTo?: string\n}\n\nexport interface SignalBus {\n on(event: 'SIGINT' | 'SIGTERM', listener: () => void): void\n off(event: 'SIGINT' | 'SIGTERM', listener: () => void): void\n}\n\nexport interface ListenDeps {\n signals?: SignalBus\n terminal?: TerminalUI\n}\n\nfunction parsePort(port: string | number | undefined): number {\n const raw = port\n const parsed = typeof raw === 'number' ? raw : Number(raw)\n\n if (!Number.isInteger(parsed) || parsed < 0 || parsed > 65_535) {\n throw new Error(`Invalid port \"${raw}\". Expected an integer between 0 and 65535.`)\n }\n\n return parsed\n}\n\n/** Maps --verify flags to a Verifier. See CONTRIBUTING.md → Adding a provider. */\nexport function buildVerifier(flags: ListenFlags): Verifier | undefined {\n if (!flags.verify) return undefined\n\n switch (flags.verify) {\n case 'stripe': {\n if (!flags.secret) {\n throw new Error('--secret is required when --verify stripe is set')\n }\n return createStripeVerifier({ secret: flags.secret })\n }\n default:\n throw new Error(`Unknown --verify provider \"${flags.verify}\". Supported: stripe`)\n }\n}\n\nasync function stopServer(server: Server | null): Promise<void> {\n if (!server) return\n await server.stop()\n}\n\nfunction printEventCapturedBestEffort(\n terminal: TerminalUI,\n event: WebhookEvent,\n result: VerificationResult | null,\n): void {\n try {\n terminal.printEventCaptured(event, result)\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n console.error(`Failed to print captured event: ${message}`)\n }\n}\n\nexport async function runListen(flags: ListenFlags, deps: ListenDeps = {}): Promise<void> {\n const port = parsePort(flags.port)\n const verifier = buildVerifier(flags)\n const dbPath = defaultDbPath()\n const signals = deps.signals ?? process\n const terminal = deps.terminal ?? createTerminal()\n const storage = createStorage(dbPath)\n\n let server: Server | null = null\n let cleanedUp = false\n let listenersAttached = false\n\n const cleanup = async (printStopped: boolean): Promise<void> => {\n if (cleanedUp) return\n cleanedUp = true\n if (listenersAttached) {\n signals.off('SIGINT', onSignal)\n signals.off('SIGTERM', onSignal)\n listenersAttached = false\n }\n\n let stopError: unknown = null\n\n try {\n await stopServer(server)\n } catch (error) {\n stopError = error\n } finally {\n storage.close()\n }\n\n if (stopError) {\n throw stopError\n }\n\n if (printStopped) {\n terminal.printListenStopped()\n }\n }\n\n let settle: (() => void) | null = null\n let fail: ((error: unknown) => void) | null = null\n const shutdown = new Promise<void>((resolve, reject) => {\n settle = resolve\n fail = reject\n })\n\n const onSignal = () => {\n void cleanup(true).then(\n () => settle?.(),\n (error) => fail?.(error),\n )\n }\n\n try {\n server = createServer({\n port,\n storage,\n verifier,\n forwardTo: flags.forwardTo,\n onEvent: (event, result) => printEventCapturedBestEffort(terminal, event, result),\n })\n\n signals.on('SIGINT', onSignal)\n signals.on('SIGTERM', onSignal)\n listenersAttached = true\n\n await server.start()\n\n if (cleanedUp) {\n return await shutdown\n }\n\n terminal.printListenStarted({\n port: server.port,\n dbPath,\n verifier: verifier?.provider,\n forwardTo: flags.forwardTo,\n })\n\n return await shutdown\n } catch (error) {\n if (cleanedUp) {\n return await shutdown\n }\n\n await cleanup(false)\n throw error\n }\n}\n\nexport const listenCommand = new Command('listen')\n .description('Start receiving webhooks')\n .option('-p, --port <port>', 'Port to listen on', '4400')\n .option('--verify <provider>', 'Verify signatures (stripe)')\n .option('--secret <secret>', 'Webhook signing secret')\n .option('--forward-to <url>', 'Forward received webhooks to this URL')\n .action(async (options) => {\n const terminal = createTerminal()\n\n try {\n await runListen(options, { terminal })\n } catch (error) {\n terminal.printError(error instanceof Error ? error.message : String(error))\n\n process.exitCode = 1\n }\n })\n","import http from 'node:http'\nimport crypto from 'node:crypto'\nimport type { ReplayResult, VerificationResult, Verifier, WebhookEvent } from '../types.js'\nimport type { createStorage } from '../storage/index.js'\n\ntype Storage = ReturnType<typeof createStorage>\n\nexport interface ServerOptions {\n port: number\n storage: Storage\n verifier?: Verifier\n forwardTo?: string\n forwardTimeoutMs?: number\n maxBodyBytes?: number\n onEvent?: (event: WebhookEvent, result: VerificationResult | null) => void\n}\n\nexport interface Server {\n readonly port: number\n start(): Promise<void>\n stop(): Promise<void>\n}\n\n// Headers we strip before forwarding. This is the RFC 7230 section 6.1\n// hop-by-hop list plus host (fetch sets this from the destination URL) and\n// content-length (fetch recomputes this from the body).\nconst FORWARD_STRIP = new Set([\n 'connection',\n 'keep-alive',\n 'proxy-authenticate',\n 'proxy-authorization',\n 'te',\n 'trailer',\n 'transfer-encoding',\n 'upgrade',\n 'host',\n 'content-length',\n])\n\nconst DEFAULT_MAX_BODY_BYTES = 1024 * 1024\nconst DEFAULT_FORWARD_TIMEOUT_MS = 5000\n\nfunction forwardedStripSet(headers: Record<string, string>): Set<string> {\n const strip = new Set(FORWARD_STRIP)\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() !== 'connection') continue\n for (const token of value.split(/[,\\s]+/)) {\n const name = token.trim().toLowerCase()\n if (name) strip.add(name)\n }\n }\n return strip\n}\n\nfunction generateEventId(): string {\n return `evt_${crypto.randomBytes(12).toString('base64url')}`\n}\n\nclass PayloadTooLargeError extends Error {\n constructor(readonly maxBytes: number) {\n super(`payload too large: max ${maxBytes} bytes`)\n this.name = 'PayloadTooLargeError'\n }\n}\n\nfunction isPayloadTooLargeError(error: unknown): error is PayloadTooLargeError {\n return error instanceof PayloadTooLargeError\n}\n\nfunction requestSockets(req: http.IncomingMessage): NodeJS.EventEmitter[] {\n const sockets = new Set<NodeJS.EventEmitter>()\n sockets.add(req.socket)\n const proxiedSocket = (req.socket as typeof req.socket & { proxy?: NodeJS.EventEmitter | null })\n .proxy\n if (proxiedSocket) sockets.add(proxiedSocket)\n return [...sockets]\n}\n\nexport function readBody(req: http.IncomingMessage, maxBytes: number): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = []\n let totalBytes = 0\n let settled = false\n const sockets = requestSockets(req)\n\n const cleanup = () => {\n req.off('data', onData)\n req.off('end', onEnd)\n req.off('error', onError)\n for (const socket of sockets) {\n socket.off('close', onSocketClose)\n socket.off('error', onSocketError)\n }\n }\n\n const rejectOnce = (error: Error) => {\n if (settled) return\n settled = true\n cleanup()\n reject(error)\n }\n\n const resolveOnce = (body: string) => {\n if (settled) return\n settled = true\n cleanup()\n resolve(body)\n }\n\n const onData = (chunk: Buffer) => {\n totalBytes += chunk.length\n if (totalBytes > maxBytes) {\n req.resume()\n rejectOnce(new PayloadTooLargeError(maxBytes))\n return\n }\n chunks.push(chunk)\n }\n\n const onEnd = () => resolveOnce(Buffer.concat(chunks, totalBytes).toString('utf8'))\n const onError = (error: Error) => rejectOnce(error)\n const onSocketClose = () => rejectOnce(new Error('socket closed during request body'))\n const onSocketError = (error: Error) => rejectOnce(error)\n\n req.on('data', onData)\n req.on('end', onEnd)\n req.on('error', onError)\n for (const socket of sockets) {\n socket.on('close', onSocketClose)\n socket.on('error', onSocketError)\n }\n })\n}\n\nfunction headersToRecord(headers: http.IncomingHttpHeaders): Record<string, string> {\n const out: Record<string, string> = {}\n for (const [key, value] of Object.entries(headers)) {\n if (value === undefined) continue\n out[key] = Array.isArray(value) ? value.join(', ') : value\n }\n return out\n}\n\nexport function headersForForwarding(headers: Record<string, string>): Record<string, string> {\n const out: Record<string, string> = {}\n const strip = forwardedStripSet(headers)\n for (const [key, value] of Object.entries(headers)) {\n if (!strip.has(key.toLowerCase())) out[key] = value\n }\n return out\n}\n\ninterface ParsedEventPath {\n pathname: string\n search: string\n}\n\nfunction forwardPathname(targetPathname: string, incomingPathname: string): string {\n if (targetPathname === '/' || targetPathname === '') return incomingPathname || '/'\n if (incomingPathname === '/' || incomingPathname === '') return targetPathname\n const base = targetPathname.endsWith('/') ? targetPathname.slice(0, -1) : targetPathname\n const incoming = incomingPathname.startsWith('/') ? incomingPathname : `/${incomingPathname}`\n return `${base}${incoming}`\n}\n\nexport function parseEventPath(path: string): ParsedEventPath {\n if (/^[A-Za-z][A-Za-z\\d+.-]*:/.test(path)) {\n const parsed = new URL(path)\n return { pathname: parsed.pathname, search: parsed.search }\n }\n\n const queryIndex = path.indexOf('?')\n if (queryIndex === -1) {\n return { pathname: path, search: '' }\n }\n\n return {\n pathname: path.slice(0, queryIndex),\n search: path.slice(queryIndex),\n }\n}\n\nfunction mergeForwardSearch(targetSearch: string, incomingSearch: string): string {\n const merged = new URLSearchParams(incomingSearch)\n const trusted = new URLSearchParams(targetSearch)\n\n for (const key of new Set(trusted.keys())) {\n merged.delete(key)\n }\n for (const [key, value] of trusted) {\n merged.append(key, value)\n }\n\n const search = merged.toString()\n return search.length > 0 ? `?${search}` : ''\n}\n\nfunction isAbortError(error: unknown): boolean {\n return error instanceof Error && error.name === 'AbortError'\n}\n\nexport async function forwardEvent(\n targetUrl: string,\n event: WebhookEvent,\n timeoutMs = DEFAULT_FORWARD_TIMEOUT_MS,\n): Promise<ReplayResult> {\n const target = new URL(targetUrl)\n const destination = new URL(target.href)\n const parsedEventPath = parseEventPath(event.path)\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), timeoutMs)\n\n destination.pathname = forwardPathname(destination.pathname, parsedEventPath.pathname)\n destination.search = mergeForwardSearch(destination.search, parsedEventPath.search)\n\n try {\n const hasBody = event.method !== 'GET' && event.method !== 'HEAD'\n const response = await fetch(destination, {\n method: event.method,\n headers: headersForForwarding(event.headers),\n body: hasBody ? event.body : undefined,\n signal: controller.signal,\n })\n\n return {\n status: response.status,\n body: await response.text(),\n }\n } catch (error) {\n if (isAbortError(error)) {\n throw new Error(`forward timed out after ${timeoutMs}ms`)\n }\n throw error\n } finally {\n clearTimeout(timeout)\n }\n}\n\nexport function createServer(opts: ServerOptions): Server {\n let boundPort = opts.port\n let httpServer: http.Server | null = null\n let isStarting = false\n const maxBodyBytes = opts.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES\n const forwardTimeoutMs = opts.forwardTimeoutMs ?? DEFAULT_FORWARD_TIMEOUT_MS\n\n const handleRequest = async (\n req: http.IncomingMessage,\n res: http.ServerResponse,\n ): Promise<void> => {\n const body = await readBody(req, maxBodyBytes)\n\n const event: WebhookEvent = {\n id: generateEventId(),\n timestamp: new Date().toISOString(),\n method: req.method ?? 'GET',\n path: req.url ?? '/',\n headers: headersToRecord(req.headers),\n body,\n }\n\n opts.storage.save(event)\n const verification = opts.verifier?.verify({ headers: event.headers, body: event.body }) ?? null\n opts.onEvent?.(event, verification)\n\n if (!opts.forwardTo) {\n res.statusCode = 200\n res.end('ok')\n return\n }\n\n try {\n const forwarded = await forwardEvent(opts.forwardTo, event, forwardTimeoutMs)\n res.statusCode = forwarded.status\n res.end(forwarded.body)\n } catch {\n res.statusCode = 502\n res.end('bad gateway')\n }\n }\n\n return {\n get port() {\n return boundPort\n },\n\n async start() {\n if (httpServer || isStarting) {\n throw new Error('server already started')\n }\n\n isStarting = true\n const server = http.createServer((req, res) => {\n handleRequest(req, res).catch((err: unknown) => {\n if (isPayloadTooLargeError(err)) {\n res.statusCode = 413\n res.end(err.message)\n return\n }\n res.statusCode = 500\n res.end(err instanceof Error ? err.message : String(err))\n })\n })\n httpServer = server\n\n try {\n await new Promise<void>((resolve, reject) => {\n const onError = (err: Error) => {\n server.off('error', onError)\n if (httpServer === server) httpServer = null\n boundPort = opts.port\n isStarting = false\n reject(err)\n }\n\n server.once('error', onError)\n server.listen(opts.port, '127.0.0.1', () => {\n server.off('error', onError)\n const addr = server.address()\n if (addr && typeof addr !== 'string') {\n boundPort = addr.port\n }\n isStarting = false\n resolve()\n })\n })\n } catch (err) {\n if (httpServer === server) httpServer = null\n boundPort = opts.port\n isStarting = false\n throw err\n }\n },\n\n async stop() {\n if (!httpServer) return\n const server = httpServer\n await new Promise<void>((resolve, reject) => {\n server.close((err) => {\n if (httpServer === server) httpServer = null\n boundPort = opts.port\n isStarting = false\n if (err) {\n reject(err)\n return\n }\n resolve()\n })\n })\n },\n }\n}\n","import os from 'node:os'\nimport fs from 'node:fs'\nimport { createRequire } from 'node:module'\nimport path from 'node:path'\nimport type * as sqlite from 'node:sqlite'\nimport { eventRowSchema, webhookEventSchema, type EventRow, type WebhookEvent } from '../types.js'\n\nconst require = createRequire(import.meta.url)\n\n// tsup/esbuild currently rewrites a static `node:sqlite` import to `sqlite`,\n// which breaks the built CLI. Resolve it at runtime so the core module specifier\n// survives the bundle unchanged.\nconst { DatabaseSync } = require('node:' + 'sqlite') as typeof sqlite\n\nexport function defaultDbPath(): string {\n return path.join(os.homedir(), '.hooklens', 'events.db')\n}\n\nfunction rowToEvent(row: EventRow): WebhookEvent {\n return webhookEventSchema.parse({\n id: row.id,\n timestamp: row.timestamp,\n method: row.method,\n path: row.path,\n headers: JSON.parse(row.headers),\n body: row.body,\n })\n}\n\nexport function createStorage(dbPath: string) {\n fs.mkdirSync(path.dirname(dbPath), { recursive: true })\n const db = new DatabaseSync(dbPath)\n\n db.exec(`\n CREATE TABLE IF NOT EXISTS events (\n id TEXT PRIMARY KEY,\n timestamp TEXT NOT NULL,\n method TEXT NOT NULL,\n path TEXT NOT NULL,\n headers TEXT NOT NULL,\n body TEXT NOT NULL\n )\n `)\n\n const insertStmt = db.prepare(\n `INSERT OR REPLACE INTO events (id, timestamp, method, path, headers, body)\n VALUES (?, ?, ?, ?, ?, ?)`,\n )\n\n const getStmt = db.prepare(`SELECT * FROM events WHERE id = ?`)\n const listAllStmt = db.prepare(`SELECT * FROM events ORDER BY timestamp DESC`)\n const listLimitedStmt = db.prepare(`SELECT * FROM events ORDER BY timestamp DESC LIMIT ?`)\n const clearStmt = db.prepare(`DELETE FROM events`)\n\n return {\n save(event: WebhookEvent): void {\n insertStmt.run(\n event.id,\n event.timestamp,\n event.method,\n event.path,\n JSON.stringify(event.headers),\n event.body,\n )\n },\n\n load(id: string): WebhookEvent | null {\n const raw = getStmt.get(id)\n if (!raw) return null\n const row = eventRowSchema.parse(raw)\n return rowToEvent(row)\n },\n\n list(limit?: number): WebhookEvent[] {\n if (limit !== undefined && (!Number.isInteger(limit) || limit <= 0)) {\n throw new Error(`Invalid limit: must be a positive integer, got ${limit}`)\n }\n const raw = limit === undefined ? listAllStmt.all() : listLimitedStmt.all(limit)\n const rows = raw.map((r) => eventRowSchema.parse(r))\n return rows.map(rowToEvent)\n },\n\n clear(): void {\n clearStmt.run()\n },\n\n close(): void {\n db.close()\n },\n }\n}\n","import { z } from 'zod'\n\n// A webhook event as it lives in memory and is exposed to the rest of the app.\n// Headers are a parsed object here -- on disk they're stored as a JSON string.\nexport const webhookEventSchema = z.object({\n id: z.string(),\n timestamp: z.string(),\n method: z.string(),\n path: z.string(),\n headers: z.record(z.string(), z.string()),\n body: z.string(),\n})\n\nexport type WebhookEvent = z.infer<typeof webhookEventSchema>\n\n// The shape of a row read directly from the SQLite events table.\n// headers is a JSON string at this layer; rowToEvent parses it.\nexport const eventRowSchema = z.object({\n id: z.string(),\n timestamp: z.string(),\n method: z.string(),\n path: z.string(),\n headers: z.string(),\n body: z.string(),\n})\n\nexport type EventRow = z.infer<typeof eventRowSchema>\n\nexport const verificationResultSchema = z.object({\n valid: z.boolean(),\n provider: z.string(),\n message: z.string(),\n code: z.enum([\n 'valid',\n 'missing_header',\n 'malformed_header',\n 'expired_timestamp',\n 'signature_mismatch',\n 'body_mutated',\n ]),\n})\n\nexport type VerificationResult = z.infer<typeof verificationResultSchema>\n\nexport const replayResultSchema = z.object({\n status: z.number().int(),\n body: z.string(),\n})\n\nexport type ReplayResult = z.infer<typeof replayResultSchema>\n\n/** Provider signature verifier. See CONTRIBUTING.md → Adding a provider. */\nexport interface Verifier {\n readonly provider: string\n verify(event: { headers: Record<string, string>; body: string }): VerificationResult\n}\n","import chalk from 'chalk'\nimport type { ReplayResult, VerificationResult, WebhookEvent } from '../types.js'\n\nexport interface ListenStartedInfo {\n port: number\n dbPath: string\n verifier?: string\n forwardTo?: string\n}\n\nexport interface TerminalUI {\n printListenStarted(info: ListenStartedInfo): void\n printEventCaptured(event: WebhookEvent, result: VerificationResult | null): void\n printEventList(events: WebhookEvent[]): void\n printReplayResult(result: ReplayResult): void\n printListenStopped(): void\n printError(message: string): void\n}\n\nfunction writeLine(stream: NodeJS.WriteStream, line: string): void {\n stream.write(`${line}\\n`)\n}\n\nfunction verificationLabel(result: VerificationResult | null): string {\n if (!result) return chalk.cyan('RECV')\n return result.valid ? chalk.green('PASS') : chalk.red('FAIL')\n}\n\nexport function createTerminal(\n stdout: NodeJS.WriteStream = process.stdout,\n stderr: NodeJS.WriteStream = process.stderr,\n): TerminalUI {\n return {\n printListenStarted(info) {\n writeLine(\n stdout,\n `${chalk.bold('Listening on')} ${chalk.cyan(`http://127.0.0.1:${info.port}`)}`,\n )\n\n writeLine(stdout, `Verifier: ${info.verifier ?? 'none'}`)\n writeLine(stdout, `Forwarding to: ${info.forwardTo ?? 'disabled'}`)\n writeLine(stdout, `Storage: ${info.dbPath}`)\n },\n\n printEventCaptured(event, result) {\n const label = verificationLabel(result)\n const summary = `${label} ${chalk.bold(event.id)} ${event.method} ${event.path}`\n\n if (!result) {\n writeLine(stdout, summary)\n return\n }\n\n writeLine(stdout, `${summary} ${result.message}`)\n },\n\n printEventList(events) {\n if (!events.length) {\n writeLine(stdout, chalk.dim('No stored events.'))\n return\n }\n\n for (const event of events) {\n const row = `${chalk.dim(event.timestamp)} ${chalk.cyan(event.method)} ${chalk.bold(event.id)} ${event.path}`\n writeLine(stdout, row)\n }\n },\n\n printReplayResult(result) {\n const summary = `${chalk.bold('Replay response:')} ${chalk.cyan(String(result.status))}`\n\n if (!result.body) {\n writeLine(stdout, summary)\n return\n }\n\n writeLine(stdout, `${summary} ${result.body}`)\n },\n\n printListenStopped() {\n writeLine(stdout, chalk.dim('Stopped listening.'))\n },\n\n printError(message) {\n writeLine(stderr, chalk.red(message))\n },\n }\n}\n","import crypto from 'node:crypto'\nimport type { VerificationResult, Verifier } from '../types.js'\nimport { getHeaderCaseInsensitive } from './headers.js'\n\nexport interface VerifyStripeOptions {\n payload: string\n header: string | null | undefined\n secret: string\n tolerance?: number\n now?: () => number\n}\n\nconst DEFAULT_TOLERANCE_SECONDS = 300\nconst PROVIDER = 'stripe'\n\ninterface ParsedHeader {\n timestamp: number\n signatures: string[]\n}\n\nfunction parseHeader(header: string): ParsedHeader | null {\n let timestamp: number | null = null\n const signatures: string[] = []\n\n for (const part of header.split(',')) {\n const eqIdx = part.indexOf('=')\n if (eqIdx === -1) return null\n\n const key = part.slice(0, eqIdx)\n const value = part.slice(eqIdx + 1)\n\n if (key === 't') {\n if (!/^\\d+$/.test(value)) return null\n timestamp = Number(value)\n } else if (key === 'v1') {\n if (value.length === 0) return null\n signatures.push(value)\n }\n }\n\n if (timestamp === null || signatures.length === 0) return null\n return { timestamp, signatures }\n}\n\nfunction computeHmac(secret: string, signedPayload: string): string {\n return crypto.createHmac('sha256', secret).update(signedPayload).digest('hex')\n}\n\nfunction constantTimeMatch(expected: string, candidates: string[]): boolean {\n const expectedBuf = Buffer.from(expected, 'utf8')\n for (const candidate of candidates) {\n if (candidate.length !== expected.length) continue\n const candidateBuf = Buffer.from(candidate, 'utf8')\n if (crypto.timingSafeEqual(expectedBuf, candidateBuf)) return true\n }\n return false\n}\n\nfunction tryCanonicalForm(payload: string): string | null {\n try {\n const canonical = JSON.stringify(JSON.parse(payload))\n return canonical === payload ? null : canonical\n } catch {\n return null\n }\n}\n\nfunction success(message: string): VerificationResult {\n return { valid: true, provider: PROVIDER, code: 'valid', message }\n}\n\nfunction failure(\n code: Exclude<VerificationResult['code'], 'valid'>,\n message: string,\n): VerificationResult {\n return { valid: false, provider: PROVIDER, code, message }\n}\n\nexport function verifyStripeSignature(opts: VerifyStripeOptions): VerificationResult {\n if (!opts.header) {\n return failure(\n 'missing_header',\n 'stripe-signature header not found. Is this actually from Stripe?',\n )\n }\n\n const parsed = parseHeader(opts.header)\n if (!parsed) {\n return failure(\n 'malformed_header',\n 'stripe-signature header is malformed. Expected format: t=timestamp,v1=signature',\n )\n }\n\n const tolerance = opts.tolerance ?? DEFAULT_TOLERANCE_SECONDS\n const nowMs = (opts.now ?? Date.now)()\n const ageSeconds = Math.floor(nowMs / 1000) - parsed.timestamp\n\n if (ageSeconds > tolerance) {\n const minutes = Math.floor(ageSeconds / 60)\n return failure(\n 'expired_timestamp',\n `Timestamp is ${minutes} minutes old. Event expired or your clock is drifting.`,\n )\n }\n\n const signedPayload = `${parsed.timestamp}.${opts.payload}`\n const expected = computeHmac(opts.secret, signedPayload)\n\n if (constantTimeMatch(expected, parsed.signatures)) {\n return success('Signature verified.')\n }\n\n const canonical = tryCanonicalForm(opts.payload)\n if (canonical !== null) {\n const expectedCanonical = computeHmac(opts.secret, `${parsed.timestamp}.${canonical}`)\n if (constantTimeMatch(expectedCanonical, parsed.signatures)) {\n return failure(\n 'body_mutated',\n 'Signature mismatch with correct secret. Body was likely parsed and re-serialized by your framework.',\n )\n }\n }\n\n return failure(\n 'signature_mismatch',\n 'Signature mismatch. Check your webhook secret matches the Stripe dashboard.',\n )\n}\n\nexport interface StripeVerifierOptions {\n secret: string\n tolerance?: number\n}\n\nexport function createStripeVerifier(opts: StripeVerifierOptions): Verifier {\n return {\n provider: PROVIDER,\n verify: (event) =>\n verifyStripeSignature({\n payload: event.body,\n header: getHeaderCaseInsensitive(event.headers, 'stripe-signature'),\n secret: opts.secret,\n tolerance: opts.tolerance,\n }),\n }\n}\n","export function getHeaderCaseInsensitive(\n headers: Record<string, string>,\n name: string,\n): string | undefined {\n const expected = name.toLowerCase()\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() === expected) return value\n }\n return undefined\n}\n","import { Command } from 'commander'\nimport { createStorage, defaultDbPath } from '../storage/index.js'\nimport { createTerminal, type TerminalUI } from '../ui/terminal.js'\n\nexport interface ListFlags {\n limit?: string | number\n}\n\nexport interface ListDeps {\n terminal?: TerminalUI\n}\n\nfunction parseLimit(limit: string | number | undefined): number {\n const raw = limit\n const parsed = typeof raw === 'number' ? raw : Number(raw)\n\n if (!Number.isInteger(parsed) || parsed <= 0) {\n throw new Error(`Invalid limit \"${raw}\". Expected a positive integer.`)\n }\n\n return parsed\n}\n\nexport async function runList(flags: ListFlags, deps: ListDeps = {}): Promise<void> {\n const limit = parseLimit(flags.limit ?? '20')\n const terminal = deps.terminal ?? createTerminal()\n const storage = createStorage(defaultDbPath())\n\n try {\n const events = storage.list(limit)\n terminal.printEventList(events)\n } finally {\n storage.close()\n }\n}\n\nexport const listCommand = new Command('list')\n .description('Show received webhook events')\n .option('-n, --limit <count>', 'Number of events to show', '20')\n .action(async (options) => {\n const terminal = createTerminal()\n\n try {\n await runList(options, { terminal })\n } catch (error) {\n terminal.printError(error instanceof Error ? error.message : String(error))\n process.exitCode = 1\n }\n })\n","import { Command } from 'commander'\nimport { forwardEvent } from '../server/index.js'\nimport { createStorage, defaultDbPath } from '../storage/index.js'\nimport { createTerminal, type TerminalUI } from '../ui/terminal.js'\n\nconst DEFAULT_REPLAY_TARGET_URL = 'http://localhost:3000/webhook'\n\nexport interface ReplayFlags {\n to?: string\n}\n\nexport interface ReplayDeps {\n terminal?: TerminalUI\n}\n\nfunction parseTargetUrl(targetUrl: string | undefined): string {\n const raw = targetUrl ?? DEFAULT_REPLAY_TARGET_URL\n\n try {\n return new URL(raw).href\n } catch {\n throw new Error(`Invalid target URL \"${raw}\".`)\n }\n}\n\nexport async function runReplay(\n eventId: string,\n flags: ReplayFlags,\n deps: ReplayDeps = {},\n): Promise<void> {\n const targetUrl = parseTargetUrl(flags.to)\n const terminal = deps.terminal ?? createTerminal()\n const storage = createStorage(defaultDbPath())\n\n try {\n const event = storage.load(eventId)\n\n if (!event) {\n throw new Error(`Event \"${eventId}\" not found.`)\n }\n\n try {\n const result = await forwardEvent(targetUrl, event)\n const body = result.body.length <= 200 ? result.body : `${result.body.slice(0, 197)}...`\n\n terminal.printReplayResult({\n status: result.status,\n body,\n })\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n throw new Error(`Failed to replay \"${eventId}\" to ${targetUrl}: ${message}`)\n }\n } finally {\n storage.close()\n }\n}\n\nexport const replayCommand = new Command('replay')\n .description('Replay a stored webhook event')\n .argument('<event-id>', 'ID of the event to replay')\n .option('--to <url>', 'Target URL to send the event to', DEFAULT_REPLAY_TARGET_URL)\n .action(async (eventId, options) => {\n const terminal = createTerminal()\n\n try {\n await runReplay(eventId, options, { terminal })\n } catch (error) {\n terminal.printError(error instanceof Error ? error.message : String(error))\n process.exitCode = 1\n }\n })\n"],"mappings":"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","names":["Command","path","require","crypto","crypto","Command","Command","Command","Command","Command"]}
1
+ {"version":3,"sources":["../src/cli/index.ts","../src/errors.ts","../src/cli/listen.ts","../src/server/index.ts","../src/storage/index.ts","../src/types.ts","../src/ui/terminal.ts","../src/verify/github.ts","../src/verify/headers.ts","../src/verify/stripe.ts","../src/cli/list.ts","../src/cli/replay.ts"],"sourcesContent":["import { Command } from 'commander'\nimport { errorMessage } from '../errors.js'\nimport { listenCommand } from './listen.js'\nimport { listCommand } from './list.js'\nimport { replayCommand } from './replay.js'\n\nconst program = new Command()\n\nprogram\n .name('hooklens')\n .description('Inspect, verify, and replay webhooks from your terminal')\n .version('0.1.0')\n\nprogram.addCommand(listenCommand)\nprogram.addCommand(listCommand)\nprogram.addCommand(replayCommand)\n\ntry {\n await program.parseAsync(process.argv)\n} catch (error) {\n console.error(errorMessage(error))\n process.exitCode = 1\n}\n","export function toError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value))\n}\n\nexport function errorMessage(value: unknown): string {\n return value instanceof Error ? value.message : String(value)\n}\n","import { Command } from 'commander'\nimport { errorMessage } from '../errors.js'\nimport { createServer, type Server } from '../server/index.js'\nimport { createStorage, defaultDbPath } from '../storage/index.js'\nimport type { VerificationResult, Verifier, WebhookEvent } from '../types.js'\nimport { createTerminal, type TerminalUI } from '../ui/terminal.js'\nimport { createGitHubVerifier } from '../verify/github.js'\nimport { createStripeVerifier } from '../verify/stripe.js'\n\nexport interface ListenFlags {\n port?: string | number\n verify?: string\n secret?: string\n forwardTo?: string\n}\n\nexport interface SignalBus {\n on(event: 'SIGINT' | 'SIGTERM', listener: () => void): void\n off(event: 'SIGINT' | 'SIGTERM', listener: () => void): void\n}\n\nexport interface ListenDeps {\n signals?: SignalBus\n terminal?: TerminalUI\n}\n\nfunction parsePort(port: string | number | undefined): number {\n const raw = port\n const parsed = typeof raw === 'number' ? raw : Number(raw)\n\n if (!Number.isInteger(parsed) || parsed < 0 || parsed > 65_535) {\n throw new Error(`Invalid port \"${raw}\". Expected an integer between 0 and 65535.`)\n }\n\n return parsed\n}\n\n/** Maps --verify flags to a Verifier. See CONTRIBUTING.md → Adding a provider. */\nexport function buildVerifier(flags: ListenFlags): Verifier | undefined {\n if (!flags.verify) return undefined\n\n switch (flags.verify) {\n case 'stripe': {\n if (!flags.secret) {\n throw new Error('--secret is required when --verify stripe is set')\n }\n return createStripeVerifier({ secret: flags.secret })\n }\n case 'github': {\n if (!flags.secret) {\n throw new Error('--secret is required when --verify github is set')\n }\n return createGitHubVerifier({ secret: flags.secret })\n }\n default:\n throw new Error(`Unknown --verify provider \"${flags.verify}\". Supported: stripe, github`)\n }\n}\n\nasync function stopServer(server: Server | null): Promise<void> {\n if (!server) return\n await server.stop()\n}\n\nfunction printEventCapturedBestEffort(\n terminal: TerminalUI,\n event: WebhookEvent,\n result: VerificationResult | null,\n): void {\n try {\n terminal.printEventCaptured(event, result)\n } catch (error) {\n console.error(`Failed to print captured event: ${errorMessage(error)}`)\n }\n}\n\nexport async function runListen(flags: ListenFlags, deps: ListenDeps = {}): Promise<void> {\n const port = parsePort(flags.port)\n const verifier = buildVerifier(flags)\n const dbPath = defaultDbPath()\n const signals = deps.signals ?? process\n const terminal = deps.terminal ?? createTerminal()\n const storage = createStorage(dbPath)\n\n let server: Server | null = null\n let cleanedUp = false\n let listenersAttached = false\n\n const cleanup = async (printStopped: boolean): Promise<void> => {\n if (cleanedUp) return\n cleanedUp = true\n if (listenersAttached) {\n signals.off('SIGINT', onSignal)\n signals.off('SIGTERM', onSignal)\n listenersAttached = false\n }\n\n let stopError: unknown = null\n\n try {\n await stopServer(server)\n } catch (error) {\n stopError = error\n } finally {\n storage.close()\n }\n\n if (stopError) {\n throw stopError\n }\n\n if (printStopped) {\n terminal.printListenStopped()\n }\n }\n\n let settle: (() => void) | null = null\n let fail: ((error: unknown) => void) | null = null\n const shutdown = new Promise<void>((resolve, reject) => {\n settle = resolve\n fail = reject\n })\n\n const onSignal = () => {\n void cleanup(true).then(\n () => settle?.(),\n (error) => fail?.(error),\n )\n }\n\n try {\n server = createServer({\n port,\n storage,\n verifier,\n forwardTo: flags.forwardTo,\n onEvent: (event, result) => printEventCapturedBestEffort(terminal, event, result),\n onForwardError: (event, error) => terminal.printForwardError(event.id, error.message),\n })\n\n signals.on('SIGINT', onSignal)\n signals.on('SIGTERM', onSignal)\n listenersAttached = true\n\n await server.start()\n\n if (cleanedUp) {\n return await shutdown\n }\n\n terminal.printListenStarted({\n port: server.port,\n dbPath,\n verifier: verifier?.provider,\n forwardTo: flags.forwardTo,\n })\n\n return await shutdown\n } catch (error) {\n if (cleanedUp) {\n return await shutdown\n }\n\n await cleanup(false)\n throw error\n }\n}\n\nexport const listenCommand = new Command('listen')\n .description('Start receiving webhooks')\n .option('-p, --port <port>', 'Port to listen on', '4400')\n .option('--verify <provider>', 'Verify signatures (stripe, github)')\n .option('--secret <secret>', 'Webhook signing secret')\n .option('--forward-to <url>', 'Forward received webhooks to this URL')\n .addHelpText(\n 'after',\n `\nExamples:\n hooklens listen\n hooklens listen -p 8080 --forward-to http://localhost:3000/webhook\n hooklens listen --verify stripe --secret whsec_xxx\n hooklens listen --verify github --secret ghsecret_xxx`,\n )\n .action(async (options) => {\n const terminal = createTerminal()\n\n try {\n await runListen(options, { terminal })\n } catch (error) {\n terminal.printError(errorMessage(error))\n\n process.exitCode = 1\n }\n })\n","import http from 'node:http'\nimport crypto from 'node:crypto'\nimport { errorMessage, toError } from '../errors.js'\nimport type { ReplayResult, VerificationResult, Verifier, WebhookEvent } from '../types.js'\nimport type { createStorage } from '../storage/index.js'\n\ntype Storage = ReturnType<typeof createStorage>\n\nexport interface ServerOptions {\n port: number\n storage: Storage\n verifier?: Verifier\n forwardTo?: string\n forwardTimeoutMs?: number\n maxBodyBytes?: number\n onEvent?: (event: WebhookEvent, result: VerificationResult | null) => void\n onForwardError?: (event: WebhookEvent, error: Error) => void\n}\n\nexport interface Server {\n readonly port: number\n start(): Promise<void>\n stop(): Promise<void>\n}\n\n// Headers we strip before forwarding. This is the RFC 7230 section 6.1\n// hop-by-hop list plus host (fetch sets this from the destination URL) and\n// content-length (fetch recomputes this from the body).\nconst FORWARD_STRIP = new Set([\n 'connection',\n 'keep-alive',\n 'proxy-authenticate',\n 'proxy-authorization',\n 'te',\n 'trailer',\n 'transfer-encoding',\n 'upgrade',\n 'host',\n 'content-length',\n])\n\nconst DEFAULT_MAX_BODY_BYTES = 1024 * 1024\nconst DEFAULT_FORWARD_TIMEOUT_MS = 5000\n\nfunction forwardedStripSet(headers: Record<string, string>): Set<string> {\n const strip = new Set(FORWARD_STRIP)\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() !== 'connection') continue\n for (const token of value.split(/[,\\s]+/)) {\n const name = token.trim().toLowerCase()\n if (name) strip.add(name)\n }\n }\n return strip\n}\n\nfunction generateEventId(): string {\n return `evt_${crypto.randomBytes(12).toString('base64url')}`\n}\n\nclass PayloadTooLargeError extends Error {\n constructor(readonly maxBytes: number) {\n super(`payload too large: max ${maxBytes} bytes`)\n this.name = 'PayloadTooLargeError'\n }\n}\n\nfunction isPayloadTooLargeError(error: unknown): error is PayloadTooLargeError {\n return error instanceof PayloadTooLargeError\n}\n\nfunction requestSockets(req: http.IncomingMessage): NodeJS.EventEmitter[] {\n const sockets = new Set<NodeJS.EventEmitter>()\n sockets.add(req.socket)\n const proxiedSocket = (req.socket as typeof req.socket & { proxy?: NodeJS.EventEmitter | null })\n .proxy\n if (proxiedSocket) sockets.add(proxiedSocket)\n return [...sockets]\n}\n\nexport function readBody(req: http.IncomingMessage, maxBytes: number): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = []\n let totalBytes = 0\n let settled = false\n const sockets = requestSockets(req)\n\n const cleanup = () => {\n req.off('data', onData)\n req.off('end', onEnd)\n req.off('error', onError)\n for (const socket of sockets) {\n socket.off('close', onSocketClose)\n socket.off('error', onSocketError)\n }\n }\n\n const rejectOnce = (error: Error) => {\n if (settled) return\n settled = true\n cleanup()\n reject(error)\n }\n\n const resolveOnce = (body: string) => {\n if (settled) return\n settled = true\n cleanup()\n resolve(body)\n }\n\n const onData = (chunk: Buffer) => {\n totalBytes += chunk.length\n if (totalBytes > maxBytes) {\n req.resume()\n rejectOnce(new PayloadTooLargeError(maxBytes))\n return\n }\n chunks.push(chunk)\n }\n\n const onEnd = () => resolveOnce(Buffer.concat(chunks, totalBytes).toString('utf8'))\n const onError = (error: Error) => rejectOnce(error)\n const onSocketClose = () => rejectOnce(new Error('socket closed during request body'))\n const onSocketError = (error: Error) => rejectOnce(error)\n\n req.on('data', onData)\n req.on('end', onEnd)\n req.on('error', onError)\n for (const socket of sockets) {\n socket.on('close', onSocketClose)\n socket.on('error', onSocketError)\n }\n })\n}\n\nfunction headersToRecord(headers: http.IncomingHttpHeaders): Record<string, string> {\n const out: Record<string, string> = {}\n for (const [key, value] of Object.entries(headers)) {\n if (value === undefined) continue\n out[key] = Array.isArray(value) ? value.join(', ') : value\n }\n return out\n}\n\nexport function headersForForwarding(headers: Record<string, string>): Record<string, string> {\n const out: Record<string, string> = {}\n const strip = forwardedStripSet(headers)\n for (const [key, value] of Object.entries(headers)) {\n if (!strip.has(key.toLowerCase())) out[key] = value\n }\n return out\n}\n\ninterface ParsedEventPath {\n pathname: string\n search: string\n}\n\nfunction forwardPathname(targetPathname: string, incomingPathname: string): string {\n if (targetPathname === '/' || targetPathname === '') return incomingPathname || '/'\n if (incomingPathname === '/' || incomingPathname === '') return targetPathname\n const base = targetPathname.endsWith('/') ? targetPathname.slice(0, -1) : targetPathname\n const incoming = incomingPathname.startsWith('/') ? incomingPathname : `/${incomingPathname}`\n return `${base}${incoming}`\n}\n\nexport function parseEventPath(path: string): ParsedEventPath {\n if (/^[A-Za-z][A-Za-z\\d+.-]*:/.test(path)) {\n const parsed = new URL(path)\n return { pathname: parsed.pathname, search: parsed.search }\n }\n\n const queryIndex = path.indexOf('?')\n if (queryIndex === -1) {\n return { pathname: path, search: '' }\n }\n\n return {\n pathname: path.slice(0, queryIndex),\n search: path.slice(queryIndex),\n }\n}\n\nfunction mergeForwardSearch(targetSearch: string, incomingSearch: string): string {\n const merged = new URLSearchParams(incomingSearch)\n const trusted = new URLSearchParams(targetSearch)\n\n for (const key of new Set(trusted.keys())) {\n merged.delete(key)\n }\n for (const [key, value] of trusted) {\n merged.append(key, value)\n }\n\n const search = merged.toString()\n return search.length > 0 ? `?${search}` : ''\n}\n\nfunction isAbortError(error: unknown): boolean {\n return error instanceof Error && error.name === 'AbortError'\n}\n\nexport async function forwardEvent(\n targetUrl: string,\n event: WebhookEvent,\n timeoutMs = DEFAULT_FORWARD_TIMEOUT_MS,\n): Promise<ReplayResult> {\n const target = new URL(targetUrl)\n const destination = new URL(target.href)\n const parsedEventPath = parseEventPath(event.path)\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), timeoutMs)\n\n destination.pathname = forwardPathname(destination.pathname, parsedEventPath.pathname)\n destination.search = mergeForwardSearch(destination.search, parsedEventPath.search)\n\n try {\n const hasBody = event.method !== 'GET' && event.method !== 'HEAD'\n const response = await fetch(destination, {\n method: event.method,\n headers: headersForForwarding(event.headers),\n body: hasBody ? event.body : undefined,\n signal: controller.signal,\n })\n\n return {\n status: response.status,\n body: await response.text(),\n }\n } catch (error) {\n if (isAbortError(error)) {\n throw new Error(`forward timed out after ${timeoutMs}ms`)\n }\n // fetch() wraps the real error (e.g. ECONNREFUSED) inside error.cause.\n // AggregateError (localhost resolving to both IPv6 and IPv4) has an empty\n // message but a useful code property. Fall back through each layer.\n const cause = error instanceof Error ? error.cause : undefined\n const code = cause instanceof Error ? (cause as NodeJS.ErrnoException).code : undefined\n const message = cause instanceof Error && cause.message ? cause.message : code\n throw new Error(message ?? errorMessage(error))\n } finally {\n clearTimeout(timeout)\n }\n}\n\nexport function createServer(opts: ServerOptions): Server {\n let boundPort = opts.port\n let httpServer: http.Server | null = null\n let isStarting = false\n const maxBodyBytes = opts.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES\n const forwardTimeoutMs = opts.forwardTimeoutMs ?? DEFAULT_FORWARD_TIMEOUT_MS\n\n const handleRequest = async (\n req: http.IncomingMessage,\n res: http.ServerResponse,\n ): Promise<void> => {\n const body = await readBody(req, maxBodyBytes)\n\n const event: WebhookEvent = {\n id: generateEventId(),\n timestamp: new Date().toISOString(),\n method: req.method ?? 'GET',\n path: req.url ?? '/',\n headers: headersToRecord(req.headers),\n body,\n }\n\n opts.storage.save(event)\n const verification = opts.verifier?.verify({ headers: event.headers, body: event.body }) ?? null\n opts.onEvent?.(event, verification)\n\n if (!opts.forwardTo) {\n res.statusCode = 200\n res.end('ok')\n return\n }\n\n try {\n const forwarded = await forwardEvent(opts.forwardTo, event, forwardTimeoutMs)\n res.statusCode = forwarded.status\n res.end(forwarded.body)\n } catch (error) {\n const err = toError(error)\n try {\n opts.onForwardError?.(event, err)\n } catch {\n // Don't let a broken callback turn a 502 into a 500.\n }\n res.statusCode = 502\n res.end(`bad gateway: ${err.message}`)\n }\n }\n\n return {\n get port() {\n return boundPort\n },\n\n async start() {\n if (httpServer || isStarting) {\n throw new Error('server already started')\n }\n\n isStarting = true\n const server = http.createServer((req, res) => {\n handleRequest(req, res).catch((err: unknown) => {\n if (isPayloadTooLargeError(err)) {\n res.statusCode = 413\n res.end(err.message)\n return\n }\n res.statusCode = 500\n res.end(errorMessage(err))\n })\n })\n httpServer = server\n\n try {\n await new Promise<void>((resolve, reject) => {\n const onError = (err: Error) => {\n server.off('error', onError)\n if (httpServer === server) httpServer = null\n boundPort = opts.port\n isStarting = false\n reject(err)\n }\n\n server.once('error', onError)\n server.listen(opts.port, '127.0.0.1', () => {\n server.off('error', onError)\n const addr = server.address()\n if (addr && typeof addr !== 'string') {\n boundPort = addr.port\n }\n isStarting = false\n resolve()\n })\n })\n } catch (err) {\n if (httpServer === server) httpServer = null\n boundPort = opts.port\n isStarting = false\n throw err\n }\n },\n\n async stop() {\n if (!httpServer) return\n const server = httpServer\n await new Promise<void>((resolve, reject) => {\n server.close((err) => {\n if (httpServer === server) httpServer = null\n boundPort = opts.port\n isStarting = false\n if (err) {\n reject(err)\n return\n }\n resolve()\n })\n })\n },\n }\n}\n","import os from 'node:os'\nimport fs from 'node:fs'\nimport { createRequire } from 'node:module'\nimport path from 'node:path'\nimport type * as sqlite from 'node:sqlite'\nimport { eventRowSchema, webhookEventSchema, type EventRow, type WebhookEvent } from '../types.js'\n\nconst require = createRequire(import.meta.url)\n\n// tsup/esbuild currently rewrites a static `node:sqlite` import to `sqlite`,\n// which breaks the built CLI. Resolve it at runtime so the core module specifier\n// survives the bundle unchanged.\nconst { DatabaseSync } = require('node:' + 'sqlite') as typeof sqlite\n\nexport function defaultDbPath(): string {\n return path.join(os.homedir(), '.hooklens', 'events.db')\n}\n\nfunction rowToEvent(row: EventRow): WebhookEvent {\n return webhookEventSchema.parse({\n id: row.id,\n timestamp: row.timestamp,\n method: row.method,\n path: row.path,\n headers: JSON.parse(row.headers),\n body: row.body,\n })\n}\n\nexport function createStorage(dbPath: string) {\n fs.mkdirSync(path.dirname(dbPath), { recursive: true })\n const db = new DatabaseSync(dbPath)\n\n db.exec(`\n CREATE TABLE IF NOT EXISTS events (\n id TEXT PRIMARY KEY,\n timestamp TEXT NOT NULL,\n method TEXT NOT NULL,\n path TEXT NOT NULL,\n headers TEXT NOT NULL,\n body TEXT NOT NULL\n )\n `)\n\n const insertStmt = db.prepare(\n `INSERT OR REPLACE INTO events (id, timestamp, method, path, headers, body)\n VALUES (?, ?, ?, ?, ?, ?)`,\n )\n\n const getStmt = db.prepare(`SELECT * FROM events WHERE id = ?`)\n const listAllStmt = db.prepare(`SELECT * FROM events ORDER BY timestamp DESC`)\n const listLimitedStmt = db.prepare(`SELECT * FROM events ORDER BY timestamp DESC LIMIT ?`)\n const clearStmt = db.prepare(`DELETE FROM events`)\n\n return {\n save(event: WebhookEvent): void {\n insertStmt.run(\n event.id,\n event.timestamp,\n event.method,\n event.path,\n JSON.stringify(event.headers),\n event.body,\n )\n },\n\n load(id: string): WebhookEvent | null {\n const raw = getStmt.get(id)\n if (!raw) return null\n const row = eventRowSchema.parse(raw)\n return rowToEvent(row)\n },\n\n list(limit?: number): WebhookEvent[] {\n if (limit !== undefined && (!Number.isInteger(limit) || limit <= 0)) {\n throw new Error(`Invalid limit: must be a positive integer, got ${limit}`)\n }\n const raw = limit === undefined ? listAllStmt.all() : listLimitedStmt.all(limit)\n const rows = raw.map((r) => eventRowSchema.parse(r))\n return rows.map(rowToEvent)\n },\n\n clear(): void {\n clearStmt.run()\n },\n\n close(): void {\n db.close()\n },\n }\n}\n","import { z } from 'zod'\n\n// A webhook event as it lives in memory and is exposed to the rest of the app.\n// Headers are a parsed object here -- on disk they're stored as a JSON string.\nexport const webhookEventSchema = z.object({\n id: z.string(),\n timestamp: z.string(),\n method: z.string(),\n path: z.string(),\n headers: z.record(z.string(), z.string()),\n body: z.string(),\n})\n\nexport type WebhookEvent = z.infer<typeof webhookEventSchema>\n\n// The shape of a row read directly from the SQLite events table.\n// headers is a JSON string at this layer; rowToEvent parses it.\nexport const eventRowSchema = z.object({\n id: z.string(),\n timestamp: z.string(),\n method: z.string(),\n path: z.string(),\n headers: z.string(),\n body: z.string(),\n})\n\nexport type EventRow = z.infer<typeof eventRowSchema>\n\nexport const verificationResultSchema = z.object({\n valid: z.boolean(),\n provider: z.string(),\n message: z.string(),\n code: z.enum([\n 'valid',\n 'missing_header',\n 'malformed_header',\n 'expired_timestamp',\n 'signature_mismatch',\n 'body_mutated',\n ]),\n})\n\nexport type VerificationResult = z.infer<typeof verificationResultSchema>\n\nexport const replayResultSchema = z.object({\n status: z.number().int(),\n body: z.string(),\n})\n\nexport type ReplayResult = z.infer<typeof replayResultSchema>\n\n/** Provider signature verifier. See CONTRIBUTING.md → Adding a provider. */\nexport interface Verifier {\n readonly provider: string\n verify(event: { headers: Record<string, string>; body: string }): VerificationResult\n}\n","import chalk from 'chalk'\nimport type { ReplayResult, VerificationResult, WebhookEvent } from '../types.js'\n\nexport interface ListenStartedInfo {\n port: number\n dbPath: string\n verifier?: string\n forwardTo?: string\n}\n\nexport interface TerminalUI {\n printListenStarted(info: ListenStartedInfo): void\n printEventCaptured(event: WebhookEvent, result: VerificationResult | null): void\n printForwardError(eventId: string, reason: string): void\n printEventList(events: WebhookEvent[]): void\n printReplayResult(result: ReplayResult): void\n printListenStopped(): void\n printError(message: string): void\n}\n\nfunction writeLine(stream: NodeJS.WriteStream, line: string): void {\n stream.write(`${line}\\n`)\n}\n\nfunction verificationLabel(result: VerificationResult | null): string {\n if (!result) return chalk.cyan('RECV')\n return result.valid ? chalk.green('PASS') : chalk.red('FAIL')\n}\n\nexport function createTerminal(\n stdout: NodeJS.WriteStream = process.stdout,\n stderr: NodeJS.WriteStream = process.stderr,\n): TerminalUI {\n return {\n printListenStarted(info) {\n writeLine(\n stdout,\n `${chalk.bold('Listening on')} ${chalk.cyan(`http://127.0.0.1:${info.port}`)}`,\n )\n\n writeLine(stdout, `Verifier: ${info.verifier ?? 'none'}`)\n writeLine(stdout, `Forwarding to: ${info.forwardTo ?? 'disabled'}`)\n writeLine(stdout, `Storage: ${info.dbPath}`)\n },\n\n printEventCaptured(event, result) {\n const label = verificationLabel(result)\n const summary = `${label} ${chalk.bold(event.id)} ${event.method} ${event.path}`\n\n if (!result) {\n writeLine(stdout, summary)\n return\n }\n\n writeLine(stdout, `${summary} ${result.message}`)\n },\n\n printForwardError(eventId, reason) {\n writeLine(stdout, `${chalk.red('FWD')} ${chalk.bold(eventId)} ${reason}`)\n },\n\n printEventList(events) {\n if (!events.length) {\n writeLine(stdout, chalk.dim('No stored events.'))\n return\n }\n\n for (const event of events) {\n const row = `${chalk.dim(event.timestamp)} ${chalk.cyan(event.method)} ${chalk.bold(event.id)} ${event.path}`\n writeLine(stdout, row)\n }\n },\n\n printReplayResult(result) {\n const summary = `${chalk.bold('Replay response:')} ${chalk.cyan(String(result.status))}`\n\n if (!result.body) {\n writeLine(stdout, summary)\n return\n }\n\n writeLine(stdout, `${summary} ${result.body}`)\n },\n\n printListenStopped() {\n writeLine(stdout, chalk.dim('Stopped listening.'))\n },\n\n printError(message) {\n writeLine(stderr, chalk.red(message))\n },\n }\n}\n","import crypto from 'node:crypto'\nimport type { VerificationResult, Verifier } from '../types.js'\nimport { getHeaderCaseInsensitive, tryCanonicalForm } from './headers.js'\n\nexport interface VerifyGitHubOptions {\n payload: string\n header: string | null | undefined\n secret: string\n}\n\nconst PROVIDER = 'github'\nconst PREFIX = 'sha256='\nconst SHA256_HEX = /^[0-9a-fA-F]{64}$/\n\nfunction computeHmac(secret: string, payload: string): string {\n return crypto.createHmac('sha256', secret).update(payload).digest('hex')\n}\n\nfunction constantTimeMatch(expected: string, actual: string): boolean {\n if (expected.length !== actual.length) return false\n const expectedBuf = Buffer.from(expected, 'utf8')\n const actualBuf = Buffer.from(actual, 'utf8')\n return crypto.timingSafeEqual(expectedBuf, actualBuf)\n}\n\nfunction success(message: string): VerificationResult {\n return { valid: true, provider: PROVIDER, code: 'valid', message }\n}\n\nfunction failure(\n code: Exclude<VerificationResult['code'], 'valid'>,\n message: string,\n): VerificationResult {\n return { valid: false, provider: PROVIDER, code, message }\n}\n\nexport function verifyGitHubSignature(opts: VerifyGitHubOptions): VerificationResult {\n if (!opts.header) {\n return failure(\n 'missing_header',\n 'x-hub-signature-256 header not found. Is this actually from GitHub?',\n )\n }\n\n if (!opts.header.startsWith(PREFIX)) {\n return failure('malformed_header', 'x-hub-signature-256 header must start with sha256=')\n }\n\n const signature = opts.header.slice(PREFIX.length)\n if (signature.length === 0) {\n return failure('malformed_header', 'x-hub-signature-256 header has no signature after sha256=')\n }\n\n if (!SHA256_HEX.test(signature)) {\n return failure('malformed_header', 'x-hub-signature-256 header has invalid sha256 hex digest')\n }\n\n const normalizedSignature = signature.toLowerCase()\n const expected = computeHmac(opts.secret, opts.payload)\n\n if (constantTimeMatch(expected, normalizedSignature)) {\n return success('Signature verified.')\n }\n\n const canonical = tryCanonicalForm(opts.payload)\n if (canonical !== null) {\n const expectedCanonical = computeHmac(opts.secret, canonical)\n if (constantTimeMatch(expectedCanonical, normalizedSignature)) {\n return failure(\n 'body_mutated',\n 'Signature mismatch with correct secret. Body was likely parsed and re-serialized by your framework.',\n )\n }\n }\n\n return failure(\n 'signature_mismatch',\n 'Signature mismatch. Check your webhook secret matches the GitHub settings.',\n )\n}\n\nexport function createGitHubVerifier(opts: { secret: string }): Verifier {\n return {\n provider: PROVIDER,\n verify: (event) =>\n verifyGitHubSignature({\n payload: event.body,\n header: getHeaderCaseInsensitive(event.headers, 'x-hub-signature-256'),\n secret: opts.secret,\n }),\n }\n}\n","export function getHeaderCaseInsensitive(\n headers: Record<string, string>,\n name: string,\n): string | undefined {\n const expected = name.toLowerCase()\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() === expected) return value\n }\n return undefined\n}\n\nexport function tryCanonicalForm(payload: string): string | null {\n try {\n const canonical = JSON.stringify(JSON.parse(payload))\n return canonical === payload ? null : canonical\n } catch {\n return null\n }\n}\n","import crypto from 'node:crypto'\nimport type { VerificationResult, Verifier } from '../types.js'\nimport { getHeaderCaseInsensitive, tryCanonicalForm } from './headers.js'\n\nexport interface VerifyStripeOptions {\n payload: string\n header: string | null | undefined\n secret: string\n tolerance?: number\n now?: () => number\n}\n\nconst DEFAULT_TOLERANCE_SECONDS = 300\nconst PROVIDER = 'stripe'\n\ninterface ParsedHeader {\n timestamp: number\n signatures: string[]\n}\n\nfunction parseHeader(header: string): ParsedHeader | null {\n let timestamp: number | null = null\n const signatures: string[] = []\n\n for (const part of header.split(',')) {\n const eqIdx = part.indexOf('=')\n if (eqIdx === -1) return null\n\n const key = part.slice(0, eqIdx)\n const value = part.slice(eqIdx + 1)\n\n if (key === 't') {\n if (!/^\\d+$/.test(value)) return null\n timestamp = Number(value)\n } else if (key === 'v1') {\n if (value.length === 0) return null\n signatures.push(value)\n }\n }\n\n if (timestamp === null || signatures.length === 0) return null\n return { timestamp, signatures }\n}\n\nfunction computeHmac(secret: string, signedPayload: string): string {\n return crypto.createHmac('sha256', secret).update(signedPayload).digest('hex')\n}\n\nfunction constantTimeMatch(expected: string, candidates: string[]): boolean {\n const expectedBuf = Buffer.from(expected, 'utf8')\n for (const candidate of candidates) {\n if (candidate.length !== expected.length) continue\n const candidateBuf = Buffer.from(candidate, 'utf8')\n if (crypto.timingSafeEqual(expectedBuf, candidateBuf)) return true\n }\n return false\n}\n\nfunction success(message: string): VerificationResult {\n return { valid: true, provider: PROVIDER, code: 'valid', message }\n}\n\nfunction failure(\n code: Exclude<VerificationResult['code'], 'valid'>,\n message: string,\n): VerificationResult {\n return { valid: false, provider: PROVIDER, code, message }\n}\n\nexport function verifyStripeSignature(opts: VerifyStripeOptions): VerificationResult {\n if (!opts.header) {\n return failure(\n 'missing_header',\n 'stripe-signature header not found. Is this actually from Stripe?',\n )\n }\n\n const parsed = parseHeader(opts.header)\n if (!parsed) {\n return failure(\n 'malformed_header',\n 'stripe-signature header is malformed. Expected format: t=timestamp,v1=signature',\n )\n }\n\n const tolerance = opts.tolerance ?? DEFAULT_TOLERANCE_SECONDS\n const nowMs = (opts.now ?? Date.now)()\n const ageSeconds = Math.floor(nowMs / 1000) - parsed.timestamp\n\n if (ageSeconds > tolerance) {\n const minutes = Math.floor(ageSeconds / 60)\n return failure(\n 'expired_timestamp',\n `Timestamp is ${minutes} minutes old. Event expired or your clock is drifting.`,\n )\n }\n\n const signedPayload = `${parsed.timestamp}.${opts.payload}`\n const expected = computeHmac(opts.secret, signedPayload)\n\n if (constantTimeMatch(expected, parsed.signatures)) {\n return success('Signature verified.')\n }\n\n const canonical = tryCanonicalForm(opts.payload)\n if (canonical !== null) {\n const expectedCanonical = computeHmac(opts.secret, `${parsed.timestamp}.${canonical}`)\n if (constantTimeMatch(expectedCanonical, parsed.signatures)) {\n return failure(\n 'body_mutated',\n 'Signature mismatch with correct secret. Body was likely parsed and re-serialized by your framework.',\n )\n }\n }\n\n return failure(\n 'signature_mismatch',\n 'Signature mismatch. Check your webhook secret matches the Stripe dashboard.',\n )\n}\n\nexport interface StripeVerifierOptions {\n secret: string\n tolerance?: number\n}\n\nexport function createStripeVerifier(opts: StripeVerifierOptions): Verifier {\n return {\n provider: PROVIDER,\n verify: (event) =>\n verifyStripeSignature({\n payload: event.body,\n header: getHeaderCaseInsensitive(event.headers, 'stripe-signature'),\n secret: opts.secret,\n tolerance: opts.tolerance,\n }),\n }\n}\n","import { Command } from 'commander'\nimport { errorMessage } from '../errors.js'\nimport { createStorage, defaultDbPath } from '../storage/index.js'\nimport { createTerminal, type TerminalUI } from '../ui/terminal.js'\n\nexport interface ListFlags {\n limit?: string | number\n}\n\nexport interface ListDeps {\n terminal?: TerminalUI\n}\n\nfunction parseLimit(limit: string | number | undefined): number {\n const raw = limit\n const parsed = typeof raw === 'number' ? raw : Number(raw)\n\n if (!Number.isInteger(parsed) || parsed <= 0) {\n throw new Error(`Invalid limit \"${raw}\". Expected a positive integer.`)\n }\n\n return parsed\n}\n\nexport async function runList(flags: ListFlags, deps: ListDeps = {}): Promise<void> {\n const limit = parseLimit(flags.limit ?? '20')\n const terminal = deps.terminal ?? createTerminal()\n const storage = createStorage(defaultDbPath())\n\n try {\n const events = storage.list(limit)\n terminal.printEventList(events)\n } finally {\n storage.close()\n }\n}\n\nexport const listCommand = new Command('list')\n .description('Show received webhook events')\n .option('-n, --limit <count>', 'Number of events to show', '20')\n .addHelpText(\n 'after',\n `\nExamples:\n hooklens list\n hooklens list -n 5`,\n )\n .action(async (options) => {\n const terminal = createTerminal()\n\n try {\n await runList(options, { terminal })\n } catch (error) {\n terminal.printError(errorMessage(error))\n process.exitCode = 1\n }\n })\n","import { Command } from 'commander'\nimport { errorMessage } from '../errors.js'\nimport { forwardEvent } from '../server/index.js'\nimport { createStorage, defaultDbPath } from '../storage/index.js'\nimport { createTerminal, type TerminalUI } from '../ui/terminal.js'\n\nconst DEFAULT_REPLAY_TARGET_URL = 'http://localhost:3000/webhook'\n\nexport interface ReplayFlags {\n to?: string\n}\n\nexport interface ReplayDeps {\n terminal?: TerminalUI\n}\n\nfunction parseTargetUrl(targetUrl: string | undefined): string {\n const raw = targetUrl ?? DEFAULT_REPLAY_TARGET_URL\n\n try {\n return new URL(raw).href\n } catch {\n throw new Error(`Invalid target URL \"${raw}\".`)\n }\n}\n\nexport async function runReplay(\n eventId: string,\n flags: ReplayFlags,\n deps: ReplayDeps = {},\n): Promise<void> {\n const targetUrl = parseTargetUrl(flags.to)\n const terminal = deps.terminal ?? createTerminal()\n const storage = createStorage(defaultDbPath())\n\n try {\n const event = storage.load(eventId)\n\n if (!event) {\n throw new Error(`Event \"${eventId}\" not found.`)\n }\n\n try {\n const result = await forwardEvent(targetUrl, event)\n const body = result.body.length <= 200 ? result.body : `${result.body.slice(0, 197)}...`\n\n terminal.printReplayResult({\n status: result.status,\n body,\n })\n } catch (error) {\n throw new Error(`Failed to replay \"${eventId}\" to ${targetUrl}: ${errorMessage(error)}`)\n }\n } finally {\n storage.close()\n }\n}\n\nexport const replayCommand = new Command('replay')\n .description('Replay a stored webhook event')\n .argument('<event-id>', 'ID of the event to replay')\n .option('--to <url>', 'Target URL to send the event to', DEFAULT_REPLAY_TARGET_URL)\n .addHelpText(\n 'after',\n `\nExamples:\n hooklens replay evt_abc123\n hooklens replay evt_abc123 --to http://localhost:8080/hook`,\n )\n .action(async (eventId, options) => {\n const terminal = createTerminal()\n\n try {\n await runReplay(eventId, options, { terminal })\n } catch (error) {\n terminal.printError(errorMessage(error))\n process.exitCode = 1\n }\n })\n"],"mappings":";;;AAAA,SAAS,WAAAA,gBAAe;;;ACAjB,SAAS,QAAQ,OAAuB;AAC7C,SAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AACjE;AAEO,SAAS,aAAa,OAAwB;AACnD,SAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC9D;;;ACNA,SAAS,eAAe;;;ACAxB,OAAO,UAAU;AACjB,OAAO,YAAY;AA2BnB,IAAM,gBAAgB,oBAAI,IAAI;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,IAAM,yBAAyB,OAAO;AACtC,IAAM,6BAA6B;AAEnC,SAAS,kBAAkB,SAA8C;AACvE,QAAM,QAAQ,IAAI,IAAI,aAAa;AACnC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AAClD,QAAI,IAAI,YAAY,MAAM,aAAc;AACxC,eAAW,SAAS,MAAM,MAAM,QAAQ,GAAG;AACzC,YAAM,OAAO,MAAM,KAAK,EAAE,YAAY;AACtC,UAAI,KAAM,OAAM,IAAI,IAAI;AAAA,IAC1B;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,kBAA0B;AACjC,SAAO,OAAO,OAAO,YAAY,EAAE,EAAE,SAAS,WAAW,CAAC;AAC5D;AAEA,IAAM,uBAAN,cAAmC,MAAM;AAAA,EACvC,YAAqB,UAAkB;AACrC,UAAM,0BAA0B,QAAQ,QAAQ;AAD7B;AAEnB,SAAK,OAAO;AAAA,EACd;AAAA,EAHqB;AAIvB;AAEA,SAAS,uBAAuB,OAA+C;AAC7E,SAAO,iBAAiB;AAC1B;AAEA,SAAS,eAAe,KAAkD;AACxE,QAAM,UAAU,oBAAI,IAAyB;AAC7C,UAAQ,IAAI,IAAI,MAAM;AACtB,QAAM,gBAAiB,IAAI,OACxB;AACH,MAAI,cAAe,SAAQ,IAAI,aAAa;AAC5C,SAAO,CAAC,GAAG,OAAO;AACpB;AAEO,SAAS,SAAS,KAA2B,UAAmC;AACrF,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,SAAmB,CAAC;AAC1B,QAAI,aAAa;AACjB,QAAI,UAAU;AACd,UAAM,UAAU,eAAe,GAAG;AAElC,UAAM,UAAU,MAAM;AACpB,UAAI,IAAI,QAAQ,MAAM;AACtB,UAAI,IAAI,OAAO,KAAK;AACpB,UAAI,IAAI,SAAS,OAAO;AACxB,iBAAW,UAAU,SAAS;AAC5B,eAAO,IAAI,SAAS,aAAa;AACjC,eAAO,IAAI,SAAS,aAAa;AAAA,MACnC;AAAA,IACF;AAEA,UAAM,aAAa,CAAC,UAAiB;AACnC,UAAI,QAAS;AACb,gBAAU;AACV,cAAQ;AACR,aAAO,KAAK;AAAA,IACd;AAEA,UAAM,cAAc,CAAC,SAAiB;AACpC,UAAI,QAAS;AACb,gBAAU;AACV,cAAQ;AACR,cAAQ,IAAI;AAAA,IACd;AAEA,UAAM,SAAS,CAAC,UAAkB;AAChC,oBAAc,MAAM;AACpB,UAAI,aAAa,UAAU;AACzB,YAAI,OAAO;AACX,mBAAW,IAAI,qBAAqB,QAAQ,CAAC;AAC7C;AAAA,MACF;AACA,aAAO,KAAK,KAAK;AAAA,IACnB;AAEA,UAAM,QAAQ,MAAM,YAAY,OAAO,OAAO,QAAQ,UAAU,EAAE,SAAS,MAAM,CAAC;AAClF,UAAM,UAAU,CAAC,UAAiB,WAAW,KAAK;AAClD,UAAM,gBAAgB,MAAM,WAAW,IAAI,MAAM,mCAAmC,CAAC;AACrF,UAAM,gBAAgB,CAAC,UAAiB,WAAW,KAAK;AAExD,QAAI,GAAG,QAAQ,MAAM;AACrB,QAAI,GAAG,OAAO,KAAK;AACnB,QAAI,GAAG,SAAS,OAAO;AACvB,eAAW,UAAU,SAAS;AAC5B,aAAO,GAAG,SAAS,aAAa;AAChC,aAAO,GAAG,SAAS,aAAa;AAAA,IAClC;AAAA,EACF,CAAC;AACH;AAEA,SAAS,gBAAgB,SAA2D;AAClF,QAAM,MAA8B,CAAC;AACrC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AAClD,QAAI,UAAU,OAAW;AACzB,QAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,IAAI,IAAI;AAAA,EACvD;AACA,SAAO;AACT;AAEO,SAAS,qBAAqB,SAAyD;AAC5F,QAAM,MAA8B,CAAC;AACrC,QAAM,QAAQ,kBAAkB,OAAO;AACvC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AAClD,QAAI,CAAC,MAAM,IAAI,IAAI,YAAY,CAAC,EAAG,KAAI,GAAG,IAAI;AAAA,EAChD;AACA,SAAO;AACT;AAOA,SAAS,gBAAgB,gBAAwB,kBAAkC;AACjF,MAAI,mBAAmB,OAAO,mBAAmB,GAAI,QAAO,oBAAoB;AAChF,MAAI,qBAAqB,OAAO,qBAAqB,GAAI,QAAO;AAChE,QAAM,OAAO,eAAe,SAAS,GAAG,IAAI,eAAe,MAAM,GAAG,EAAE,IAAI;AAC1E,QAAM,WAAW,iBAAiB,WAAW,GAAG,IAAI,mBAAmB,IAAI,gBAAgB;AAC3F,SAAO,GAAG,IAAI,GAAG,QAAQ;AAC3B;AAEO,SAAS,eAAeC,OAA+B;AAC5D,MAAI,2BAA2B,KAAKA,KAAI,GAAG;AACzC,UAAM,SAAS,IAAI,IAAIA,KAAI;AAC3B,WAAO,EAAE,UAAU,OAAO,UAAU,QAAQ,OAAO,OAAO;AAAA,EAC5D;AAEA,QAAM,aAAaA,MAAK,QAAQ,GAAG;AACnC,MAAI,eAAe,IAAI;AACrB,WAAO,EAAE,UAAUA,OAAM,QAAQ,GAAG;AAAA,EACtC;AAEA,SAAO;AAAA,IACL,UAAUA,MAAK,MAAM,GAAG,UAAU;AAAA,IAClC,QAAQA,MAAK,MAAM,UAAU;AAAA,EAC/B;AACF;AAEA,SAAS,mBAAmB,cAAsB,gBAAgC;AAChF,QAAM,SAAS,IAAI,gBAAgB,cAAc;AACjD,QAAM,UAAU,IAAI,gBAAgB,YAAY;AAEhD,aAAW,OAAO,IAAI,IAAI,QAAQ,KAAK,CAAC,GAAG;AACzC,WAAO,OAAO,GAAG;AAAA,EACnB;AACA,aAAW,CAAC,KAAK,KAAK,KAAK,SAAS;AAClC,WAAO,OAAO,KAAK,KAAK;AAAA,EAC1B;AAEA,QAAM,SAAS,OAAO,SAAS;AAC/B,SAAO,OAAO,SAAS,IAAI,IAAI,MAAM,KAAK;AAC5C;AAEA,SAAS,aAAa,OAAyB;AAC7C,SAAO,iBAAiB,SAAS,MAAM,SAAS;AAClD;AAEA,eAAsB,aACpB,WACA,OACA,YAAY,4BACW;AACvB,QAAM,SAAS,IAAI,IAAI,SAAS;AAChC,QAAM,cAAc,IAAI,IAAI,OAAO,IAAI;AACvC,QAAM,kBAAkB,eAAe,MAAM,IAAI;AACjD,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,UAAU,WAAW,MAAM,WAAW,MAAM,GAAG,SAAS;AAE9D,cAAY,WAAW,gBAAgB,YAAY,UAAU,gBAAgB,QAAQ;AACrF,cAAY,SAAS,mBAAmB,YAAY,QAAQ,gBAAgB,MAAM;AAElF,MAAI;AACF,UAAM,UAAU,MAAM,WAAW,SAAS,MAAM,WAAW;AAC3D,UAAM,WAAW,MAAM,MAAM,aAAa;AAAA,MACxC,QAAQ,MAAM;AAAA,MACd,SAAS,qBAAqB,MAAM,OAAO;AAAA,MAC3C,MAAM,UAAU,MAAM,OAAO;AAAA,MAC7B,QAAQ,WAAW;AAAA,IACrB,CAAC;AAED,WAAO;AAAA,MACL,QAAQ,SAAS;AAAA,MACjB,MAAM,MAAM,SAAS,KAAK;AAAA,IAC5B;AAAA,EACF,SAAS,OAAO;AACd,QAAI,aAAa,KAAK,GAAG;AACvB,YAAM,IAAI,MAAM,2BAA2B,SAAS,IAAI;AAAA,IAC1D;AAIA,UAAM,QAAQ,iBAAiB,QAAQ,MAAM,QAAQ;AACrD,UAAM,OAAO,iBAAiB,QAAS,MAAgC,OAAO;AAC9E,UAAM,UAAU,iBAAiB,SAAS,MAAM,UAAU,MAAM,UAAU;AAC1E,UAAM,IAAI,MAAM,WAAW,aAAa,KAAK,CAAC;AAAA,EAChD,UAAE;AACA,iBAAa,OAAO;AAAA,EACtB;AACF;AAEO,SAAS,aAAa,MAA6B;AACxD,MAAI,YAAY,KAAK;AACrB,MAAI,aAAiC;AACrC,MAAI,aAAa;AACjB,QAAM,eAAe,KAAK,gBAAgB;AAC1C,QAAM,mBAAmB,KAAK,oBAAoB;AAElD,QAAM,gBAAgB,OACpB,KACA,QACkB;AAClB,UAAM,OAAO,MAAM,SAAS,KAAK,YAAY;AAE7C,UAAM,QAAsB;AAAA,MAC1B,IAAI,gBAAgB;AAAA,MACpB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC,QAAQ,IAAI,UAAU;AAAA,MACtB,MAAM,IAAI,OAAO;AAAA,MACjB,SAAS,gBAAgB,IAAI,OAAO;AAAA,MACpC;AAAA,IACF;AAEA,SAAK,QAAQ,KAAK,KAAK;AACvB,UAAM,eAAe,KAAK,UAAU,OAAO,EAAE,SAAS,MAAM,SAAS,MAAM,MAAM,KAAK,CAAC,KAAK;AAC5F,SAAK,UAAU,OAAO,YAAY;AAElC,QAAI,CAAC,KAAK,WAAW;AACnB,UAAI,aAAa;AACjB,UAAI,IAAI,IAAI;AACZ;AAAA,IACF;AAEA,QAAI;AACF,YAAM,YAAY,MAAM,aAAa,KAAK,WAAW,OAAO,gBAAgB;AAC5E,UAAI,aAAa,UAAU;AAC3B,UAAI,IAAI,UAAU,IAAI;AAAA,IACxB,SAAS,OAAO;AACd,YAAM,MAAM,QAAQ,KAAK;AACzB,UAAI;AACF,aAAK,iBAAiB,OAAO,GAAG;AAAA,MAClC,QAAQ;AAAA,MAER;AACA,UAAI,aAAa;AACjB,UAAI,IAAI,gBAAgB,IAAI,OAAO,EAAE;AAAA,IACvC;AAAA,EACF;AAEA,SAAO;AAAA,IACL,IAAI,OAAO;AACT,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,QAAQ;AACZ,UAAI,cAAc,YAAY;AAC5B,cAAM,IAAI,MAAM,wBAAwB;AAAA,MAC1C;AAEA,mBAAa;AACb,YAAM,SAAS,KAAK,aAAa,CAAC,KAAK,QAAQ;AAC7C,sBAAc,KAAK,GAAG,EAAE,MAAM,CAAC,QAAiB;AAC9C,cAAI,uBAAuB,GAAG,GAAG;AAC/B,gBAAI,aAAa;AACjB,gBAAI,IAAI,IAAI,OAAO;AACnB;AAAA,UACF;AACA,cAAI,aAAa;AACjB,cAAI,IAAI,aAAa,GAAG,CAAC;AAAA,QAC3B,CAAC;AAAA,MACH,CAAC;AACD,mBAAa;AAEb,UAAI;AACF,cAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,gBAAM,UAAU,CAAC,QAAe;AAC9B,mBAAO,IAAI,SAAS,OAAO;AAC3B,gBAAI,eAAe,OAAQ,cAAa;AACxC,wBAAY,KAAK;AACjB,yBAAa;AACb,mBAAO,GAAG;AAAA,UACZ;AAEA,iBAAO,KAAK,SAAS,OAAO;AAC5B,iBAAO,OAAO,KAAK,MAAM,aAAa,MAAM;AAC1C,mBAAO,IAAI,SAAS,OAAO;AAC3B,kBAAM,OAAO,OAAO,QAAQ;AAC5B,gBAAI,QAAQ,OAAO,SAAS,UAAU;AACpC,0BAAY,KAAK;AAAA,YACnB;AACA,yBAAa;AACb,oBAAQ;AAAA,UACV,CAAC;AAAA,QACH,CAAC;AAAA,MACH,SAAS,KAAK;AACZ,YAAI,eAAe,OAAQ,cAAa;AACxC,oBAAY,KAAK;AACjB,qBAAa;AACb,cAAM;AAAA,MACR;AAAA,IACF;AAAA,IAEA,MAAM,OAAO;AACX,UAAI,CAAC,WAAY;AACjB,YAAM,SAAS;AACf,YAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,eAAO,MAAM,CAAC,QAAQ;AACpB,cAAI,eAAe,OAAQ,cAAa;AACxC,sBAAY,KAAK;AACjB,uBAAa;AACb,cAAI,KAAK;AACP,mBAAO,GAAG;AACV;AAAA,UACF;AACA,kBAAQ;AAAA,QACV,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AAAA,EACF;AACF;;;AC5WA,OAAO,QAAQ;AACf,OAAO,QAAQ;AACf,SAAS,qBAAqB;AAC9B,OAAO,UAAU;;;ACHjB,SAAS,SAAS;AAIX,IAAM,qBAAqB,EAAE,OAAO;AAAA,EACzC,IAAI,EAAE,OAAO;AAAA,EACb,WAAW,EAAE,OAAO;AAAA,EACpB,QAAQ,EAAE,OAAO;AAAA,EACjB,MAAM,EAAE,OAAO;AAAA,EACf,SAAS,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC;AAAA,EACxC,MAAM,EAAE,OAAO;AACjB,CAAC;AAMM,IAAM,iBAAiB,EAAE,OAAO;AAAA,EACrC,IAAI,EAAE,OAAO;AAAA,EACb,WAAW,EAAE,OAAO;AAAA,EACpB,QAAQ,EAAE,OAAO;AAAA,EACjB,MAAM,EAAE,OAAO;AAAA,EACf,SAAS,EAAE,OAAO;AAAA,EAClB,MAAM,EAAE,OAAO;AACjB,CAAC;AAIM,IAAM,2BAA2B,EAAE,OAAO;AAAA,EAC/C,OAAO,EAAE,QAAQ;AAAA,EACjB,UAAU,EAAE,OAAO;AAAA,EACnB,SAAS,EAAE,OAAO;AAAA,EAClB,MAAM,EAAE,KAAK;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACH,CAAC;AAIM,IAAM,qBAAqB,EAAE,OAAO;AAAA,EACzC,QAAQ,EAAE,OAAO,EAAE,IAAI;AAAA,EACvB,MAAM,EAAE,OAAO;AACjB,CAAC;;;ADxCD,IAAMC,WAAU,cAAc,YAAY,GAAG;AAK7C,IAAM,EAAE,aAAa,IAAIA,SAAQ,aAAkB;AAE5C,SAAS,gBAAwB;AACtC,SAAO,KAAK,KAAK,GAAG,QAAQ,GAAG,aAAa,WAAW;AACzD;AAEA,SAAS,WAAW,KAA6B;AAC/C,SAAO,mBAAmB,MAAM;AAAA,IAC9B,IAAI,IAAI;AAAA,IACR,WAAW,IAAI;AAAA,IACf,QAAQ,IAAI;AAAA,IACZ,MAAM,IAAI;AAAA,IACV,SAAS,KAAK,MAAM,IAAI,OAAO;AAAA,IAC/B,MAAM,IAAI;AAAA,EACZ,CAAC;AACH;AAEO,SAAS,cAAc,QAAgB;AAC5C,KAAG,UAAU,KAAK,QAAQ,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;AACtD,QAAM,KAAK,IAAI,aAAa,MAAM;AAElC,KAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GASP;AAED,QAAM,aAAa,GAAG;AAAA,IACpB;AAAA;AAAA,EAEF;AAEA,QAAM,UAAU,GAAG,QAAQ,mCAAmC;AAC9D,QAAM,cAAc,GAAG,QAAQ,8CAA8C;AAC7E,QAAM,kBAAkB,GAAG,QAAQ,sDAAsD;AACzF,QAAM,YAAY,GAAG,QAAQ,oBAAoB;AAEjD,SAAO;AAAA,IACL,KAAK,OAA2B;AAC9B,iBAAW;AAAA,QACT,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,QACN,KAAK,UAAU,MAAM,OAAO;AAAA,QAC5B,MAAM;AAAA,MACR;AAAA,IACF;AAAA,IAEA,KAAK,IAAiC;AACpC,YAAM,MAAM,QAAQ,IAAI,EAAE;AAC1B,UAAI,CAAC,IAAK,QAAO;AACjB,YAAM,MAAM,eAAe,MAAM,GAAG;AACpC,aAAO,WAAW,GAAG;AAAA,IACvB;AAAA,IAEA,KAAK,OAAgC;AACnC,UAAI,UAAU,WAAc,CAAC,OAAO,UAAU,KAAK,KAAK,SAAS,IAAI;AACnE,cAAM,IAAI,MAAM,kDAAkD,KAAK,EAAE;AAAA,MAC3E;AACA,YAAM,MAAM,UAAU,SAAY,YAAY,IAAI,IAAI,gBAAgB,IAAI,KAAK;AAC/E,YAAM,OAAO,IAAI,IAAI,CAAC,MAAM,eAAe,MAAM,CAAC,CAAC;AACnD,aAAO,KAAK,IAAI,UAAU;AAAA,IAC5B;AAAA,IAEA,QAAc;AACZ,gBAAU,IAAI;AAAA,IAChB;AAAA,IAEA,QAAc;AACZ,SAAG,MAAM;AAAA,IACX;AAAA,EACF;AACF;;;AE1FA,OAAO,WAAW;AAoBlB,SAAS,UAAU,QAA4B,MAAoB;AACjE,SAAO,MAAM,GAAG,IAAI;AAAA,CAAI;AAC1B;AAEA,SAAS,kBAAkB,QAA2C;AACpE,MAAI,CAAC,OAAQ,QAAO,MAAM,KAAK,MAAM;AACrC,SAAO,OAAO,QAAQ,MAAM,MAAM,MAAM,IAAI,MAAM,IAAI,MAAM;AAC9D;AAEO,SAAS,eACd,SAA6B,QAAQ,QACrC,SAA6B,QAAQ,QACzB;AACZ,SAAO;AAAA,IACL,mBAAmB,MAAM;AACvB;AAAA,QACE;AAAA,QACA,GAAG,MAAM,KAAK,cAAc,CAAC,IAAI,MAAM,KAAK,oBAAoB,KAAK,IAAI,EAAE,CAAC;AAAA,MAC9E;AAEA,gBAAU,QAAQ,aAAa,KAAK,YAAY,MAAM,EAAE;AACxD,gBAAU,QAAQ,kBAAkB,KAAK,aAAa,UAAU,EAAE;AAClE,gBAAU,QAAQ,YAAY,KAAK,MAAM,EAAE;AAAA,IAC7C;AAAA,IAEA,mBAAmB,OAAO,QAAQ;AAChC,YAAM,QAAQ,kBAAkB,MAAM;AACtC,YAAM,UAAU,GAAG,KAAK,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC,IAAI,MAAM,MAAM,IAAI,MAAM,IAAI;AAE9E,UAAI,CAAC,QAAQ;AACX,kBAAU,QAAQ,OAAO;AACzB;AAAA,MACF;AAEA,gBAAU,QAAQ,GAAG,OAAO,IAAI,OAAO,OAAO,EAAE;AAAA,IAClD;AAAA,IAEA,kBAAkB,SAAS,QAAQ;AACjC,gBAAU,QAAQ,GAAG,MAAM,IAAI,KAAK,CAAC,IAAI,MAAM,KAAK,OAAO,CAAC,IAAI,MAAM,EAAE;AAAA,IAC1E;AAAA,IAEA,eAAe,QAAQ;AACrB,UAAI,CAAC,OAAO,QAAQ;AAClB,kBAAU,QAAQ,MAAM,IAAI,mBAAmB,CAAC;AAChD;AAAA,MACF;AAEA,iBAAW,SAAS,QAAQ;AAC1B,cAAM,MAAM,GAAG,MAAM,IAAI,MAAM,SAAS,CAAC,IAAI,MAAM,KAAK,MAAM,MAAM,CAAC,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC,IAAI,MAAM,IAAI;AAC3G,kBAAU,QAAQ,GAAG;AAAA,MACvB;AAAA,IACF;AAAA,IAEA,kBAAkB,QAAQ;AACxB,YAAM,UAAU,GAAG,MAAM,KAAK,kBAAkB,CAAC,IAAI,MAAM,KAAK,OAAO,OAAO,MAAM,CAAC,CAAC;AAEtF,UAAI,CAAC,OAAO,MAAM;AAChB,kBAAU,QAAQ,OAAO;AACzB;AAAA,MACF;AAEA,gBAAU,QAAQ,GAAG,OAAO,IAAI,OAAO,IAAI,EAAE;AAAA,IAC/C;AAAA,IAEA,qBAAqB;AACnB,gBAAU,QAAQ,MAAM,IAAI,oBAAoB,CAAC;AAAA,IACnD;AAAA,IAEA,WAAW,SAAS;AAClB,gBAAU,QAAQ,MAAM,IAAI,OAAO,CAAC;AAAA,IACtC;AAAA,EACF;AACF;;;AC5FA,OAAOC,aAAY;;;ACAZ,SAAS,yBACd,SACA,MACoB;AACpB,QAAM,WAAW,KAAK,YAAY;AAClC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AAClD,QAAI,IAAI,YAAY,MAAM,SAAU,QAAO;AAAA,EAC7C;AACA,SAAO;AACT;AAEO,SAAS,iBAAiB,SAAgC;AAC/D,MAAI;AACF,UAAM,YAAY,KAAK,UAAU,KAAK,MAAM,OAAO,CAAC;AACpD,WAAO,cAAc,UAAU,OAAO;AAAA,EACxC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ADRA,IAAM,WAAW;AACjB,IAAM,SAAS;AACf,IAAM,aAAa;AAEnB,SAAS,YAAY,QAAgB,SAAyB;AAC5D,SAAOC,QAAO,WAAW,UAAU,MAAM,EAAE,OAAO,OAAO,EAAE,OAAO,KAAK;AACzE;AAEA,SAAS,kBAAkB,UAAkB,QAAyB;AACpE,MAAI,SAAS,WAAW,OAAO,OAAQ,QAAO;AAC9C,QAAM,cAAc,OAAO,KAAK,UAAU,MAAM;AAChD,QAAM,YAAY,OAAO,KAAK,QAAQ,MAAM;AAC5C,SAAOA,QAAO,gBAAgB,aAAa,SAAS;AACtD;AAEA,SAAS,QAAQ,SAAqC;AACpD,SAAO,EAAE,OAAO,MAAM,UAAU,UAAU,MAAM,SAAS,QAAQ;AACnE;AAEA,SAAS,QACP,MACA,SACoB;AACpB,SAAO,EAAE,OAAO,OAAO,UAAU,UAAU,MAAM,QAAQ;AAC3D;AAEO,SAAS,sBAAsB,MAA+C;AACnF,MAAI,CAAC,KAAK,QAAQ;AAChB,WAAO;AAAA,MACL;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,MAAI,CAAC,KAAK,OAAO,WAAW,MAAM,GAAG;AACnC,WAAO,QAAQ,oBAAoB,oDAAoD;AAAA,EACzF;AAEA,QAAM,YAAY,KAAK,OAAO,MAAM,OAAO,MAAM;AACjD,MAAI,UAAU,WAAW,GAAG;AAC1B,WAAO,QAAQ,oBAAoB,2DAA2D;AAAA,EAChG;AAEA,MAAI,CAAC,WAAW,KAAK,SAAS,GAAG;AAC/B,WAAO,QAAQ,oBAAoB,0DAA0D;AAAA,EAC/F;AAEA,QAAM,sBAAsB,UAAU,YAAY;AAClD,QAAM,WAAW,YAAY,KAAK,QAAQ,KAAK,OAAO;AAEtD,MAAI,kBAAkB,UAAU,mBAAmB,GAAG;AACpD,WAAO,QAAQ,qBAAqB;AAAA,EACtC;AAEA,QAAM,YAAY,iBAAiB,KAAK,OAAO;AAC/C,MAAI,cAAc,MAAM;AACtB,UAAM,oBAAoB,YAAY,KAAK,QAAQ,SAAS;AAC5D,QAAI,kBAAkB,mBAAmB,mBAAmB,GAAG;AAC7D,aAAO;AAAA,QACL;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,EACF;AACF;AAEO,SAAS,qBAAqB,MAAoC;AACvE,SAAO;AAAA,IACL,UAAU;AAAA,IACV,QAAQ,CAAC,UACP,sBAAsB;AAAA,MACpB,SAAS,MAAM;AAAA,MACf,QAAQ,yBAAyB,MAAM,SAAS,qBAAqB;AAAA,MACrE,QAAQ,KAAK;AAAA,IACf,CAAC;AAAA,EACL;AACF;;;AE3FA,OAAOC,aAAY;AAYnB,IAAM,4BAA4B;AAClC,IAAMC,YAAW;AAOjB,SAAS,YAAY,QAAqC;AACxD,MAAI,YAA2B;AAC/B,QAAM,aAAuB,CAAC;AAE9B,aAAW,QAAQ,OAAO,MAAM,GAAG,GAAG;AACpC,UAAM,QAAQ,KAAK,QAAQ,GAAG;AAC9B,QAAI,UAAU,GAAI,QAAO;AAEzB,UAAM,MAAM,KAAK,MAAM,GAAG,KAAK;AAC/B,UAAM,QAAQ,KAAK,MAAM,QAAQ,CAAC;AAElC,QAAI,QAAQ,KAAK;AACf,UAAI,CAAC,QAAQ,KAAK,KAAK,EAAG,QAAO;AACjC,kBAAY,OAAO,KAAK;AAAA,IAC1B,WAAW,QAAQ,MAAM;AACvB,UAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,iBAAW,KAAK,KAAK;AAAA,IACvB;AAAA,EACF;AAEA,MAAI,cAAc,QAAQ,WAAW,WAAW,EAAG,QAAO;AAC1D,SAAO,EAAE,WAAW,WAAW;AACjC;AAEA,SAASC,aAAY,QAAgB,eAA+B;AAClE,SAAOC,QAAO,WAAW,UAAU,MAAM,EAAE,OAAO,aAAa,EAAE,OAAO,KAAK;AAC/E;AAEA,SAASC,mBAAkB,UAAkB,YAA+B;AAC1E,QAAM,cAAc,OAAO,KAAK,UAAU,MAAM;AAChD,aAAW,aAAa,YAAY;AAClC,QAAI,UAAU,WAAW,SAAS,OAAQ;AAC1C,UAAM,eAAe,OAAO,KAAK,WAAW,MAAM;AAClD,QAAID,QAAO,gBAAgB,aAAa,YAAY,EAAG,QAAO;AAAA,EAChE;AACA,SAAO;AACT;AAEA,SAASE,SAAQ,SAAqC;AACpD,SAAO,EAAE,OAAO,MAAM,UAAUJ,WAAU,MAAM,SAAS,QAAQ;AACnE;AAEA,SAASK,SACP,MACA,SACoB;AACpB,SAAO,EAAE,OAAO,OAAO,UAAUL,WAAU,MAAM,QAAQ;AAC3D;AAEO,SAAS,sBAAsB,MAA+C;AACnF,MAAI,CAAC,KAAK,QAAQ;AAChB,WAAOK;AAAA,MACL;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAAS,YAAY,KAAK,MAAM;AACtC,MAAI,CAAC,QAAQ;AACX,WAAOA;AAAA,MACL;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,YAAY,KAAK,aAAa;AACpC,QAAM,SAAS,KAAK,OAAO,KAAK,KAAK;AACrC,QAAM,aAAa,KAAK,MAAM,QAAQ,GAAI,IAAI,OAAO;AAErD,MAAI,aAAa,WAAW;AAC1B,UAAM,UAAU,KAAK,MAAM,aAAa,EAAE;AAC1C,WAAOA;AAAA,MACL;AAAA,MACA,gBAAgB,OAAO;AAAA,IACzB;AAAA,EACF;AAEA,QAAM,gBAAgB,GAAG,OAAO,SAAS,IAAI,KAAK,OAAO;AACzD,QAAM,WAAWJ,aAAY,KAAK,QAAQ,aAAa;AAEvD,MAAIE,mBAAkB,UAAU,OAAO,UAAU,GAAG;AAClD,WAAOC,SAAQ,qBAAqB;AAAA,EACtC;AAEA,QAAM,YAAY,iBAAiB,KAAK,OAAO;AAC/C,MAAI,cAAc,MAAM;AACtB,UAAM,oBAAoBH,aAAY,KAAK,QAAQ,GAAG,OAAO,SAAS,IAAI,SAAS,EAAE;AACrF,QAAIE,mBAAkB,mBAAmB,OAAO,UAAU,GAAG;AAC3D,aAAOE;AAAA,QACL;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAOA;AAAA,IACL;AAAA,IACA;AAAA,EACF;AACF;AAOO,SAAS,qBAAqB,MAAuC;AAC1E,SAAO;AAAA,IACL,UAAUL;AAAA,IACV,QAAQ,CAAC,UACP,sBAAsB;AAAA,MACpB,SAAS,MAAM;AAAA,MACf,QAAQ,yBAAyB,MAAM,SAAS,kBAAkB;AAAA,MAClE,QAAQ,KAAK;AAAA,MACb,WAAW,KAAK;AAAA,IAClB,CAAC;AAAA,EACL;AACF;;;AP/GA,SAAS,UAAU,MAA2C;AAC5D,QAAM,MAAM;AACZ,QAAM,SAAS,OAAO,QAAQ,WAAW,MAAM,OAAO,GAAG;AAEzD,MAAI,CAAC,OAAO,UAAU,MAAM,KAAK,SAAS,KAAK,SAAS,OAAQ;AAC9D,UAAM,IAAI,MAAM,iBAAiB,GAAG,6CAA6C;AAAA,EACnF;AAEA,SAAO;AACT;AAGO,SAAS,cAAc,OAA0C;AACtE,MAAI,CAAC,MAAM,OAAQ,QAAO;AAE1B,UAAQ,MAAM,QAAQ;AAAA,IACpB,KAAK,UAAU;AACb,UAAI,CAAC,MAAM,QAAQ;AACjB,cAAM,IAAI,MAAM,kDAAkD;AAAA,MACpE;AACA,aAAO,qBAAqB,EAAE,QAAQ,MAAM,OAAO,CAAC;AAAA,IACtD;AAAA,IACA,KAAK,UAAU;AACb,UAAI,CAAC,MAAM,QAAQ;AACjB,cAAM,IAAI,MAAM,kDAAkD;AAAA,MACpE;AACA,aAAO,qBAAqB,EAAE,QAAQ,MAAM,OAAO,CAAC;AAAA,IACtD;AAAA,IACA;AACE,YAAM,IAAI,MAAM,8BAA8B,MAAM,MAAM,8BAA8B;AAAA,EAC5F;AACF;AAEA,eAAe,WAAW,QAAsC;AAC9D,MAAI,CAAC,OAAQ;AACb,QAAM,OAAO,KAAK;AACpB;AAEA,SAAS,6BACP,UACA,OACA,QACM;AACN,MAAI;AACF,aAAS,mBAAmB,OAAO,MAAM;AAAA,EAC3C,SAAS,OAAO;AACd,YAAQ,MAAM,mCAAmC,aAAa,KAAK,CAAC,EAAE;AAAA,EACxE;AACF;AAEA,eAAsB,UAAU,OAAoB,OAAmB,CAAC,GAAkB;AACxF,QAAM,OAAO,UAAU,MAAM,IAAI;AACjC,QAAM,WAAW,cAAc,KAAK;AACpC,QAAM,SAAS,cAAc;AAC7B,QAAM,UAAU,KAAK,WAAW;AAChC,QAAM,WAAW,KAAK,YAAY,eAAe;AACjD,QAAM,UAAU,cAAc,MAAM;AAEpC,MAAI,SAAwB;AAC5B,MAAI,YAAY;AAChB,MAAI,oBAAoB;AAExB,QAAM,UAAU,OAAO,iBAAyC;AAC9D,QAAI,UAAW;AACf,gBAAY;AACZ,QAAI,mBAAmB;AACrB,cAAQ,IAAI,UAAU,QAAQ;AAC9B,cAAQ,IAAI,WAAW,QAAQ;AAC/B,0BAAoB;AAAA,IACtB;AAEA,QAAI,YAAqB;AAEzB,QAAI;AACF,YAAM,WAAW,MAAM;AAAA,IACzB,SAAS,OAAO;AACd,kBAAY;AAAA,IACd,UAAE;AACA,cAAQ,MAAM;AAAA,IAChB;AAEA,QAAI,WAAW;AACb,YAAM;AAAA,IACR;AAEA,QAAI,cAAc;AAChB,eAAS,mBAAmB;AAAA,IAC9B;AAAA,EACF;AAEA,MAAI,SAA8B;AAClC,MAAI,OAA0C;AAC9C,QAAM,WAAW,IAAI,QAAc,CAAC,SAAS,WAAW;AACtD,aAAS;AACT,WAAO;AAAA,EACT,CAAC;AAED,QAAM,WAAW,MAAM;AACrB,SAAK,QAAQ,IAAI,EAAE;AAAA,MACjB,MAAM,SAAS;AAAA,MACf,CAAC,UAAU,OAAO,KAAK;AAAA,IACzB;AAAA,EACF;AAEA,MAAI;AACF,aAAS,aAAa;AAAA,MACpB;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW,MAAM;AAAA,MACjB,SAAS,CAAC,OAAO,WAAW,6BAA6B,UAAU,OAAO,MAAM;AAAA,MAChF,gBAAgB,CAAC,OAAO,UAAU,SAAS,kBAAkB,MAAM,IAAI,MAAM,OAAO;AAAA,IACtF,CAAC;AAED,YAAQ,GAAG,UAAU,QAAQ;AAC7B,YAAQ,GAAG,WAAW,QAAQ;AAC9B,wBAAoB;AAEpB,UAAM,OAAO,MAAM;AAEnB,QAAI,WAAW;AACb,aAAO,MAAM;AAAA,IACf;AAEA,aAAS,mBAAmB;AAAA,MAC1B,MAAM,OAAO;AAAA,MACb;AAAA,MACA,UAAU,UAAU;AAAA,MACpB,WAAW,MAAM;AAAA,IACnB,CAAC;AAED,WAAO,MAAM;AAAA,EACf,SAAS,OAAO;AACd,QAAI,WAAW;AACb,aAAO,MAAM;AAAA,IACf;AAEA,UAAM,QAAQ,KAAK;AACnB,UAAM;AAAA,EACR;AACF;AAEO,IAAM,gBAAgB,IAAI,QAAQ,QAAQ,EAC9C,YAAY,0BAA0B,EACtC,OAAO,qBAAqB,qBAAqB,MAAM,EACvD,OAAO,uBAAuB,oCAAoC,EAClE,OAAO,qBAAqB,wBAAwB,EACpD,OAAO,sBAAsB,uCAAuC,EACpE;AAAA,EACC;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAMF,EACC,OAAO,OAAO,YAAY;AACzB,QAAM,WAAW,eAAe;AAEhC,MAAI;AACF,UAAM,UAAU,SAAS,EAAE,SAAS,CAAC;AAAA,EACvC,SAAS,OAAO;AACd,aAAS,WAAW,aAAa,KAAK,CAAC;AAEvC,YAAQ,WAAW;AAAA,EACrB;AACF,CAAC;;;AQjMH,SAAS,WAAAM,gBAAe;AAaxB,SAAS,WAAW,OAA4C;AAC9D,QAAM,MAAM;AACZ,QAAM,SAAS,OAAO,QAAQ,WAAW,MAAM,OAAO,GAAG;AAEzD,MAAI,CAAC,OAAO,UAAU,MAAM,KAAK,UAAU,GAAG;AAC5C,UAAM,IAAI,MAAM,kBAAkB,GAAG,iCAAiC;AAAA,EACxE;AAEA,SAAO;AACT;AAEA,eAAsB,QAAQ,OAAkB,OAAiB,CAAC,GAAkB;AAClF,QAAM,QAAQ,WAAW,MAAM,SAAS,IAAI;AAC5C,QAAM,WAAW,KAAK,YAAY,eAAe;AACjD,QAAM,UAAU,cAAc,cAAc,CAAC;AAE7C,MAAI;AACF,UAAM,SAAS,QAAQ,KAAK,KAAK;AACjC,aAAS,eAAe,MAAM;AAAA,EAChC,UAAE;AACA,YAAQ,MAAM;AAAA,EAChB;AACF;AAEO,IAAM,cAAc,IAAIC,SAAQ,MAAM,EAC1C,YAAY,8BAA8B,EAC1C,OAAO,uBAAuB,4BAA4B,IAAI,EAC9D;AAAA,EACC;AAAA,EACA;AAAA;AAAA;AAAA;AAIF,EACC,OAAO,OAAO,YAAY;AACzB,QAAM,WAAW,eAAe;AAEhC,MAAI;AACF,UAAM,QAAQ,SAAS,EAAE,SAAS,CAAC;AAAA,EACrC,SAAS,OAAO;AACd,aAAS,WAAW,aAAa,KAAK,CAAC;AACvC,YAAQ,WAAW;AAAA,EACrB;AACF,CAAC;;;ACxDH,SAAS,WAAAC,gBAAe;AAMxB,IAAM,4BAA4B;AAUlC,SAAS,eAAe,WAAuC;AAC7D,QAAM,MAAM,aAAa;AAEzB,MAAI;AACF,WAAO,IAAI,IAAI,GAAG,EAAE;AAAA,EACtB,QAAQ;AACN,UAAM,IAAI,MAAM,uBAAuB,GAAG,IAAI;AAAA,EAChD;AACF;AAEA,eAAsB,UACpB,SACA,OACA,OAAmB,CAAC,GACL;AACf,QAAM,YAAY,eAAe,MAAM,EAAE;AACzC,QAAM,WAAW,KAAK,YAAY,eAAe;AACjD,QAAM,UAAU,cAAc,cAAc,CAAC;AAE7C,MAAI;AACF,UAAM,QAAQ,QAAQ,KAAK,OAAO;AAElC,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,MAAM,UAAU,OAAO,cAAc;AAAA,IACjD;AAEA,QAAI;AACF,YAAM,SAAS,MAAM,aAAa,WAAW,KAAK;AAClD,YAAM,OAAO,OAAO,KAAK,UAAU,MAAM,OAAO,OAAO,GAAG,OAAO,KAAK,MAAM,GAAG,GAAG,CAAC;AAEnF,eAAS,kBAAkB;AAAA,QACzB,QAAQ,OAAO;AAAA,QACf;AAAA,MACF,CAAC;AAAA,IACH,SAAS,OAAO;AACd,YAAM,IAAI,MAAM,qBAAqB,OAAO,QAAQ,SAAS,KAAK,aAAa,KAAK,CAAC,EAAE;AAAA,IACzF;AAAA,EACF,UAAE;AACA,YAAQ,MAAM;AAAA,EAChB;AACF;AAEO,IAAM,gBAAgB,IAAIC,SAAQ,QAAQ,EAC9C,YAAY,+BAA+B,EAC3C,SAAS,cAAc,2BAA2B,EAClD,OAAO,cAAc,mCAAmC,yBAAyB,EACjF;AAAA,EACC;AAAA,EACA;AAAA;AAAA;AAAA;AAIF,EACC,OAAO,OAAO,SAAS,YAAY;AAClC,QAAM,WAAW,eAAe;AAEhC,MAAI;AACF,UAAM,UAAU,SAAS,SAAS,EAAE,SAAS,CAAC;AAAA,EAChD,SAAS,OAAO;AACd,aAAS,WAAW,aAAa,KAAK,CAAC;AACvC,YAAQ,WAAW;AAAA,EACrB;AACF,CAAC;;;AXxEH,IAAM,UAAU,IAAIC,SAAQ;AAE5B,QACG,KAAK,UAAU,EACf,YAAY,yDAAyD,EACrE,QAAQ,OAAO;AAElB,QAAQ,WAAW,aAAa;AAChC,QAAQ,WAAW,WAAW;AAC9B,QAAQ,WAAW,aAAa;AAEhC,IAAI;AACF,QAAM,QAAQ,WAAW,QAAQ,IAAI;AACvC,SAAS,OAAO;AACd,UAAQ,MAAM,aAAa,KAAK,CAAC;AACjC,UAAQ,WAAW;AACrB;","names":["Command","path","require","crypto","crypto","crypto","PROVIDER","computeHmac","crypto","constantTimeMatch","success","failure","Command","Command","Command","Command","Command"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hooklens",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Inspect, verify, and replay webhooks from your terminal",
5
5
  "type": "module",
6
6
  "bin": {
@@ -49,6 +49,7 @@
49
49
  "LICENSE"
50
50
  ],
51
51
  "devDependencies": {
52
+ "@octokit/webhooks-methods": "^6.0.0",
52
53
  "@types/node": "^22.0.0",
53
54
  "eslint": "^9.0.0",
54
55
  "husky": "^9.1.7",