@better-webhook/cli 3.2.0 → 3.4.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.
@@ -0,0 +1,205 @@
1
+ import { createHmac } from "crypto";
2
+ export function generateStripeSignature(payload, secret, timestamp) {
3
+ const ts = timestamp || Math.floor(Date.now() / 1000);
4
+ const signedPayload = `${ts}.${payload}`;
5
+ const signature = createHmac("sha256", secret)
6
+ .update(signedPayload)
7
+ .digest("hex");
8
+ return {
9
+ header: "Stripe-Signature",
10
+ value: `t=${ts},v1=${signature}`,
11
+ };
12
+ }
13
+ export function generateGitHubSignature(payload, secret) {
14
+ const signature = createHmac("sha256", secret).update(payload).digest("hex");
15
+ return {
16
+ header: "X-Hub-Signature-256",
17
+ value: `sha256=${signature}`,
18
+ };
19
+ }
20
+ export function generateShopifySignature(payload, secret) {
21
+ const signature = createHmac("sha256", secret)
22
+ .update(payload)
23
+ .digest("base64");
24
+ return {
25
+ header: "X-Shopify-Hmac-SHA256",
26
+ value: signature,
27
+ };
28
+ }
29
+ export function generateTwilioSignature(payload, secret, url) {
30
+ const signatureInput = url + payload;
31
+ const signature = createHmac("sha1", secret)
32
+ .update(signatureInput)
33
+ .digest("base64");
34
+ return {
35
+ header: "X-Twilio-Signature",
36
+ value: signature,
37
+ };
38
+ }
39
+ export function generateSlackSignature(payload, secret, timestamp) {
40
+ const ts = timestamp || Math.floor(Date.now() / 1000);
41
+ const signatureBaseString = `v0:${ts}:${payload}`;
42
+ const signature = createHmac("sha256", secret)
43
+ .update(signatureBaseString)
44
+ .digest("hex");
45
+ return {
46
+ header: "X-Slack-Signature",
47
+ value: `v0=${signature}`,
48
+ };
49
+ }
50
+ export function generateLinearSignature(payload, secret) {
51
+ const signature = createHmac("sha256", secret).update(payload).digest("hex");
52
+ return {
53
+ header: "Linear-Signature",
54
+ value: signature,
55
+ };
56
+ }
57
+ export function generateClerkSignature(payload, secret, timestamp, webhookId) {
58
+ const ts = timestamp || Math.floor(Date.now() / 1000);
59
+ const msgId = webhookId || `msg_${Date.now()}`;
60
+ const signedPayload = `${msgId}.${ts}.${payload}`;
61
+ const signature = createHmac("sha256", secret)
62
+ .update(signedPayload)
63
+ .digest("base64");
64
+ return {
65
+ header: "Svix-Signature",
66
+ value: `v1,${signature}`,
67
+ };
68
+ }
69
+ export function generateSendGridSignature(payload, secret, timestamp) {
70
+ const ts = timestamp || Math.floor(Date.now() / 1000);
71
+ const signedPayload = `${ts}${payload}`;
72
+ const signature = createHmac("sha256", secret)
73
+ .update(signedPayload)
74
+ .digest("base64");
75
+ return {
76
+ header: "X-Twilio-Email-Event-Webhook-Signature",
77
+ value: signature,
78
+ };
79
+ }
80
+ export function generateRagieSignature(payload, secret) {
81
+ const signature = createHmac("sha256", secret).update(payload).digest("hex");
82
+ return {
83
+ header: "X-Signature",
84
+ value: signature,
85
+ };
86
+ }
87
+ export function generateSignature(provider, payload, secret, options) {
88
+ const timestamp = options?.timestamp;
89
+ switch (provider) {
90
+ case "stripe":
91
+ return generateStripeSignature(payload, secret, timestamp);
92
+ case "github":
93
+ return generateGitHubSignature(payload, secret);
94
+ case "shopify":
95
+ return generateShopifySignature(payload, secret);
96
+ case "twilio":
97
+ if (!options?.url) {
98
+ throw new Error("Twilio signature requires URL");
99
+ }
100
+ return generateTwilioSignature(payload, secret, options.url);
101
+ case "slack":
102
+ return generateSlackSignature(payload, secret, timestamp);
103
+ case "linear":
104
+ return generateLinearSignature(payload, secret);
105
+ case "clerk":
106
+ return generateClerkSignature(payload, secret, timestamp, options?.webhookId);
107
+ case "sendgrid":
108
+ return generateSendGridSignature(payload, secret, timestamp);
109
+ case "ragie":
110
+ return generateRagieSignature(payload, secret);
111
+ case "discord":
112
+ case "custom":
113
+ default:
114
+ return null;
115
+ }
116
+ }
117
+ export function getProviderHeaders(provider, options) {
118
+ const headers = [];
119
+ const timestamp = options?.timestamp || Math.floor(Date.now() / 1000);
120
+ switch (provider) {
121
+ case "stripe":
122
+ headers.push({ key: "Content-Type", value: "application/json" }, {
123
+ key: "User-Agent",
124
+ value: "Stripe/1.0 (+https://stripe.com/docs/webhooks)",
125
+ });
126
+ break;
127
+ case "github":
128
+ headers.push({ key: "Content-Type", value: "application/json" }, { key: "User-Agent", value: "GitHub-Hookshot/better-webhook" }, { key: "X-GitHub-Event", value: options?.event || "push" }, {
129
+ key: "X-GitHub-Delivery",
130
+ value: options?.webhookId || generateDeliveryId(),
131
+ });
132
+ break;
133
+ case "shopify":
134
+ headers.push({ key: "Content-Type", value: "application/json" }, { key: "X-Shopify-Topic", value: options?.event || "orders/create" }, { key: "X-Shopify-Shop-Domain", value: "example.myshopify.com" }, { key: "X-Shopify-API-Version", value: "2024-01" });
135
+ break;
136
+ case "slack":
137
+ headers.push({ key: "Content-Type", value: "application/json" }, { key: "X-Slack-Request-Timestamp", value: String(timestamp) });
138
+ break;
139
+ case "clerk":
140
+ headers.push({ key: "Content-Type", value: "application/json" }, { key: "Svix-Id", value: options?.webhookId || `msg_${Date.now()}` }, { key: "Svix-Timestamp", value: String(timestamp) });
141
+ break;
142
+ case "sendgrid":
143
+ headers.push({ key: "Content-Type", value: "application/json" }, {
144
+ key: "X-Twilio-Email-Event-Webhook-Timestamp",
145
+ value: String(timestamp),
146
+ });
147
+ break;
148
+ case "twilio":
149
+ headers.push({
150
+ key: "Content-Type",
151
+ value: "application/x-www-form-urlencoded",
152
+ });
153
+ break;
154
+ case "linear":
155
+ headers.push({ key: "Content-Type", value: "application/json" }, {
156
+ key: "Linear-Delivery",
157
+ value: options?.webhookId || generateDeliveryId(),
158
+ });
159
+ break;
160
+ case "discord":
161
+ headers.push({ key: "Content-Type", value: "application/json" }, { key: "User-Agent", value: "Discord-Webhook/1.0" });
162
+ break;
163
+ case "ragie":
164
+ headers.push({ key: "Content-Type", value: "application/json" }, {
165
+ key: "X-Ragie-Event",
166
+ value: options?.event || "document_status_updated",
167
+ }, {
168
+ key: "X-Ragie-Delivery",
169
+ value: options?.webhookId || generateDeliveryId(),
170
+ });
171
+ break;
172
+ default:
173
+ headers.push({ key: "Content-Type", value: "application/json" });
174
+ }
175
+ return headers;
176
+ }
177
+ function generateDeliveryId() {
178
+ const chars = "0123456789abcdef";
179
+ let id = "";
180
+ for (let i = 0; i < 36; i++) {
181
+ if (i === 8 || i === 13 || i === 18 || i === 23) {
182
+ id += "-";
183
+ }
184
+ else {
185
+ id += chars[Math.floor(Math.random() * chars.length)];
186
+ }
187
+ }
188
+ return id;
189
+ }
190
+ export function verifySignature(provider, payload, signature, secret, options) {
191
+ const generated = generateSignature(provider, payload, secret, options);
192
+ if (!generated) {
193
+ return false;
194
+ }
195
+ const a = generated.value;
196
+ const b = signature;
197
+ if (a.length !== b.length) {
198
+ return false;
199
+ }
200
+ let result = 0;
201
+ for (let i = 0; i < a.length; i++) {
202
+ result |= a.charCodeAt(i) ^ b.charCodeAt(i);
203
+ }
204
+ return result === 0;
205
+ }
@@ -0,0 +1,24 @@
1
+ import { type TemplatesIndex, type LocalTemplate, type RemoteTemplate } from "../types/index.js";
2
+ export declare class TemplateManager {
3
+ private baseDir;
4
+ private templatesDir;
5
+ private cacheFile;
6
+ private indexCache;
7
+ constructor(baseDir?: string);
8
+ getTemplatesDir(): string;
9
+ fetchRemoteIndex(forceRefresh?: boolean): Promise<TemplatesIndex>;
10
+ listRemoteTemplates(options?: {
11
+ forceRefresh?: boolean;
12
+ }): Promise<RemoteTemplate[]>;
13
+ downloadTemplate(templateId: string): Promise<LocalTemplate>;
14
+ listLocalTemplates(): LocalTemplate[];
15
+ getLocalTemplate(templateId: string): LocalTemplate | null;
16
+ deleteLocalTemplate(templateId: string): boolean;
17
+ searchTemplates(query: string): Promise<{
18
+ remote: RemoteTemplate[];
19
+ local: LocalTemplate[];
20
+ }>;
21
+ clearCache(): void;
22
+ deleteAllLocalTemplates(): number;
23
+ }
24
+ export declare function getTemplateManager(baseDir?: string): TemplateManager;
@@ -0,0 +1,246 @@
1
+ import { request } from "undici";
2
+ import { existsSync, mkdirSync, readFileSync, readdirSync, rmdirSync, unlinkSync, writeFileSync, } from "fs";
3
+ import { join, basename } from "path";
4
+ import { homedir } from "os";
5
+ import { TemplatesIndexSchema, WebhookTemplateSchema, } from "../types/index.js";
6
+ const GITHUB_RAW_BASE = "https://raw.githubusercontent.com/endalk200/better-webhook/main";
7
+ const TEMPLATES_INDEX_URL = `${GITHUB_RAW_BASE}/templates/templates.json`;
8
+ export class TemplateManager {
9
+ baseDir;
10
+ templatesDir;
11
+ cacheFile;
12
+ indexCache = null;
13
+ constructor(baseDir) {
14
+ this.baseDir = baseDir || join(homedir(), ".better-webhook");
15
+ this.templatesDir = join(this.baseDir, "templates");
16
+ this.cacheFile = join(this.baseDir, "templates-cache.json");
17
+ if (!existsSync(this.baseDir)) {
18
+ mkdirSync(this.baseDir, { recursive: true });
19
+ }
20
+ if (!existsSync(this.templatesDir)) {
21
+ mkdirSync(this.templatesDir, { recursive: true });
22
+ }
23
+ }
24
+ getTemplatesDir() {
25
+ return this.templatesDir;
26
+ }
27
+ async fetchRemoteIndex(forceRefresh = false) {
28
+ if (!forceRefresh && this.indexCache) {
29
+ return this.indexCache;
30
+ }
31
+ if (!forceRefresh && existsSync(this.cacheFile)) {
32
+ try {
33
+ const cached = JSON.parse(readFileSync(this.cacheFile, "utf-8"));
34
+ const cacheAge = Date.now() - (cached.cachedAt || 0);
35
+ if (cacheAge < 3600000) {
36
+ this.indexCache = cached.index;
37
+ return cached.index;
38
+ }
39
+ }
40
+ catch {
41
+ }
42
+ }
43
+ try {
44
+ const { statusCode, body } = await request(TEMPLATES_INDEX_URL);
45
+ if (statusCode !== 200) {
46
+ throw new Error(`HTTP ${statusCode}`);
47
+ }
48
+ const text = await body.text();
49
+ const json = JSON.parse(text);
50
+ const index = TemplatesIndexSchema.parse(json);
51
+ this.indexCache = index;
52
+ writeFileSync(this.cacheFile, JSON.stringify({ index, cachedAt: Date.now() }, null, 2));
53
+ return index;
54
+ }
55
+ catch (error) {
56
+ if (existsSync(this.cacheFile)) {
57
+ try {
58
+ const cached = JSON.parse(readFileSync(this.cacheFile, "utf-8"));
59
+ if (cached.index) {
60
+ this.indexCache = cached.index;
61
+ return cached.index;
62
+ }
63
+ }
64
+ catch {
65
+ }
66
+ }
67
+ throw new Error(`Failed to fetch templates index: ${error.message}`);
68
+ }
69
+ }
70
+ async listRemoteTemplates(options) {
71
+ const index = await this.fetchRemoteIndex(!!options?.forceRefresh);
72
+ const localIds = new Set(this.listLocalTemplates().map((t) => t.id));
73
+ return index.templates.map((metadata) => ({
74
+ metadata,
75
+ isDownloaded: localIds.has(metadata.id),
76
+ }));
77
+ }
78
+ async downloadTemplate(templateId) {
79
+ const index = await this.fetchRemoteIndex();
80
+ const templateMeta = index.templates.find((t) => t.id === templateId);
81
+ if (!templateMeta) {
82
+ throw new Error(`Template not found: ${templateId}`);
83
+ }
84
+ const templateUrl = `${GITHUB_RAW_BASE}/templates/${templateMeta.file}`;
85
+ try {
86
+ const { statusCode, body } = await request(templateUrl);
87
+ if (statusCode !== 200) {
88
+ throw new Error(`HTTP ${statusCode}`);
89
+ }
90
+ const text = await body.text();
91
+ const json = JSON.parse(text);
92
+ const template = WebhookTemplateSchema.parse(json);
93
+ const providerDir = join(this.templatesDir, templateMeta.provider);
94
+ if (!existsSync(providerDir)) {
95
+ mkdirSync(providerDir, { recursive: true });
96
+ }
97
+ const fileName = `${templateId}.json`;
98
+ const filePath = join(providerDir, fileName);
99
+ const localTemplate = {
100
+ id: templateId,
101
+ metadata: templateMeta,
102
+ template,
103
+ downloadedAt: new Date().toISOString(),
104
+ filePath,
105
+ };
106
+ const saveData = {
107
+ ...template,
108
+ _metadata: {
109
+ ...templateMeta,
110
+ downloadedAt: localTemplate.downloadedAt,
111
+ },
112
+ };
113
+ writeFileSync(filePath, JSON.stringify(saveData, null, 2));
114
+ return localTemplate;
115
+ }
116
+ catch (error) {
117
+ throw new Error(`Failed to download template ${templateId}: ${error.message}`);
118
+ }
119
+ }
120
+ listLocalTemplates() {
121
+ const templates = [];
122
+ if (!existsSync(this.templatesDir)) {
123
+ return templates;
124
+ }
125
+ const scanDir = (dir) => {
126
+ const entries = readdirSync(dir, { withFileTypes: true });
127
+ for (const entry of entries) {
128
+ const fullPath = join(dir, entry.name);
129
+ if (entry.isDirectory()) {
130
+ scanDir(fullPath);
131
+ }
132
+ else if (entry.isFile() && entry.name.endsWith(".json")) {
133
+ try {
134
+ const content = JSON.parse(readFileSync(fullPath, "utf-8"));
135
+ const metadata = content._metadata;
136
+ if (metadata) {
137
+ const { _metadata, ...templateData } = content;
138
+ templates.push({
139
+ id: metadata.id,
140
+ metadata,
141
+ template: templateData,
142
+ downloadedAt: metadata.downloadedAt || new Date().toISOString(),
143
+ filePath: fullPath,
144
+ });
145
+ }
146
+ else {
147
+ const id = basename(entry.name, ".json");
148
+ templates.push({
149
+ id,
150
+ metadata: {
151
+ id,
152
+ name: id,
153
+ provider: "custom",
154
+ event: "unknown",
155
+ file: entry.name,
156
+ },
157
+ template: content,
158
+ downloadedAt: new Date().toISOString(),
159
+ filePath: fullPath,
160
+ });
161
+ }
162
+ }
163
+ catch {
164
+ }
165
+ }
166
+ }
167
+ };
168
+ scanDir(this.templatesDir);
169
+ return templates;
170
+ }
171
+ getLocalTemplate(templateId) {
172
+ const templates = this.listLocalTemplates();
173
+ return templates.find((t) => t.id === templateId) || null;
174
+ }
175
+ deleteLocalTemplate(templateId) {
176
+ const template = this.getLocalTemplate(templateId);
177
+ if (!template) {
178
+ return false;
179
+ }
180
+ try {
181
+ unlinkSync(template.filePath);
182
+ return true;
183
+ }
184
+ catch {
185
+ return false;
186
+ }
187
+ }
188
+ async searchTemplates(query) {
189
+ const queryLower = query.toLowerCase();
190
+ const remote = await this.listRemoteTemplates();
191
+ const local = this.listLocalTemplates();
192
+ const matchesMeta = (meta) => {
193
+ return (meta.id.toLowerCase().includes(queryLower) ||
194
+ meta.name.toLowerCase().includes(queryLower) ||
195
+ meta.provider.toLowerCase().includes(queryLower) ||
196
+ meta.event.toLowerCase().includes(queryLower) ||
197
+ (meta.description?.toLowerCase().includes(queryLower) ?? false));
198
+ };
199
+ return {
200
+ remote: remote.filter((t) => matchesMeta(t.metadata)),
201
+ local: local.filter((t) => matchesMeta(t.metadata)),
202
+ };
203
+ }
204
+ clearCache() {
205
+ this.indexCache = null;
206
+ if (existsSync(this.cacheFile)) {
207
+ unlinkSync(this.cacheFile);
208
+ }
209
+ }
210
+ deleteAllLocalTemplates() {
211
+ const templates = this.listLocalTemplates();
212
+ let deleted = 0;
213
+ for (const template of templates) {
214
+ try {
215
+ unlinkSync(template.filePath);
216
+ deleted++;
217
+ }
218
+ catch {
219
+ }
220
+ }
221
+ if (existsSync(this.templatesDir)) {
222
+ const entries = readdirSync(this.templatesDir, { withFileTypes: true });
223
+ for (const entry of entries) {
224
+ if (entry.isDirectory()) {
225
+ const dirPath = join(this.templatesDir, entry.name);
226
+ try {
227
+ const contents = readdirSync(dirPath);
228
+ if (contents.length === 0) {
229
+ rmdirSync(dirPath);
230
+ }
231
+ }
232
+ catch {
233
+ }
234
+ }
235
+ }
236
+ }
237
+ return deleted;
238
+ }
239
+ }
240
+ let instance = null;
241
+ export function getTemplateManager(baseDir) {
242
+ if (!instance) {
243
+ instance = new TemplateManager(baseDir);
244
+ }
245
+ return instance;
246
+ }
package/dist/index.cjs CHANGED
@@ -25,6 +25,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
25
25
 
