@better-webhook/cli 3.4.3 → 3.5.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 (41) hide show
  1. package/README.md +35 -0
  2. package/dist/dashboard/assets/index-CxrRCNTh.css +1 -0
  3. package/dist/dashboard/assets/index-Dlqdzwyc.js +23 -0
  4. package/dist/dashboard/index.html +2 -2
  5. package/dist/index.cjs +341 -24
  6. package/dist/index.js +341 -24
  7. package/package.json +5 -4
  8. package/dist/commands/capture.d.ts +0 -2
  9. package/dist/commands/capture.js +0 -30
  10. package/dist/commands/captures.d.ts +0 -2
  11. package/dist/commands/captures.js +0 -217
  12. package/dist/commands/dashboard.d.ts +0 -2
  13. package/dist/commands/dashboard.js +0 -65
  14. package/dist/commands/index.d.ts +0 -6
  15. package/dist/commands/index.js +0 -6
  16. package/dist/commands/replay.d.ts +0 -2
  17. package/dist/commands/replay.js +0 -140
  18. package/dist/commands/run.d.ts +0 -2
  19. package/dist/commands/run.js +0 -181
  20. package/dist/commands/templates.d.ts +0 -2
  21. package/dist/commands/templates.js +0 -285
  22. package/dist/core/capture-server.d.ts +0 -31
  23. package/dist/core/capture-server.js +0 -298
  24. package/dist/core/dashboard-api.d.ts +0 -8
  25. package/dist/core/dashboard-api.js +0 -271
  26. package/dist/core/dashboard-server.d.ts +0 -20
  27. package/dist/core/dashboard-server.js +0 -124
  28. package/dist/core/executor.d.ts +0 -11
  29. package/dist/core/executor.js +0 -130
  30. package/dist/core/index.d.ts +0 -5
  31. package/dist/core/index.js +0 -5
  32. package/dist/core/replay-engine.d.ts +0 -18
  33. package/dist/core/replay-engine.js +0 -208
  34. package/dist/core/signature.d.ts +0 -24
  35. package/dist/core/signature.js +0 -199
  36. package/dist/core/template-manager.d.ts +0 -24
  37. package/dist/core/template-manager.js +0 -246
  38. package/dist/dashboard/assets/index-BSfTbn4Y.js +0 -23
  39. package/dist/dashboard/assets/index-zDTVdss_.css +0 -1
  40. package/dist/types/index.d.ts +0 -299
  41. package/dist/types/index.js +0 -86
