@mintline/mcp 1.0.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 +79 -0
- package/dist/cli.js +2 -0
- package/dist/index.js +382 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Mintline MCP Server
|
|
2
|
+
|
|
3
|
+
Connect AI assistants to your Mintline receipts and transactions via the [Model Context Protocol](https://modelcontextprotocol.io).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @mintline/mcp
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
### 1. Get your API key
|
|
14
|
+
|
|
15
|
+
Create an API key at [mintline.ai/app/settings/api-keys](https://mintline.ai/app/settings/api-keys)
|
|
16
|
+
|
|
17
|
+
### 2. Configure Claude Desktop
|
|
18
|
+
|
|
19
|
+
Add to your `~/.claude/claude_desktop_config.json`:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"mcpServers": {
|
|
24
|
+
"mintline": {
|
|
25
|
+
"command": "mintline-mcp",
|
|
26
|
+
"env": {
|
|
27
|
+
"MINTLINE_API_KEY": "ml_live_your_api_key_here"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 3. Restart Claude Desktop
|
|
35
|
+
|
|
36
|
+
The Mintline tools will now be available.
|
|
37
|
+
|
|
38
|
+
## Available Tools
|
|
39
|
+
|
|
40
|
+
| Tool | Description |
|
|
41
|
+
|------|-------------|
|
|
42
|
+
| `list_receipts` | Search and filter receipts by vendor, status |
|
|
43
|
+
| `get_receipt` | Get receipt details with line items |
|
|
44
|
+
| `list_transactions` | Search and filter bank transactions |
|
|
45
|
+
| `get_transaction` | Get transaction details |
|
|
46
|
+
| `list_statements` | List uploaded bank statements |
|
|
47
|
+
| `list_matches` | View proposed receipt-transaction matches |
|
|
48
|
+
| `confirm_match` | Confirm a proposed match |
|
|
49
|
+
| `reject_match` | Reject a proposed match |
|
|
50
|
+
|
|
51
|
+
## Example Prompts
|
|
52
|
+
|
|
53
|
+
- "Show me my unmatched receipts"
|
|
54
|
+
- "Find receipts from Amazon"
|
|
55
|
+
- "What transactions need matching?"
|
|
56
|
+
- "Confirm the top match"
|
|
57
|
+
- "Show details for receipt rcpt_01abc123"
|
|
58
|
+
|
|
59
|
+
## Environment Variables
|
|
60
|
+
|
|
61
|
+
| Variable | Required | Description |
|
|
62
|
+
|----------|----------|-------------|
|
|
63
|
+
| `MINTLINE_API_KEY` | Yes | Your Mintline API key |
|
|
64
|
+
| `MINTLINE_API_URL` | No | API URL (default: https://api.mintline.ai) |
|
|
65
|
+
|
|
66
|
+
## Development
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
git clone https://github.com/mintlineai/mintline-mcp.git
|
|
70
|
+
cd mintline-mcp
|
|
71
|
+
npm install
|
|
72
|
+
|
|
73
|
+
# Run locally
|
|
74
|
+
MINTLINE_API_KEY=ml_live_... node src/index.js
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
MIT
|
package/dist/cli.js
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
// src/index.js
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import {
|
|
5
|
+
CallToolRequestSchema,
|
|
6
|
+
ListToolsRequestSchema
|
|
7
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
8
|
+
|
|
9
|
+
// src/client.js
|
|
10
|
+
var BASE_URL = process.env.MINTLINE_API_URL || "https://api.mintline.ai";
|
|
11
|
+
function createClient(apiKey2) {
|
|
12
|
+
if (!apiKey2) {
|
|
13
|
+
throw new Error("MINTLINE_API_KEY is required");
|
|
14
|
+
}
|
|
15
|
+
async function request(method, path, body) {
|
|
16
|
+
const res = await fetch(`${BASE_URL}${path}`, {
|
|
17
|
+
method,
|
|
18
|
+
headers: {
|
|
19
|
+
"Authorization": `Bearer ${apiKey2}`,
|
|
20
|
+
"Content-Type": "application/json"
|
|
21
|
+
},
|
|
22
|
+
body: body ? JSON.stringify(body) : void 0
|
|
23
|
+
});
|
|
24
|
+
const data = await res.json();
|
|
25
|
+
if (!data.success) {
|
|
26
|
+
throw new Error(data.error?.message || "Request failed");
|
|
27
|
+
}
|
|
28
|
+
return data;
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
// Receipts
|
|
32
|
+
async listReceipts(params = {}) {
|
|
33
|
+
const query = new URLSearchParams();
|
|
34
|
+
if (params.limit) query.set("limit", params.limit);
|
|
35
|
+
if (params.offset) query.set("offset", params.offset);
|
|
36
|
+
if (params.search) query.set("q", params.search);
|
|
37
|
+
if (params.status) query.set("status", params.status);
|
|
38
|
+
const qs = query.toString();
|
|
39
|
+
return request("GET", `/api/receipts${qs ? `?${qs}` : ""}`);
|
|
40
|
+
},
|
|
41
|
+
async getReceipt(id) {
|
|
42
|
+
return request("GET", `/api/receipts/${id}`);
|
|
43
|
+
},
|
|
44
|
+
// Transactions
|
|
45
|
+
async listTransactions(params = {}) {
|
|
46
|
+
const query = new URLSearchParams();
|
|
47
|
+
if (params.limit) query.set("limit", params.limit);
|
|
48
|
+
if (params.offset) query.set("offset", params.offset);
|
|
49
|
+
if (params.search) query.set("q", params.search);
|
|
50
|
+
if (params.status) query.set("status", params.status);
|
|
51
|
+
if (params.statementId) query.set("statementId", params.statementId);
|
|
52
|
+
const qs = query.toString();
|
|
53
|
+
return request("GET", `/api/transactions${qs ? `?${qs}` : ""}`);
|
|
54
|
+
},
|
|
55
|
+
async getTransaction(id) {
|
|
56
|
+
return request("GET", `/api/transactions/${id}`);
|
|
57
|
+
},
|
|
58
|
+
// Statements
|
|
59
|
+
async listStatements(params = {}) {
|
|
60
|
+
const query = new URLSearchParams();
|
|
61
|
+
if (params.limit) query.set("limit", params.limit);
|
|
62
|
+
if (params.offset) query.set("offset", params.offset);
|
|
63
|
+
const qs = query.toString();
|
|
64
|
+
return request("GET", `/api/statements${qs ? `?${qs}` : ""}`);
|
|
65
|
+
},
|
|
66
|
+
// Matches
|
|
67
|
+
async listMatches(params = {}) {
|
|
68
|
+
const query = new URLSearchParams();
|
|
69
|
+
if (params.limit) query.set("limit", params.limit);
|
|
70
|
+
if (params.offset) query.set("offset", params.offset);
|
|
71
|
+
if (params.status) query.set("status", params.status);
|
|
72
|
+
const qs = query.toString();
|
|
73
|
+
return request("GET", `/api/matches${qs ? `?${qs}` : ""}`);
|
|
74
|
+
},
|
|
75
|
+
async confirmMatch(id) {
|
|
76
|
+
return request("POST", `/api/matches/${id}/confirm`);
|
|
77
|
+
},
|
|
78
|
+
async rejectMatch(id, reason) {
|
|
79
|
+
return request("POST", `/api/matches/${id}/reject`, { reason });
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/tools.js
|
|
85
|
+
var tools = [
|
|
86
|
+
{
|
|
87
|
+
name: "list_receipts",
|
|
88
|
+
description: "List receipts with optional filtering. Use this to find receipts by vendor name, date range, or match status.",
|
|
89
|
+
inputSchema: {
|
|
90
|
+
type: "object",
|
|
91
|
+
properties: {
|
|
92
|
+
search: {
|
|
93
|
+
type: "string",
|
|
94
|
+
description: "Search by vendor name"
|
|
95
|
+
},
|
|
96
|
+
status: {
|
|
97
|
+
type: "string",
|
|
98
|
+
enum: ["all", "matched", "unmatched", "hidden"],
|
|
99
|
+
description: "Filter by match status"
|
|
100
|
+
},
|
|
101
|
+
limit: {
|
|
102
|
+
type: "number",
|
|
103
|
+
description: "Max results to return (default 20)"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: "get_receipt",
|
|
110
|
+
description: "Get detailed information about a specific receipt including line items and matched transaction.",
|
|
111
|
+
inputSchema: {
|
|
112
|
+
type: "object",
|
|
113
|
+
properties: {
|
|
114
|
+
id: {
|
|
115
|
+
type: "string",
|
|
116
|
+
description: "Receipt ID (e.g., rcpt_01abc123)"
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
required: ["id"]
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: "list_transactions",
|
|
124
|
+
description: "List bank transactions with optional filtering. Use this to find transactions by description or match status.",
|
|
125
|
+
inputSchema: {
|
|
126
|
+
type: "object",
|
|
127
|
+
properties: {
|
|
128
|
+
search: {
|
|
129
|
+
type: "string",
|
|
130
|
+
description: "Search by transaction description"
|
|
131
|
+
},
|
|
132
|
+
status: {
|
|
133
|
+
type: "string",
|
|
134
|
+
enum: ["all", "matched", "unmatched", "hidden"],
|
|
135
|
+
description: "Filter by match status"
|
|
136
|
+
},
|
|
137
|
+
statementId: {
|
|
138
|
+
type: "string",
|
|
139
|
+
description: "Filter by bank statement ID"
|
|
140
|
+
},
|
|
141
|
+
limit: {
|
|
142
|
+
type: "number",
|
|
143
|
+
description: "Max results to return (default 20)"
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: "get_transaction",
|
|
150
|
+
description: "Get detailed information about a specific bank transaction.",
|
|
151
|
+
inputSchema: {
|
|
152
|
+
type: "object",
|
|
153
|
+
properties: {
|
|
154
|
+
id: {
|
|
155
|
+
type: "string",
|
|
156
|
+
description: "Transaction ID (e.g., btxn_01abc123)"
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
required: ["id"]
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: "list_statements",
|
|
164
|
+
description: "List uploaded bank statements.",
|
|
165
|
+
inputSchema: {
|
|
166
|
+
type: "object",
|
|
167
|
+
properties: {
|
|
168
|
+
limit: {
|
|
169
|
+
type: "number",
|
|
170
|
+
description: "Max results to return (default 20)"
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
name: "list_matches",
|
|
177
|
+
description: "List proposed matches between receipts and transactions. Use this to review matches that need confirmation.",
|
|
178
|
+
inputSchema: {
|
|
179
|
+
type: "object",
|
|
180
|
+
properties: {
|
|
181
|
+
status: {
|
|
182
|
+
type: "string",
|
|
183
|
+
enum: ["proposed", "confirmed", "rejected"],
|
|
184
|
+
description: "Filter by match status (default: proposed)"
|
|
185
|
+
},
|
|
186
|
+
limit: {
|
|
187
|
+
type: "number",
|
|
188
|
+
description: "Max results to return (default 20)"
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
name: "confirm_match",
|
|
195
|
+
description: "Confirm a proposed match between a receipt and transaction. This links them together permanently.",
|
|
196
|
+
inputSchema: {
|
|
197
|
+
type: "object",
|
|
198
|
+
properties: {
|
|
199
|
+
id: {
|
|
200
|
+
type: "string",
|
|
201
|
+
description: "Match ID to confirm"
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
required: ["id"]
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
name: "reject_match",
|
|
209
|
+
description: "Reject a proposed match. The receipt and transaction will not be suggested as a match again.",
|
|
210
|
+
inputSchema: {
|
|
211
|
+
type: "object",
|
|
212
|
+
properties: {
|
|
213
|
+
id: {
|
|
214
|
+
type: "string",
|
|
215
|
+
description: "Match ID to reject"
|
|
216
|
+
},
|
|
217
|
+
reason: {
|
|
218
|
+
type: "string",
|
|
219
|
+
description: "Optional reason for rejection"
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
required: ["id"]
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
];
|
|
226
|
+
async function handleTool(client2, name, args) {
|
|
227
|
+
switch (name) {
|
|
228
|
+
case "list_receipts": {
|
|
229
|
+
const result = await client2.listReceipts({
|
|
230
|
+
search: args.search,
|
|
231
|
+
status: args.status,
|
|
232
|
+
limit: args.limit || 20
|
|
233
|
+
});
|
|
234
|
+
return formatReceipts(result.data);
|
|
235
|
+
}
|
|
236
|
+
case "get_receipt": {
|
|
237
|
+
const result = await client2.getReceipt(args.id);
|
|
238
|
+
return formatReceiptDetail(result.data);
|
|
239
|
+
}
|
|
240
|
+
case "list_transactions": {
|
|
241
|
+
const result = await client2.listTransactions({
|
|
242
|
+
search: args.search,
|
|
243
|
+
status: args.status,
|
|
244
|
+
statementId: args.statementId,
|
|
245
|
+
limit: args.limit || 20
|
|
246
|
+
});
|
|
247
|
+
return formatTransactions(result.data);
|
|
248
|
+
}
|
|
249
|
+
case "get_transaction": {
|
|
250
|
+
const result = await client2.getTransaction(args.id);
|
|
251
|
+
return formatTransaction(result.data);
|
|
252
|
+
}
|
|
253
|
+
case "list_statements": {
|
|
254
|
+
const result = await client2.listStatements({
|
|
255
|
+
limit: args.limit || 20
|
|
256
|
+
});
|
|
257
|
+
return formatStatements(result.data);
|
|
258
|
+
}
|
|
259
|
+
case "list_matches": {
|
|
260
|
+
const result = await client2.listMatches({
|
|
261
|
+
status: args.status || "proposed",
|
|
262
|
+
limit: args.limit || 20
|
|
263
|
+
});
|
|
264
|
+
return formatMatches(result.data);
|
|
265
|
+
}
|
|
266
|
+
case "confirm_match": {
|
|
267
|
+
await client2.confirmMatch(args.id);
|
|
268
|
+
return `Match ${args.id} confirmed successfully.`;
|
|
269
|
+
}
|
|
270
|
+
case "reject_match": {
|
|
271
|
+
await client2.rejectMatch(args.id, args.reason);
|
|
272
|
+
return `Match ${args.id} rejected.${args.reason ? ` Reason: ${args.reason}` : ""}`;
|
|
273
|
+
}
|
|
274
|
+
default:
|
|
275
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function formatReceipts(receipts) {
|
|
279
|
+
if (!receipts?.length) return "No receipts found.";
|
|
280
|
+
return receipts.map(
|
|
281
|
+
(r) => `\u2022 ${r.id}: ${r.vendor?.name || r.vendorName || "Unknown"} - ${r.totalAmount || "?"} ${r.currency || ""} (${r.purchaseDate ? new Date(r.purchaseDate).toLocaleDateString() : "no date"})`
|
|
282
|
+
).join("\n");
|
|
283
|
+
}
|
|
284
|
+
function formatReceiptDetail(r) {
|
|
285
|
+
let text = `Receipt: ${r.id}
|
|
286
|
+
`;
|
|
287
|
+
text += `Vendor: ${r.vendor?.name || r.vendorName || "Unknown"}
|
|
288
|
+
`;
|
|
289
|
+
text += `Date: ${r.purchaseDate ? new Date(r.purchaseDate).toLocaleDateString() : "N/A"}
|
|
290
|
+
`;
|
|
291
|
+
text += `Total: ${r.totalAmount || "?"} ${r.currency || ""}
|
|
292
|
+
`;
|
|
293
|
+
if (r.items?.length) {
|
|
294
|
+
text += `
|
|
295
|
+
Line Items:
|
|
296
|
+
`;
|
|
297
|
+
r.items.forEach((item) => {
|
|
298
|
+
text += ` \u2022 ${item.description}: ${item.totalPrice}
|
|
299
|
+
`;
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
if (r.matchedTransaction) {
|
|
303
|
+
text += `
|
|
304
|
+
Matched to: ${r.matchedTransaction.id} - ${r.matchedTransaction.description}`;
|
|
305
|
+
}
|
|
306
|
+
return text;
|
|
307
|
+
}
|
|
308
|
+
function formatTransactions(transactions) {
|
|
309
|
+
if (!transactions?.length) return "No transactions found.";
|
|
310
|
+
return transactions.map(
|
|
311
|
+
(t) => `\u2022 ${t.id}: ${t.description} - ${t.amount} ${t.currency || ""} (${t.transactionDate ? new Date(t.transactionDate).toLocaleDateString() : "no date"})`
|
|
312
|
+
).join("\n");
|
|
313
|
+
}
|
|
314
|
+
function formatTransaction(t) {
|
|
315
|
+
let text = `Transaction: ${t.id}
|
|
316
|
+
`;
|
|
317
|
+
text += `Description: ${t.description}
|
|
318
|
+
`;
|
|
319
|
+
text += `Amount: ${t.amount} ${t.currency || ""}
|
|
320
|
+
`;
|
|
321
|
+
text += `Date: ${t.transactionDate ? new Date(t.transactionDate).toLocaleDateString() : "N/A"}
|
|
322
|
+
`;
|
|
323
|
+
text += `Type: ${t.transactionType || "N/A"}`;
|
|
324
|
+
return text;
|
|
325
|
+
}
|
|
326
|
+
function formatStatements(statements) {
|
|
327
|
+
if (!statements?.length) return "No statements found.";
|
|
328
|
+
return statements.map(
|
|
329
|
+
(s) => `\u2022 ${s.id}: ${s.institutionName} - ${s.statementDate ? new Date(s.statementDate).toLocaleDateString() : "no date"} (${s.transactionCount || 0} transactions)`
|
|
330
|
+
).join("\n");
|
|
331
|
+
}
|
|
332
|
+
function formatMatches(matches) {
|
|
333
|
+
if (!matches?.length) return "No matches found.";
|
|
334
|
+
return matches.map(
|
|
335
|
+
(m) => `\u2022 ${m.id}: Receipt ${m.receiptId} \u2194 Transaction ${m.transactionId} (${Math.round(m.confidenceScore * 100)}% confidence) [${m.status}]`
|
|
336
|
+
).join("\n");
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// src/index.js
|
|
340
|
+
var apiKey = process.env.MINTLINE_API_KEY;
|
|
341
|
+
if (!apiKey) {
|
|
342
|
+
console.error("Error: MINTLINE_API_KEY environment variable is required");
|
|
343
|
+
process.exit(1);
|
|
344
|
+
}
|
|
345
|
+
var client = createClient(apiKey);
|
|
346
|
+
var server = new Server(
|
|
347
|
+
{
|
|
348
|
+
name: "mintline-mcp",
|
|
349
|
+
version: "1.0.0"
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
capabilities: {
|
|
353
|
+
tools: {}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
);
|
|
357
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
358
|
+
return { tools };
|
|
359
|
+
});
|
|
360
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
361
|
+
const { name, arguments: args } = request.params;
|
|
362
|
+
try {
|
|
363
|
+
const result = await handleTool(client, name, args || {});
|
|
364
|
+
return {
|
|
365
|
+
content: [{ type: "text", text: result }]
|
|
366
|
+
};
|
|
367
|
+
} catch (error) {
|
|
368
|
+
return {
|
|
369
|
+
content: [{ type: "text", text: `Error: ${error.message}` }],
|
|
370
|
+
isError: true
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
async function main() {
|
|
375
|
+
const transport = new StdioServerTransport();
|
|
376
|
+
await server.connect(transport);
|
|
377
|
+
console.error("Mintline MCP server running");
|
|
378
|
+
}
|
|
379
|
+
main().catch((error) => {
|
|
380
|
+
console.error("Fatal error:", error);
|
|
381
|
+
process.exit(1);
|
|
382
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mintline/mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for Mintline - connect AI assistants to your receipts and transactions",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mintline-mcp": "./dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "esbuild src/index.js --bundle --platform=node --format=esm --outfile=dist/index.js --external:@modelcontextprotocol/sdk && node scripts/build-cli.js",
|
|
12
|
+
"prepublishOnly": "npm run build",
|
|
13
|
+
"start": "node dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"keywords": [
|
|
19
|
+
"mcp",
|
|
20
|
+
"mintline",
|
|
21
|
+
"receipts",
|
|
22
|
+
"ai",
|
|
23
|
+
"claude"
|
|
24
|
+
],
|
|
25
|
+
"author": "Mintline",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"esbuild": "^0.27.2"
|
|
35
|
+
}
|
|
36
|
+
}
|