recker 1.0.28 → 1.0.29-next.604e03b

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 (102) hide show
  1. package/README.md +28 -1
  2. package/dist/ai/client-ai.d.ts +41 -0
  3. package/dist/ai/client-ai.js +391 -0
  4. package/dist/ai/index.d.ts +2 -0
  5. package/dist/ai/index.js +2 -0
  6. package/dist/ai/memory.d.ts +35 -0
  7. package/dist/ai/memory.js +136 -0
  8. package/dist/browser/ai/client-ai.d.ts +41 -0
  9. package/dist/browser/ai/client-ai.js +391 -0
  10. package/dist/browser/ai/memory.d.ts +35 -0
  11. package/dist/browser/ai/memory.js +136 -0
  12. package/dist/browser/core/client.d.ts +6 -1
  13. package/dist/browser/core/client.js +18 -0
  14. package/dist/browser/transport/undici.js +11 -2
  15. package/dist/browser/types/ai-client.d.ts +32 -0
  16. package/dist/browser/types/ai-client.js +1 -0
  17. package/dist/browser/types/ai.d.ts +1 -1
  18. package/dist/cli/index.js +261 -1
  19. package/dist/cli/tui/scroll-buffer.js +4 -4
  20. package/dist/cli/tui/shell.d.ts +4 -0
  21. package/dist/cli/tui/shell.js +500 -19
  22. package/dist/core/client.d.ts +6 -1
  23. package/dist/core/client.js +18 -0
  24. package/dist/mcp/server.js +15 -0
  25. package/dist/mcp/tools/scrape.d.ts +3 -0
  26. package/dist/mcp/tools/scrape.js +156 -0
  27. package/dist/mcp/tools/security.d.ts +3 -0
  28. package/dist/mcp/tools/security.js +471 -0
  29. package/dist/mcp/tools/seo.d.ts +3 -0
  30. package/dist/mcp/tools/seo.js +427 -0
  31. package/dist/presets/anthropic.d.ts +3 -1
  32. package/dist/presets/anthropic.js +11 -1
  33. package/dist/presets/azure-openai.d.ts +3 -1
  34. package/dist/presets/azure-openai.js +11 -1
  35. package/dist/presets/cohere.d.ts +3 -1
  36. package/dist/presets/cohere.js +8 -2
  37. package/dist/presets/deepseek.d.ts +3 -1
  38. package/dist/presets/deepseek.js +8 -2
  39. package/dist/presets/fireworks.d.ts +3 -1
  40. package/dist/presets/fireworks.js +8 -2
  41. package/dist/presets/gemini.d.ts +3 -1
  42. package/dist/presets/gemini.js +8 -1
  43. package/dist/presets/groq.d.ts +3 -1
  44. package/dist/presets/groq.js +8 -2
  45. package/dist/presets/huggingface.d.ts +3 -1
  46. package/dist/presets/huggingface.js +8 -1
  47. package/dist/presets/mistral.d.ts +3 -1
  48. package/dist/presets/mistral.js +8 -2
  49. package/dist/presets/openai.d.ts +3 -1
  50. package/dist/presets/openai.js +9 -2
  51. package/dist/presets/perplexity.d.ts +3 -1
  52. package/dist/presets/perplexity.js +8 -2
  53. package/dist/presets/registry.d.ts +4 -0
  54. package/dist/presets/registry.js +48 -0
  55. package/dist/presets/replicate.d.ts +3 -1
  56. package/dist/presets/replicate.js +8 -1
  57. package/dist/presets/together.d.ts +3 -1
  58. package/dist/presets/together.js +8 -2
  59. package/dist/presets/xai.d.ts +3 -1
  60. package/dist/presets/xai.js +8 -2
  61. package/dist/scrape/index.d.ts +2 -0
  62. package/dist/scrape/index.js +1 -0
  63. package/dist/scrape/spider.d.ts +61 -0
  64. package/dist/scrape/spider.js +250 -0
  65. package/dist/seo/analyzer.js +27 -0
  66. package/dist/seo/index.d.ts +3 -1
  67. package/dist/seo/index.js +1 -0
  68. package/dist/seo/rules/accessibility.js +620 -54
  69. package/dist/seo/rules/best-practices.d.ts +2 -0
  70. package/dist/seo/rules/best-practices.js +188 -0
  71. package/dist/seo/rules/crawl.d.ts +2 -0
  72. package/dist/seo/rules/crawl.js +307 -0
  73. package/dist/seo/rules/cwv.d.ts +2 -0
  74. package/dist/seo/rules/cwv.js +337 -0
  75. package/dist/seo/rules/ecommerce.d.ts +2 -0
  76. package/dist/seo/rules/ecommerce.js +252 -0
  77. package/dist/seo/rules/i18n.d.ts +2 -0
  78. package/dist/seo/rules/i18n.js +222 -0
  79. package/dist/seo/rules/index.d.ts +32 -0
  80. package/dist/seo/rules/index.js +71 -0
  81. package/dist/seo/rules/internal-linking.d.ts +2 -0
  82. package/dist/seo/rules/internal-linking.js +375 -0
  83. package/dist/seo/rules/local.d.ts +2 -0
  84. package/dist/seo/rules/local.js +265 -0
  85. package/dist/seo/rules/pwa.d.ts +2 -0
  86. package/dist/seo/rules/pwa.js +302 -0
  87. package/dist/seo/rules/readability.d.ts +2 -0
  88. package/dist/seo/rules/readability.js +255 -0
  89. package/dist/seo/rules/security.js +406 -28
  90. package/dist/seo/rules/social.d.ts +2 -0
  91. package/dist/seo/rules/social.js +373 -0
  92. package/dist/seo/rules/types.d.ts +155 -0
  93. package/dist/seo/seo-spider.d.ts +47 -0
  94. package/dist/seo/seo-spider.js +362 -0
  95. package/dist/seo/types.d.ts +24 -0
  96. package/dist/transport/undici.js +11 -2
  97. package/dist/types/ai-client.d.ts +32 -0
  98. package/dist/types/ai-client.js +1 -0
  99. package/dist/types/ai.d.ts +1 -1
  100. package/dist/utils/colors.d.ts +2 -0
  101. package/dist/utils/colors.js +4 -0
  102. package/package.json +1 -1