26
26
  // src/index.ts
27
27
  var import_commander7 = require("commander");
28
+ var import_node_module = require("module");
28
29
 
29
30
  // src/commands/templates.ts
30
31
  var import_commander = require("commander");
@@ -745,6 +746,13 @@ function generateSendGridSignature(payload, secret, timestamp) {
745
746
  value: signature
746
747
  };
747
748
  }
749
+ function generateRagieSignature(payload, secret) {
750
+ const signature = (0, import_crypto.createHmac)("sha256", secret).update(payload).digest("hex");
751
+ return {
752
+ header: "X-Signature",
753
+ value: signature
754
+ };
755
+ }
748
756
  function generateSignature(provider, payload, secret, options) {
749
757
  const timestamp = options?.timestamp;
750
758
  switch (provider) {
@@ -772,6 +780,8 @@ function generateSignature(provider, payload, secret, options) {
772
780
  );
773
781
  case "sendgrid":
774
782
  return generateSendGridSignature(payload, secret, timestamp);
783
+ case "ragie":
784
+ return generateRagieSignature(payload, secret);
775
785
  case "discord":
776
786
  case "custom":
777
787
  default:
@@ -853,6 +863,19 @@ function getProviderHeaders(provider, options) {
853
863
  { key: "User-Agent", value: "Discord-Webhook/1.0" }
854
864
  );
855
865
  break;
866
+ case "ragie":
867
+ headers.push(
868
+ { key: "Content-Type", value: "application/json" },
869
+ {
870
+ key: "X-Ragie-Event",
871
+ value: options?.event || "document_status_updated"
872
+ },
873
+ {
874
+ key: "X-Ragie-Delivery",
875
+ value: options?.webhookId || generateDeliveryId()
876
+ }
877
+ );
878
+ break;
856
879
  default:
857
880
  headers.push({ key: "Content-Type", value: "application/json" });
858
881
  }
@@ -2405,8 +2428,13 @@ function createDashboardApiRouter(options = {}) {
2405
2428
  var import_meta = {};
2406
2429
  function resolveDashboardDistDir(runtimeDir) {
2407
2430
  const candidates = [
2431
+ // Bundled CLI: dist/index.js -> dist/dashboard
2432
+ import_path4.default.resolve(runtimeDir, "dashboard"),
2433
+ // Legacy/unbundled: dist/core -> dist/dashboard
2408
2434
  import_path4.default.resolve(runtimeDir, "..", "dashboard"),
2435
+ // Dev from src/core -> dist/dashboard
2409
2436
  import_path4.default.resolve(runtimeDir, "..", "..", "dist", "dashboard"),
2437
+ // Dev from src/core -> apps/dashboard/dist
2410
2438
  import_path4.default.resolve(runtimeDir, "..", "..", "..", "dashboard", "dist")
2411
2439
  ];
2412
2440
  for (const distDir of candidates) {
@@ -2581,8 +2609,11 @@ var dashboard = new import_commander6.Command().name("dashboard").description("S
2581
2609
  });
2582
2610
 
2583
2611
  // src/index.ts
2612
+ var import_meta2 = {};
2613
+ var require2 = (0, import_node_module.createRequire)(import_meta2.url);
2614
+ var packageJson = require2("../package.json");
2584
2615
  var program = new import_commander7.Command().name("better-webhook").description(
2585
2616
  "Modern CLI for developing, capturing, and replaying webhooks locally"
2586
- ).version("2.0.0");
2617
+ ).version(packageJson.version);
2587
2618
  program.addCommand(templates).addCommand(run).addCommand(capture).addCommand(captures).addCommand(replay).addCommand(dashboard);
2588
2619
  program.parseAsync(process.argv);
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command as Command7 } from "commander";
5
+ import { createRequire } from "module";
5
6
 
6
7
  // src/commands/templates.ts
7
8
  import { Command } from "commander";
@@ -730,6 +731,13 @@ function generateSendGridSignature(payload, secret, timestamp) {
730
731
  value: signature
731
732
  };
732
733
  }
