@better-webhook/cli 3.9.0 → 3.10.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.
Files changed (56) hide show
  1. package/dist/_binary_entry.js +29 -0
  2. package/dist/commands/capture.d.ts +2 -0
  3. package/dist/commands/capture.js +33 -0
  4. package/dist/commands/captures.d.ts +2 -0
  5. package/dist/commands/captures.js +316 -0
  6. package/dist/commands/dashboard.d.ts +2 -0
  7. package/dist/commands/dashboard.js +70 -0
  8. package/dist/commands/index.d.ts +6 -0
  9. package/dist/commands/index.js +6 -0
  10. package/dist/commands/replay.d.ts +2 -0
  11. package/dist/commands/replay.js +140 -0
  12. package/dist/commands/run.d.ts +2 -0
  13. package/dist/commands/run.js +182 -0
  14. package/dist/commands/templates.d.ts +2 -0
  15. package/dist/commands/templates.js +285 -0
  16. package/dist/core/capture-server.d.ts +37 -0
  17. package/dist/core/capture-server.js +400 -0
  18. package/dist/core/capture-server.test.d.ts +1 -0
  19. package/dist/core/capture-server.test.js +86 -0
  20. package/dist/core/cli-version.d.ts +1 -0
  21. package/dist/core/cli-version.js +30 -0
  22. package/dist/core/cli-version.test.d.ts +1 -0
  23. package/dist/core/cli-version.test.js +42 -0
  24. package/dist/core/dashboard-api.d.ts +8 -0
  25. package/dist/core/dashboard-api.js +333 -0
  26. package/dist/core/dashboard-server.d.ts +24 -0
  27. package/dist/core/dashboard-server.js +224 -0
  28. package/dist/core/debug-output.d.ts +3 -0
  29. package/dist/core/debug-output.js +69 -0
  30. package/dist/core/debug-verify.d.ts +25 -0
  31. package/dist/core/debug-verify.js +253 -0
  32. package/dist/core/executor.d.ts +11 -0
  33. package/dist/core/executor.js +152 -0
  34. package/dist/core/index.d.ts +5 -0
  35. package/dist/core/index.js +5 -0
  36. package/dist/core/replay-engine.d.ts +20 -0
  37. package/dist/core/replay-engine.js +293 -0
  38. package/dist/core/replay-engine.test.d.ts +1 -0
  39. package/dist/core/replay-engine.test.js +482 -0
  40. package/dist/core/runtime-paths.d.ts +2 -0
  41. package/dist/core/runtime-paths.js +65 -0
  42. package/dist/core/runtime-paths.test.d.ts +1 -0
  43. package/dist/core/runtime-paths.test.js +50 -0
  44. package/dist/core/signature.d.ts +25 -0
  45. package/dist/core/signature.js +224 -0
  46. package/dist/core/signature.test.d.ts +1 -0
  47. package/dist/core/signature.test.js +38 -0
  48. package/dist/core/template-manager.d.ts +33 -0
  49. package/dist/core/template-manager.js +313 -0
  50. package/dist/core/template-manager.test.d.ts +1 -0
  51. package/dist/core/template-manager.test.js +236 -0
  52. package/dist/index.cjs +135 -20
  53. package/dist/index.js +123 -8
  54. package/dist/types/index.d.ts +312 -0
  55. package/dist/types/index.js +87 -0
  56. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1762,7 +1762,9 @@ function resolveRuntimePackageVersion(runtimeDir) {
1762
1762
  continue;
1763
1763
  }
1764
1764
  visitedRoots.add(cliPackageRoot);
1765
- const version = readPackageVersion(path2.join(cliPackageRoot, "package.json"));
1765
+ const version = readPackageVersion(
1766
+ path2.join(cliPackageRoot, "package.json")
1767
+ );
1766
1768
  if (version) {
1767
1769
  return version;
1768
1770
  }