@@ -0,0 +1,362 @@
1
+ import { Spider } from '../scrape/spider.js';
2
+ import { analyzeSeo } from './analyzer.js';
3
+ import { createClient } from '../core/client.js';
4
+ import * as fs from 'fs/promises';
5
+ export class SeoSpider {
6
+ spider;
7
+ options;
8
+ seoResults = new Map();
9
+ constructor(options = {}) {
10
+ this.options = options;
11
+ this.spider = new Spider(options);
12
+ }
13
+ async crawl(startUrl) {
14
+ const result = await this.spider.crawl(startUrl);
15
+ if (!this.options.seo) {
16
+ return {
17
+ ...result,
18
+ pages: result.pages,
19
+ siteWideIssues: [],
20
+ summary: {
21
+ totalPages: result.pages.length,
22
+ pagesWithErrors: 0,
23
+ pagesWithWarnings: 0,
24
+ avgScore: 0,
25
+ duplicateTitles: 0,
26
+ duplicateDescriptions: 0,
27
+ duplicateH1s: 0,
28
+ orphanPages: 0,
29
+ },
30
+ };
31
+ }
32
+ const seoPages = await this.analyzePages(result.pages);
33
+ const siteWideIssues = this.detectSiteWideIssues(seoPages);
34
+ const summary = this.calculateSummary(seoPages, siteWideIssues);
35
+ const seoResult = {
36
+ ...result,
37
+ pages: seoPages,
38
+ siteWideIssues,
39
+ summary,
40
+ };
41
+ if (this.options.output) {
42
+ await this.saveReport(seoResult);
43
+ }
44
+ return seoResult;
45
+ }
46
+ async analyzePages(pages) {
47
+ const results = [];
48
+ const client = createClient({
49
+ timeout: this.options.timeout || 10000,
50
+ headers: {
51
+ 'User-Agent': this.options.userAgent || 'Recker Spider/1.0',
52
+ },
53
+ });
54
+ for (const page of pages) {
55
+ if (page.error || page.status >= 400) {
56
+ results.push({
57
+ ...page,
58
+ seoReport: undefined,
59
+ });
60
+ continue;
61
+ }
62
+ try {
63
+ const response = await client.get(page.url);
64
+ const html = await response.text();
65
+ const seoReport = await analyzeSeo(html, { baseUrl: page.url });
66
+ const seoPage = {
67
+ ...page,
68
+ seoReport,
69
+ };
70
+ results.push(seoPage);
71
+ this.seoResults.set(page.url, seoReport);
72
+ this.options.onSeoAnalysis?.(seoPage);
73
+ }
74
+ catch {
75
+ results.push({
76
+ ...page,
77
+ seoReport: undefined,
78
+ });
79
+ }
80
+ }
81
+ return results;
82
+ }
83
+ createReportFromPageData(page) {
84
+ const checks = [];
85
+ if (page.title) {
86
+ const titleLength = page.title.length;
87
+ if (titleLength < 30) {
88
+ checks.push({
89
+ name: 'Title Length',
90
+ status: 'warn',
91
+ message: `Title is too short (${titleLength} chars)`,
92
+ value: titleLength,
93
+ recommendation: 'Title should be 50-60 characters',
94
+ });
95
+ }
96
+ else if (titleLength > 60) {
97
+ checks.push({
98
+ name: 'Title Length',
99
+ status: 'warn',
100
+ message: `Title is too long (${titleLength} chars)`,
101
+ value: titleLength,
102
+ recommendation: 'Title should be 50-60 characters',
103
+ });
104
+ }
105
+ else {
106
+ checks.push({
107
+ name: 'Title Length',
108
+ status: 'pass',
109
+ message: `Good title length (${titleLength} chars)`,
110
+ value: titleLength,
111
+ });
112
+ }
113
+ }
114
+ else {
115
+ checks.push({
116
+ name: 'Title',
117
+ status: 'fail',
118
+ message: 'Page has no title',
119
+ recommendation: 'Add a descriptive <title> tag',
120
+ });
121
+ }
122
+ const internalLinks = page.links.filter(l => l.type === 'internal').length;
123
+ const externalLinks = page.links.filter(l => l.type === 'external').length;
124
+ if (internalLinks === 0) {
125
+ checks.push({
126
+ name: 'Internal Links',
127
+ status: 'warn',
128
+ message: 'No internal links found',
129
+ recommendation: 'Add internal links to improve site structure',
130
+ });
131
+ }
132
+ else {
133
+ checks.push({
134
+ name: 'Internal Links',
135
+ status: 'pass',
136
+ message: `${internalLinks} internal links found`,
137
+ value: internalLinks,
138
+ });
139
+ }
140
+ const scoreSum = checks.reduce((sum, c) => {
141
+ if (c.status === 'pass')
142
+ return sum + 100;
143
+ if (c.status === 'warn')
144
+ return sum + 50;
145
+ return sum;
146
+ }, 0);
147
+ const score = checks.length > 0 ? Math.round(scoreSum / checks.length) : 0;
148
+ return {
149
+ url: page.url,
150
+ timestamp: new Date(),
151
+ grade: this.scoreToGrade(score),
152
+ score,
153
+ checks,
154
+ title: page.title ? { text: page.title, length: page.title.length } : undefined,
155
+ headings: {
156
+ structure: [],
157
+ h1Count: 0,
158
+ hasProperHierarchy: false,
159
+ issues: [],
160
+ },
161
+ content: {
162
+ wordCount: 0,
163
+ characterCount: 0,
164
+ sentenceCount: 0,
165
+ paragraphCount: 0,
166
+ readingTimeMinutes: 0,
167
+ avgWordsPerSentence: 0,
168
+ avgParagraphLength: 0,
169
+ listCount: 0,
170
+ strongTagCount: 0,
171
+ emTagCount: 0,
172
+ },
173
+ links: {
174
+ total: page.links.length,
175
+ internal: internalLinks,
176
+ external: externalLinks,
177
+ nofollow: 0,
178
+ broken: 0,
179
+ withoutText: page.links.filter(l => !l.text?.trim()).length,
180
+ sponsoredLinks: 0,
181
+ ugcLinks: 0,
182
+ },
183
+ images: {
184
+ total: 0,
185
+ withAlt: 0,
186
+ withoutAlt: 0,
187
+ lazy: 0,
188
+ missingDimensions: 0,
189
+ modernFormats: 0,
190
+ altTextLengths: [],
191
+ imageFilenames: [],
192
+ imagesWithAsyncDecoding: 0,
193
+ },
194
+ social: {
195
+ openGraph: {
196
+ present: false,
197
+ hasTitle: false,
198
+ hasDescription: false,
199
+ hasImage: false,
200
+ hasUrl: false,
201
+ issues: [],
202
+ },
203
+ twitterCard: {
204
+ present: false,
205
+ hasCard: false,
206
+ hasTitle: false,
207
+ hasDescription: false,
208
+ hasImage: false,
209
+ issues: [],
210
+ },
211
+ },
212
+ technical: {
213
+ hasCanonical: false,
214
+ hasRobotsMeta: false,
215
+ hasViewport: false,
216
+ hasCharset: false,
217
+ hasLang: false,
218
+ },
219
+ jsonLd: {
220
+ count: 0,
221
+ types: [],
222
+ },
223
+ };
224
+ }
225
+ detectSiteWideIssues(pages) {
226
+ const issues = [];
227
+ const titleGroups = new Map();
228
+ const descriptionGroups = new Map();
229
+ const h1Groups = new Map();
230
+ for (const page of pages) {
231
+ if (!page.seoReport)
232
+ continue;
233
+ const title = page.seoReport.title?.text?.trim();
234
+ if (title) {
235
+ const urls = titleGroups.get(title) || [];
236
+ urls.push(page.url);
237
+ titleGroups.set(title, urls);
238
+ }
239
+ const desc = page.seoReport.metaDescription?.text?.trim();
240
+ if (desc) {
241
+ const urls = descriptionGroups.get(desc) || [];
242
+ urls.push(page.url);
243
+ descriptionGroups.set(desc, urls);
244
+ }
245
+ const h1 = page.seoReport.headings?.structure?.find(h => h.level === 1)?.text?.trim();
246
+ if (h1) {
247
+ const urls = h1Groups.get(h1) || [];
248
+ urls.push(page.url);
249
+ h1Groups.set(h1, urls);
250
+ }
251
+ }
252
+ for (const [title, urls] of titleGroups) {
253
+ if (urls.length > 1) {
254
+ issues.push({
255
+ type: 'duplicate-title',
256
+ severity: 'error',
257
+ message: `${urls.length} pages share the same title`,
258
+ affectedUrls: urls,
259
+ value: title,
260
+ });
261
+ }
262
+ }
263
+ for (const [desc, urls] of descriptionGroups) {
264
+ if (urls.length > 1) {
265
+ issues.push({
266
+ type: 'duplicate-description',
267
+ severity: 'warning',
268
+ message: `${urls.length} pages share the same meta description`,
269
+ affectedUrls: urls,
270
+ value: desc,
271
+ });
272
+ }
273
+ }
274
+ for (const [h1, urls] of h1Groups) {
275
+ if (urls.length > 1) {
276
+ issues.push({
277
+ type: 'duplicate-h1',
278
+ severity: 'warning',
279
+ message: `${urls.length} pages share the same H1 heading`,
280
+ affectedUrls: urls,
281
+ value: h1,
282
+ });
283
+ }
284
+ }
285
+ const linkedUrls = new Set();
286
+ for (const page of pages) {
287
+ for (const link of page.links) {
288
+ if (link.type === 'internal' && link.href) {
289
+ linkedUrls.add(link.href);
290
+ }
291
+ }
292
+ }
293
+ const orphanPages = pages
294
+ .filter(p => !linkedUrls.has(p.url) && p.depth > 0)
295
+ .map(p => p.url);
296
+ if (orphanPages.length > 0) {
297
+ issues.push({
298
+ type: 'orphan-page',
299
+ severity: 'warning',
300
+ message: `${orphanPages.length} page(s) have no internal links pointing to them`,
301
+ affectedUrls: orphanPages,
302
+ });
303
+ }
304
+ return issues;
305
+ }
306
+ calculateSummary(pages, issues) {
307
+ const pagesWithSeo = pages.filter(p => p.seoReport);
308
+ const scores = pagesWithSeo.map(p => p.seoReport.score);
309
+ const avgScore = scores.length > 0
310
+ ? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length)
311
+ : 0;
312
+ const pagesWithErrors = pagesWithSeo.filter(p => p.seoReport.checks.some(c => c.status === 'fail')).length;
313
+ const pagesWithWarnings = pagesWithSeo.filter(p => p.seoReport.checks.some(c => c.status === 'warn')).length;
314
+ const duplicateTitles = issues.filter(i => i.type === 'duplicate-title').length;
315
+ const duplicateDescriptions = issues.filter(i => i.type === 'duplicate-description').length;
316
+ const duplicateH1s = issues.filter(i => i.type === 'duplicate-h1').length;
317
+ const orphanPages = issues
318
+ .filter(i => i.type === 'orphan-page')
319
+ .reduce((sum, i) => sum + i.affectedUrls.length, 0);
320
+ return {
321
+ totalPages: pages.length,
322
+ pagesWithErrors,
323
+ pagesWithWarnings,
324
+ avgScore,
325
+ duplicateTitles,
326
+ duplicateDescriptions,
327
+ duplicateH1s,
328
+ orphanPages,
329
+ };
330
+ }
331
+ scoreToGrade(score) {
332
+ if (score >= 90)
333
+ return 'A';
334
+ if (score >= 80)
335
+ return 'B';
336
+ if (score >= 70)
337
+ return 'C';
338
+ if (score >= 60)
339
+ return 'D';
340
+ return 'F';
341
+ }
342
+ async saveReport(result) {
343
+ if (!this.options.output)
344
+ return;
345
+ const reportData = {
346
+ ...result,
347
+ visited: Array.from(result.visited),
348
+ generatedAt: new Date().toISOString(),
349
+ };
350
+ await fs.writeFile(this.options.output, JSON.stringify(reportData, null, 2), 'utf-8');
351
+ }
352
+ abort() {
353
+ this.spider.abort();
354
+ }
355
+ isRunning() {
356
+ return this.spider.isRunning();
357
+ }
358
+ }
359
+ export async function seoSpider(url, options) {
360
+ const spider = new SeoSpider(options);
361
+ return spider.crawl(url);
362
+ }
@@ -86,11 +86,20 @@ export interface TechnicalSeo {
86
86
  hasLang: boolean;
87
87
  langValue?: string;
88
88
  }