734
+ function generateRagieSignature(payload, secret) {
735
+ const signature = createHmac("sha256", secret).update(payload).digest("hex");
736
+ return {
737
+ header: "X-Signature",
738
+ value: signature
739
+ };
740
+ }
733
741
  function generateSignature(provider, payload, secret, options) {
734
742
  const timestamp = options?.timestamp;
735
743
  switch (provider) {
@@ -757,6 +765,8 @@ function generateSignature(provider, payload, secret, options) {
757
765
  );
758
766
  case "sendgrid":
759
767
  return generateSendGridSignature(payload, secret, timestamp);
768
+ case "ragie":
769
+ return generateRagieSignature(payload, secret);
760
770
  case "discord":
761
771
  case "custom":
762
772
  default:
@@ -838,6 +848,19 @@ function getProviderHeaders(provider, options) {
838
848
  { key: "User-Agent", value: "Discord-Webhook/1.0" }
839
849
  );
840
850
  break;
851
+ case "ragie":
852
+ headers.push(
853
+ { key: "Content-Type", value: "application/json" },
854
+ {
855
+ key: "X-Ragie-Event",
856
+ value: options?.event || "document_status_updated"
857
+ },
858
+ {
859
+ key: "X-Ragie-Delivery",
860
+ value: options?.webhookId || generateDeliveryId()
861
+ }
862
+ );
863
+ break;
841
864
  default:
842
865
  headers.push({ key: "Content-Type", value: "application/json" });
843
866
  }
@@ -2398,8 +2421,13 @@ function createDashboardApiRouter(options = {}) {
2398
2421
  // src/core/dashboard-server.ts
2399
2422
  function resolveDashboardDistDir(runtimeDir) {
2400
2423
  const candidates = [
2424
+ // Bundled CLI: dist/index.js -> dist/dashboard
2425
+ path.resolve(runtimeDir, "dashboard"),
2426
+ // Legacy/unbundled: dist/core -> dist/dashboard
2401
2427
  path.resolve(runtimeDir, "..", "dashboard"),
2428
+ // Dev from src/core -> dist/dashboard
2402
2429
  path.resolve(runtimeDir, "..", "..", "dist", "dashboard"),
2430
+ // Dev from src/core -> apps/dashboard/dist
2403
2431
  path.resolve(runtimeDir, "..", "..", "..", "dashboard", "dist")
2404
2432
  ];
2405
2433
  for (const distDir of candidates) {
@@ -2574,8 +2602,10 @@ var dashboard = new Command6().name("dashboard").description("Start the local da
2574
2602
  });
2575
2603
 
2576
2604
  // src/index.ts
2605
+ var require2 = createRequire(import.meta.url);
2606
+ var packageJson = require2("../package.json");
2577
2607
  var program = new Command7().name("better-webhook").description(
2578
2608
  "Modern CLI for developing, capturing, and replaying webhooks locally"
2579
- ).version("2.0.0");
2609
+ ).version(packageJson.version);
2580
2610
  program.addCommand(templates).addCommand(run).addCommand(capture).addCommand(captures).addCommand(replay).addCommand(dashboard);
2581
2611
  program.parseAsync(process.argv);