docrev 0.6.7 → 0.6.13

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 CHANGED
@@ -1,18 +1,14 @@
1
1
  # docrev
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/docrev)](https://www.npmjs.com/package/docrev)
4
+ [![npm downloads](https://img.shields.io/npm/dm/docrev)](https://www.npmjs.com/package/docrev)
5
+ [![node](https://img.shields.io/node/v/docrev)](https://nodejs.org)
4
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
+ [![CI](https://github.com/gcol33/docrev/actions/workflows/ci.yml/badge.svg)](https://github.com/gcol33/docrev/actions/workflows/ci.yml)
5
8
 
6
- Write scientific papers in Markdown. Generate Word documents for collaborators. Import their feedback. Repeat.
9
+ A CLI for writing scientific papers in Markdown while collaborating with Word users.
7
10
 
8
- ```
9
- Markdown ──► docrev ──► Word/PDF ──► Collaborators
10
- ▲ │
11
- └─────────── docrev ◄─────────────────────┘
12
- (import feedback)
13
- ```
14
-
15
- Scientific papers go through many revision cycles with collaborators and reviewers. Track changes become unreadable, versions multiply, equations break when copying, figures get embedded at wrong resolutions. docrev keeps your source in plain Markdown under version control while generating Word documents for the review cycle. Your collaborators keep using Word as usual. You handle conversion on your end.
11
+ You write in Markdown under version control. Your collaborators use Word. docrev converts between the two, preserving track changes, comments, equations, and cross-references.
16
12
 
17
13
  ## Install
18
14
 
@@ -21,20 +17,20 @@ npm install -g docrev
21
17
  brew install pandoc
22
18
  ```
23
19
 
24
- ## Start a Project
20
+ Pandoc is required for document conversion. On Windows use `winget install JohnMacFarlane.Pandoc`, on Linux use `apt install pandoc`.
25
21
 
26
- From scratch:
27
- ```bash
28
- rev new my-paper
29
- cd my-paper
30
- ```
22
+ ## Getting Started
23
+
24
+ ### Starting from a Word Document
25
+
26
+ If you have an existing manuscript in Word:
31
27
 
32
- From existing Word document:
33
28
  ```bash
34
29
  rev import manuscript.docx
35
30
  ```
36
31
 
37
- Project structure:
32
+ This converts your document to markdown, splitting it into sections:
33
+
38
34
  ```
39
35
  my-paper/
40
36
  ├── introduction.md
@@ -45,130 +41,211 @@ my-paper/
45
41
  └── rev.yaml
46
42
  ```
47
43
 
48
- ## Markdown Basics
44
+ Track changes and comments from the Word document are preserved as annotations in the markdown files (see below).
49
45
 
50
- ```markdown
51
- # Heading
46
+ ### Starting from Scratch
52
47
 
53
- Paragraph text. **Bold** and *italic*.
48
+ To start a new paper in markdown:
54
49
 
55
- - Bullet point
56
- - Another point
50
+ ```bash
51
+ rev new my-paper
52
+ cd my-paper
53
+ ```
54
+
55
+ This creates the same project structure with empty section files. Write your paper in the markdown files, then build Word documents to share with collaborators.
56
+
57
+ ## The Revision Cycle
58
+
59
+ ### 1. Build and Share
57
60
 
58
- 1. Numbered item
59
- 2. Second item
61
+ Generate a Word document from your markdown:
62
+
63
+ ```bash
64
+ rev build docx
60
65
  ```
61
66
 
62
- ### Citations
67
+ Send this to your collaborators. They review it in Word, adding comments and track changes as usual.
63
68
 
64
- references.bib:
65
- ```bibtex
66
- @article{Smith2020,
67
- author = {Smith, Jane},
68
- title = {Paper Title},
69
- journal = {Nature},
70
- year = {2020}
71
- }
69
+ ### 2. Import Feedback
70
+
71
+ When collaborators return the reviewed document, import their feedback:
72
+
73
+ ```bash
74
+ rev sections reviewed.docx
72
75
  ```
73
76
 
74
- In text:
77
+ This updates your markdown files with their comments and track changes, converted to inline annotations.
78
+
79
+ ### 3. Review Track Changes
80
+
81
+ Track changes appear as inline annotations in your markdown:
82
+
75
83
  ```markdown
76
- Previous studies [@Smith2020] demonstrated this.
84
+ The sample size was {--100--}{++150++} individuals.
85
+ We collected data {~~monthly~>weekly~~} from each site.
77
86
  ```
78
87
 
79
- Output: "Previous studies (Smith 2020) demonstrated this."
88
+ - `{++text++}` inserted text
89
+ - `{--text--}` — deleted text
90
+ - `{~~old~>new~~}` — substitution
80
91
 
81
- ### Equations
92
+ To accept a change, keep the new text and delete the markup. To reject it, keep the old text. When you're done, the file is clean markdown.
93
+
94
+ ### 4. Respond to Comments
82
95
 
83
- Inline: `$E = mc^2$`
96
+ Comments appear inline in your markdown:
84
97
 
85
- Display:
86
98
  ```markdown
87
- $$
88
- \hat{p} = \frac{\sum_d w_d p_d}{\sum_d w_d}
89
- $$
99
+ We used a random sampling approach.
100
+ {>>Reviewer 2: Please clarify the sampling method.<<}
90
101
  ```
91
102
 
92
- ### Figures
103
+ List all comments in a file:
93
104
 
94
- ```markdown
95
- ![Study site locations](figures/map.png){#fig:map}
105
+ ```bash
106
+ rev comments methods.md
107
+ ```
108
+
109
+ Reply from the command line:
96
110
 
97
- Results shown in @fig:map indicate regional variation.
111
+ ```bash
112
+ rev config user "Your Name" # one-time setup
113
+ rev reply methods.md -n 1 -m "Added clarification in paragraph 2"
98
114
  ```
99
115
 
100
- Output: "Results shown in Figure 1 indicate regional variation."
116
+ Your reply threads beneath the original:
101
117
 
102
- Figure numbers update automatically when reordered.
118
+ ```markdown
119
+ We used a random sampling approach.
120
+ {>>Reviewer 2: Please clarify the sampling method.<<}
121
+ {>>Your Name: Added clarification in paragraph 2.<<}
122
+ ```
103
123
 
104
- ## Build
124
+ Mark comments as resolved:
105
125
 
106
126
  ```bash
107
- rev build docx # Word document
108
- rev build pdf # PDF
109
- rev build --dual # Clean + comments versions
110
- rev watch docx # Auto-rebuild on save
127
+ rev resolve methods.md -n 1
111
128
  ```
112
129
 
113
- ## Handle Reviewer Feedback
130
+ ### 5. Rebuild with Comment Threads
131
+
132
+ Generate both a clean version and one showing the comment threads:
114
133
 
115
- Import reviewed document:
116
134
  ```bash
117
- rev sections reviewed.docx
135
+ rev build --dual
136
+ ```
137
+
138
+ This produces:
139
+ - `paper.docx` — clean, for submission
140
+ - `paper_comments.docx` — includes comment threads as Word comments
141
+
142
+ Your collaborators see the full conversation in the comments pane.
143
+
144
+ ### 6. Repeat
145
+
146
+ Send the updated Word document. Import new feedback with `rev sections`. Continue until done.
147
+
148
+ ## Before Submission
149
+
150
+ ### Validate Your Bibliography
151
+
152
+ Check that DOIs in your bibliography resolve correctly:
153
+
154
+ ```bash
155
+ rev doi check references.bib
118
156
  ```
119
157
 
120
- View comments:
158
+ Find DOIs for entries missing them:
159
+
121
160
  ```bash
122
- rev comments methods.md
161
+ rev doi lookup references.bib
123
162
  ```
124
163
 
125
- Reply to comment:
164
+ Add a citation directly from a DOI:
165
+
126
166
  ```bash
127
- rev config user "Your Name"
128
- rev reply methods.md -n 1 -m "Clarified sampling methodology"
167
+ rev doi add 10.1038/s41586-020-2649-2
129
168
  ```
130
169
 
131
- Rebuild with threaded comments:
170
+ ### Run Pre-Submission Checks
171
+
172
+ Check for broken references, missing citations, and common issues:
173
+
132
174
  ```bash
133
- rev build --dual
175
+ rev check
134
176
  ```
135
177
 
136
- Output:
137
- - `paper.docx` (clean)
138
- - `paper_comments.docx` (with comment threads)
178
+ ## Writing in Markdown
139
179
 
140
- ## Commands
180
+ ### Citations
181
+
182
+ Add references to `references.bib`:
183
+
184
+ ```bibtex
185
+ @article{Smith2020,
186
+ author = {Smith, Jane},
187
+ title = {Paper Title},
188
+ journal = {Nature},
189
+ year = {2020},
190
+ doi = {10.1038/example}
191
+ }
192
+ ```
193
+
194
+ Cite in text:
195
+
196
+ ```markdown
197
+ Previous work [@Smith2020] established this relationship.
198
+ Multiple sources support this [@Smith2020; @Jones2021].
199
+ ```
200
+
201
+ ### Equations
202
+
203
+ Inline equations use single dollar signs: `$E = mc^2$`
204
+
205
+ Display equations use double dollar signs:
206
+
207
+ ```markdown
208
+ $$
209
+ \bar{x} = \frac{1}{n} \sum_{i=1}^{n} x_i
210
+ $$
211
+ ```
212
+
213
+ ### Figures and Cross-References
214
+
215
+ ```markdown
216
+ ![Study site locations](figures/map.png){#fig:map}
217
+
218
+ Results are shown in @fig:map.
219
+ ```
220
+
221
+ The reference `@fig:map` becomes "Figure 1" in the output. Numbers update automatically when figures are reordered.
222
+
223
+ Tables and equations work the same way with `@tbl:label` and `@eq:label`.
224
+
225
+ ## Useful Commands
141
226
 
142
227
  | Task | Command |
143
228
  |------|---------|
144
- | New project | `rev new my-paper` |
145
- | Import Word | `rev import manuscript.docx` |
229
+ | Start new project | `rev new my-paper` |
230
+ | Import Word document | `rev import manuscript.docx` |
231
+ | Import feedback | `rev sections reviewed.docx` |
232
+ | List comments | `rev comments methods.md` |
233
+ | Reply to comment | `rev reply methods.md -n 1 -m "response"` |
146
234
  | Build Word | `rev build docx` |
147
235
  | Build PDF | `rev build pdf` |
148
- | Import feedback | `rev sections reviewed.docx` |
149
- | View comments | `rev comments methods.md` |
150
- | Reply to comment | `rev reply methods.md -n 1 -m "text"` |
236
+ | Build both clean and annotated | `rev build --dual` |
237
+ | Check DOIs | `rev doi check references.bib` |
238
+ | Find missing DOIs | `rev doi lookup references.bib` |
151
239
  | Word count | `rev word-count` |
152
- | Validate DOIs | `rev doi check` |
153
- | Pre-submit check | `rev check` |
240
+ | Pre-submission check | `rev check` |
241
+ | Watch for changes | `rev watch docx` |
154
242
 
155
- Full reference: [docs/commands.md](docs/commands.md)
243
+ Full command reference: [docs/commands.md](docs/commands.md)
156
244
 
157
245
  ## Requirements
158
246
 
159
247
  - Node.js 18+
160
- - Pandoc
161
-
162
- ```bash
163
- # macOS
164
- brew install pandoc
165
-
166
- # Windows
167
- winget install JohnMacFarlane.Pandoc
168
-
169
- # Linux
170
- sudo apt install pandoc
171
- ```
248
+ - Pandoc 2.11+
172
249
 
173
250
  ## License
174
251
 
package/lib/import.js CHANGED
@@ -268,7 +268,8 @@ export function insertCommentsIntoMarkdown(markdown, comments, anchors, options
268
268
 
269
269
  if (occurrences.length === 1) {
270
270
  // Unique match - easy case
271
- return { ...c, pos: occurrences[0] + anchor.length, anchorText: anchor };
271
+ // Position at START of anchor (comment goes before, anchor gets marked)
272
+ return { ...c, pos: occurrences[0], anchorText: anchor, anchorEnd: occurrences[0] + anchor.length };
272
273
  }
273
274
 
274
275
  // Multiple occurrences - use context for disambiguation
@@ -321,16 +322,26 @@ export function insertCommentsIntoMarkdown(markdown, comments, anchors, options
321
322
  // Mark this position as used for tie-breaking subsequent comments
322
323
  usedPositions.add(bestIdx);
323
324
 
324
- return { ...c, pos: bestIdx + anchor.length, anchorText: anchor };
325
- }).filter((c) => c.pos > 0);
325
+ // Position at START of anchor (comment goes before, anchor gets marked)
326
+ return { ...c, pos: bestIdx, anchorText: anchor, anchorEnd: bestIdx + anchor.length };
327
+ }).filter((c) => c.pos >= 0);
326
328
 
327
329
  // Sort by position descending (insert from end to avoid offset issues)
328
330
  commentsWithPositions.sort((a, b) => b.pos - a.pos);
329
331
 
330
- // Insert each comment
332
+ // Insert each comment with anchor marking
331
333
  for (const c of commentsWithPositions) {
332
- const commentMark = ` {>>${c.author}: ${c.text}<<}`;
333
- result = result.slice(0, c.pos) + commentMark + result.slice(c.pos);
334
+ const comment = `{>>${c.author}: ${c.text}<<}`;
335
+ if (c.anchorText && c.anchorEnd) {
336
+ // Replace anchor text with: {>>comment<<}[anchor]{.mark}
337
+ const before = result.slice(0, c.pos);
338
+ const anchor = result.slice(c.pos, c.anchorEnd);
339
+ const after = result.slice(c.anchorEnd);
340
+ result = before + comment + `[${anchor}]{.mark}` + after;
341
+ } else {
342
+ // No anchor - just insert comment at position
343
+ result = result.slice(0, c.pos) + ` ${comment}` + result.slice(c.pos);
344
+ }
334
345
  }
335
346
 
336
347
  // Log warnings unless quiet mode
@@ -2,9 +2,9 @@
2
2
  * Word comment injection with reply threading
3
3
  *
4
4
  * Flow:
5
- * 1. prepareMarkdownWithMarkers() - Parse comments, detect Guy→Gilles reply pairs
6
- * - Guy comments get markers: ⟦CMS:n⟧anchor⟦CME:n⟧
7
- * - Gilles replies: no markers (they attach to parent comment)
5
+ * 1. prepareMarkdownWithMarkers() - Parse comments, detect reply relationships
6
+ * - First comment in a cluster = parent (gets markers: ⟦CMS:n⟧anchor⟦CME:n⟧)
7
+ * - Subsequent adjacent comments = replies (no markers, attach to parent)
8
8
  * 2. Pandoc converts to DOCX
9
9
  * 3. injectCommentsAtMarkers() - Insert comment ranges for parents only
10
10
  * - Replies go in comments.xml with parent reference in commentsExtended.xml
@@ -71,50 +71,41 @@ export function prepareMarkdownWithMarkers(markdown) {
71
71
  return { markedMarkdown: markdown, comments: [] };
72
72
  }
73
73
 
74
- // Detect reply relationships: Gilles immediately following Guy = reply
74
+ // Detect reply relationships based on adjacency
75
+ // First comment in a cluster = parent, all subsequent = replies to that parent
75
76
  // Comments are "adjacent" if there's only whitespace between them (< 50 chars)
76
77
  const ADJACENT_THRESHOLD = 50;
77
78
  const comments = [];
78
- let lastGuyIdx = -1;
79
+ let clusterParentIdx = -1; // Index of first comment in current cluster
79
80
  let lastCommentEnd = -1;
80
81
 
81
82
  for (let i = 0; i < rawMatches.length; i++) {
82
83
  const m = rawMatches[i];
83
- const isGuy = m.author === 'Guy Colling';
84
- const isGilles = m.author === 'Gilles Colling';
85
84
 
86
85
  // Check if this comment is adjacent to the previous one
87
86
  const gap = lastCommentEnd >= 0 ? m.start - lastCommentEnd : Infinity;
88
87
  const isAdjacent = gap < ADJACENT_THRESHOLD;
89
88
 
90
- // Reset lastGuyIdx if there's a gap (comments not in same cluster)
89
+ // Reset cluster if there's a gap (comments not in same cluster)
91
90
  if (!isAdjacent) {
92
- lastGuyIdx = -1;
91
+ clusterParentIdx = -1;
93
92
  }
94
93
 
95
- if (isGuy) {
94
+ if (clusterParentIdx === -1) {
95
+ // First comment in cluster = parent (regardless of author)
96
96
  comments.push({
97
97
  ...m,
98
98
  isReply: false,
99
99
  parentIdx: null,
100
100
  commentIdx: comments.length
101
101
  });
102
- lastGuyIdx = comments.length - 1;
103
- } else if (isGilles && lastGuyIdx >= 0 && isAdjacent) {
104
- // Gilles immediately following Guy (same cluster) = reply
105
- comments.push({
106
- ...m,
107
- isReply: true,
108
- parentIdx: lastGuyIdx,
109
- commentIdx: comments.length
110
- });
111
- // Don't reset lastGuyIdx - multiple replies could follow
102
+ clusterParentIdx = comments.length - 1;
112
103
  } else {
113
- // Standalone comment (not a reply)
104
+ // Subsequent comment in cluster = reply to first comment
114
105
  comments.push({
115
106
  ...m,
116
- isReply: false,
117
- parentIdx: null,
107
+ isReply: true,
108
+ parentIdx: clusterParentIdx,
118
109
  commentIdx: comments.length
119
110
  });
120
111
  }
@@ -122,6 +113,21 @@ export function prepareMarkdownWithMarkers(markdown) {
122
113
  lastCommentEnd = m.end;
123
114
  }
124
115
 
116
+ // Propagate anchors from replies to parents
117
+ // If a reply has an anchor but its parent doesn't, move the anchor to the parent
118
+ // Track flags for special handling during marker generation
119
+ for (const c of comments) {
120
+ if (c.isReply && c.anchor && c.parentIdx !== null) {
121
+ const parent = comments[c.parentIdx];
122
+ if (!parent.anchor) {
123
+ parent.anchor = c.anchor;
124
+ parent.anchorFromReply = true; // Parent's anchor came from a reply (markers placed by reply)
125
+ c.placesParentMarkers = true; // This reply should place the parent's markers
126
+ c.anchor = null;
127
+ }
128
+ }
129
+ }
130
+
125
131
  // Build marked markdown - only parent comments get markers
126
132
  // Process from end to start to preserve positions
127
133
  let markedMarkdown = markdown;
@@ -131,12 +137,57 @@ export function prepareMarkdownWithMarkers(markdown) {
131
137
 
132
138
  if (c.isReply) {
133
139
  // Reply: remove from document entirely (will be in comments.xml only)
134
- markedMarkdown = markedMarkdown.slice(0, c.start) + markedMarkdown.slice(c.end);
140
+ // Also consume leading whitespace to avoid double spaces
141
+ let removeStart = c.start;
142
+ while (removeStart > 0 && /\s/.test(markedMarkdown[removeStart - 1])) {
143
+ removeStart--;
144
+ }
145
+
146
+ // If this reply places parent's markers (anchor was propagated)
147
+ if (c.placesParentMarkers && c.parentIdx !== null) {
148
+ // Extract anchor text from the original match
149
+ const anchorMatch = c.fullMatch.match(/\[([^\]]+)\]\{\.mark\}$/);
150
+ if (anchorMatch) {
151
+ const anchorText = anchorMatch[1];
152
+ // Output markers with PARENT's index around the anchor text
153
+ const parentIdx = c.parentIdx;
154
+ const replacement = `${MARKER_START_PREFIX}${parentIdx}${MARKER_SUFFIX}${anchorText}${MARKER_END_PREFIX}${parentIdx}${MARKER_SUFFIX}`;
155
+ markedMarkdown = markedMarkdown.slice(0, removeStart) + replacement + markedMarkdown.slice(c.end);
156
+ } else {
157
+ markedMarkdown = markedMarkdown.slice(0, removeStart) + markedMarkdown.slice(c.end);
158
+ }
159
+ } else {
160
+ markedMarkdown = markedMarkdown.slice(0, removeStart) + markedMarkdown.slice(c.end);
161
+ }
135
162
  } else {
136
- // Parent comment: replace with markers
137
- const anchor = c.anchor || '';
138
- const replacement = `${MARKER_START_PREFIX}${i}${MARKER_SUFFIX}${anchor}${MARKER_END_PREFIX}${i}${MARKER_SUFFIX}`;
139
- markedMarkdown = markedMarkdown.slice(0, c.start) + replacement + markedMarkdown.slice(c.end);
163
+ // Parent comment
164
+ if (c.anchorFromReply) {
165
+ // Anchor markers are placed by the reply, just remove this comment
166
+ let removeStart = c.start;
167
+ while (removeStart > 0 && /\s/.test(markedMarkdown[removeStart - 1])) {
168
+ removeStart--;
169
+ }
170
+ markedMarkdown = markedMarkdown.slice(0, removeStart) + markedMarkdown.slice(c.end);
171
+ } else {
172
+ // Normal case: replace with markers
173
+ let anchor = c.anchor || '';
174
+
175
+ // If no anchor, try to use the next word as fallback to avoid empty ranges
176
+ if (!anchor) {
177
+ const afterComment = markedMarkdown.slice(c.end);
178
+ // Match the next word (skip leading whitespace/punctuation)
179
+ const nextWordMatch = afterComment.match(/^\s*([a-zA-Z0-9]+)/);
180
+ if (nextWordMatch) {
181
+ anchor = nextWordMatch[1];
182
+ // Also need to consume the matched text from afterComment
183
+ c.consumeAfter = nextWordMatch[0].length;
184
+ }
185
+ }
186
+
187
+ const replacement = `${MARKER_START_PREFIX}${i}${MARKER_SUFFIX}${anchor}${MARKER_END_PREFIX}${i}${MARKER_SUFFIX}`;
188
+ const endPos = c.end + (c.consumeAfter || 0);
189
+ markedMarkdown = markedMarkdown.slice(0, c.start) + replacement + markedMarkdown.slice(endPos);
190
+ }
140
191
  }
141
192
  }
142
193
 
@@ -239,11 +290,7 @@ function createCommentsExtensibleXml(comments) {
239
290
  return xml;
240
291
  }
241
292
 
242
- // Known Windows Live user IDs for authors (from manual_comments.docx)
243
- const AUTHOR_USER_IDS = {
244
- 'Guy Colling': '9ff4d97962428673',
245
- 'Gilles Colling': '46e930a4c4b85dfd',
246
- };
293
+ // Generate deterministic user IDs for authors (no hardcoded personal data)
247
294
 
248
295
  function createPeopleXml(comments) {
249
296
  // Extract unique authors
@@ -265,7 +312,7 @@ function createPeopleXml(comments) {
265
312
  xml += 'mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh">';
266
313
 
267
314
  for (const author of authors) {
268
- const userId = AUTHOR_USER_IDS[author] || generateUserId(author);
315
+ const userId = generateUserId(author);
269
316
  xml += `<w15:person w15:author="${escapeXml(author)}">`;
270
317
  xml += `<w15:presenceInfo w15:providerId="Windows Live" w15:userId="${userId}"/>`;
271
318
  xml += `</w15:person>`;
@@ -366,9 +413,14 @@ export async function injectCommentsAtMarkers(docxPath, comments, outputPath) {
366
413
  const endInText = fullText.indexOf(endMarker);
367
414
  if (startInText === -1 || endInText === -1) continue;
368
415
 
369
- const textBefore = fullText.slice(0, startInText);
416
+ let textBefore = fullText.slice(0, startInText);
370
417
  const anchorText = fullText.slice(startInText + startMarker.length, endInText);
371
- const textAfter = fullText.slice(endInText + endMarker.length);
418
+ let textAfter = fullText.slice(endInText + endMarker.length);
419
+
420
+ // When anchor is empty, normalize double spaces to single space
421
+ if (!anchorText && textBefore.endsWith(' ') && textAfter.startsWith(' ')) {
422
+ textAfter = textAfter.slice(1); // Remove leading space from textAfter
423
+ }
372
424
 
373
425
  // Build replacement
374
426
  let replacement = '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docrev",
3
- "version": "0.6.7",
3
+ "version": "0.6.13",
4
4
  "description": "Academic paper revision workflow: Word ↔ Markdown round-trips, DOI validation, reviewer comments",
5
5
  "type": "module",
6
6
  "types": "types/index.d.ts",