89
+ export interface SeoTiming {
90
+ ttfb?: number;
91
+ total?: number;
92
+ dns?: number;
93
+ tcp?: number;
94
+ tls?: number;
95
+ download?: number;
96
+ }
89
97
  export interface SeoReport {
90
98
  url: string;
91
99
  timestamp: Date;
92
100
  grade: string;
93
101
  score: number;
102
+ timing?: SeoTiming;
94
103
  checks: SeoCheckResult[];
95
104
  title?: {
96
105
  text: string;
@@ -100,6 +109,21 @@ export interface SeoReport {
100
109
  text: string;
101
110
  length: number;
102
111
  };
112
+ openGraph?: {
113
+ title?: string;
114
+ description?: string;
115
+ image?: string;
116
+ url?: string;
117
+ type?: string;
118
+ siteName?: string;
119
+ };
120
+ twitterCard?: {
121
+ card?: string;
122
+ title?: string;
123
+ description?: string;
124
+ image?: string;
125
+ site?: string;
126
+ };
103
127
  headings: HeadingAnalysis;
104
128
  content: ContentMetrics;
105
129
  links: LinkAnalysis;
@@ -215,8 +215,17 @@ export class UndiciTransport {
215
215
  const uploadTotal = contentLengthHeader ? parseInt(contentLengthHeader, 10) : undefined;
216
216
  let currentUrl;
217
217
  if (this.baseUrl) {
218
- const path = req.url.startsWith(this.baseUrl) ? req.url.substring(this.baseUrl.length) : req.url;
219
- currentUrl = new URL(path, this.baseUrl).toString();
218
+ if (req.url.startsWith(this.baseUrl)) {
219
+ currentUrl = req.url;
220
+ }
221
+ else if (req.url.startsWith('http://') || req.url.startsWith('https://')) {
222
+ currentUrl = req.url;
223
+ }
224
+ else {
225
+ const base = this.baseUrl.endsWith('/') ? this.baseUrl.slice(0, -1) : this.baseUrl;
226
+ const path = req.url.startsWith('/') ? req.url : '/' + req.url;
227
+ currentUrl = base + path;
228
+ }
220
229
  }
221
230
  else {
222
231
  currentUrl = req.url;
@@ -0,0 +1,32 @@
1
+ import type { AIResponse, AIStream, ChatMessage, AIProvider } from './ai.js';
2
+ export interface AIMemoryConfig {
3
+ maxPairs?: number;
4
+ systemPrompt?: string;
5
+ }
6
+ export interface PresetAIConfig {
7
+ provider: AIProvider;
8
+ apiKey?: string;
9
+ model: string;
10
+ baseUrl?: string;
11
+ memory?: AIMemoryConfig;
12
+ organization?: string;
13
+ headers?: Record<string, string>;
14
+ resourceName?: string;
15
+ deploymentName?: string;
16
+ apiVersion?: string;
17
+ }
18
+ export interface ClientAI {
19
+ chat(prompt: string): Promise<AIResponse>;
20
+ chatStream(prompt: string): Promise<AIStream>;
21
+ prompt(prompt: string): Promise<AIResponse>;
22
+ promptStream(prompt: string): Promise<AIStream>;
23
+ clearMemory(): void;
24
+ getMemory(): readonly ChatMessage[];
25
+ setMemoryConfig(config: Partial<AIMemoryConfig>): void;
26
+ getMemoryConfig(): AIMemoryConfig;
27
+ readonly provider: AIProvider;
28
+ readonly model: string;
29
+ }
30
+ export interface ClientOptionsWithAI {
31
+ _aiConfig?: PresetAIConfig;
32
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,4 +1,4 @@
1
- export type AIProvider = 'openai' | 'anthropic' | 'google' | 'replicate' | 'huggingface' | 'ollama' | 'custom';
1
+ export type AIProvider = 'openai' | 'anthropic' | 'google' | 'replicate' | 'huggingface' | 'ollama' | 'groq' | 'mistral' | 'cohere' | 'together' | 'perplexity' | 'deepseek' | 'fireworks' | 'xai' | 'azure-openai' | 'cloudflare-workers-ai' | 'custom';
2
2
  export type MessageRole = 'system' | 'user' | 'assistant' | 'tool';
3
3
  export interface ChatMessage {
4
4
  role: MessageRole;
@@ -9,6 +9,7 @@ export declare const magenta: (s: string | number) => string;
9
9
  export declare const cyan: (s: string | number) => string;
10
10
  export declare const white: (s: string | number) => string;
11
11
  export declare const gray: (s: string | number) => string;
12
+ export declare const orange: (s: string | number) => string;
12
13
  export declare const bgBlack: (s: string | number) => string;
13
14
  export declare const bgRed: (s: string | number) => string;
14
15
  export declare const bgGreen: (s: string | number) => string;
@@ -29,6 +30,7 @@ declare const colors: {
29
30
  cyan: (s: string | number) => string;
30
31
  white: (s: string | number) => string;
31
32
  gray: (s: string | number) => string;
33
+ orange: (s: string | number) => string;
32
34
  bgBlack: (s: string | number) => string;
33
35
  bgRed: (s: string | number) => string;
34
36
  bgGreen: (s: string | number) => string;
@@ -33,6 +33,9 @@ export const magenta = code(35, 39);
33
33
  export const cyan = code(36, 39);
34
34
  export const white = code(37, 39);
35
35
  export const gray = code(90, 39);
36
+ export const orange = hasColors
37
+ ? (s) => `\x1b[38;5;208m${String(s)}\x1b[39m`
38
+ : (s) => String(s);
36
39
  export const bgBlack = code(40, 49);
37
40
  export const bgRed = code(41, 49);
38
41
  export const bgGreen = code(42, 49);
@@ -53,6 +56,7 @@ const colors = {
53
56
  cyan,
54
57
  white,
55
58
  gray,
59
+ orange,
56
60
  bgBlack,
57
61
  bgRed,
58
62
  bgGreen,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "recker",
3
- "version": "1.0.28",
3
+ "version": "1.0.29-next.604e03b",
4
4
  "description": "AI & DevX focused HTTP client for Node.js 18+",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",