doc2vec 1.3.0 → 2.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.
@@ -0,0 +1,248 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.ContentProcessor = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const crypto = __importStar(require("crypto"));
40
+ class ContentProcessor {
41
+ constructor() {
42
+ this.maxTokens = 1000;
43
+ this.minTokens = 150;
44
+ this.overlapPercent = 0.1;
45
+ }
46
+ async readFile(filePath) {
47
+ const ext = path.extname(filePath).toLowerCase();
48
+ try {
49
+ switch (ext) {
50
+ case '.pdf':
51
+ return await this.readPdf(filePath);
52
+ case '.doc':
53
+ return await this.readDoc(filePath);
54
+ case '.docx':
55
+ return await this.readDocx(filePath);
56
+ case '.html':
57
+ case '.htm':
58
+ const html = fs.readFileSync(filePath, 'utf-8');
59
+ return this.stripHtml(html);
60
+ default:
61
+ return fs.readFileSync(filePath, 'utf-8');
62
+ }
63
+ }
64
+ catch (error) {
65
+ console.error(`Error reading file ${filePath}:`, error);
66
+ return null;
67
+ }
68
+ }
69
+ async readPdf(filePath) {
70
+ try {
71
+ const pdfParse = require('pdf-parse');
72
+ const buffer = fs.readFileSync(filePath);
73
+ const data = await pdfParse(buffer);
74
+ return `# ${path.basename(filePath, '.pdf')}\n\n${data.text}`;
75
+ }
76
+ catch (error) {
77
+ if (error.code === 'MODULE_NOT_FOUND') {
78
+ console.warn(`PDF parsing not available for ${filePath}. Install pdf-parse for PDF support.`);
79
+ return `# ${path.basename(filePath, '.pdf')}\n\n[PDF content - install pdf-parse for extraction]`;
80
+ }
81
+ throw error;
82
+ }
83
+ }
84
+ async readDoc(filePath) {
85
+ try {
86
+ const WordExtractor = require('word-extractor');
87
+ const extractor = new WordExtractor();
88
+ const extracted = await extractor.extract(filePath);
89
+ const text = extracted.getBody();
90
+ // Create content with filename as title
91
+ let content = `# ${path.basename(filePath, '.doc')}\n\n`;
92
+ // Clean up the text
93
+ const cleanedText = text
94
+ .replace(/\r\n/g, '\n')
95
+ .replace(/\n{3,}/g, '\n\n')
96
+ .trim();
97
+ content += cleanedText;
98
+ return content;
99
+ }
100
+ catch (error) {
101
+ if (error.code === 'MODULE_NOT_FOUND') {
102
+ console.warn(`DOC parsing not available for ${filePath}. Install word-extractor for DOC support.`);
103
+ return `# ${path.basename(filePath, '.doc')}\n\n[DOC content - install word-extractor for extraction]`;
104
+ }
105
+ throw error;
106
+ }
107
+ }
108
+ async readDocx(filePath) {
109
+ try {
110
+ const mammoth = require('mammoth');
111
+ const result = await mammoth.convertToHtml({ path: filePath });
112
+ const html = result.value;
113
+ // Log any warnings
114
+ if (result.messages.length > 0) {
115
+ console.log(`Mammoth warnings for ${filePath}: ${result.messages.map((m) => m.message).join(', ')}`);
116
+ }
117
+ // Create content with filename as title
118
+ let content = `# ${path.basename(filePath, '.docx')}\n\n`;
119
+ // Strip HTML to get plain text
120
+ content += this.stripHtml(html);
121
+ // Clean up excessive line breaks
122
+ content = content.replace(/\n{3,}/g, '\n\n').trim();
123
+ return content;
124
+ }
125
+ catch (error) {
126
+ if (error.code === 'MODULE_NOT_FOUND') {
127
+ console.warn(`DOCX parsing not available for ${filePath}. Install mammoth for DOCX support.`);
128
+ return `# ${path.basename(filePath, '.docx')}\n\n[DOCX content - install mammoth for extraction]`;
129
+ }
130
+ throw error;
131
+ }
132
+ }
133
+ stripHtml(html) {
134
+ return html
135
+ .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
136
+ .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
137
+ .replace(/<[^>]+>/g, '')
138
+ .replace(/&nbsp;/g, ' ')
139
+ .replace(/&amp;/g, '&')
140
+ .replace(/&lt;/g, '<')
141
+ .replace(/&gt;/g, '>')
142
+ .replace(/&quot;/g, '"')
143
+ .replace(/\s+/g, ' ')
144
+ .trim();
145
+ }
146
+ generateHash(content) {
147
+ return crypto.createHash('sha256').update(content).digest('hex');
148
+ }
149
+ tokenize(text) {
150
+ return text.split(/(\s+)/).filter(token => token.length > 0);
151
+ }
152
+ chunkContent(content, filePath, version) {
153
+ const chunks = [];
154
+ const lines = content.split('\n');
155
+ let buffer = '';
156
+ let headingHierarchy = [];
157
+ let bufferHeadings = [];
158
+ let chunkCounter = 0;
159
+ const computeTopicHierarchy = () => {
160
+ if (bufferHeadings.length === 0)
161
+ return headingHierarchy;
162
+ const deepestLevel = Math.max(...bufferHeadings.map(h => h.level));
163
+ const deepestHeadings = bufferHeadings.filter(h => h.level === deepestLevel);
164
+ if (deepestHeadings.length > 1 && deepestLevel > 1) {
165
+ return headingHierarchy.slice(0, deepestLevel - 1);
166
+ }
167
+ return headingHierarchy;
168
+ };
169
+ const createChunk = (content, hierarchy) => {
170
+ const breadcrumbs = hierarchy.filter(h => h).join(' > ');
171
+ const contextPrefix = breadcrumbs ? `[Topic: ${breadcrumbs}]\n` : '';
172
+ const searchableText = contextPrefix + content.trim();
173
+ const chunkId = this.generateHash(searchableText);
174
+ return {
175
+ chunkId,
176
+ content: searchableText,
177
+ version,
178
+ section: hierarchy[hierarchy.length - 1] || 'Introduction',
179
+ headingHierarchy: hierarchy.filter(h => h),
180
+ chunkIndex: chunkCounter++,
181
+ totalChunks: 0,
182
+ url: `file://${filePath}`,
183
+ hash: chunkId
184
+ };
185
+ };
186
+ const flushBuffer = (force = false) => {
187
+ const trimmed = buffer.trim();
188
+ if (!trimmed)
189
+ return;
190
+ const tokenCount = this.tokenize(trimmed).length;
191
+ if (tokenCount < this.minTokens && !force)
192
+ return;
193
+ const hierarchy = computeTopicHierarchy();
194
+ if (tokenCount > this.maxTokens) {
195
+ const tokens = this.tokenize(trimmed);
196
+ const overlapSize = Math.floor(this.maxTokens * this.overlapPercent);
197
+ for (let i = 0; i < tokens.length; i += (this.maxTokens - overlapSize)) {
198
+ const subTokens = tokens.slice(i, i + this.maxTokens);
199
+ chunks.push(createChunk(subTokens.join(''), hierarchy));
200
+ }
201
+ }
202
+ else {
203
+ chunks.push(createChunk(trimmed, hierarchy));
204
+ }
205
+ buffer = '';
206
+ bufferHeadings = [];
207
+ };
208
+ for (const line of lines) {
209
+ const isHeading = line.startsWith('#');
210
+ if (isHeading) {
211
+ const match = line.match(/^(#+)/);
212
+ const level = match ? match[1].length : 1;
213
+ const headingText = line
214
+ .replace(/^#+\s*/, '')
215
+ .replace(/\[.*?\]\(#[^)]*\)/g, '')
216
+ .trim();
217
+ const currentTokens = this.tokenize(buffer.trim()).length;
218
+ const hasContent = currentTokens > 0;
219
+ const isSmall = currentTokens < this.minTokens;
220
+ const deepestLevel = bufferHeadings.length > 0
221
+ ? Math.max(...bufferHeadings.map(h => h.level))
222
+ : 0;
223
+ const shouldMerge = hasContent && isSmall && bufferHeadings.length > 0 && level >= deepestLevel;
224
+ if (!shouldMerge && hasContent) {
225
+ flushBuffer();
226
+ }
227
+ headingHierarchy = headingHierarchy.slice(0, level - 1);
228
+ headingHierarchy[level - 1] = headingText;
229
+ bufferHeadings.push({ level, text: headingText });
230
+ buffer += line + '\n';
231
+ }
232
+ else {
233
+ buffer += line + '\n';
234
+ if (this.tokenize(buffer).length >= this.maxTokens) {
235
+ flushBuffer();
236
+ }
237
+ }
238
+ }
239
+ flushBuffer(true);
240
+ // Update total chunks
241
+ const total = chunks.length;
242
+ chunks.forEach(chunk => {
243
+ chunk.totalChunks = total;
244
+ });
245
+ return chunks;
246
+ }
247
+ }
248
+ exports.ContentProcessor = ContentProcessor;
@@ -0,0 +1,146 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.FolderSyncer = void 0;
40
+ const chokidar_1 = __importDefault(require("chokidar"));
41
+ const path = __importStar(require("path"));
42
+ const fs = __importStar(require("fs"));
43
+ class FolderSyncer {
44
+ constructor(folderPath, options) {
45
+ this.fsWatcher = null;
46
+ this._isSyncing = false;
47
+ this.folderPath = folderPath;
48
+ this.options = options;
49
+ }
50
+ get isSyncing() {
51
+ return this._isSyncing;
52
+ }
53
+ start() {
54
+ if (this._isSyncing)
55
+ return;
56
+ const globPattern = this.options.recursive
57
+ ? path.join(this.folderPath, '**', '*')
58
+ : path.join(this.folderPath, '*');
59
+ this.fsWatcher = chokidar_1.default.watch(globPattern, {
60
+ ignored: /(^|[\/\\])\../, // ignore dotfiles
61
+ persistent: true,
62
+ ignoreInitial: true,
63
+ awaitWriteFinish: {
64
+ stabilityThreshold: 500,
65
+ pollInterval: 100
66
+ }
67
+ });
68
+ this.fsWatcher
69
+ .on('add', async (filePath) => {
70
+ if (this.shouldProcess(filePath)) {
71
+ console.log(`File added: ${filePath}`);
72
+ await this.options.onFileAdd(filePath);
73
+ }
74
+ })
75
+ .on('change', async (filePath) => {
76
+ if (this.shouldProcess(filePath)) {
77
+ console.log(`File changed: ${filePath}`);
78
+ await this.options.onFileChange(filePath);
79
+ }
80
+ })
81
+ .on('unlink', async (filePath) => {
82
+ // For delete events, only check extension (file no longer exists)
83
+ if (this.hasValidExtension(filePath)) {
84
+ console.log(`File deleted: ${filePath}`);
85
+ await this.options.onFileDelete(filePath);
86
+ }
87
+ })
88
+ .on('error', (error) => {
89
+ console.error('Syncer error:', error);
90
+ });
91
+ this._isSyncing = true;
92
+ console.log(`Started syncing: ${this.folderPath}`);
93
+ }
94
+ stop() {
95
+ if (this.fsWatcher) {
96
+ this.fsWatcher.close();
97
+ this.fsWatcher = null;
98
+ }
99
+ this._isSyncing = false;
100
+ console.log('Stopped syncing');
101
+ }
102
+ shouldProcess(filePath) {
103
+ // Check if it's a file (not directory)
104
+ try {
105
+ const stat = fs.statSync(filePath);
106
+ if (!stat.isFile())
107
+ return false;
108
+ }
109
+ catch {
110
+ return false;
111
+ }
112
+ return this.hasValidExtension(filePath);
113
+ }
114
+ hasValidExtension(filePath) {
115
+ // Check extension
116
+ if (this.options.extensions.length === 0)
117
+ return true;
118
+ const ext = path.extname(filePath).toLowerCase();
119
+ return this.options.extensions.includes(ext);
120
+ }
121
+ getSyncedFiles() {
122
+ const files = [];
123
+ const walkDir = (dir) => {
124
+ try {
125
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
126
+ for (const entry of entries) {
127
+ if (entry.name.startsWith('.'))
128
+ continue;
129
+ const fullPath = path.join(dir, entry.name);
130
+ if (entry.isDirectory() && this.options.recursive) {
131
+ walkDir(fullPath);
132
+ }
133
+ else if (entry.isFile() && this.shouldProcess(fullPath)) {
134
+ files.push(fullPath);
135
+ }
136
+ }
137
+ }
138
+ catch (error) {
139
+ console.error(`Error reading directory ${dir}:`, error);
140
+ }
141
+ };
142
+ walkDir(this.folderPath);
143
+ return files;
144
+ }
145
+ }
146
+ exports.FolderSyncer = FolderSyncer;
@@ -0,0 +1,156 @@
1
+ #!/usr/bin/env npx ts-node
2
+ "use strict";
3
+ /**
4
+ * Migration script to convert DocSync SQLite database to sqlite-vec format
5
+ *
6
+ * This script reads the chunks from the DocSync database (created by the Swift app)
7
+ * and migrates them to a proper vec0 virtual table that the MCP server can query.
8
+ *
9
+ * Usage:
10
+ * npx ts-node migrate-to-vec0.ts <source-db> <target-db>
11
+ *
12
+ * Example:
13
+ * npx ts-node migrate-to-vec0.ts ~/Documents/docsync.db ./my-docs.db
14
+ */
15
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ var desc = Object.getOwnPropertyDescriptor(m, k);
18
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
19
+ desc = { enumerable: true, get: function() { return m[k]; } };
20
+ }
21
+ Object.defineProperty(o, k2, desc);
22
+ }) : (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ o[k2] = m[k];
25
+ }));
26
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
27
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
28
+ }) : function(o, v) {
29
+ o["default"] = v;
30
+ });
31
+ var __importStar = (this && this.__importStar) || (function () {
32
+ var ownKeys = function(o) {
33
+ ownKeys = Object.getOwnPropertyNames || function (o) {
34
+ var ar = [];
35
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
36
+ return ar;
37
+ };
38
+ return ownKeys(o);
39
+ };
40
+ return function (mod) {
41
+ if (mod && mod.__esModule) return mod;
42
+ var result = {};
43
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
44
+ __setModuleDefault(result, mod);
45
+ return result;
46
+ };
47
+ })();
48
+ var __importDefault = (this && this.__importDefault) || function (mod) {
49
+ return (mod && mod.__esModule) ? mod : { "default": mod };
50
+ };
51
+ Object.defineProperty(exports, "__esModule", { value: true });
52
+ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
53
+ const sqliteVec = __importStar(require("sqlite-vec"));
54
+ const fs = __importStar(require("fs"));
55
+ function migrate(sourceDbPath, targetDbPath) {
56
+ console.log(`Migrating from ${sourceDbPath} to ${targetDbPath}`);
57
+ if (!fs.existsSync(sourceDbPath)) {
58
+ console.error(`Source database not found: ${sourceDbPath}`);
59
+ process.exit(1);
60
+ }
61
+ // Open source database (DocSync format)
62
+ const sourceDb = new better_sqlite3_1.default(sourceDbPath);
63
+ // Check if source has vec_items table
64
+ const tableCheck = sourceDb.prepare(`
65
+ SELECT name FROM sqlite_master WHERE type='table' AND name='vec_items'
66
+ `).get();
67
+ if (!tableCheck) {
68
+ console.error('Source database does not have vec_items table');
69
+ process.exit(1);
70
+ }
71
+ // Get row count
72
+ const countResult = sourceDb.prepare('SELECT COUNT(*) as count FROM vec_items').get();
73
+ console.log(`Found ${countResult.count} chunks to migrate`);
74
+ if (countResult.count === 0) {
75
+ console.log('No chunks to migrate');
76
+ process.exit(0);
77
+ }
78
+ // Open/create target database with sqlite-vec
79
+ const targetDb = new better_sqlite3_1.default(targetDbPath);
80
+ sqliteVec.load(targetDb);
81
+ // Create vec0 virtual table
82
+ targetDb.exec(`
83
+ CREATE VIRTUAL TABLE IF NOT EXISTS vec_items USING vec0(
84
+ embedding float[3072],
85
+ product_name text,
86
+ version text,
87
+ heading_hierarchy text,
88
+ section text,
89
+ chunk_id text primary key,
90
+ content text,
91
+ url text,
92
+ hash text,
93
+ chunk_index integer,
94
+ total_chunks integer
95
+ )
96
+ `);
97
+ console.log('Created vec0 virtual table');
98
+ // Prepare statements
99
+ const selectStmt = sourceDb.prepare(`
100
+ SELECT embedding, product_name, version, heading_hierarchy, section,
101
+ chunk_id, content, url, hash, chunk_index, total_chunks
102
+ FROM vec_items
103
+ `);
104
+ const insertStmt = targetDb.prepare(`
105
+ INSERT OR REPLACE INTO vec_items
106
+ (embedding, product_name, version, heading_hierarchy, section, chunk_id, content, url, hash, chunk_index, total_chunks)
107
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
108
+ `);
109
+ // Migrate chunks
110
+ let migratedCount = 0;
111
+ let skippedCount = 0;
112
+ const rows = selectStmt.all();
113
+ for (const row of rows) {
114
+ try {
115
+ // The embedding is stored as a blob in the source, convert to Float32Array
116
+ let embedding;
117
+ if (row.embedding && row.embedding.length > 0) {
118
+ // Convert blob to Float32Array
119
+ const buffer = Buffer.from(row.embedding);
120
+ embedding = new Float32Array(buffer.buffer, buffer.byteOffset, buffer.length / 4);
121
+ }
122
+ else {
123
+ // Skip rows without embeddings
124
+ console.log(`Skipping chunk ${row.chunk_id.substring(0, 8)}... (no embedding)`);
125
+ skippedCount++;
126
+ continue;
127
+ }
128
+ insertStmt.run(embedding, row.product_name, row.version, row.heading_hierarchy, row.section, row.chunk_id, row.content, row.url, row.hash, BigInt(row.chunk_index || 0), BigInt(row.total_chunks || 0));
129
+ migratedCount++;
130
+ if (migratedCount % 100 === 0) {
131
+ console.log(`Migrated ${migratedCount} chunks...`);
132
+ }
133
+ }
134
+ catch (error) {
135
+ console.error(`Error migrating chunk ${row.chunk_id}:`, error);
136
+ skippedCount++;
137
+ }
138
+ }
139
+ console.log(`\nMigration complete!`);
140
+ console.log(` Migrated: ${migratedCount} chunks`);
141
+ console.log(` Skipped: ${skippedCount} chunks`);
142
+ console.log(`\nTarget database: ${targetDbPath}`);
143
+ console.log(`You can now use this database with the MCP server.`);
144
+ sourceDb.close();
145
+ targetDb.close();
146
+ }
147
+ // Main
148
+ const args = process.argv.slice(2);
149
+ if (args.length < 2) {
150
+ console.log('Usage: npx ts-node migrate-to-vec0.ts <source-db> <target-db>');
151
+ console.log('');
152
+ console.log('Example:');
153
+ console.log(' npx ts-node migrate-to-vec0.ts ~/Documents/docsync.db ./my-docs.db');
154
+ process.exit(1);
155
+ }
156
+ migrate(args[0], args[1]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doc2vec",
3
- "version": "1.3.0",
3
+ "version": "2.0.0",
4
4
  "type": "commonjs",
5
5
  "description": "",
6
6
  "main": "dist/doc2vec.js",
@@ -36,12 +36,14 @@
36
36
  "dotenv": "^16.3.1",
37
37
  "js-yaml": "^4.1.0",
38
38
  "jsdom": "^26.0.0",
39
+ "mammoth": "^1.11.0",
39
40
  "openai": "^4.20.1",
40
41
  "pdfjs-dist": "^5.3.31",
41
42
  "puppeteer": "^24.1.1",
42
43
  "sanitize-html": "^2.11.0",
43
44
  "sqlite-vec": "0.1.7-alpha.2",
44
- "turndown": "^7.1.2"
45
+ "turndown": "^7.1.2",
46
+ "word-extractor": "^1.0.4"
45
47
  },
46
48
  "devDependencies": {
47
49
  "@types/better-sqlite3": "^7.6.12",