folderlake 0.2.0__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.
- folderlake-0.2.0/LICENSE +21 -0
- folderlake-0.2.0/PKG-INFO +278 -0
- folderlake-0.2.0/README.md +261 -0
- folderlake-0.2.0/pyproject.toml +33 -0
- folderlake-0.2.0/setup.cfg +4 -0
- folderlake-0.2.0/src/folderlake/__init__.py +3 -0
- folderlake-0.2.0/src/folderlake/appserver.py +307 -0
- folderlake-0.2.0/src/folderlake/assets/echarts.min.js +45 -0
- folderlake-0.2.0/src/folderlake/catalog.py +184 -0
- folderlake-0.2.0/src/folderlake/cli.py +209 -0
- folderlake-0.2.0/src/folderlake/cypher.py +100 -0
- folderlake-0.2.0/src/folderlake/extractors.py +360 -0
- folderlake-0.2.0/src/folderlake/hierarchy.py +297 -0
- folderlake-0.2.0/src/folderlake/home.html +305 -0
- folderlake-0.2.0/src/folderlake/llm.py +210 -0
- folderlake-0.2.0/src/folderlake/mcp_server.py +331 -0
- folderlake-0.2.0/src/folderlake/preview.py +57 -0
- folderlake-0.2.0/src/folderlake/registry.py +157 -0
- folderlake-0.2.0/src/folderlake/relations.py +103 -0
- folderlake-0.2.0/src/folderlake/server.py +329 -0
- folderlake-0.2.0/src/folderlake/topics.py +591 -0
- folderlake-0.2.0/src/folderlake/viewer.html +397 -0
- folderlake-0.2.0/src/folderlake.egg-info/PKG-INFO +278 -0
- folderlake-0.2.0/src/folderlake.egg-info/SOURCES.txt +36 -0
- folderlake-0.2.0/src/folderlake.egg-info/dependency_links.txt +1 -0
- folderlake-0.2.0/src/folderlake.egg-info/entry_points.txt +3 -0
- folderlake-0.2.0/src/folderlake.egg-info/requires.txt +10 -0
- folderlake-0.2.0/src/folderlake.egg-info/top_level.txt +1 -0
- folderlake-0.2.0/tests/test_app.py +159 -0
- folderlake-0.2.0/tests/test_cypher.py +83 -0
- folderlake-0.2.0/tests/test_extractors.py +107 -0
- folderlake-0.2.0/tests/test_hierarchy.py +119 -0
- folderlake-0.2.0/tests/test_incremental.py +42 -0
- folderlake-0.2.0/tests/test_mcp.py +161 -0
- folderlake-0.2.0/tests/test_phrases_llm.py +208 -0
- folderlake-0.2.0/tests/test_relations.py +58 -0
- folderlake-0.2.0/tests/test_server.py +116 -0
- folderlake-0.2.0/tests/test_topics.py +105 -0
folderlake-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zhengang Dai
|
|
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,278 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: folderlake
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Point it at a folder. Know what's in it. A local file catalog with topics, metadata and a relationship graph, stored in one DuckDB file.
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Dist: duckdb
|
|
9
|
+
Requires-Dist: python-docx
|
|
10
|
+
Requires-Dist: pypdf
|
|
11
|
+
Requires-Dist: openpyxl
|
|
12
|
+
Provides-Extra: llm
|
|
13
|
+
Requires-Dist: litellm>=1.40; extra == "llm"
|
|
14
|
+
Provides-Extra: mcp
|
|
15
|
+
Requires-Dist: mcp>=1.0; extra == "mcp"
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# FolderLake
|
|
19
|
+
|
|
20
|
+
**Point it at a folder. Know what's in it — an LLM reads the files,
|
|
21
|
+
and an MCP server lets your other agents query the result.**
|
|
22
|
+
|
|
23
|
+
FolderLake scans a folder once and builds a local catalog of every file
|
|
24
|
+
in it — **LLM topic tags, a one-line summary and key entities per
|
|
25
|
+
file**, plus a graph of how the files relate to each other. The
|
|
26
|
+
catalog is a single DuckDB file that lives next to your data. No
|
|
27
|
+
server, no account, nothing leaves your machine.
|
|
28
|
+
|
|
29
|
+
- **LLM topics** — plug in a model (Ollama local, OpenAI, Anthropic,
|
|
30
|
+
any OpenAI-compatible endpoint) for phrase tags and real summaries;
|
|
31
|
+
a built-in TF-IDF tagger covers folders with no model configured.
|
|
32
|
+
- **MCP built in** — `folderlake-mcp` exposes the catalog to Claude
|
|
33
|
+
Code, Claude Desktop and other MCP clients: search, browse, inspect
|
|
34
|
+
and traverse the graph without opening the UI.
|
|
35
|
+
- **Zero setup** — one `pip install`, one command, a web UI opens.
|
|
36
|
+
- **Fast** — 1,000 mixed files scan in ~13 s; rescans only touch new or
|
|
37
|
+
changed files (~0.1 s when nothing changed).
|
|
38
|
+
- **Light** — a 1,000-file catalog is ~1.6 MB on disk.
|
|
39
|
+
- **Many formats** — txt, md, html, docx, pdf, csv, tsv, json, parquet,
|
|
40
|
+
xlsx and sqlite out of the box.
|
|
41
|
+
|
|
42
|
+
## Live demo
|
|
43
|
+
|
|
44
|
+
**[Open the demo](https://daizack.github.io/FolderLake/)** — a real
|
|
45
|
+
FolderLake catalog of 193 public files (AG News, 20 Newsgroups, Wikipedia
|
|
46
|
+
articles and the seaborn datasets), browsable in your browser.
|
|
47
|
+
|
|
48
|
+
The demo's topic tags and summaries were generated by **Muse** (Meta's
|
|
49
|
+
AI assistant) acting as FolderLake's LLM tier — see
|
|
50
|
+
[How the demo was made](#how-the-demo-was-made). With no LLM configured,
|
|
51
|
+
FolderLake falls back to a built-in TF-IDF tagger; the demo shows what
|
|
52
|
+
the LLM tier adds: phrase tags like `car air conditioning` instead of
|
|
53
|
+
single words, and a real one-sentence summary per file.
|
|
54
|
+
|
|
55
|
+
Two things worth trying in the demo: click a topic in the tree — the
|
|
56
|
+
list and the graph filter together — and open the Wikipedia article
|
|
57
|
+
on the Titanic: it links straight to the Titanic passenger datasets.
|
|
58
|
+
FolderLake adds a *reference* link whenever a text names a dataset's
|
|
59
|
+
subject (a capitalised mention like "Titanic" or "Penguins"), on top
|
|
60
|
+
of shared topics, shared entities and explicit filename references.
|
|
61
|
+
|
|
62
|
+

|
|
63
|
+
|
|
64
|
+

|
|
65
|
+
|
|
66
|
+

|
|
67
|
+
|
|
68
|
+
<img src="docs/screenshots/mobile.png" alt="FolderLake on a phone" width="320">
|
|
69
|
+
|
|
70
|
+
## Quick start
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install "git+https://github.com/DaiZack/FolderLake"
|
|
74
|
+
# (not on PyPI yet — GitHub is the source; or `pip install -e .` from a clone)
|
|
75
|
+
|
|
76
|
+
folderlake app # opens the FolderLake home page
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+

|
|
80
|
+
|
|
81
|
+
That's the whole setup. `app` opens a home page in your browser right
|
|
82
|
+
away — no dialog, no waiting. Paste or browse to a folder, hit
|
|
83
|
+
**Scan folder**, and watch the progress bar; when the scan finishes
|
|
84
|
+
the catalog opens. Every catalog you build is remembered on the home
|
|
85
|
+
page (stored in a small SQLite registry in `~/.folderlake/`): reopen
|
|
86
|
+
one in a click, **Refresh** it to pick up added/removed files, or
|
|
87
|
+
**Delete** it. Each history entry shows the settings it was scanned
|
|
88
|
+
with. Inside a catalog, a **← FolderLake Home** link brings you back.
|
|
89
|
+
|
|
90
|
+
The catalog viewer itself: filters and the topic tree on the left,
|
|
91
|
+
the file list and the relationship graph side by side in the middle
|
|
92
|
+
(on a phone they take turns, graph first), and a preview pane on the
|
|
93
|
+
right. Clicking a topic filters the list and the graph together.
|
|
94
|
+
Data files get a *data review* in the preview
|
|
95
|
+
pane — schema, null counts, min/max, approximate uniques — plus a
|
|
96
|
+
ready-to-run DuckDB query.
|
|
97
|
+
|
|
98
|
+
Prefer the terminal:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
folderlake scan ~/Documents/notes # build or update the catalog
|
|
102
|
+
folderlake serve ~/Documents/notes # rescan + open the viewer
|
|
103
|
+
folderlake cypher ~/Documents/notes # export the graph as Neo4j Cypher
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Every command is incremental: unchanged files are skipped by file stats
|
|
107
|
+
alone, so re-running on a big folder is cheap.
|
|
108
|
+
|
|
109
|
+
## Using an LLM for topics
|
|
110
|
+
|
|
111
|
+
With no LLM configured you get rule-based TF-IDF phrase tags. Point
|
|
112
|
+
FolderLake at any chat model and each new or changed file gets LLM tags,
|
|
113
|
+
a one-sentence summary and key entities instead (results are cached by
|
|
114
|
+
content hash, so each file is sent exactly once). In the app, the
|
|
115
|
+
home page's Settings card holds the same options — no environment
|
|
116
|
+
variables needed; the environment variables below remain for the
|
|
117
|
+
CLI and scripts:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
pip install "folderlake[llm] @ git+https://github.com/DaiZack/FolderLake"
|
|
121
|
+
# (adds LiteLLM as the transport)
|
|
122
|
+
|
|
123
|
+
# Local model via Ollama (default provider)
|
|
124
|
+
FOLDERLAKE_LLM_MODEL=qwen3 folderlake scan ~/notes --llm
|
|
125
|
+
|
|
126
|
+
# Any OpenAI-compatible endpoint (OpenAI, DeepSeek, Groq, LM Studio, vLLM…)
|
|
127
|
+
FOLDERLAKE_LLM_PROVIDER=openai \
|
|
128
|
+
FOLDERLAKE_LLM_MODEL=gpt-4o-mini \
|
|
129
|
+
FOLDERLAKE_LLM_API_KEY=sk-… \
|
|
130
|
+
folderlake scan ~/notes --llm
|
|
131
|
+
|
|
132
|
+
# Anthropic
|
|
133
|
+
FOLDERLAKE_LLM_PROVIDER=anthropic \
|
|
134
|
+
FOLDERLAKE_LLM_MODEL=claude-haiku-4-5 \
|
|
135
|
+
FOLDERLAKE_LLM_API_KEY=sk-ant-… \
|
|
136
|
+
folderlake scan ~/notes --llm
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Data files are never sent raw: the LLM sees only a profile card
|
|
140
|
+
(`DESCRIBE` + `SUMMARIZE` output — column names and aggregate stats),
|
|
141
|
+
never sample row values. If the LLM is unreachable, the scan falls back
|
|
142
|
+
to rule topics and says so in its stats.
|
|
143
|
+
|
|
144
|
+
## Let other agents query it (MCP)
|
|
145
|
+
|
|
146
|
+
FolderLake ships an MCP server with six read-only tools — `stats`,
|
|
147
|
+
`search`, `list_files`, `file_detail`, `related`, `topics` — so any MCP
|
|
148
|
+
client (Claude Code, Cursor, your own agent) can ask questions about a
|
|
149
|
+
catalog without opening files:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
pip install "folderlake[mcp] @ git+https://github.com/DaiZack/FolderLake"
|
|
153
|
+
folderlake mcp ~/Documents/notes # stdio MCP server
|
|
154
|
+
# or the folderlake-mcp entry point
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Tech stack
|
|
158
|
+
|
|
159
|
+
| Layer | Choice |
|
|
160
|
+
|---|---|
|
|
161
|
+
| Language | Python 3.11+, standard library first |
|
|
162
|
+
| Catalog store | **DuckDB** — one `.duckdb` file per scanned folder (files, topics, entities, relations, extracted text, topic tree) |
|
|
163
|
+
| Extraction | `python-docx`, `pypdf`, `openpyxl`, stdlib for txt/md/html/csv/json/sqlite; DuckDB itself reads parquet/csv |
|
|
164
|
+
| Rule topics | Corpus TF-IDF with phrase mining (English word bigrams, CJK bigrams/trigrams), title boost, boilerplate suppression |
|
|
165
|
+
| Topic tree | Agglomerative hierarchical clustering over TF-IDF, per file kind; labels by term lift; rendered as a collapsible tree |
|
|
166
|
+
| LLM tier | Provider layer over **LiteLLM** (optional extra) with a built-in client as fallback — Ollama, OpenAI-compatible, Anthropic, Hugging Face |
|
|
167
|
+
| Viewer | One HTML file, vanilla JS + **Apache ECharts** 5.5.1 (vendored, the only JS dependency), served by stdlib `http.server`; responsive down to phone widths |
|
|
168
|
+
| Graph export | Idempotent Neo4j Cypher (`MERGE` script) |
|
|
169
|
+
| Agent access | Official `mcp` Python SDK (optional extra), stdio transport |
|
|
170
|
+
| App state | SQLite registry (stdlib `sqlite3`) in `~/.folderlake/registry.sqlite3` — catalog history with per-scan settings snapshots, plus the user settings |
|
|
171
|
+
| Tests | pytest — 59 tests covering extractors, topics, relations, hierarchy, server and MCP |
|
|
172
|
+
|
|
173
|
+
### The ML / NLP pipeline, briefly
|
|
174
|
+
|
|
175
|
+
- **Tokenization** — English unigrams + word bigrams; Chinese character
|
|
176
|
+
bigrams/trigrams; data files contribute joined column-name phrases
|
|
177
|
+
(`total_bill` → "total bill").
|
|
178
|
+
- **Phrase mining** — candidate tags are scored with TF-IDF (smoothed
|
|
179
|
+
IDF, title/filename boost ×3, phrase-length bonus); a phrase's
|
|
180
|
+
component words are suppressed so tags read like `car air
|
|
181
|
+
conditioning`, not `car`, `air`, `conditioning`.
|
|
182
|
+
- **LLM classification** — with a model configured, each file's
|
|
183
|
+
"hotspot" segments (~10k chars: opening, densest passages, closing)
|
|
184
|
+
go to the model, which returns structured JSON (tags / summary /
|
|
185
|
+
entities); results are cached by content hash. Data files are
|
|
186
|
+
described by schema + profile statistics only — sample values never
|
|
187
|
+
reach the topic model.
|
|
188
|
+
- **Topic tree** — per-kind agglomerative hierarchical clustering
|
|
189
|
+
(centroid linkage, cosine over L2-normalized TF-IDF vectors, corpus
|
|
190
|
+
document-frequency hygiene); node labels are picked by term *lift*
|
|
191
|
+
against the whole kind, and "forced marriage" nodes whose halves
|
|
192
|
+
barely resemble each other are flattened away.
|
|
193
|
+
- **Entities** — capitalized-sequence extraction (≥2 mentions) in the
|
|
194
|
+
rule tier; the model's entity list in the LLM tier.
|
|
195
|
+
- **Embeddings** — deliberately not used anywhere; see
|
|
196
|
+
[Limitations](#limitations-honestly).
|
|
197
|
+
|
|
198
|
+
## Demo data
|
|
199
|
+
|
|
200
|
+
The 193 files in the live demo are public material, assembled to look
|
|
201
|
+
like a real mixed folder:
|
|
202
|
+
|
|
203
|
+
| Kind | Count | Source |
|
|
204
|
+
|---|---|---|
|
|
205
|
+
| Texts | 130 | Posts sampled from the **20 Newsgroups** corpus, plus **Wikipedia** article excerpts (English and Chinese), as `.txt` |
|
|
206
|
+
| Documents | 33 | **AG News** articles packaged as `.pdf` / `.docx` / `.html`, plus longer Wikipedia articles |
|
|
207
|
+
| Data | 30 | The 10 **seaborn** example datasets (tips, iris, penguins, titanic, diamonds, flights, mpg, planets, exercise, attention), each in 3 formats (CSV / JSON / Parquet or XLSX) |
|
|
208
|
+
|
|
209
|
+
Two Wikipedia leads — *RMS Titanic* and *Penguin* — are included on
|
|
210
|
+
purpose: they are what the Titanic and penguins datasets are *about*,
|
|
211
|
+
so the demo contains genuine text→data links.
|
|
212
|
+
|
|
213
|
+
Wikipedia text is CC BY-SA; 20 Newsgroups and AG News are standard
|
|
214
|
+
public research corpora; the seaborn datasets ship with the
|
|
215
|
+
BSD-licensed seaborn library. The corpus itself is not distributed
|
|
216
|
+
with this repository — only the rendered catalog snapshot is
|
|
217
|
+
published, and articles are trimmed to excerpts.
|
|
218
|
+
|
|
219
|
+
## How the demo was made
|
|
220
|
+
|
|
221
|
+
The live demo is a static snapshot of a real catalog, produced like
|
|
222
|
+
this (all steps reproducible from this repo):
|
|
223
|
+
|
|
224
|
+
1. A 193-file corpus of public datasets was scanned with
|
|
225
|
+
`folderlake scan --llm`.
|
|
226
|
+
2. The "LLM" was **Muse**, served through the stub in
|
|
227
|
+
[`tools/llm_stub.py`](tools/llm_stub.py): it answers FolderLake's
|
|
228
|
+
ordinary OpenAI-compatible requests with responses Muse authored
|
|
229
|
+
for each file — so the demo exercises the real provider layer,
|
|
230
|
+
prompt packaging, JSON parsing and hash caching, with Muse as the
|
|
231
|
+
model. The scan stats reported `llm: 193, rule: 0`.
|
|
232
|
+
3. The viewer was snapshotted to a single self-contained HTML file
|
|
233
|
+
(kept in [`demo/`](demo/) in this repo), which GitHub Actions
|
|
234
|
+
deploys to GitHub Pages on every push that touches it.
|
|
235
|
+
|
|
236
|
+
To recreate the sample folder from the screenshots in early docs:
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
python tools/make_demo_folder.py # writes ./demo_folder (bike shop + garden)
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Benchmarks
|
|
243
|
+
|
|
244
|
+
Measured in a small shared-CPU sandbox VM (absolute numbers will be
|
|
245
|
+
better on a real machine; treat them as a floor):
|
|
246
|
+
|
|
247
|
+
| Operation | Result |
|
|
248
|
+
|---|---|
|
|
249
|
+
| Full scan, 1,000 mixed files (rule topics) | 12.6 s, catalog 1.59 MB |
|
|
250
|
+
| Incremental rescan, nothing changed | 0.09 s |
|
|
251
|
+
| Full scan, 2,000 files (topic pipeline v2) | ~38 s |
|
|
252
|
+
| Topic tree build, 193 files | ~1.5 s |
|
|
253
|
+
|
|
254
|
+
## Limitations, honestly
|
|
255
|
+
|
|
256
|
+
- Relations grow quadratically before deduplication — very large
|
|
257
|
+
folders (5,000+ files) get heavy graph tables; per-tag edge budgets
|
|
258
|
+
are planned but not yet shipped.
|
|
259
|
+
- The topic tree is built for kinds with at most 400 files (the
|
|
260
|
+
clustering is O(N²)); larger kinds keep flat topic facets.
|
|
261
|
+
- Search uses a `LIKE` fallback when DuckDB's FTS extension is
|
|
262
|
+
unavailable.
|
|
263
|
+
- Embeddings are deliberately **not** a dependency: if they are added,
|
|
264
|
+
they will arrive through the same provider layer as the LLM tier,
|
|
265
|
+
never as a bundled torch install.
|
|
266
|
+
|
|
267
|
+
## Development
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
pip install -e ".[llm,mcp]"
|
|
271
|
+
pytest # 59 tests
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Demo data: see [Demo data](#demo-data) above.
|
|
275
|
+
|
|
276
|
+
## License
|
|
277
|
+
|
|
278
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# FolderLake
|
|
2
|
+
|
|
3
|
+
**Point it at a folder. Know what's in it — an LLM reads the files,
|
|
4
|
+
and an MCP server lets your other agents query the result.**
|
|
5
|
+
|
|
6
|
+
FolderLake scans a folder once and builds a local catalog of every file
|
|
7
|
+
in it — **LLM topic tags, a one-line summary and key entities per
|
|
8
|
+
file**, plus a graph of how the files relate to each other. The
|
|
9
|
+
catalog is a single DuckDB file that lives next to your data. No
|
|
10
|
+
server, no account, nothing leaves your machine.
|
|
11
|
+
|
|
12
|
+
- **LLM topics** — plug in a model (Ollama local, OpenAI, Anthropic,
|
|
13
|
+
any OpenAI-compatible endpoint) for phrase tags and real summaries;
|
|
14
|
+
a built-in TF-IDF tagger covers folders with no model configured.
|
|
15
|
+
- **MCP built in** — `folderlake-mcp` exposes the catalog to Claude
|
|
16
|
+
Code, Claude Desktop and other MCP clients: search, browse, inspect
|
|
17
|
+
and traverse the graph without opening the UI.
|
|
18
|
+
- **Zero setup** — one `pip install`, one command, a web UI opens.
|
|
19
|
+
- **Fast** — 1,000 mixed files scan in ~13 s; rescans only touch new or
|
|
20
|
+
changed files (~0.1 s when nothing changed).
|
|
21
|
+
- **Light** — a 1,000-file catalog is ~1.6 MB on disk.
|
|
22
|
+
- **Many formats** — txt, md, html, docx, pdf, csv, tsv, json, parquet,
|
|
23
|
+
xlsx and sqlite out of the box.
|
|
24
|
+
|
|
25
|
+
## Live demo
|
|
26
|
+
|
|
27
|
+
**[Open the demo](https://daizack.github.io/FolderLake/)** — a real
|
|
28
|
+
FolderLake catalog of 193 public files (AG News, 20 Newsgroups, Wikipedia
|
|
29
|
+
articles and the seaborn datasets), browsable in your browser.
|
|
30
|
+
|
|
31
|
+
The demo's topic tags and summaries were generated by **Muse** (Meta's
|
|
32
|
+
AI assistant) acting as FolderLake's LLM tier — see
|
|
33
|
+
[How the demo was made](#how-the-demo-was-made). With no LLM configured,
|
|
34
|
+
FolderLake falls back to a built-in TF-IDF tagger; the demo shows what
|
|
35
|
+
the LLM tier adds: phrase tags like `car air conditioning` instead of
|
|
36
|
+
single words, and a real one-sentence summary per file.
|
|
37
|
+
|
|
38
|
+
Two things worth trying in the demo: click a topic in the tree — the
|
|
39
|
+
list and the graph filter together — and open the Wikipedia article
|
|
40
|
+
on the Titanic: it links straight to the Titanic passenger datasets.
|
|
41
|
+
FolderLake adds a *reference* link whenever a text names a dataset's
|
|
42
|
+
subject (a capitalised mention like "Titanic" or "Penguins"), on top
|
|
43
|
+
of shared topics, shared entities and explicit filename references.
|
|
44
|
+
|
|
45
|
+

|
|
46
|
+
|
|
47
|
+

|
|
48
|
+
|
|
49
|
+

|
|
50
|
+
|
|
51
|
+
<img src="docs/screenshots/mobile.png" alt="FolderLake on a phone" width="320">
|
|
52
|
+
|
|
53
|
+
## Quick start
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install "git+https://github.com/DaiZack/FolderLake"
|
|
57
|
+
# (not on PyPI yet — GitHub is the source; or `pip install -e .` from a clone)
|
|
58
|
+
|
|
59
|
+
folderlake app # opens the FolderLake home page
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+

|
|
63
|
+
|
|
64
|
+
That's the whole setup. `app` opens a home page in your browser right
|
|
65
|
+
away — no dialog, no waiting. Paste or browse to a folder, hit
|
|
66
|
+
**Scan folder**, and watch the progress bar; when the scan finishes
|
|
67
|
+
the catalog opens. Every catalog you build is remembered on the home
|
|
68
|
+
page (stored in a small SQLite registry in `~/.folderlake/`): reopen
|
|
69
|
+
one in a click, **Refresh** it to pick up added/removed files, or
|
|
70
|
+
**Delete** it. Each history entry shows the settings it was scanned
|
|
71
|
+
with. Inside a catalog, a **← FolderLake Home** link brings you back.
|
|
72
|
+
|
|
73
|
+
The catalog viewer itself: filters and the topic tree on the left,
|
|
74
|
+
the file list and the relationship graph side by side in the middle
|
|
75
|
+
(on a phone they take turns, graph first), and a preview pane on the
|
|
76
|
+
right. Clicking a topic filters the list and the graph together.
|
|
77
|
+
Data files get a *data review* in the preview
|
|
78
|
+
pane — schema, null counts, min/max, approximate uniques — plus a
|
|
79
|
+
ready-to-run DuckDB query.
|
|
80
|
+
|
|
81
|
+
Prefer the terminal:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
folderlake scan ~/Documents/notes # build or update the catalog
|
|
85
|
+
folderlake serve ~/Documents/notes # rescan + open the viewer
|
|
86
|
+
folderlake cypher ~/Documents/notes # export the graph as Neo4j Cypher
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Every command is incremental: unchanged files are skipped by file stats
|
|
90
|
+
alone, so re-running on a big folder is cheap.
|
|
91
|
+
|
|
92
|
+
## Using an LLM for topics
|
|
93
|
+
|
|
94
|
+
With no LLM configured you get rule-based TF-IDF phrase tags. Point
|
|
95
|
+
FolderLake at any chat model and each new or changed file gets LLM tags,
|
|
96
|
+
a one-sentence summary and key entities instead (results are cached by
|
|
97
|
+
content hash, so each file is sent exactly once). In the app, the
|
|
98
|
+
home page's Settings card holds the same options — no environment
|
|
99
|
+
variables needed; the environment variables below remain for the
|
|
100
|
+
CLI and scripts:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
pip install "folderlake[llm] @ git+https://github.com/DaiZack/FolderLake"
|
|
104
|
+
# (adds LiteLLM as the transport)
|
|
105
|
+
|
|
106
|
+
# Local model via Ollama (default provider)
|
|
107
|
+
FOLDERLAKE_LLM_MODEL=qwen3 folderlake scan ~/notes --llm
|
|
108
|
+
|
|
109
|
+
# Any OpenAI-compatible endpoint (OpenAI, DeepSeek, Groq, LM Studio, vLLM…)
|
|
110
|
+
FOLDERLAKE_LLM_PROVIDER=openai \
|
|
111
|
+
FOLDERLAKE_LLM_MODEL=gpt-4o-mini \
|
|
112
|
+
FOLDERLAKE_LLM_API_KEY=sk-… \
|
|
113
|
+
folderlake scan ~/notes --llm
|
|
114
|
+
|
|
115
|
+
# Anthropic
|
|
116
|
+
FOLDERLAKE_LLM_PROVIDER=anthropic \
|
|
117
|
+
FOLDERLAKE_LLM_MODEL=claude-haiku-4-5 \
|
|
118
|
+
FOLDERLAKE_LLM_API_KEY=sk-ant-… \
|
|
119
|
+
folderlake scan ~/notes --llm
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Data files are never sent raw: the LLM sees only a profile card
|
|
123
|
+
(`DESCRIBE` + `SUMMARIZE` output — column names and aggregate stats),
|
|
124
|
+
never sample row values. If the LLM is unreachable, the scan falls back
|
|
125
|
+
to rule topics and says so in its stats.
|
|
126
|
+
|
|
127
|
+
## Let other agents query it (MCP)
|
|
128
|
+
|
|
129
|
+
FolderLake ships an MCP server with six read-only tools — `stats`,
|
|
130
|
+
`search`, `list_files`, `file_detail`, `related`, `topics` — so any MCP
|
|
131
|
+
client (Claude Code, Cursor, your own agent) can ask questions about a
|
|
132
|
+
catalog without opening files:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
pip install "folderlake[mcp] @ git+https://github.com/DaiZack/FolderLake"
|
|
136
|
+
folderlake mcp ~/Documents/notes # stdio MCP server
|
|
137
|
+
# or the folderlake-mcp entry point
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Tech stack
|
|
141
|
+
|
|
142
|
+
| Layer | Choice |
|
|
143
|
+
|---|---|
|
|
144
|
+
| Language | Python 3.11+, standard library first |
|
|
145
|
+
| Catalog store | **DuckDB** — one `.duckdb` file per scanned folder (files, topics, entities, relations, extracted text, topic tree) |
|
|
146
|
+
| Extraction | `python-docx`, `pypdf`, `openpyxl`, stdlib for txt/md/html/csv/json/sqlite; DuckDB itself reads parquet/csv |
|
|
147
|
+
| Rule topics | Corpus TF-IDF with phrase mining (English word bigrams, CJK bigrams/trigrams), title boost, boilerplate suppression |
|
|
148
|
+
| Topic tree | Agglomerative hierarchical clustering over TF-IDF, per file kind; labels by term lift; rendered as a collapsible tree |
|
|
149
|
+
| LLM tier | Provider layer over **LiteLLM** (optional extra) with a built-in client as fallback — Ollama, OpenAI-compatible, Anthropic, Hugging Face |
|
|
150
|
+
| Viewer | One HTML file, vanilla JS + **Apache ECharts** 5.5.1 (vendored, the only JS dependency), served by stdlib `http.server`; responsive down to phone widths |
|
|
151
|
+
| Graph export | Idempotent Neo4j Cypher (`MERGE` script) |
|
|
152
|
+
| Agent access | Official `mcp` Python SDK (optional extra), stdio transport |
|
|
153
|
+
| App state | SQLite registry (stdlib `sqlite3`) in `~/.folderlake/registry.sqlite3` — catalog history with per-scan settings snapshots, plus the user settings |
|
|
154
|
+
| Tests | pytest — 59 tests covering extractors, topics, relations, hierarchy, server and MCP |
|
|
155
|
+
|
|
156
|
+
### The ML / NLP pipeline, briefly
|
|
157
|
+
|
|
158
|
+
- **Tokenization** — English unigrams + word bigrams; Chinese character
|
|
159
|
+
bigrams/trigrams; data files contribute joined column-name phrases
|
|
160
|
+
(`total_bill` → "total bill").
|
|
161
|
+
- **Phrase mining** — candidate tags are scored with TF-IDF (smoothed
|
|
162
|
+
IDF, title/filename boost ×3, phrase-length bonus); a phrase's
|
|
163
|
+
component words are suppressed so tags read like `car air
|
|
164
|
+
conditioning`, not `car`, `air`, `conditioning`.
|
|
165
|
+
- **LLM classification** — with a model configured, each file's
|
|
166
|
+
"hotspot" segments (~10k chars: opening, densest passages, closing)
|
|
167
|
+
go to the model, which returns structured JSON (tags / summary /
|
|
168
|
+
entities); results are cached by content hash. Data files are
|
|
169
|
+
described by schema + profile statistics only — sample values never
|
|
170
|
+
reach the topic model.
|
|
171
|
+
- **Topic tree** — per-kind agglomerative hierarchical clustering
|
|
172
|
+
(centroid linkage, cosine over L2-normalized TF-IDF vectors, corpus
|
|
173
|
+
document-frequency hygiene); node labels are picked by term *lift*
|
|
174
|
+
against the whole kind, and "forced marriage" nodes whose halves
|
|
175
|
+
barely resemble each other are flattened away.
|
|
176
|
+
- **Entities** — capitalized-sequence extraction (≥2 mentions) in the
|
|
177
|
+
rule tier; the model's entity list in the LLM tier.
|
|
178
|
+
- **Embeddings** — deliberately not used anywhere; see
|
|
179
|
+
[Limitations](#limitations-honestly).
|
|
180
|
+
|
|
181
|
+
## Demo data
|
|
182
|
+
|
|
183
|
+
The 193 files in the live demo are public material, assembled to look
|
|
184
|
+
like a real mixed folder:
|
|
185
|
+
|
|
186
|
+
| Kind | Count | Source |
|
|
187
|
+
|---|---|---|
|
|
188
|
+
| Texts | 130 | Posts sampled from the **20 Newsgroups** corpus, plus **Wikipedia** article excerpts (English and Chinese), as `.txt` |
|
|
189
|
+
| Documents | 33 | **AG News** articles packaged as `.pdf` / `.docx` / `.html`, plus longer Wikipedia articles |
|
|
190
|
+
| Data | 30 | The 10 **seaborn** example datasets (tips, iris, penguins, titanic, diamonds, flights, mpg, planets, exercise, attention), each in 3 formats (CSV / JSON / Parquet or XLSX) |
|
|
191
|
+
|
|
192
|
+
Two Wikipedia leads — *RMS Titanic* and *Penguin* — are included on
|
|
193
|
+
purpose: they are what the Titanic and penguins datasets are *about*,
|
|
194
|
+
so the demo contains genuine text→data links.
|
|
195
|
+
|
|
196
|
+
Wikipedia text is CC BY-SA; 20 Newsgroups and AG News are standard
|
|
197
|
+
public research corpora; the seaborn datasets ship with the
|
|
198
|
+
BSD-licensed seaborn library. The corpus itself is not distributed
|
|
199
|
+
with this repository — only the rendered catalog snapshot is
|
|
200
|
+
published, and articles are trimmed to excerpts.
|
|
201
|
+
|
|
202
|
+
## How the demo was made
|
|
203
|
+
|
|
204
|
+
The live demo is a static snapshot of a real catalog, produced like
|
|
205
|
+
this (all steps reproducible from this repo):
|
|
206
|
+
|
|
207
|
+
1. A 193-file corpus of public datasets was scanned with
|
|
208
|
+
`folderlake scan --llm`.
|
|
209
|
+
2. The "LLM" was **Muse**, served through the stub in
|
|
210
|
+
[`tools/llm_stub.py`](tools/llm_stub.py): it answers FolderLake's
|
|
211
|
+
ordinary OpenAI-compatible requests with responses Muse authored
|
|
212
|
+
for each file — so the demo exercises the real provider layer,
|
|
213
|
+
prompt packaging, JSON parsing and hash caching, with Muse as the
|
|
214
|
+
model. The scan stats reported `llm: 193, rule: 0`.
|
|
215
|
+
3. The viewer was snapshotted to a single self-contained HTML file
|
|
216
|
+
(kept in [`demo/`](demo/) in this repo), which GitHub Actions
|
|
217
|
+
deploys to GitHub Pages on every push that touches it.
|
|
218
|
+
|
|
219
|
+
To recreate the sample folder from the screenshots in early docs:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
python tools/make_demo_folder.py # writes ./demo_folder (bike shop + garden)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Benchmarks
|
|
226
|
+
|
|
227
|
+
Measured in a small shared-CPU sandbox VM (absolute numbers will be
|
|
228
|
+
better on a real machine; treat them as a floor):
|
|
229
|
+
|
|
230
|
+
| Operation | Result |
|
|
231
|
+
|---|---|
|
|
232
|
+
| Full scan, 1,000 mixed files (rule topics) | 12.6 s, catalog 1.59 MB |
|
|
233
|
+
| Incremental rescan, nothing changed | 0.09 s |
|
|
234
|
+
| Full scan, 2,000 files (topic pipeline v2) | ~38 s |
|
|
235
|
+
| Topic tree build, 193 files | ~1.5 s |
|
|
236
|
+
|
|
237
|
+
## Limitations, honestly
|
|
238
|
+
|
|
239
|
+
- Relations grow quadratically before deduplication — very large
|
|
240
|
+
folders (5,000+ files) get heavy graph tables; per-tag edge budgets
|
|
241
|
+
are planned but not yet shipped.
|
|
242
|
+
- The topic tree is built for kinds with at most 400 files (the
|
|
243
|
+
clustering is O(N²)); larger kinds keep flat topic facets.
|
|
244
|
+
- Search uses a `LIKE` fallback when DuckDB's FTS extension is
|
|
245
|
+
unavailable.
|
|
246
|
+
- Embeddings are deliberately **not** a dependency: if they are added,
|
|
247
|
+
they will arrive through the same provider layer as the LLM tier,
|
|
248
|
+
never as a bundled torch install.
|
|
249
|
+
|
|
250
|
+
## Development
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
pip install -e ".[llm,mcp]"
|
|
254
|
+
pytest # 59 tests
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Demo data: see [Demo data](#demo-data) above.
|
|
258
|
+
|
|
259
|
+
## License
|
|
260
|
+
|
|
261
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "folderlake"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
description = "Point it at a folder. Know what's in it. A local file catalog with topics, metadata and a relationship graph, stored in one DuckDB file."
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"duckdb",
|
|
13
|
+
"python-docx",
|
|
14
|
+
"pypdf",
|
|
15
|
+
"openpyxl",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.optional-dependencies]
|
|
19
|
+
llm = ["litellm>=1.40"]
|
|
20
|
+
mcp = ["mcp>=1.0"]
|
|
21
|
+
|
|
22
|
+
[project.scripts]
|
|
23
|
+
folderlake = "folderlake.cli:main"
|
|
24
|
+
folderlake-mcp = "folderlake.mcp_server:main"
|
|
25
|
+
|
|
26
|
+
[tool.setuptools.packages.find]
|
|
27
|
+
where = ["src"]
|
|
28
|
+
|
|
29
|
+
[tool.setuptools.package-data]
|
|
30
|
+
folderlake = ["*.html", "assets/*.js"]
|
|
31
|
+
|
|
32
|
+
[tool.pytest.ini_options]
|
|
33
|
+
testpaths = ["tests"]
|