@@ -1,285 +0,0 @@
1
- import { Command } from "commander";
2
- import ora from "ora";
3
- import prompts from "prompts";
4
- import chalk from "chalk";
5
- import { getTemplateManager } from "../core/template-manager.js";
6
- const listCommand = new Command()
7
- .name("list")
8
- .alias("ls")
9
- .description("List available remote templates from the repository")
10
- .option("-p, --provider <provider>", "Filter by provider (stripe, github, etc.)")
11
- .option("-r, --refresh", "Force refresh the template index cache")
12
- .action(async (options) => {
13
- const spinner = ora("Fetching remote templates...").start();
14
- try {
15
- const manager = getTemplateManager();
16
- const templates = await manager.listRemoteTemplates({
17
- forceRefresh: !!options.refresh,
18
- });
19
- spinner.stop();
20
- if (templates.length === 0) {
21
- console.log(chalk.yellow("šŸ“­ No remote templates found."));
22
- return;
23
- }
24
- let filtered = templates;
25
- if (options.provider) {
26
- filtered = templates.filter((t) => t.metadata.provider.toLowerCase() ===
27
- options.provider?.toLowerCase());
28
- }
29
- if (filtered.length === 0) {
30
- console.log(chalk.yellow(`šŸ“­ No templates found for provider: ${options.provider}`));
31
- return;
32
- }
33
- console.log(chalk.bold("\nšŸ“¦ Available Templates\n"));
34
- const byProvider = new Map();
35
- for (const t of filtered) {
36
- const provider = t.metadata.provider;
37
- if (!byProvider.has(provider)) {
38
- byProvider.set(provider, []);
39
- }
40
- byProvider.get(provider).push(t);
41
- }
42
- for (const [provider, providerTemplates] of byProvider) {
43
- console.log(chalk.cyan.bold(` ${provider.toUpperCase()}`));
44
- for (const t of providerTemplates) {
45
- const status = t.isDownloaded
46
- ? chalk.green("āœ“ downloaded")
47
- : chalk.gray("ā—‹ remote");
48
- console.log(` ${chalk.white(t.metadata.id)} ${status}`);
49
- if (t.metadata.description) {
50
- console.log(chalk.gray(` ${t.metadata.description}`));
51
- }
52
- }
53
- console.log();
54
- }
55
- console.log(chalk.gray(` Total: ${filtered.length} templates`));
56
- console.log(chalk.gray(` Download: better-webhook templates download <id>\n`));
57
- }
58
- catch (error) {
59
- spinner.fail("Failed to fetch templates");
60
- console.error(chalk.red(error.message));
61
- process.exitCode = 1;
62
- }
63
- });
64
- const downloadCommand = new Command()
65
- .name("download")
66
- .alias("get")
67
- .argument("[templateId]", "Template ID to download")
68
- .description("Download a template to local storage")
69
- .option("-a, --all", "Download all available templates")
70
- .option("-r, --refresh", "Force refresh the template index cache")
71
- .action(async (templateId, options) => {
72
- const manager = getTemplateManager();
73
- if (options?.all) {
74
- const spinner = ora("Fetching template list...").start();
75
- try {
76
- const templates = await manager.listRemoteTemplates({
77
- forceRefresh: true,
78
- });
79
- const toDownload = templates.filter((t) => !t.isDownloaded);
80
- spinner.stop();
81
- if (toDownload.length === 0) {
82
- console.log(chalk.green("āœ“ All templates already downloaded"));
83
- return;
84
- }
85
- console.log(chalk.bold(`\nDownloading ${toDownload.length} templates...\n`));
86
- for (const t of toDownload) {
87
- const downloadSpinner = ora(`Downloading ${t.metadata.id}...`).start();
88
- try {
89
- await manager.downloadTemplate(t.metadata.id);
90
- downloadSpinner.succeed(`Downloaded ${t.metadata.id}`);
91
- }
92
- catch (error) {
93
- downloadSpinner.fail(`Failed: ${t.metadata.id} - ${error.message}`);
94
- }
95
- }
96
- console.log(chalk.green("\nāœ“ Download complete\n"));
97
- }
98
- catch (error) {
99
- spinner.fail("Failed to fetch templates");
100
- console.error(chalk.red(error.message));
101
- process.exitCode = 1;
102
- }
103
- return;
104
- }
105
- if (!templateId) {
106
- const spinner = ora("Fetching templates...").start();
107
- try {
108
- const templates = await manager.listRemoteTemplates({
109
- forceRefresh: !!options?.refresh,
110
- });
111
- spinner.stop();
112
- const notDownloaded = templates.filter((t) => !t.isDownloaded);
113
- if (notDownloaded.length === 0) {
114
- console.log(chalk.green("āœ“ All templates already downloaded"));
115
- return;
116
- }
117
- const choices = notDownloaded.map((t) => ({
118
- title: t.metadata.id,
119
- description: `${t.metadata.provider} - ${t.metadata.event}`,
120
- value: t.metadata.id,
121
- }));
122
- const response = await prompts({
123
- type: "select",
124
- name: "templateId",
125
- message: "Select a template to download:",
126
- choices,
127
- });
128
- if (!response.templateId) {
129
- console.log(chalk.yellow("Cancelled"));
130
- return;
131
- }
132
- templateId = response.templateId;
133
- }
134
- catch (error) {
135
- spinner.fail("Failed to fetch templates");
136
- console.error(chalk.red(error.message));
137
- process.exitCode = 1;
138
- return;
139
- }
140
- }
141
- const spinner = ora(`Downloading ${templateId}...`).start();
142
- try {
143
- const template = await manager.downloadTemplate(templateId);
144
- spinner.succeed(`Downloaded ${templateId}`);
145
- console.log(chalk.gray(` Saved to: ${template.filePath}`));
146
- console.log(chalk.gray(` Run with: better-webhook run ${templateId}\n`));
147
- }
148
- catch (error) {
149
- spinner.fail(`Failed to download ${templateId}`);
150
- console.error(chalk.red(error.message));
151
- process.exitCode = 1;
152
- }
153
- });
154
- const localCommand = new Command()
155
- .name("local")
156
- .description("List downloaded local templates")
157
- .option("-p, --provider <provider>", "Filter by provider")
158
- .action((options) => {
159
- const manager = getTemplateManager();
160
- let templates = manager.listLocalTemplates();
161
- if (options.provider) {
162
- templates = templates.filter((t) => t.metadata.provider.toLowerCase() === options.provider?.toLowerCase());
163
- }
164
- if (templates.length === 0) {
165
- console.log(chalk.yellow("\nšŸ“­ No local templates found."));
166
- console.log(chalk.gray(" Download templates with: better-webhook templates download\n"));
167
- return;
168
- }
169
- console.log(chalk.bold("\nšŸ“ Local Templates\n"));
170
- const byProvider = new Map();
171
- for (const t of templates) {
172
- const provider = t.metadata.provider;
173
- if (!byProvider.has(provider)) {
174
- byProvider.set(provider, []);
175
- }
176
- byProvider.get(provider).push(t);
177
- }
178
- for (const [provider, providerTemplates] of byProvider) {
179
- console.log(chalk.cyan.bold(` ${provider.toUpperCase()}`));
180
- for (const t of providerTemplates) {
181
- console.log(` ${chalk.white(t.id)}`);
182
- console.log(chalk.gray(` Event: ${t.metadata.event}`));
183
- console.log(chalk.gray(` Downloaded: ${new Date(t.downloadedAt).toLocaleDateString()}`));
184
- }
185
- console.log();
186
- }
187
- console.log(chalk.gray(` Total: ${templates.length} templates`));
188
- console.log(chalk.gray(` Storage: ${manager.getTemplatesDir()}\n`));
189
- });
190
- const searchCommand = new Command()
191
- .name("search")
192
- .argument("<query>", "Search query")
193
- .description("Search templates by name, provider, or event")
194
- .action(async (query) => {
195
- const spinner = ora("Searching...").start();
196
- try {
197
- const manager = getTemplateManager();
198
- const results = await manager.searchTemplates(query);
199
- spinner.stop();
200
- const totalCount = results.remote.length + results.local.length;
201
- if (totalCount === 0) {
202
- console.log(chalk.yellow(`\nšŸ“­ No templates found for: "${query}"\n`));
203
- return;
204
- }
205
- console.log(chalk.bold(`\nšŸ” Search Results for "${query}"\n`));
206
- if (results.local.length > 0) {
207
- console.log(chalk.cyan.bold(" LOCAL TEMPLATES"));
208
- for (const t of results.local) {
209
- console.log(` ${chalk.green("āœ“")} ${t.id} (${t.metadata.provider})`);
210
- }
211
- console.log();
212
- }
213
- if (results.remote.length > 0) {
214
- console.log(chalk.cyan.bold(" REMOTE TEMPLATES"));
215
- for (const t of results.remote) {
216
- const status = t.isDownloaded ? chalk.green("āœ“") : chalk.gray("ā—‹");
217
- console.log(` ${status} ${t.metadata.id} (${t.metadata.provider})`);
218
- }
219
- console.log();
220
- }
221
- console.log(chalk.gray(` Found: ${totalCount} templates\n`));
222
- }
223
- catch (error) {
224
- spinner.fail("Search failed");
225
- console.error(chalk.red(error.message));
226
- process.exitCode = 1;
227
- }
228
- });
229
- const cacheCommand = new Command()
230
- .name("cache")
231
- .description("Manage template cache")
232
- .option("-c, --clear", "Clear the template cache")
233
- .action((options) => {
234
- if (options.clear) {
235
- const manager = getTemplateManager();
236
- manager.clearCache();
237
- console.log(chalk.green("āœ“ Template cache cleared"));
238
- }
239
- else {
240
- console.log("Use --clear to clear the template cache");
241
- }
242
- });
243
- const cleanCommand = new Command()
244
- .name("clean")
245
- .alias("remove-all")
246
- .description("Remove all downloaded templates")
247
- .option("-f, --force", "Skip confirmation prompt")
248
- .action(async (options) => {
249
- const manager = getTemplateManager();
250
- const templates = manager.listLocalTemplates();
251
- if (templates.length === 0) {
252
- console.log(chalk.yellow("\nšŸ“­ No local templates to remove.\n"));
253
- return;
254
- }
255
- console.log(chalk.bold(`\nšŸ—‘ļø Found ${templates.length} downloaded template(s)\n`));
256
- for (const t of templates) {
257
- console.log(chalk.gray(` ${t.id} (${t.metadata.provider})`));
258
- }
259
- console.log();
260
- if (!options.force) {
261
- const response = await prompts({
262
- type: "confirm",
263
- name: "confirm",
264
- message: `Delete all ${templates.length} template(s)?`,
265
- initial: false,
266
- });
267
- if (!response.confirm) {
268
- console.log(chalk.yellow("Cancelled"));
269
- return;
270
- }
271
- }
272
- const deleted = manager.deleteAllLocalTemplates();
273
- console.log(chalk.green(`\nāœ“ Removed ${deleted} template(s)`));
274
- console.log(chalk.gray(` Storage: ${manager.getTemplatesDir()}\n`));
275
- });
276
- export const templates = new Command()
277
- .name("templates")
278
- .alias("t")
279
- .description("Manage webhook templates")
280
- .addCommand(listCommand)
281
- .addCommand(downloadCommand)
282
- .addCommand(localCommand)
283
- .addCommand(searchCommand)
284
- .addCommand(cacheCommand)
285
- .addCommand(cleanCommand);
@@ -1,31 +0,0 @@
1
- import type { CapturedWebhook, CaptureFile } from "../types/index.js";
2
- export interface CaptureServerOptions {
3
- capturesDir?: string;
4
- enableWebSocket?: boolean;
5
- onCapture?: (args: {
6
- file: string;
7
- capture: CapturedWebhook;
8
- }) => void;
9
- }
10
- export declare class CaptureServer {
11
- private server;
12
- private wss;
13
- private capturesDir;
14
- private clients;
15
- private captureCount;
16
- private enableWebSocket;
17
- private onCapture?;
18
- constructor(options?: string | CaptureServerOptions);
19
- getCapturesDir(): string;
20
- start(port?: number, host?: string): Promise<number>;
21
- stop(): Promise<void>;
22
- private handleRequest;
23
- private detectProvider;
24
- private broadcast;
25
- private sendToClient;
26
- listCaptures(limit?: number): CaptureFile[];
27
- getCapture(captureId: string): CaptureFile | null;
28
- deleteCapture(captureId: string): boolean;
29
- getClientCount(): number;
30
- }
31
- export declare function getCaptureServer(capturesDir?: string): CaptureServer;
@@ -1,298 +0,0 @@
1
- import { createServer, } from "http";
2
- import { WebSocketServer } from "ws";
3
- import { writeFileSync, mkdirSync, existsSync, readdirSync, readFileSync, unlinkSync, } from "fs";
4
- import { join } from "path";
5
- import { randomUUID } from "crypto";
6
- import { homedir } from "os";
7
- export class CaptureServer {
8
- server = null;
9
- wss = null;
10
- capturesDir;
11
- clients = new Set();
12
- captureCount = 0;
13
- enableWebSocket;
14
- onCapture;
15
- constructor(options) {
16
- const capturesDir = typeof options === "string" ? options : options?.capturesDir;
17
- this.capturesDir =
18
- capturesDir || join(homedir(), ".better-webhook", "captures");
19
- this.enableWebSocket =
20
- typeof options === "object" ? options?.enableWebSocket !== false : true;
21
- this.onCapture =
22
- typeof options === "object" ? options?.onCapture : undefined;
23
- if (!existsSync(this.capturesDir)) {
24
- mkdirSync(this.capturesDir, { recursive: true });
25
- }
26
- }
27
- getCapturesDir() {
28
- return this.capturesDir;
29
- }
30
- async start(port = 3001, host = "0.0.0.0") {
31
- return new Promise((resolve, reject) => {
32
- this.server = createServer((req, res) => this.handleRequest(req, res));
33
- if (this.enableWebSocket) {
34
- this.wss = new WebSocketServer({ server: this.server });
35
- this.wss.on("connection", (ws) => {
36
- this.clients.add(ws);
37
- console.log("šŸ“” Dashboard connected via WebSocket");
38
- ws.on("close", () => {
39
- this.clients.delete(ws);
40
- console.log("šŸ“” Dashboard disconnected");
41
- });
42
- ws.on("error", (error) => {
43
- console.error("WebSocket error:", error);
44
- this.clients.delete(ws);
45
- });
46
- this.sendToClient(ws, {
47
- type: "captures_updated",
48
- payload: {
49
- captures: this.listCaptures(),
50
- count: this.captureCount,
51
- },
52
- });
53
- });
54
- }
55
- this.server.on("error", (err) => {
56
- if (err.code === "EADDRINUSE") {
57
- reject(new Error(`Port ${port} is already in use`));
58
- }
59
- else {
60
- reject(err);
61
- }
62
- });
63
- this.server.listen(port, host, () => {
64
- const address = this.server?.address();
65
- const actualPort = typeof address === "object" ? address?.port || port : port;
66
- console.log(`\nšŸŽ£ Webhook Capture Server`);
67
- console.log(` Listening on http://${host === "0.0.0.0" ? "localhost" : host}:${actualPort}`);
68
- console.log(` šŸ“ Captures saved to: ${this.capturesDir}`);
69
- console.log(` šŸ’” Send webhooks to any path to capture them`);
70
- if (this.enableWebSocket) {
71
- console.log(` 🌐 WebSocket available for real-time updates`);
72
- }
73
- console.log(` ā¹ļø Press Ctrl+C to stop\n`);
74
- resolve(actualPort);
75
- });
76
- });
77
- }
78
- async stop() {
79
- return new Promise((resolve) => {
80
- for (const client of this.clients) {
81
- client.close();
82
- }
83
- this.clients.clear();
84
- if (this.wss) {
85
- this.wss.close();
86
- this.wss = null;
87
- }
88
- if (this.server) {
89
- this.server.close(() => {
90
- console.log("\nšŸ›‘ Capture server stopped");
91
- resolve();
92
- });
93
- }
94
- else {
95
- resolve();
96
- }
97
- });
98
- }
99
- async handleRequest(req, res) {
100
- if (req.headers.upgrade?.toLowerCase() === "websocket") {
101
- return;
102
- }
103
- const timestamp = new Date().toISOString();
104
- const id = randomUUID();
105
- const url = req.url || "/";
106
- const urlParts = new URL(url, `http://${req.headers.host || "localhost"}`);
107
- const query = {};
108
- for (const [key, value] of urlParts.searchParams.entries()) {
109
- if (query[key]) {
110
- if (Array.isArray(query[key])) {
111
- query[key].push(value);
112
- }
113
- else {
114
- query[key] = [query[key], value];
115
- }
116
- }
117
- else {
118
- query[key] = value;
119
- }
120
- }
121
- const chunks = [];
122
- for await (const chunk of req) {
123
- chunks.push(chunk);
124
- }
125
- const rawBody = Buffer.concat(chunks).toString("utf8");
126
- let body = null;
127
- const contentType = req.headers["content-type"] || "";
128
- if (rawBody) {
129
- if (contentType.includes("application/json")) {
130
- try {
131
- body = JSON.parse(rawBody);
132
- }
133
- catch {
134
- body = rawBody;
135
- }
136
- }
137
- else if (contentType.includes("application/x-www-form-urlencoded")) {
138
- body = Object.fromEntries(new URLSearchParams(rawBody));
139
- }
140
- else {
141
- body = rawBody;
142
- }
143
- }
144
- const provider = this.detectProvider(req.headers, body);
145
- const captured = {
146
- id,
147
- timestamp,
148
- method: (req.method || "GET"),
149
- url,
150
- path: urlParts.pathname,
151
- headers: req.headers,
152
- body,
153
- rawBody,
154
- query,
155
- provider,
156
- contentType: contentType || undefined,
157
- contentLength: rawBody.length,
158
- };
159
- const date = new Date(timestamp);
160
- const dateStr = date.toISOString().split("T")[0];
161
- const timeStr = date
162
- .toISOString()
163
- .split("T")[1]
164
- ?.replace(/[:.]/g, "-")
165
- .slice(0, 8);
166
- const filename = `${dateStr}_${timeStr}_${id.slice(0, 8)}.json`;
167
- const filepath = join(this.capturesDir, filename);
168
- try {
169
- writeFileSync(filepath, JSON.stringify(captured, null, 2));
170
- this.captureCount++;
171
- const providerStr = provider ? ` [${provider}]` : "";
172
- console.log(`šŸ“¦ ${req.method} ${urlParts.pathname}${providerStr} -> ${filename}`);
173
- this.onCapture?.({ file: filename, capture: captured });
174
- if (this.enableWebSocket) {
175
- this.broadcast({
176
- type: "capture",
177
- payload: {
178
- file: filename,
179
- capture: captured,
180
- },
181
- });
182
- }
183
- }
184
- catch (error) {
185
- console.error(`āŒ Failed to save capture:`, error);
186
- }
187
- res.statusCode = 200;
188
- res.setHeader("Content-Type", "application/json");
189
- res.setHeader("X-Capture-Id", id);
190
- res.end(JSON.stringify({
191
- success: true,
192
- message: "Webhook captured successfully",
193
- id,
194
- timestamp,
195
- file: filename,
196
- }));
197
- }
198
- detectProvider(headers, body) {
199
- if (headers["stripe-signature"]) {
200
- return "stripe";
201
- }
202
- if (headers["x-github-event"] || headers["x-hub-signature-256"]) {
203
- return "github";
204
- }
205
- if (headers["x-signature"]) {
206
- if (body &&
207
- typeof body === "object" &&
208
- "type" in body &&
209
- "payload" in body &&
210
- "nonce" in body) {
211
- return "ragie";
212
- }
213
- }
214
- if (headers["x-shopify-hmac-sha256"] || headers["x-shopify-topic"]) {
215
- return "shopify";
216
- }
217
- if (headers["x-twilio-signature"]) {
218
- return "twilio";
219
- }
220
- if (headers["x-twilio-email-event-webhook-signature"]) {
221
- return "sendgrid";
222
- }
223
- if (headers["x-slack-signature"]) {
224
- return "slack";
225
- }
226
- if (headers["x-signature-ed25519"]) {
227
- return "discord";
228
- }
229
- if (headers["linear-signature"]) {
230
- return "linear";
231
- }
232
- if (headers["svix-signature"]) {
233
- return "clerk";
234
- }
235
- return undefined;
236
- }
237
- broadcast(message) {
238
- const data = JSON.stringify(message);
239
- for (const client of this.clients) {
240
- if (client.readyState === 1) {
241
- client.send(data);
242
- }
243
- }
244
- }
245
- sendToClient(client, message) {
246
- if (client.readyState === 1) {
247
- client.send(JSON.stringify(message));
248
- }
249
- }
250
- listCaptures(limit = 100) {
251
- if (!existsSync(this.capturesDir)) {
252
- return [];
253
- }
254
- const files = readdirSync(this.capturesDir)
255
- .filter((f) => f.endsWith(".json"))
256
- .sort()
257
- .reverse()
258
- .slice(0, limit);
259
- const captures = [];
260
- for (const file of files) {
261
- try {
262
- const content = readFileSync(join(this.capturesDir, file), "utf-8");
263
- const capture = JSON.parse(content);
264
- captures.push({ file, capture });
265
- }
266
- catch {
267
- }
268
- }
269
- return captures;
270
- }
271
- getCapture(captureId) {
272
- const captures = this.listCaptures(1000);
273
- return (captures.find((c) => c.capture.id === captureId || c.file.includes(captureId)) || null);
274
- }
275
- deleteCapture(captureId) {
276
- const capture = this.getCapture(captureId);
277
- if (!capture) {
278
- return false;
279
- }
280
- try {
281
- unlinkSync(join(this.capturesDir, capture.file));
282
- return true;
283
- }
284
- catch {
285
- return false;
286
- }
287
- }
288
- getClientCount() {
289
- return this.clients.size;
290
- }
291
- }
292
- let instance = null;
293
- export function getCaptureServer(capturesDir) {
294
- if (!instance) {
295
- instance = new CaptureServer(capturesDir);
296
- }
297
- return instance;
298
- }
@@ -1,8 +0,0 @@
1
- import express from "express";
2
- import { type WebSocketMessage } from "../types/index.js";
3
- export interface DashboardApiOptions {
4
- capturesDir?: string;
5
- templatesBaseDir?: string;
6
- broadcast?: (message: WebSocketMessage) => void;
7
- }
8
- export declare function createDashboardApiRouter(options?: DashboardApiOptions): express.Router;