doclex-cli 0.1.1__tar.gz
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.
- doclex_cli-0.1.1/.gitignore +14 -0
- doclex_cli-0.1.1/CHANGELOG.md +41 -0
- doclex_cli-0.1.1/CLAUDE.md +346 -0
- doclex_cli-0.1.1/LICENSE +21 -0
- doclex_cli-0.1.1/PKG-INFO +221 -0
- doclex_cli-0.1.1/PLAN.md +264 -0
- doclex_cli-0.1.1/README.es.md +201 -0
- doclex_cli-0.1.1/README.md +201 -0
- doclex_cli-0.1.1/benchmarks/beir_eval.py +359 -0
- doclex_cli-0.1.1/benchmarks/compare.py +103 -0
- doclex_cli-0.1.1/pyproject.toml +47 -0
- doclex_cli-0.1.1/src/doclex/__init__.py +0 -0
- doclex_cli-0.1.1/src/doclex/cache/__init__.py +0 -0
- doclex_cli-0.1.1/src/doclex/cache/store.py +196 -0
- doclex_cli-0.1.1/src/doclex/cli.py +430 -0
- doclex_cli-0.1.1/src/doclex/config.py +43 -0
- doclex_cli-0.1.1/src/doclex/extraction/__init__.py +0 -0
- doclex_cli-0.1.1/src/doclex/extraction/extractor.py +72 -0
- doclex_cli-0.1.1/src/doclex/index/__init__.py +0 -0
- doclex_cli-0.1.1/src/doclex/index/bm25_index.py +120 -0
- doclex_cli-0.1.1/src/doclex/ingestion/__init__.py +0 -0
- doclex_cli-0.1.1/src/doclex/ingestion/scanner.py +87 -0
- doclex_cli-0.1.1/src/doclex/ingestion/watcher.py +94 -0
- doclex_cli-0.1.1/src/doclex/metadata/__init__.py +0 -0
- doclex_cli-0.1.1/src/doclex/metadata/db.py +250 -0
- doclex_cli-0.1.1/src/doclex/metadata/models.py +55 -0
- doclex_cli-0.1.1/src/doclex/pipeline.py +257 -0
- doclex_cli-0.1.1/src/doclex/registry.py +103 -0
- doclex_cli-0.1.1/src/doclex/segmentation/__init__.py +0 -0
- doclex_cli-0.1.1/src/doclex/segmentation/segmenter.py +398 -0
- doclex_cli-0.1.1/tests/__init__.py +0 -0
- doclex_cli-0.1.1/tests/conftest.py +80 -0
- doclex_cli-0.1.1/tests/test_cache.py +248 -0
- doclex_cli-0.1.1/tests/test_cli.py +329 -0
- doclex_cli-0.1.1/tests/test_db.py +290 -0
- doclex_cli-0.1.1/tests/test_extraction.py +41 -0
- doclex_cli-0.1.1/tests/test_index.py +130 -0
- doclex_cli-0.1.1/tests/test_registry.py +157 -0
- doclex_cli-0.1.1/tests/test_scanner.py +214 -0
- doclex_cli-0.1.1/tests/test_segmentation.py +222 -0
- doclex_cli-0.1.1/uv.lock +1639 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
|
+
|
|
8
|
+
## [0.1.1] - 2026-08-02
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Search result snippets now anchor on the window with the highest density of query-term matches instead of the first occurrence of the first term, so the snippet favors where the query actually clusters rather than an early, isolated mention.
|
|
13
|
+
|
|
14
|
+
## [0.1.0] - 2026-08-02
|
|
15
|
+
|
|
16
|
+
First release — CLI MVP (Fase 1): local, embeddings-free document search over PDF, DOCX, XLSX, CSV, Markdown and TXT files.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- Local document search CLI (`doclex`) — BM25 lexical search, no embeddings, no vector DB, no cloud.
|
|
21
|
+
- Ingestion pipeline: folder scanning with hash-based change detection, Kreuzberg-based text extraction, structural segmentation per file type (pages, sheets, sections, blocks), BM25X indexing, SQLite metadata store, and a local extraction cache.
|
|
22
|
+
- Real-time incremental updates via Watchdog file monitoring, plus full reconciliation on startup.
|
|
23
|
+
- `doclex add`/`projects`/`remove` — named project registry mapping folder paths to project names, rejecting overlapping/nested folders.
|
|
24
|
+
- `doclex index --project <name> [--file ...] [--force]` — incremental or forced full reindexing, with per-file targeting.
|
|
25
|
+
- `doclex search <query> --project <name> [--top] [--full]` — top-k BM25 search with citations (file, location, score, snippet).
|
|
26
|
+
- `doclex status --project <name> [--file ...] [--all] [--filter]` — corpus/index status, file listing, and per-file inspection.
|
|
27
|
+
- OCR support (`--ocr`) for scanned PDFs, and layout-aware segmentation via Kreuzberg.
|
|
28
|
+
- Recursive character-based splitter keeping segments within a 1024-character limit while preserving semantic boundaries.
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- CLI output migrated to Rich (progress bars, tables) instead of plain text.
|
|
33
|
+
- Segmentation cache replaced with a unified page-index-style tree; split sub-segments now nest under their extracted heading parent.
|
|
34
|
+
- `list`/`reindex` merged into `index`/`status` flags; cache directory renamed from `.cache` to `.doclex`.
|
|
35
|
+
- Layout-aware PDF segmentation made the default (removed the `--precise` flag).
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- Ghost BM25 index entries left behind after a forced reindex (index and SQLite are now rebuilt fully in sync).
|
|
40
|
+
- `status`/`search` no longer create the index directory before a project has actually been indexed.
|
|
41
|
+
- Search result snippets now tokenize the query the same way BM25X tokenizes documents (splitting on punctuation, not just whitespace) and snap to word boundaries instead of cutting words mid-way.
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
Antes de escribir cualquier código, genera un plan de implementación completo con estructura de carpetas, módulos, schema SQLite y tools MCP. Espera confirmación antes de proceder.
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Doclex MCP
|
|
6
|
+
|
|
7
|
+
Servidor MCP para búsqueda local dentro de documentos usando **extracción de texto + segmentación estructural + BM25**.
|
|
8
|
+
|
|
9
|
+
El objetivo es permitir que un agente de IA pueda consultar una carpeta local con documentos heterogéneos y recuperar fragmentos relevantes con citas precisas.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Objetivo
|
|
14
|
+
|
|
15
|
+
Construir un servidor MCP que permita buscar información dentro de archivos locales sin usar embeddings, sin base de datos vectorial y sin depender de servicios externos.
|
|
16
|
+
|
|
17
|
+
Debe funcionar sobre una carpeta local configurable y mantener un índice actualizado automáticamente cuando los archivos cambian.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Alcance
|
|
22
|
+
|
|
23
|
+
El sistema debe:
|
|
24
|
+
|
|
25
|
+
* escanear una carpeta local
|
|
26
|
+
* detectar archivos compatibles
|
|
27
|
+
* extraer su contenido a texto
|
|
28
|
+
* segmentar el contenido en unidades buscables
|
|
29
|
+
* indexar esos segmentos con BM25
|
|
30
|
+
* actualizar el índice cuando los archivos cambien
|
|
31
|
+
* exponer herramientas MCP para búsqueda y recuperación de resultados
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Tipos de archivo soportados
|
|
36
|
+
|
|
37
|
+
El sistema debe estar diseñado para soportar al menos:
|
|
38
|
+
|
|
39
|
+
* PDF
|
|
40
|
+
* TXT
|
|
41
|
+
* Markdown
|
|
42
|
+
* DOCX
|
|
43
|
+
* XLSX
|
|
44
|
+
* CSV
|
|
45
|
+
|
|
46
|
+
La extracción debe delegarse a Kreuzberg.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Arquitectura general
|
|
51
|
+
|
|
52
|
+
El sistema se compone de cinco partes principales:
|
|
53
|
+
|
|
54
|
+
### 1. Ingestion Layer
|
|
55
|
+
|
|
56
|
+
Responsable de descubrir archivos dentro de una carpeta y decidir cuándo un archivo debe procesarse o reprocesarse.
|
|
57
|
+
|
|
58
|
+
Debe detectar:
|
|
59
|
+
|
|
60
|
+
* archivos nuevos
|
|
61
|
+
* archivos modificados
|
|
62
|
+
* archivos eliminados
|
|
63
|
+
|
|
64
|
+
Debe ignorar carpetas internas del sistema como `.cache` o equivalentes.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
### 2. Extraction Layer
|
|
69
|
+
|
|
70
|
+
Responsable de convertir archivos a texto utilizable.
|
|
71
|
+
|
|
72
|
+
Debe usar Kreuzberg como backend de extracción.
|
|
73
|
+
|
|
74
|
+
La salida de esta capa no debe depender directamente del formato original del extractor, sino normalizarse a una estructura interna propia.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
### 3. Segmentation Layer
|
|
79
|
+
|
|
80
|
+
Responsable de dividir el contenido en unidades buscables.
|
|
81
|
+
|
|
82
|
+
No se debe indexar el archivo completo como una sola unidad, ni usar chunking ciego por tamaño fijo.
|
|
83
|
+
|
|
84
|
+
La segmentación debe seguir la estructura natural del documento según el tipo de archivo:
|
|
85
|
+
|
|
86
|
+
* PDF → página o bloque
|
|
87
|
+
* XLSX → hoja o tabla
|
|
88
|
+
* DOCX → sección o heading
|
|
89
|
+
* TXT / MD → bloque o heading
|
|
90
|
+
* CSV → tabla o fragmento tabular
|
|
91
|
+
|
|
92
|
+
Cada segmento debe conservar referencia clara a su archivo de origen y su ubicación exacta.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
### 4. Index Layer
|
|
97
|
+
|
|
98
|
+
Responsable de indexar y consultar segmentos usando BM25.
|
|
99
|
+
|
|
100
|
+
Debe usar BM25X como motor principal.
|
|
101
|
+
|
|
102
|
+
La unidad de indexación debe ser el segmento, no el archivo completo.
|
|
103
|
+
|
|
104
|
+
Debe soportar:
|
|
105
|
+
|
|
106
|
+
* agregar segmentos nuevos
|
|
107
|
+
* actualizar segmentos de un archivo modificado
|
|
108
|
+
* eliminar segmentos de un archivo borrado
|
|
109
|
+
* buscar top-k resultados
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
### 5. Metadata Layer
|
|
114
|
+
|
|
115
|
+
Responsable de mantener el estado del corpus y del índice.
|
|
116
|
+
|
|
117
|
+
Debe usar SQLite.
|
|
118
|
+
|
|
119
|
+
Debe almacenar al menos:
|
|
120
|
+
|
|
121
|
+
* ruta del archivo
|
|
122
|
+
* hash del archivo
|
|
123
|
+
* fecha de modificación
|
|
124
|
+
* tamaño
|
|
125
|
+
* tipo MIME
|
|
126
|
+
* estado de indexación
|
|
127
|
+
* relación entre archivo y segmentos indexados
|
|
128
|
+
|
|
129
|
+
Esto se usa para saber qué archivos deben reprocesarse y para mantener consistencia entre disco, caché e índice.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Caché local
|
|
134
|
+
|
|
135
|
+
El sistema debe mantener una caché local derivada del contenido procesado.
|
|
136
|
+
|
|
137
|
+
La caché debe permitir evitar reprocesar archivos sin cambios y facilitar depuración.
|
|
138
|
+
|
|
139
|
+
Debe almacenar por archivo:
|
|
140
|
+
|
|
141
|
+
* texto completo extraído
|
|
142
|
+
* metadatos
|
|
143
|
+
* segmentos generados
|
|
144
|
+
|
|
145
|
+
La caché no debe ser la fuente principal de búsqueda, pero sí la fuente intermedia entre extracción e indexación.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Actualización automática
|
|
150
|
+
|
|
151
|
+
El sistema debe monitorear la carpeta configurada en tiempo real usando Watchdog.
|
|
152
|
+
|
|
153
|
+
Cuando un archivo se crea, modifica o elimina, el sistema debe:
|
|
154
|
+
|
|
155
|
+
1. detectar el cambio
|
|
156
|
+
2. aplicar debounce
|
|
157
|
+
3. revalidar el estado real del archivo
|
|
158
|
+
4. reprocesar solo lo necesario
|
|
159
|
+
5. actualizar caché, SQLite e índice BM25
|
|
160
|
+
|
|
161
|
+
Además del monitoreo por eventos, el sistema debe poder ejecutar reconciliación completa al iniciar.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Flujo de indexación
|
|
166
|
+
|
|
167
|
+
### Al iniciar
|
|
168
|
+
|
|
169
|
+
* escanear la carpeta objetivo
|
|
170
|
+
* comparar archivos actuales con el estado en SQLite
|
|
171
|
+
* detectar nuevos, modificados y eliminados
|
|
172
|
+
* procesar cada cambio
|
|
173
|
+
* construir o actualizar índice
|
|
174
|
+
* activar el monitor de archivos
|
|
175
|
+
|
|
176
|
+
### Cuando cambia un archivo
|
|
177
|
+
|
|
178
|
+
* recalcular hash y metadatos
|
|
179
|
+
* reextraer contenido
|
|
180
|
+
* regenerar segmentos
|
|
181
|
+
* reemplazar en índice solo los segmentos de ese archivo
|
|
182
|
+
* actualizar caché y manifiesto
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Flujo de búsqueda
|
|
187
|
+
|
|
188
|
+
Cuando un agente realiza una consulta:
|
|
189
|
+
|
|
190
|
+
1. recibir query de texto
|
|
191
|
+
2. aplicar normalización básica
|
|
192
|
+
3. ejecutar búsqueda BM25 sobre segmentos
|
|
193
|
+
4. recuperar top-k resultados
|
|
194
|
+
5. devolver resultados enriquecidos con metadatos y contexto
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Resultado esperado de una búsqueda
|
|
199
|
+
|
|
200
|
+
Cada resultado debe incluir:
|
|
201
|
+
|
|
202
|
+
* texto del segmento encontrado
|
|
203
|
+
* score de relevancia
|
|
204
|
+
* archivo de origen
|
|
205
|
+
* tipo de archivo
|
|
206
|
+
* ubicación interna:
|
|
207
|
+
|
|
208
|
+
* página
|
|
209
|
+
* hoja
|
|
210
|
+
* sección
|
|
211
|
+
* bloque
|
|
212
|
+
* snippet o texto resumido del match
|
|
213
|
+
* identificador estable del segmento
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Requisitos de diseño
|
|
218
|
+
|
|
219
|
+
El sistema debe ser:
|
|
220
|
+
|
|
221
|
+
* local-first
|
|
222
|
+
* incremental
|
|
223
|
+
* explicable
|
|
224
|
+
* determinista
|
|
225
|
+
* barato en recursos
|
|
226
|
+
* fácil de depurar
|
|
227
|
+
|
|
228
|
+
Debe priorizar:
|
|
229
|
+
|
|
230
|
+
* precisión léxica
|
|
231
|
+
* trazabilidad
|
|
232
|
+
* simplicidad operacional
|
|
233
|
+
|
|
234
|
+
sobre:
|
|
235
|
+
|
|
236
|
+
* semántica profunda
|
|
237
|
+
* recuperación por embeddings
|
|
238
|
+
* razonamiento generativo durante búsqueda
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Lo que no debe hacer
|
|
243
|
+
|
|
244
|
+
El sistema no debe:
|
|
245
|
+
|
|
246
|
+
* usar embeddings
|
|
247
|
+
* usar base de datos vectorial
|
|
248
|
+
* subir archivos a la nube
|
|
249
|
+
* depender de servicios externos para búsqueda
|
|
250
|
+
* hacer OCR por defecto
|
|
251
|
+
* generar respuestas directamente dentro del índice
|
|
252
|
+
|
|
253
|
+
La responsabilidad del sistema es **recuperar evidencia**, no responder preguntas por sí mismo.
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Integración MCP
|
|
258
|
+
|
|
259
|
+
El servidor debe exponer herramientas MCP para que un agente pueda al menos:
|
|
260
|
+
|
|
261
|
+
* buscar dentro del corpus
|
|
262
|
+
* listar archivos indexados
|
|
263
|
+
* consultar detalles de un archivo
|
|
264
|
+
* forzar reindexación si es necesario
|
|
265
|
+
|
|
266
|
+
La interfaz MCP debe estar pensada para que un agente pueda hacer workflows tipo:
|
|
267
|
+
|
|
268
|
+
1. buscar
|
|
269
|
+
2. leer evidencia
|
|
270
|
+
3. refinar búsqueda
|
|
271
|
+
4. responder usando citas
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## Stack esperado
|
|
276
|
+
|
|
277
|
+
* Python
|
|
278
|
+
* MCP SDK
|
|
279
|
+
* Kreuzberg
|
|
280
|
+
* BM25X
|
|
281
|
+
* Watchdog
|
|
282
|
+
* SQLite
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Prioridad de implementación
|
|
287
|
+
|
|
288
|
+
### Fase 1 — CLI (MVP)
|
|
289
|
+
|
|
290
|
+
El pipeline debe implementarse y verificarse primero como CLI, antes de exponer cualquier interfaz MCP.
|
|
291
|
+
|
|
292
|
+
Primera versión funcional con:
|
|
293
|
+
|
|
294
|
+
* escaneo de carpeta
|
|
295
|
+
* extracción (Kreuzberg)
|
|
296
|
+
* segmentación estructural por tipo de archivo
|
|
297
|
+
* indexación BM25 (BM25X)
|
|
298
|
+
* búsqueda top-k desde línea de comandos
|
|
299
|
+
* actualización incremental (Watchdog)
|
|
300
|
+
* caché local y estado en SQLite
|
|
301
|
+
|
|
302
|
+
La CLI se organiza alrededor de **proyectos**: un proyecto es un nombre registrado que apunta a una carpeta. Toda resolución de carpeta pasa por un proyecto registrado — no existe un flag `--folder` suelto. Esto evita ambigüedad sobre contra qué carpeta se opera y permite detectar carpetas solapadas entre proyectos (rechazadas al registrar).
|
|
303
|
+
|
|
304
|
+
La CLI debe permitir al menos:
|
|
305
|
+
|
|
306
|
+
* `doclex add <nombre> <carpeta>` — registra una carpeta bajo un nombre de proyecto (rechaza nombres duplicados, carpetas inexistentes y carpetas que se solapen con un proyecto ya registrado)
|
|
307
|
+
* `doclex list` — lista los proyectos registrados
|
|
308
|
+
* `doclex remove <nombre>` — elimina un proyecto y purga siempre su caché e índice asociados
|
|
309
|
+
* `doclex index --project <nombre> [--file <archivo> ...] [--force]` — indexa el proyecto de forma incremental, o reindexa archivos puntuales con `--file` (repetible), o fuerza reextracción total con `--force`
|
|
310
|
+
* `doclex search <query> --project <nombre> [--top N] [--full]` — busca y muestra resultados con citas; `<query>` es un argumento posicional
|
|
311
|
+
* `doclex status --project <nombre> [--file <archivo> ...] [--all] [--filter indexed|error|pending]` — muestra estado del corpus, lista archivos indexados (`--all`) o inspecciona archivos específicos
|
|
312
|
+
|
|
313
|
+
`add`/`list`/`remove` son comandos planos, no un subgrupo `project`, ya que `--project` ya se usa como flag en `index`/`search`/`status`. `doclex list` lista **proyectos**, no archivos — para listar archivos de un proyecto se usa `status --all`. No existe un comando `doclex reindex` independiente: queda cubierto por `index --file`.
|
|
314
|
+
|
|
315
|
+
El resultado de `doclex search` debe mostrar por cada resultado: archivo de origen, ubicación (página / hoja / sección), score de relevancia y snippet del texto encontrado.
|
|
316
|
+
|
|
317
|
+
### Fase 2 — Servidor MCP
|
|
318
|
+
|
|
319
|
+
La capa MCP se agrega únicamente después de que el pipeline de extracción, segmentación e indexación esté verificado y funcionando correctamente vía CLI.
|
|
320
|
+
|
|
321
|
+
Las herramientas MCP son un wrapper sobre el mismo pipeline ya validado.
|
|
322
|
+
|
|
323
|
+
### Fase 3 — Futuro opcional
|
|
324
|
+
|
|
325
|
+
Posibles mejoras fuera del alcance actual:
|
|
326
|
+
|
|
327
|
+
* OCR opcional
|
|
328
|
+
* filtros avanzados
|
|
329
|
+
* query expansion
|
|
330
|
+
* query rewriting
|
|
331
|
+
* búsqueda multi-paso
|
|
332
|
+
* integración con agentes más complejos
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## Resumen funcional
|
|
337
|
+
|
|
338
|
+
Doclex MCP debe convertir una carpeta local de documentos en un corpus buscable para agentes, con actualización automática, recuperación precisa y resultados citables.
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## Notas para Claude Code
|
|
343
|
+
|
|
344
|
+
- Instalar dependencias con uv, no con pip
|
|
345
|
+
- Usar pyproject.toml para gestión del proyecto
|
|
346
|
+
- No crear archivos de código sin antes mostrar el plan completo y recibir confirmación
|
doclex_cli-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 EnPaiva_93
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: doclex-cli
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Local document search — BM25 lexical search over PDFs, DOCX, XLSX, CSV, Markdown and TXT files. No embeddings, no vector DB, no cloud.
|
|
5
|
+
Project-URL: Repository, https://github.com/EnPaiva93/doclex-cli
|
|
6
|
+
Author-email: EnPaiva_93 <enpaiva93@gmail.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Python: >=3.12
|
|
10
|
+
Requires-Dist: aiosqlite
|
|
11
|
+
Requires-Dist: bm25x
|
|
12
|
+
Requires-Dist: kreuzberg==4.8.2
|
|
13
|
+
Requires-Dist: typer
|
|
14
|
+
Requires-Dist: watchdog
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
17
|
+
Requires-Dist: pytest-asyncio; extra == 'dev'
|
|
18
|
+
Requires-Dist: ranx; extra == 'dev'
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
<h1 align="center">Doclex</h1>
|
|
22
|
+
|
|
23
|
+
<p align="center"><em>English · <a href="README.es.md">Español</a></em></p>
|
|
24
|
+
|
|
25
|
+
<p align="center">
|
|
26
|
+
<img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License: MIT">
|
|
27
|
+
<img src="https://img.shields.io/badge/python-3.12%2B-blue.svg" alt="Python 3.12+">
|
|
28
|
+
</p>
|
|
29
|
+
|
|
30
|
+
<p align="center">A CLI that turns a local folder of documents into a searchable, citable corpus. Point it at a folder; extraction, segmentation, indexing, and change tracking happen automatically.</p>
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
Doclex is the orchestration layer that ties them together so you never have to think about the pipeline yourself:
|
|
35
|
+
|
|
36
|
+
- **Plug and play** — point it at a folder. Extraction, structural segmentation, indexing, and change tracking all happen automatically; there's no pipeline to wire up.
|
|
37
|
+
- **SQLite is the source of truth** — every file's hash, mtime, and indexing status live in one place, so Doclex always knows exactly what changed and reprocesses only that.
|
|
38
|
+
- **Local-first** — everything runs on your machine, nothing leaves it. No embeddings, no vector database, no cloud.
|
|
39
|
+
- **Deterministic** — BM25 lexical scoring, no black-box similarity.
|
|
40
|
+
- **Traceable** — every result cites its file, page/sheet/section, and score.
|
|
41
|
+
- **Incremental** — only reprocesses files that actually changed.
|
|
42
|
+
|
|
43
|
+
## Install
|
|
44
|
+
|
|
45
|
+
Requires Python ≥3.12 and [uv](https://docs.astral.sh/uv/).
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
git clone https://github.com/EnPaiva93/doclex-cli.git
|
|
49
|
+
cd doclex-cli
|
|
50
|
+
uv sync
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Quickstart
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# register a folder as a project
|
|
57
|
+
uv run doclex add mydocs ./path/to/documents
|
|
58
|
+
|
|
59
|
+
# build the index
|
|
60
|
+
uv run doclex index --project mydocs
|
|
61
|
+
|
|
62
|
+
# search it
|
|
63
|
+
uv run doclex search "quarterly budget" --project mydocs
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
[1] budget_2024.pdf — page 3 (score: 4.812)
|
|
68
|
+
"...the quarterly budget was approved with a 12% increase in..."
|
|
69
|
+
|
|
70
|
+
[2] notes.md — Planning (score: 2.107)
|
|
71
|
+
"...need to revisit the budget assumptions before..."
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Walkthrough
|
|
75
|
+
|
|
76
|
+
A typical day-to-day flow, from an empty project to keeping it in sync:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# 1. register the folder you want to search
|
|
80
|
+
doclex add contracts ~/Documents/legal/contracts
|
|
81
|
+
|
|
82
|
+
# 2. build the index — extracts, segments, and indexes every supported file
|
|
83
|
+
doclex index --project contracts
|
|
84
|
+
```
|
|
85
|
+
```
|
|
86
|
+
Processing: 42 new, 0 modified, 0 deleted
|
|
87
|
+
✓ Done.
|
|
88
|
+
```
|
|
89
|
+
```bash
|
|
90
|
+
# 3. search it
|
|
91
|
+
doclex search "termination clause" --project contracts
|
|
92
|
+
```
|
|
93
|
+
```
|
|
94
|
+
[1] MSA_2023_AcmeCorp.pdf — Section 9.2 (score: 6.104)
|
|
95
|
+
"...either party may terminate this Agreement upon 30 days written notice..."
|
|
96
|
+
|
|
97
|
+
[2] vendor_agreement_v3.docx — Termination (score: 5.887)
|
|
98
|
+
"...termination for convenience requires 60 days advance notice to..."
|
|
99
|
+
```
|
|
100
|
+
```bash
|
|
101
|
+
# 4. check what's indexed
|
|
102
|
+
doclex status --project contracts
|
|
103
|
+
```
|
|
104
|
+
```
|
|
105
|
+
Folder ~/Documents/legal/contracts
|
|
106
|
+
Indexed 42 files
|
|
107
|
+
Segments 318
|
|
108
|
+
BM25 index 318 docs
|
|
109
|
+
Disk state up to date
|
|
110
|
+
```
|
|
111
|
+
```bash
|
|
112
|
+
# 5. a new contract shows up on disk — reindexing picks it up automatically
|
|
113
|
+
doclex index --project contracts
|
|
114
|
+
```
|
|
115
|
+
```
|
|
116
|
+
Processing: 1 new, 0 modified, 0 deleted
|
|
117
|
+
✓ Done.
|
|
118
|
+
```
|
|
119
|
+
```bash
|
|
120
|
+
# 6. see every registered project
|
|
121
|
+
doclex list
|
|
122
|
+
```
|
|
123
|
+
```
|
|
124
|
+
Name Folder
|
|
125
|
+
contracts /home/you/Documents/legal/contracts
|
|
126
|
+
```
|
|
127
|
+
```bash
|
|
128
|
+
# 7. done with it — deregister and purge its cache/index
|
|
129
|
+
doclex remove contracts
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Each project is independent — register as many non-overlapping folders as you need, each with its own cache and index:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
doclex add hr-policies ~/Documents/hr
|
|
136
|
+
doclex index --project hr-policies
|
|
137
|
+
doclex search "vacation days" --project hr-policies --top 5
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Projects
|
|
141
|
+
|
|
142
|
+
Every command operates on a **registered project** — a name bound to a folder — instead of a raw `--folder` flag. This keeps folder resolution unambiguous and lets Doclex reject overlapping folders (a subfolder registered as its own project would silently duplicate indexing and caching):
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
doclex add mydocs ./path/to/documents
|
|
146
|
+
doclex add mydocs ./other # error: name already exists
|
|
147
|
+
doclex add sub ./path/to/documents/subdir # error: overlaps with 'mydocs'
|
|
148
|
+
doclex remove mydocs # deregisters and purges its cache/index
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Commands
|
|
152
|
+
|
|
153
|
+
| Command | Description |
|
|
154
|
+
|---|---|
|
|
155
|
+
| `doclex add <name> <folder>` | Register a folder under a project name |
|
|
156
|
+
| `doclex list` | List registered projects |
|
|
157
|
+
| `doclex remove <name>` | Remove a project and purge its cache/index |
|
|
158
|
+
| `doclex index --project <name>` | Scan and index (incremental by default) |
|
|
159
|
+
| `doclex search <query> --project <name>` | Search and print results with citations |
|
|
160
|
+
| `doclex status --project <name>` | Show corpus/index status |
|
|
161
|
+
|
|
162
|
+
Run `doclex <command> --help` for all flags and examples.
|
|
163
|
+
|
|
164
|
+
### Flags in practice
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
# force a full re-extraction
|
|
168
|
+
doclex index --project mydocs --force
|
|
169
|
+
|
|
170
|
+
# reindex specific files after editing them
|
|
171
|
+
doclex index --project mydocs --file report.pdf --file notes.docx
|
|
172
|
+
|
|
173
|
+
# more results, full segment text instead of a snippet
|
|
174
|
+
doclex search "risk assessment" --project mydocs --top 20 --full
|
|
175
|
+
|
|
176
|
+
# list every indexed file and its status
|
|
177
|
+
doclex status --project mydocs --all --filter error
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Architecture
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
folder → scan/watch → extract (Kreuzberg) → segment → BM25X index
|
|
184
|
+
↕
|
|
185
|
+
SQLite (files, segments)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
| Layer | Responsibility |
|
|
189
|
+
|---|---|
|
|
190
|
+
| **Ingestion** | Scans a project's folder, hashes files to detect new/modified/deleted ones, skips its own cache directory |
|
|
191
|
+
| **Extraction** | Delegates to [Kreuzberg](https://github.com/Goldziher/kreuzberg) to turn each file into plain text, normalized to an internal format |
|
|
192
|
+
| **Segmentation** | Splits text along the file's natural structure — never blind fixed-size chunking (PDF → page, XLSX → sheet, DOCX → section, TXT/MD → block) |
|
|
193
|
+
| **Index** | Indexes segments (not whole files) with BM25X; supports incremental add/update/delete |
|
|
194
|
+
| **Metadata** | SQLite tracks each file's hash, mtime, size, MIME type, and indexing status, plus which segments belong to it |
|
|
195
|
+
|
|
196
|
+
A local cache stores extracted text and generated segments per file, so unchanged files are never reprocessed.
|
|
197
|
+
|
|
198
|
+
## Supported file types
|
|
199
|
+
|
|
200
|
+
PDF · TXT · Markdown · DOCX · XLSX · CSV
|
|
201
|
+
|
|
202
|
+
## Design principles
|
|
203
|
+
|
|
204
|
+
Doclex is built to be local-first, incremental, explainable, deterministic, and cheap to run. It deliberately does **not**:
|
|
205
|
+
|
|
206
|
+
- use embeddings or a vector database
|
|
207
|
+
- upload files anywhere
|
|
208
|
+
- depend on external services to search
|
|
209
|
+
- run OCR by default
|
|
210
|
+
- generate answers — it retrieves evidence, it doesn't interpret it
|
|
211
|
+
|
|
212
|
+
## Stack
|
|
213
|
+
|
|
214
|
+
Python · [Typer](https://typer.tiangolo.com/) · [Kreuzberg](https://github.com/Goldziher/kreuzberg) · BM25X · Watchdog · SQLite
|
|
215
|
+
|
|
216
|
+
## Development
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
uv sync --extra dev
|
|
220
|
+
uv run pytest
|
|
221
|
+
```
|