@warmio/mcp 5.0.1 → 7.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 +167 -165
- package/dist/config-paths.d.ts +2 -1
- package/dist/config-paths.js +6 -4
- package/dist/http.d.ts +6 -0
- package/dist/http.js +36 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +100 -6
- package/dist/install.d.ts +2 -0
- package/dist/install.js +209 -94
- package/dist/schemas.d.ts +1198 -186
- package/dist/schemas.js +226 -139
- package/dist/server.d.ts +5 -3
- package/dist/server.js +96 -32
- package/dist/types.d.ts +5 -2
- package/dist/types.js +8 -3
- package/dist/warm-api-client.d.ts +222 -163
- package/dist/warm-api-client.js +110 -222
- package/package.json +2 -2
package/dist/warm-api-client.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import { getWarmApiKeyPath, readConfigFile } from './config-paths.js';
|
|
2
|
-
const DEFAULT_API_URL = process.env.WARM_API_URL || 'https://warm.io';
|
|
1
|
+
import { getWarmApiKeyPath, readConfigFile, } from './config-paths.js';
|
|
2
|
+
const DEFAULT_API_URL = process.env.WARM_API_URL || 'https://app.warm.io';
|
|
3
3
|
const DEFAULT_REQUEST_TIMEOUT_MS = (() => {
|
|
4
4
|
const raw = Number(process.env.WARM_API_TIMEOUT_MS || 10_000);
|
|
5
5
|
return Number.isFinite(raw) && raw > 0 ? raw : 10_000;
|
|
6
6
|
})();
|
|
7
|
-
|
|
8
|
-
function roundMoney(value) {
|
|
9
|
-
return Math.round(value * 100) / 100;
|
|
10
|
-
}
|
|
7
|
+
const cachedApiKeys = new Map();
|
|
11
8
|
function getRequestSignal(timeoutMs) {
|
|
12
9
|
if (typeof AbortSignal.timeout === 'function') {
|
|
13
10
|
return AbortSignal.timeout(timeoutMs);
|
|
@@ -17,157 +14,61 @@ function getRequestSignal(timeoutMs) {
|
|
|
17
14
|
timer.unref?.();
|
|
18
15
|
return controller.signal;
|
|
19
16
|
}
|
|
20
|
-
function normalizeAccountType(rawType) {
|
|
21
|
-
switch (rawType) {
|
|
22
|
-
case 'depository':
|
|
23
|
-
case 'credit':
|
|
24
|
-
case 'loan':
|
|
25
|
-
case 'investment':
|
|
26
|
-
return rawType;
|
|
27
|
-
default:
|
|
28
|
-
return 'other';
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
function normalizeStateAccount(account) {
|
|
32
|
-
return {
|
|
33
|
-
available_balance: account.available_balance == null ? null : roundMoney(account.available_balance),
|
|
34
|
-
balance: roundMoney(account.current_balance ?? 0),
|
|
35
|
-
institution: account.institution_name ?? null,
|
|
36
|
-
mask: account.mask ?? null,
|
|
37
|
-
name: account.name || 'Unknown Account',
|
|
38
|
-
subtype: account.subtype ?? null,
|
|
39
|
-
type: normalizeAccountType(account.type),
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
function normalizeAccounts(accounts) {
|
|
43
|
-
return {
|
|
44
|
-
accounts: accounts.map((account) => {
|
|
45
|
-
const normalized = normalizeStateAccount(account);
|
|
46
|
-
return {
|
|
47
|
-
balance: normalized.balance,
|
|
48
|
-
institution: normalized.institution,
|
|
49
|
-
mask: normalized.mask,
|
|
50
|
-
name: normalized.name,
|
|
51
|
-
subtype: normalized.subtype,
|
|
52
|
-
type: normalized.type,
|
|
53
|
-
};
|
|
54
|
-
}),
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
function normalizeFinancialState(input) {
|
|
58
|
-
const latestSnapshot = input.snapshots?.[0];
|
|
59
|
-
const categorySpending = latestSnapshot?.spending_by_category ?? [];
|
|
60
|
-
return {
|
|
61
|
-
budgets: (input.budgets || []).map((budget) => ({
|
|
62
|
-
amount: budget.amount ?? 0,
|
|
63
|
-
name: budget.name || '',
|
|
64
|
-
percent_used: budget.percent_used ?? 0,
|
|
65
|
-
period: budget.period || '',
|
|
66
|
-
remaining: budget.remaining ?? 0,
|
|
67
|
-
spent: budget.spent ?? 0,
|
|
68
|
-
status: budget.status ?? null,
|
|
69
|
-
})),
|
|
70
|
-
category_spending: categorySpending.map((item) => ({
|
|
71
|
-
amount: roundMoney(item?.amount ?? item?.total ?? 0),
|
|
72
|
-
category: item?.category || 'Uncategorized',
|
|
73
|
-
})),
|
|
74
|
-
generated_at: input.generatedAt,
|
|
75
|
-
goals: (input.goals || []).map((goal) => ({
|
|
76
|
-
category: goal.category ?? null,
|
|
77
|
-
current: goal.current ?? 0,
|
|
78
|
-
monthly_contribution_needed: goal.monthly_contribution_needed ?? null,
|
|
79
|
-
name: goal.name || '',
|
|
80
|
-
progress_percent: goal.progress_percent ?? 0,
|
|
81
|
-
status: goal.status ?? null,
|
|
82
|
-
target: goal.target ?? 0,
|
|
83
|
-
target_date: goal.target_date ?? null,
|
|
84
|
-
})),
|
|
85
|
-
health: {
|
|
86
|
-
data_completeness: input.health?.data_completeness ?? null,
|
|
87
|
-
label: input.health?.label ?? null,
|
|
88
|
-
message: input.health?.message ?? null,
|
|
89
|
-
pillars: input.health?.pillars
|
|
90
|
-
? {
|
|
91
|
-
borrow: input.health.pillars.borrow ?? null,
|
|
92
|
-
build: input.health.pillars.build ?? null,
|
|
93
|
-
save: input.health.pillars.save ?? null,
|
|
94
|
-
spend: input.health.pillars.spend ?? null,
|
|
95
|
-
}
|
|
96
|
-
: null,
|
|
97
|
-
score: input.health?.score ?? null,
|
|
98
|
-
},
|
|
99
|
-
holdings: (input.holdings || []).map((holding) => ({
|
|
100
|
-
account_id: holding.account_id || '',
|
|
101
|
-
cost_basis: holding.cost_basis ?? null,
|
|
102
|
-
quantity: roundMoney(holding.quantity ?? 0),
|
|
103
|
-
security_name: holding.security_name ?? null,
|
|
104
|
-
symbol: holding.symbol ?? null,
|
|
105
|
-
type: holding.type ?? null,
|
|
106
|
-
value: holding.value == null ? null : roundMoney(holding.value),
|
|
107
|
-
})),
|
|
108
|
-
liabilities: (input.liabilities || []).map((liability) => ({
|
|
109
|
-
account_id: liability.account_id || '',
|
|
110
|
-
apr_percentage: liability.apr_percentage ?? null,
|
|
111
|
-
balance: liability.balance == null ? null : roundMoney(liability.balance),
|
|
112
|
-
is_overdue: liability.is_overdue ?? null,
|
|
113
|
-
minimum_payment: liability.minimum_payment == null ? null : roundMoney(liability.minimum_payment),
|
|
114
|
-
next_payment_due_date: liability.next_payment_due_date ?? null,
|
|
115
|
-
type: liability.type || 'unknown',
|
|
116
|
-
})),
|
|
117
|
-
recurring: (input.recurring || []).map((stream) => ({
|
|
118
|
-
active: stream.active === true || stream.is_active === true,
|
|
119
|
-
amount: roundMoney(stream.amount ?? stream.last_amount ?? stream.average_amount ?? 0),
|
|
120
|
-
frequency: stream.frequency || '',
|
|
121
|
-
merchant: stream.merchant || stream.merchant_name || stream.description || '',
|
|
122
|
-
next_date: stream.next_date ?? null,
|
|
123
|
-
type: stream.type ?? stream.stream_type ?? null,
|
|
124
|
-
})),
|
|
125
|
-
snapshots: (input.snapshots || [])
|
|
126
|
-
.map((snapshot) => ({
|
|
127
|
-
date: snapshot.date || snapshot.snapshot_date || snapshot.period_end || null,
|
|
128
|
-
net_worth: snapshot.net_worth ?? 0,
|
|
129
|
-
total_assets: snapshot.total_assets ?? 0,
|
|
130
|
-
total_liabilities: snapshot.total_liabilities ?? 0,
|
|
131
|
-
}))
|
|
132
|
-
.filter((snapshot) => typeof snapshot.date === 'string')
|
|
133
|
-
.map((snapshot) => ({
|
|
134
|
-
date: snapshot.date,
|
|
135
|
-
net_worth: roundMoney(snapshot.net_worth),
|
|
136
|
-
total_assets: roundMoney(snapshot.total_assets),
|
|
137
|
-
total_liabilities: roundMoney(snapshot.total_liabilities),
|
|
138
|
-
})),
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
17
|
function createWarmApiClientConfig(options) {
|
|
142
18
|
return {
|
|
19
|
+
audience: options.audience || 'context',
|
|
143
20
|
apiKeyResolver: options.apiKeyResolver,
|
|
144
21
|
apiUrl: options.apiUrl || DEFAULT_API_URL,
|
|
145
22
|
fetchImplementation: options.fetchImplementation || fetch,
|
|
146
23
|
requestTimeoutMs: options.requestTimeoutMs || DEFAULT_REQUEST_TIMEOUT_MS,
|
|
147
24
|
};
|
|
148
25
|
}
|
|
149
|
-
export function getConfiguredApiKey() {
|
|
150
|
-
if (
|
|
151
|
-
return
|
|
26
|
+
export function getConfiguredApiKey(audience = 'context') {
|
|
27
|
+
if (cachedApiKeys.has(audience)) {
|
|
28
|
+
return cachedApiKeys.get(audience) ?? null;
|
|
152
29
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
cachedApiKey = readConfigFile(getWarmApiKeyPath());
|
|
158
|
-
return cachedApiKey;
|
|
30
|
+
const environmentKey = audience === 'automation' ? 'WARM_AUTOMATION_API_KEY' : 'WARM_CONTEXT_API_KEY';
|
|
31
|
+
const apiKey = process.env[environmentKey]?.trim() || readConfigFile(getWarmApiKeyPath(audience));
|
|
32
|
+
cachedApiKeys.set(audience, apiKey);
|
|
33
|
+
return apiKey;
|
|
159
34
|
}
|
|
160
35
|
export function resetConfiguredApiKeyCache() {
|
|
161
|
-
|
|
36
|
+
cachedApiKeys.clear();
|
|
37
|
+
}
|
|
38
|
+
export class WarmApiError extends Error {
|
|
39
|
+
body;
|
|
40
|
+
headers;
|
|
41
|
+
status;
|
|
42
|
+
constructor(message, response) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.name = 'WarmApiError';
|
|
45
|
+
this.body = response.body;
|
|
46
|
+
this.headers = response.headers;
|
|
47
|
+
this.status = response.status;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function toWarmApiError(response) {
|
|
51
|
+
const errorMessages = {
|
|
52
|
+
401: 'Invalid or expired API key. Regenerate at https://warm.io/settings',
|
|
53
|
+
403: 'API access is available on paid plans only. Upgrade at https://warm.io/settings',
|
|
54
|
+
429: 'Rate limit exceeded. Try again in a few minutes.',
|
|
55
|
+
};
|
|
56
|
+
if (errorMessages[response.status]) {
|
|
57
|
+
return new WarmApiError(errorMessages[response.status], response);
|
|
58
|
+
}
|
|
59
|
+
const detail = typeof response.body === 'object' && response.body !== null && 'error' in response.body
|
|
60
|
+
? String(response.body.error)
|
|
61
|
+
: `HTTP ${response.status}`;
|
|
62
|
+
return new WarmApiError(detail, response);
|
|
162
63
|
}
|
|
163
|
-
|
|
64
|
+
async function requestWarmApi(request, options) {
|
|
164
65
|
const requestOptions = createWarmApiClientConfig(options);
|
|
165
|
-
const apiKey = requestOptions.apiKeyResolver?.() ?? getConfiguredApiKey();
|
|
66
|
+
const apiKey = requestOptions.apiKeyResolver?.() ?? getConfiguredApiKey(requestOptions.audience);
|
|
166
67
|
if (!apiKey) {
|
|
167
|
-
throw new Error(
|
|
68
|
+
throw new Error(`No ${requestOptions.audience} API key configured. Run "npx @warmio/mcp install --mode ${requestOptions.audience}" or set ${requestOptions.audience === 'automation' ? 'WARM_AUTOMATION_API_KEY' : 'WARM_CONTEXT_API_KEY'}.`);
|
|
168
69
|
}
|
|
169
|
-
const url = new URL(endpoint, requestOptions.apiUrl);
|
|
170
|
-
Object.entries(
|
|
70
|
+
const url = new URL(request.endpoint, requestOptions.apiUrl);
|
|
71
|
+
Object.entries(request.query || {}).forEach(([key, value]) => {
|
|
171
72
|
if (value) {
|
|
172
73
|
url.searchParams.append(key, value);
|
|
173
74
|
}
|
|
@@ -175,10 +76,13 @@ export async function apiRequest(endpoint, params = {}, options = {}) {
|
|
|
175
76
|
let response;
|
|
176
77
|
try {
|
|
177
78
|
response = await requestOptions.fetchImplementation(url.toString(), {
|
|
79
|
+
...(request.method && request.method !== 'GET' ? { method: request.method } : {}),
|
|
178
80
|
headers: {
|
|
179
81
|
Accept: 'application/json',
|
|
180
82
|
Authorization: `Bearer ${apiKey}`,
|
|
83
|
+
...(request.body ? { 'Content-Type': 'application/json' } : {}),
|
|
181
84
|
},
|
|
85
|
+
...(request.body ? { body: JSON.stringify(request.body) } : {}),
|
|
182
86
|
signal: getRequestSignal(requestOptions.requestTimeoutMs),
|
|
183
87
|
});
|
|
184
88
|
}
|
|
@@ -191,107 +95,91 @@ export async function apiRequest(endpoint, params = {}, options = {}) {
|
|
|
191
95
|
}
|
|
192
96
|
throw error;
|
|
193
97
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
403: 'API access is available on paid plans only. Upgrade at https://warm.io/settings',
|
|
198
|
-
429: 'Rate limit exceeded. Try again in a few minutes.',
|
|
199
|
-
};
|
|
200
|
-
if (errorMessages[response.status]) {
|
|
201
|
-
throw new Error(errorMessages[response.status]);
|
|
202
|
-
}
|
|
203
|
-
let detail = `HTTP ${response.status}`;
|
|
98
|
+
const text = await response.text();
|
|
99
|
+
let body = null;
|
|
100
|
+
if (text) {
|
|
204
101
|
try {
|
|
205
|
-
|
|
206
|
-
if (body?.error) {
|
|
207
|
-
detail = body.error;
|
|
208
|
-
}
|
|
102
|
+
body = JSON.parse(text);
|
|
209
103
|
}
|
|
210
104
|
catch {
|
|
211
|
-
|
|
105
|
+
body = text;
|
|
212
106
|
}
|
|
213
|
-
throw new Error(detail);
|
|
214
107
|
}
|
|
215
|
-
return
|
|
108
|
+
return {
|
|
109
|
+
body: body,
|
|
110
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
111
|
+
status: response.status,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
async function requestSuccessfulJson(request, options) {
|
|
115
|
+
const response = await requestWarmApi(request, options);
|
|
116
|
+
if (response.status >= 400) {
|
|
117
|
+
throw toWarmApiError(response);
|
|
118
|
+
}
|
|
119
|
+
return response.body;
|
|
216
120
|
}
|
|
217
121
|
export function createWarmApiClient(options = {}) {
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
return normalizeAccounts(response.accounts || []);
|
|
221
|
-
};
|
|
222
|
-
const getFinancialState = async () => {
|
|
223
|
-
const [snapshotsResponse, recurringResponse, budgetsResponse, goalsResponse, healthResponse, liabilitiesResponse, holdingsResponse,] = await Promise.all([
|
|
224
|
-
apiRequest('/api/export', { dataset: 'snapshots' }, options),
|
|
225
|
-
apiRequest('/api/export', { dataset: 'recurring' }, options),
|
|
226
|
-
apiRequest('/api/export', { dataset: 'budgets' }, options),
|
|
227
|
-
apiRequest('/api/export', { dataset: 'goals' }, options),
|
|
228
|
-
apiRequest('/api/export', { dataset: 'health' }, options),
|
|
229
|
-
apiRequest('/api/export', { dataset: 'liabilities' }, options),
|
|
230
|
-
apiRequest('/api/export', { dataset: 'holdings' }, options),
|
|
231
|
-
]);
|
|
232
|
-
const generatedAt = holdingsResponse.generated_at ||
|
|
233
|
-
liabilitiesResponse.generated_at ||
|
|
234
|
-
healthResponse.generated_at ||
|
|
235
|
-
goalsResponse.generated_at ||
|
|
236
|
-
budgetsResponse.generated_at ||
|
|
237
|
-
recurringResponse.generated_at ||
|
|
238
|
-
snapshotsResponse.generated_at ||
|
|
239
|
-
new Date().toISOString();
|
|
240
|
-
return normalizeFinancialState({
|
|
241
|
-
budgets: budgetsResponse.budgets,
|
|
242
|
-
generatedAt,
|
|
243
|
-
goals: goalsResponse.goals,
|
|
244
|
-
health: healthResponse,
|
|
245
|
-
holdings: holdingsResponse.holdings,
|
|
246
|
-
liabilities: liabilitiesResponse.liabilities,
|
|
247
|
-
recurring: recurringResponse.recurring || recurringResponse.recurring_transactions,
|
|
248
|
-
snapshots: snapshotsResponse.snapshots,
|
|
249
|
-
});
|
|
250
|
-
};
|
|
122
|
+
const getFinancialContext = async () => await requestSuccessfulJson({ endpoint: '/api/financial-context' }, options);
|
|
123
|
+
const getFinancialContextMeta = async () => await requestSuccessfulJson({ endpoint: '/api/financial-context/meta' }, options);
|
|
251
124
|
const getTransactions = async (input) => {
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
pagination: {
|
|
264
|
-
has_more: nextCursor !== null,
|
|
265
|
-
limit: response.pagination?.limit ?? input.limit,
|
|
266
|
-
next_cursor: nextCursor,
|
|
267
|
-
},
|
|
268
|
-
txns: (response.transactions || []).map((transaction) => ({
|
|
269
|
-
amount: roundMoney(transaction.amount ?? 0),
|
|
270
|
-
category: transaction.primary_category ?? null,
|
|
271
|
-
date: transaction.date ?? null,
|
|
272
|
-
description: transaction.name ?? null,
|
|
273
|
-
detailed_category: transaction.detailed_category ?? null,
|
|
274
|
-
id: transaction.id ?? null,
|
|
275
|
-
merchant: transaction.merchant_name ?? null,
|
|
276
|
-
})),
|
|
277
|
-
};
|
|
125
|
+
const rawInput = input;
|
|
126
|
+
if (typeof rawInput.month === 'string' && rawInput.latest !== undefined) {
|
|
127
|
+
throw new Error('`month` and `latest` are mutually exclusive.');
|
|
128
|
+
}
|
|
129
|
+
if (typeof rawInput.month === 'string') {
|
|
130
|
+
return await requestSuccessfulJson({ endpoint: '/api/financial-context/transactions', query: { month: rawInput.month } }, options);
|
|
131
|
+
}
|
|
132
|
+
if (rawInput.latest === true) {
|
|
133
|
+
return await requestSuccessfulJson({ endpoint: '/api/financial-context/transactions', query: { latest: '1' } }, options);
|
|
134
|
+
}
|
|
135
|
+
throw new Error('Call getTransactions with `month` in YYYY-MM format or `latest: true`.');
|
|
278
136
|
};
|
|
279
137
|
const verifyKey = async () => {
|
|
280
|
-
const response = await
|
|
138
|
+
const response = await requestSuccessfulJson({ endpoint: '/api/verify' }, options);
|
|
281
139
|
return {
|
|
140
|
+
audience: response.audience,
|
|
282
141
|
status: response.status || (response.valid ? 'ok' : 'invalid'),
|
|
283
142
|
valid: response.valid === true,
|
|
284
143
|
};
|
|
285
144
|
};
|
|
145
|
+
const validateAudience = async () => {
|
|
146
|
+
const result = await verifyKey();
|
|
147
|
+
const expectedAudience = createWarmApiClientConfig(options).audience;
|
|
148
|
+
if (!result.valid || result.audience !== expectedAudience) {
|
|
149
|
+
const article = expectedAudience === 'automation' ? 'an' : 'a';
|
|
150
|
+
throw new Error(`This MCP mode requires ${article} ${expectedAudience} API key. Run "npx @warmio/mcp install --mode ${expectedAudience}" to configure a separate key.`);
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
};
|
|
154
|
+
const searchOperations = async (query) => await requestSuccessfulJson({ body: query ? { query } : {}, endpoint: '/api/automation/operations/search', method: 'POST' }, options);
|
|
155
|
+
const describeOperation = async (operationId, input) => await requestSuccessfulJson({
|
|
156
|
+
body: { operation_id: operationId, ...(input ? { input } : {}) },
|
|
157
|
+
endpoint: '/api/automation/operations/describe',
|
|
158
|
+
method: 'POST',
|
|
159
|
+
}, options);
|
|
160
|
+
const invokeOperation = async (operationId, input, approvalId) => await requestSuccessfulJson({
|
|
161
|
+
body: {
|
|
162
|
+
operation_id: operationId,
|
|
163
|
+
input: input ?? {},
|
|
164
|
+
...(approvalId ? { approval_id: approvalId } : {}),
|
|
165
|
+
},
|
|
166
|
+
endpoint: '/api/automation/operations/invoke',
|
|
167
|
+
method: 'POST',
|
|
168
|
+
}, options);
|
|
286
169
|
return {
|
|
287
|
-
|
|
288
|
-
|
|
170
|
+
getFinancialContext,
|
|
171
|
+
getFinancialContextMeta,
|
|
289
172
|
getTransactions,
|
|
173
|
+
searchOperations,
|
|
174
|
+
describeOperation,
|
|
175
|
+
invokeOperation,
|
|
176
|
+
validateAudience,
|
|
290
177
|
verifyKey,
|
|
291
178
|
};
|
|
292
179
|
}
|
|
293
|
-
export async function verifyWarmApiKey(apiKey) {
|
|
180
|
+
export async function verifyWarmApiKey(apiKey, audience = 'context') {
|
|
294
181
|
return createWarmApiClient({
|
|
182
|
+
audience,
|
|
295
183
|
apiKeyResolver: () => apiKey,
|
|
296
184
|
}).verifyKey();
|
|
297
185
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@warmio/mcp",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Warm CLI and
|
|
3
|
+
"version": "7.0.0",
|
|
4
|
+
"description": "Warm CLI and compact context or automation MCP server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|