@@ -3162,6 +3164,7 @@ var WebhookProviderSchema = z.enum([
3162
3164
  "shopify",
3163
3165
  "twilio",
3164
3166
  "ragie",
3167
+ "recall",
3165
3168
  "sendgrid",
3166
3169
  "slack",
3167
3170
  "discord",
@@ -3855,6 +3858,7 @@ import prompts2 from "prompts";
3855
3858
  import { request as request2 } from "undici";
3856
3859
 
3857
3860
  // src/core/signature.ts
3861
+ import { Buffer as Buffer2 } from "buffer";
3858
3862
  import { createHmac } from "crypto";
3859
3863
  function generateStripeSignature(payload, secret, timestamp) {
3860
3864
  const ts = timestamp || Math.floor(Date.now() / 1e3);
@@ -3929,6 +3933,25 @@ function generateRagieSignature(payload, secret) {
3929
3933
  value: signature
3930
3934
  };
3931
3935
  }
3936
+ function generateRecallSignature(payload, secret, timestamp, webhookId) {
3937
+ if (!secret.startsWith("whsec_")) {
3938
+ throw new Error(
3939
+ "Recall signature generation requires a secret with the whsec_ prefix"
3940
+ );
3941
+ }
3942
+ const ts = timestamp ?? Math.floor(Date.now() / 1e3);
3943
+ const msgId = webhookId ?? `msg_${Date.now()}`;
3944
+ const key = Buffer2.from(secret.slice("whsec_".length), "base64");
3945
+ if (key.length === 0) {
3946
+ throw new Error("Recall signing secret is invalid");
3947
+ }
3948
+ const signedPayload = `${msgId}.${ts}.${payload}`;
3949
+ const signature = createHmac("sha256", key).update(signedPayload).digest("base64");
3950
+ return {
3951
+ header: "Webhook-Signature",
3952
+ value: `v1,${signature}`
3953
+ };
3954
+ }
3932
3955
  function generateSignature(provider, payload, secret, options) {
3933
3956
  const timestamp = options?.timestamp;
3934
3957
  switch (provider) {
@@ -3958,6 +3981,13 @@ function generateSignature(provider, payload, secret, options) {
3958
3981
  return generateSendGridSignature(payload, secret, timestamp);
3959
3982
  case "ragie":
3960
3983
  return generateRagieSignature(payload, secret);
3984
+ case "recall":
3985
+ return generateRecallSignature(
3986
+ payload,
3987
+ secret,
3988
+ timestamp,
3989
+ options?.webhookId
3990
+ );
3961
3991
  case "discord":
3962
3992
  case "custom":
3963
3993
  default:
@@ -4046,6 +4076,13 @@ function getProviderHeaders(provider, options) {
4046
4076
  // Event type + nonce are included in the JSON body envelope.
4047
4077
  );
4048
4078
  break;
4079
+ case "recall":
4080
+ headers.push(
4081
+ { key: "Content-Type", value: "application/json" },
4082
+ { key: "Webhook-Id", value: options?.webhookId || `msg_${Date.now()}` },
4083
+ { key: "Webhook-Timestamp", value: String(timestamp) }
4084
+ );
4085
+ break;
4049
4086
  default:
4050
4087
  headers.push({ key: "Content-Type", value: "application/json" });
4051
4088
  }
@@ -4084,8 +4121,14 @@ async function executeWebhook(options) {
4084
4121
  }
4085
4122
  }
4086
4123
  if (options.secret && options.provider && bodyStr) {
4124
+ const timestampHeader = headers["Webhook-Timestamp"] || headers["webhook-timestamp"] || headers["Svix-Timestamp"] || headers["svix-timestamp"] || headers["X-Slack-Request-Timestamp"] || headers["x-slack-request-timestamp"] || headers["X-Twilio-Email-Event-Webhook-Timestamp"] || headers["x-twilio-email-event-webhook-timestamp"];
4125
+ const parsedTimestamp = timestampHeader ? Number.parseInt(timestampHeader, 10) : void 0;
4126
+ const timestamp = Number.isFinite(parsedTimestamp) ? parsedTimestamp : void 0;
4127
+ const webhookId = headers["Webhook-Id"] || headers["webhook-id"] || headers["Svix-Id"] || headers["svix-id"] || headers["X-GitHub-Delivery"] || headers["x-github-delivery"];
4087
4128
  const sig = generateSignature(options.provider, bodyStr, options.secret, {
4088
- url: options.url
4129
+ url: options.url,
4130
+ timestamp,
4131
+ webhookId
4089
4132
  });
4090
4133
  if (sig) {
4091
4134
  headers[sig.header] = sig.value;
@@ -4200,6 +4243,7 @@ function getSecretEnvVarName(provider) {
4200
4243
  shopify: "SHOPIFY_WEBHOOK_SECRET",
4201
4244
  twilio: "TWILIO_WEBHOOK_SECRET",
4202
4245
  ragie: "RAGIE_WEBHOOK_SECRET",
4246
+ recall: "RECALL_WEBHOOK_SECRET",
4203
4247
  slack: "SLACK_WEBHOOK_SECRET",
4204
4248
  linear: "LINEAR_WEBHOOK_SECRET",
4205
4249
  clerk: "CLERK_WEBHOOK_SECRET",
@@ -4646,6 +4690,12 @@ var CaptureServer = class {
4646
4690
  return "ragie";
4647
4691
  }
4648
4692
  }
4693
+ if (this.hasStandardWebhookHeaders(headers)) {
4694
+ const recallUserAgent = this.headerIncludes(headers["user-agent"], "recall");
4695
+ if (recallUserAgent || this.hasRecallStandardWebhookShape(body)) {
4696
+ return "recall";
4697
+ }
4698
+ }
4649
4699
  if (headers["x-shopify-hmac-sha256"] || headers["x-shopify-topic"]) {
4650
4700
  return "shopify";
4651
4701
  }
@@ -4665,6 +4715,9 @@ var CaptureServer = class {
4665
4715
  return "linear";
4666
4716
  }
4667
4717
  if (headers["svix-signature"]) {
4718
+ if (body && typeof body === "object" && "event" in body && typeof body.event === "string" && body.event.startsWith("bot.")) {
4719
+ return "recall";
4720
+ }
4668
4721
  return "clerk";
4669
4722
  }
4670
4723
  return void 0;
@@ -4680,6 +4733,57 @@ var CaptureServer = class {
4680
4733
  }
4681
4734
  }
4682
4735
  }
4736
+ hasStandardWebhookHeaders(headers) {
4737
+ return Boolean(
4738
+ headers["webhook-signature"] || headers["webhook-id"] && headers["webhook-timestamp"]
4739
+ );
4740
+ }
4741
+ hasRecallStandardWebhookShape(body) {
4742
+ if (!body || typeof body !== "object") {
4743
+ return false;
4744
+ }
4745
+ const payload = body;
4746
+ if (this.hasRecallEventPrefix(payload.event)) {
4747
+ return true;
4748
+ }
4749
+ if (this.hasRecallResourceCombination(payload)) {
4750
+ return true;
4751
+ }
4752
+ const nestedData = payload.data;
4753
+ if (nestedData && typeof nestedData === "object") {
4754
+ return this.hasRecallResourceCombination(
4755
+ nestedData
4756
+ );
4757
+ }
4758
+ return false;
4759
+ }
4760
+ hasRecallEventPrefix(event) {
4761
+ if (typeof event !== "string") {
4762
+ return false;
4763
+ }
4764
+ return ["bot.", "transcript.", "participant_events."].some(
4765
+ (prefix) => event.startsWith(prefix)
4766
+ );
4767
+ }
4768
+ hasRecallResourceCombination(payload) {
4769
+ const hasRealtimeEndpoint = "realtime_endpoint" in payload;
4770
+ const hasRecording = "recording" in payload;
4771
+ const hasParticipantEvents = "participant_events" in payload;
4772
+ const hasTranscript = "transcript" in payload;
4773
+ return hasRealtimeEndpoint && hasRecording && (hasParticipantEvents || hasTranscript);
4774
+ }
4775
+ headerIncludes(headerValue, searchText) {
4776
+ const normalizedSearchText = searchText.toLowerCase();
4777
+ if (typeof headerValue === "string") {
4778
+ return headerValue.toLowerCase().includes(normalizedSearchText);
4779
+ }
4780
+ if (Array.isArray(headerValue)) {
4781
+ return headerValue.some(
4782
+ (value) => value.toLowerCase().includes(normalizedSearchText)
4783
+ );
4784
+ }
4785
+ return false;
4786
+ }
4683
4787
  /**
4684
4788
  * Send message to a specific client
4685
4789
  */
@@ -4881,6 +4985,7 @@ var ReplayEngine = class {
4881
4985
  "x-twilio-signature",
4882
4986
  "x-slack-signature",
4883
4987
  "svix-signature",
4988
+ "webhook-signature",
4884
4989
  "linear-signature"
4885
4990
  ];
4886
4991
  const headers = [];
@@ -4952,10 +5057,19 @@ var ReplayEngine = class {
4952
5057
  }
4953
5058
  if (capture2.provider === "ragie" && capture2.body) {
4954
5059
  const body = capture2.body;
5060
+ if (typeof body.type === "string") {
5061
+ return body.type;
5062
+ }
4955
5063
  if (typeof body.event_type === "string") {
4956
5064
  return body.event_type;
4957
5065
  }
4958
5066
  }
5067
+ if (capture2.provider === "recall" && capture2.body) {
5068
+ const body = capture2.body;
5069
+ if (typeof body.event === "string") {
5070
+ return body.event;
5071
+ }
5072
+ }
4959
5073
  const shopifyTopic = headers["x-shopify-topic"];
4960
5074
  if (shopifyTopic) {
4961
5075
  return Array.isArray(shopifyTopic) ? shopifyTopic[0] : shopifyTopic;
@@ -5542,6 +5656,7 @@ function getSecretEnvVarName2(provider) {
5542
5656
  shopify: "SHOPIFY_WEBHOOK_SECRET",
5543
5657
  twilio: "TWILIO_WEBHOOK_SECRET",
5544
5658
  ragie: "RAGIE_WEBHOOK_SECRET",
5659
+ recall: "RECALL_WEBHOOK_SECRET",
5545
5660
  slack: "SLACK_WEBHOOK_SECRET",
5546
5661
  linear: "LINEAR_WEBHOOK_SECRET",
5547
5662
  clerk: "CLERK_WEBHOOK_SECRET",
@@ -6119,13 +6234,13 @@ var dashboard = new Command6().name("dashboard").description("Start the local da
6119
6234
  process.exitCode = 1;
6120
6235
  return;
6121
6236
  }
6237
+ const capturePort = Number.parseInt(String(options.capturePort), 10);
6238
+ if (!Number.isFinite(capturePort) || capturePort < 0 || capturePort > 65535) {
6239
+ console.error(source_default.red("Invalid capture port number"));
6240
+ process.exitCode = 1;
6241
+ return;
6242
+ }
6122
6243
  try {
6123
- const capturePort = Number.parseInt(String(options.capturePort), 10);
6124
- if (!Number.isFinite(capturePort) || capturePort < 0 || capturePort > 65535) {
6125
- console.error(source_default.red("Invalid capture port number"));
6126
- process.exitCode = 1;
6127
- return;
6128
- }
6129
6244
  const verbose = Boolean(options.verbose || options.debug);
6130
6245
  const { url, server, capture: capture2 } = await startDashboardServer({
6131
6246
  host: options.host,
@@ -0,0 +1,312 @@
1
+ import { z } from "zod";
2
+ export declare const HttpMethodSchema: z.ZodEnum<["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]>;
3
+ export type HttpMethod = z.infer<typeof HttpMethodSchema>;
4
+ export declare const HeaderEntrySchema: z.ZodObject<{
5
+ key: z.ZodString;
6
+ value: z.ZodString;
7
+ }, "strip", z.ZodTypeAny, {
8
+ value: string;
9
+ key: string;
10
+ }, {
11
+ value: string;
12
+ key: string;
13
+ }>;
14
+ export type HeaderEntry = z.infer<typeof HeaderEntrySchema>;
15
+ export declare const WebhookProviderSchema: z.ZodEnum<["stripe", "github", "shopify", "twilio", "ragie", "recall", "sendgrid", "slack", "discord", "linear", "clerk", "custom"]>;
16
+ export type WebhookProvider = z.infer<typeof WebhookProviderSchema>;
17
+ export declare const TemplateMetadataSchema: z.ZodObject<{
18
+ id: z.ZodString;
19
+ name: z.ZodString;
20
+ description: z.ZodOptional<z.ZodString>;
21
+ provider: z.ZodEnum<["stripe", "github", "shopify", "twilio", "ragie", "recall", "sendgrid", "slack", "discord", "linear", "clerk", "custom"]>;
22
+ event: z.ZodString;
23
+ file: z.ZodString;
24
+ version: z.ZodOptional<z.ZodString>;
25
+ docsUrl: z.ZodOptional<z.ZodString>;
26
+ }, "strip", z.ZodTypeAny, {
27
+ id: string;
28
+ name: string;
29
+ provider: "stripe" | "github" | "shopify" | "twilio" | "ragie" | "recall" | "sendgrid" | "slack" | "discord" | "linear" | "clerk" | "custom";
30
+ event: string;
31
+ file: string;
32
+ description?: string | undefined;
33
+ version?: string | undefined;
34
+ docsUrl?: string | undefined;
35
+ }, {
36
+ id: string;
37
+ name: string;
38
+ provider: "stripe" | "github" | "shopify" | "twilio" | "ragie" | "recall" | "sendgrid" | "slack" | "discord" | "linear" | "clerk" | "custom";
39
+ event: string;
40
+ file: string;
41
+ description?: string | undefined;
42
+ version?: string | undefined;
43
+ docsUrl?: string | undefined;
44
+ }>;
45
+ export type TemplateMetadata = z.infer<typeof TemplateMetadataSchema>;
46
+ export declare const TemplatesIndexSchema: z.ZodObject<{
47
+ version: z.ZodString;
48
+ templates: z.ZodArray<z.ZodObject<{
49
+ id: z.ZodString;
50
+ name: z.ZodString;
51
+ description: z.ZodOptional<z.ZodString>;
52
+ provider: z.ZodEnum<["stripe", "github", "shopify", "twilio", "ragie", "recall", "sendgrid", "slack", "discord", "linear", "clerk", "custom"]>;
53
+ event: z.ZodString;
54
+ file: z.ZodString;
55
+ version: z.ZodOptional<z.ZodString>;
56
+ docsUrl: z.ZodOptional<z.ZodString>;
57
+ }, "strip", z.ZodTypeAny, {
58
+ id: string;
59
+ name: string;
60
+ provider: "stripe" | "github" | "shopify" | "twilio" | "ragie" | "recall" | "sendgrid" | "slack" | "discord" | "linear" | "clerk" | "custom";
61
+ event: string;
62
+ file: string;
63
+ description?: string | undefined;
64
+ version?: string | undefined;
65
+ docsUrl?: string | undefined;
66
+ }, {
67
+ id: string;
68
+ name: string;
69
+ provider: "stripe" | "github" | "shopify" | "twilio" | "ragie" | "recall" | "sendgrid" | "slack" | "discord" | "linear" | "clerk" | "custom";
70
+ event: string;
71
+ file: string;
72
+ description?: string | undefined;
73
+ version?: string | undefined;
74
+ docsUrl?: string | undefined;
75
+ }>, "many">;
76
+ }, "strip", z.ZodTypeAny, {
77
+ version: string;
78
+ templates: {
79
+ id: string;
80
+ name: string;
81
+ provider: "stripe" | "github" | "shopify" | "twilio" | "ragie" | "recall" | "sendgrid" | "slack" | "discord" | "linear" | "clerk" | "custom";
82
+ event: string;
83
+ file: string;
84
+ description?: string | undefined;
85
+ version?: string | undefined;
86
+ docsUrl?: string | undefined;
87
+ }[];
88
+ }, {
89
+ version: string;
90
+ templates: {
91
+ id: string;
92
+ name: string;
93
+ provider: "stripe" | "github" | "shopify" | "twilio" | "ragie" | "recall" | "sendgrid" | "slack" | "discord" | "linear" | "clerk" | "custom";
94
+ event: string;
95
+ file: string;
96
+ description?: string | undefined;
97
+ version?: string | undefined;
98
+ docsUrl?: string | undefined;
99
+ }[];
100
+ }>;
101
+ export type TemplatesIndex = z.infer<typeof TemplatesIndexSchema>;
102
+ export declare const WebhookTemplateSchema: z.ZodObject<{
103
+ url: z.ZodOptional<z.ZodString>;
104
+ method: z.ZodDefault<z.ZodEnum<["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]>>;
105
+ headers: z.ZodDefault<z.ZodArray<z.ZodObject<{
106
+ key: z.ZodString;
107
+ value: z.ZodString;
108
+ }, "strip", z.ZodTypeAny, {
109
+ value: string;
110
+ key: string;
111
+ }, {
112
+ value: string;
113
+ key: string;
114
+ }>, "many">>;
115
+ body: z.ZodOptional<z.ZodAny>;
116
+ provider: z.ZodOptional<z.ZodEnum<["stripe", "github", "shopify", "twilio", "ragie", "recall", "sendgrid", "slack", "discord", "linear", "clerk", "custom"]>>;
117
+ event: z.ZodOptional<z.ZodString>;
118
+ description: z.ZodOptional<z.ZodString>;
119
+ }, "strip", z.ZodTypeAny, {
120
+ method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
121
+ headers: {
122
+ value: string;
123
+ key: string;
124
+ }[];
125
+ description?: string | undefined;
126
+ provider?: "stripe" | "github" | "shopify" | "twilio" | "ragie" | "recall" | "sendgrid" | "slack" | "discord" | "linear" | "clerk" | "custom" | undefined;
127
+ event?: string | undefined;
128
+ url?: string | undefined;
129
+ body?: any;
130
+ }, {
131
+ description?: string | undefined;
132
+ provider?: "stripe" | "github" | "shopify" | "twilio" | "ragie" | "recall" | "sendgrid" | "slack" | "discord" | "linear" | "clerk" | "custom" | undefined;
133
+ event?: string | undefined;
134
+ url?: string | undefined;
135
+ method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS" | undefined;
136
+ headers?: {
137
+ value: string;
138
+ key: string;
139
+ }[] | undefined;
140
+ body?: any;
141
+ }>;
142
+ export type WebhookTemplate = z.infer<typeof WebhookTemplateSchema>;
143
+ export interface LocalTemplate {
144
+ id: string;
145
+ metadata: TemplateMetadata;
146
+ template: WebhookTemplate;
147
+ downloadedAt: string;
148
+ filePath: string;
149
+ }
150
+ export interface RemoteTemplate {
151
+ metadata: TemplateMetadata;
152
+ isDownloaded: boolean;
153
+ }
154
+ export declare const CapturedWebhookSchema: z.ZodObject<{
155
+ id: z.ZodString;
156
+ timestamp: z.ZodString;
157
+ method: z.ZodEnum<["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]>;
158
+ url: z.ZodString;
159
+ path: z.ZodString;
160
+ headers: z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>;
161
+ body: z.ZodOptional<z.ZodAny>;
162
+ rawBody: z.ZodString;
163
+ query: z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>;
164
+ provider: z.ZodOptional<z.ZodEnum<["stripe", "github", "shopify", "twilio", "ragie", "recall", "sendgrid", "slack", "discord", "linear", "clerk", "custom"]>>;
165
+ contentType: z.ZodOptional<z.ZodString>;
166
+ contentLength: z.ZodOptional<z.ZodNumber>;
167
+ }, "strip", z.ZodTypeAny, {
168
+ path: string;
169
+ id: string;
170
+ url: string;
171
+ method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
172
+ headers: Record<string, string | string[]>;
173
+ timestamp: string;
174
+ rawBody: string;
175
+ query: Record<string, string | string[]>;
176
+ provider?: "stripe" | "github" | "shopify" | "twilio" | "ragie" | "recall" | "sendgrid" | "slack" | "discord" | "linear" | "clerk" | "custom" | undefined;
177
+ body?: any;
178
+ contentType?: string | undefined;
179
+ contentLength?: number | undefined;
180
+ }, {
181
+ path: string;
182
+ id: string;
183
+ url: string;
184
+ method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
185
+ headers: Record<string, string | string[]>;
186
+ timestamp: string;
187
+ rawBody: string;
188
+ query: Record<string, string | string[]>;
189
+ provider?: "stripe" | "github" | "shopify" | "twilio" | "ragie" | "recall" | "sendgrid" | "slack" | "discord" | "linear" | "clerk" | "custom" | undefined;
190
+ body?: any;
191
+ contentType?: string | undefined;
192
+ contentLength?: number | undefined;
193
+ }>;
194
+ export type CapturedWebhook = z.infer<typeof CapturedWebhookSchema>;
195
+ export interface CaptureFile {
196
+ file: string;
197
+ capture: CapturedWebhook;
198
+ }
199
+ export interface WebhookExecutionOptions {
200
+ url: string;
201
+ method?: HttpMethod;
202
+ headers?: HeaderEntry[];
203
+ body?: unknown;
204
+ secret?: string;
205
+ provider?: WebhookProvider;
206
+ timeout?: number;
207
+ }
208
+ export interface WebhookExecutionResult {
209
+ status: number;
210
+ statusText: string;
211
+ headers: Record<string, string | string[]>;
212
+ body: unknown;
213
+ bodyText: string;
214
+ json?: unknown;
215
+ duration: number;
216
+ }
217
+ export interface ReplayOptions {
218
+ targetUrl: string;
219
+ method?: HttpMethod;
220
+ headers?: HeaderEntry[];
221
+ }
222
+ export declare const ConfigSchema: z.ZodObject<{
223
+ version: z.ZodDefault<z.ZodString>;
224
+ templatesDir: z.ZodOptional<z.ZodString>;
225
+ capturesDir: z.ZodOptional<z.ZodString>;
226
+ defaultTargetUrl: z.ZodOptional<z.ZodString>;
227
+ secrets: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
228
+ dashboard: z.ZodOptional<z.ZodObject<{
229
+ port: z.ZodDefault<z.ZodNumber>;
230
+ host: z.ZodDefault<z.ZodString>;
231
+ }, "strip", z.ZodTypeAny, {
232
+ port: number;
233
+ host: string;
234
+ }, {
235
+ port?: number | undefined;
236
+ host?: string | undefined;
237
+ }>>;
238
+ capture: z.ZodOptional<z.ZodObject<{
239
+ port: z.ZodDefault<z.ZodNumber>;
240
+ host: z.ZodDefault<z.ZodString>;
241
+ }, "strip", z.ZodTypeAny, {
242
+ port: number;
243
+ host: string;
244
+ }, {
245
+ port?: number | undefined;
246
+ host?: string | undefined;
247
+ }>>;
248
+ }, "strip", z.ZodTypeAny, {
249
+ version: string;
250
+ templatesDir?: string | undefined;
251
+ capturesDir?: string | undefined;
252
+ defaultTargetUrl?: string | undefined;
253
+ secrets?: Record<string, string> | undefined;
254
+ dashboard?: {
255
+ port: number;
256
+ host: string;
257
+ } | undefined;
258
+ capture?: {
259
+ port: number;
260
+ host: string;
261
+ } | undefined;
262
+ }, {
263
+ version?: string | undefined;
264
+ templatesDir?: string | undefined;
265
+ capturesDir?: string | undefined;
266
+ defaultTargetUrl?: string | undefined;
267
+ secrets?: Record<string, string> | undefined;
268
+ dashboard?: {
269
+ port?: number | undefined;
270
+ host?: string | undefined;
271
+ } | undefined;
272
+ capture?: {
273
+ port?: number | undefined;
274
+ host?: string | undefined;
275
+ } | undefined;
276
+ }>;
277
+ export type Config = z.infer<typeof ConfigSchema>;
278
+ export interface DashboardState {
279
+ templates: {
280
+ remote: RemoteTemplate[];
281
+ local: LocalTemplate[];
282
+ };
283
+ captures: CaptureFile[];
284
+ config: Config;
285
+ }
286
+ export interface WebSocketMessage {
287
+ type: "capture" | "templates_updated" | "captures_updated" | "replay_result" | "error";
288
+ payload: unknown;
289
+ }
290
+ export interface SignatureOptions {
291
+ provider: WebhookProvider;
292
+ payload: string;
293
+ secret: string;
294
+ timestamp?: number;
295
+ }
296
+ export interface GeneratedSignature {
297
+ header: string;
298
+ value: string;
299
+ }
300
+ export interface SaveAsTemplateOptions {
301
+ id?: string;
302
+ name?: string;
303
+ event?: string;
304
+ description?: string;
305
+ url?: string;
306
+ overwrite?: boolean;
307
+ }
308
+ export interface SaveAsTemplateResult {
309
+ id: string;
310
+ filePath: string;
311
+ template: WebhookTemplate;
312
+ }
@@ -0,0 +1,87 @@
1
+ import { z } from "zod";
2
+ export const HttpMethodSchema = z.enum([
3
+ "GET",
4
+ "POST",
5
+ "PUT",
6
+ "PATCH",
7
+ "DELETE",
8
+ "HEAD",
9
+ "OPTIONS",
10
+ ]);
11
+ export const HeaderEntrySchema = z.object({
12
+ key: z.string().min(1),
13
+ value: z.string(),
14
+ });
15
+ export const WebhookProviderSchema = z.enum([
16
+ "stripe",
17
+ "github",
18
+ "shopify",
19
+ "twilio",
20
+ "ragie",
21
+ "recall",
22
+ "sendgrid",
23
+ "slack",
24
+ "discord",
25
+ "linear",
26
+ "clerk",
27
+ "custom",
28
+ ]);
29
+ export const TemplateMetadataSchema = z.object({
30
+ id: z.string(),
31
+ name: z.string(),
32
+ description: z.string().optional(),
33
+ provider: WebhookProviderSchema,
34
+ event: z.string(),
35
+ file: z.string(),
36
+ version: z.string().optional(),
37
+ docsUrl: z.string().url().optional(),
38
+ });
39
+ export const TemplatesIndexSchema = z.object({
40
+ version: z.string(),
41
+ templates: z.array(TemplateMetadataSchema),
42
+ });
43
+ export const WebhookTemplateSchema = z.object({
44
+ url: z.string().url().optional(),
45
+ method: HttpMethodSchema.default("POST"),
46
+ headers: z.array(HeaderEntrySchema).default([]),
47
+ body: z.any().optional(),
48
+ provider: WebhookProviderSchema.optional(),
49
+ event: z.string().optional(),
50
+ description: z.string().optional(),
51
+ });
52
+ export const CapturedWebhookSchema = z.object({
53
+ id: z.string(),
54
+ timestamp: z.string(),
55
+ method: HttpMethodSchema,
56
+ url: z.string(),
57
+ path: z.string(),
58
+ headers: z.record(z.union([z.string(), z.array(z.string())])),
59
+ body: z.any().optional(),
60
+ rawBody: z.string(),
61
+ query: z.record(z.union([z.string(), z.array(z.string())])),
62
+ provider: WebhookProviderSchema.optional(),
63
+ contentType: z.string().optional(),
64
+ contentLength: z.number().optional(),
65
+ });
66
+ export const ConfigSchema = z.object({
67
+ version: z.string().default("2.0.0"),
68
+ templatesDir: z.string().optional(),
69
+ capturesDir: z.string().optional(),
70
+ defaultTargetUrl: z.string().url().optional(),
71
+ secrets: z
72
+ .record(z.string())
73
+ .optional()
74
+ .describe("Provider secrets for signature generation"),
75
+ dashboard: z
76
+ .object({
77
+ port: z.number().default(4000),
78
+ host: z.string().default("localhost"),
79
+ })
80
+ .optional(),
81
+ capture: z
82
+ .object({
83
+ port: z.number().default(3001),
84
+ host: z.string().default("0.0.0.0"),
85
+ })
86
+ .optional(),
87
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-webhook/cli",
3
- "version": "3.9.0",
3
+ "version": "3.10.0",
4
4
  "description": "Modern CLI for developing, capturing, and replaying webhooks locally with dashboard UI.",
5
5
  "type": "module",
6
6
  "bin": {