threadshelf 1.2.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.
- package/CHANGELOG.md +185 -0
- package/LICENSE +21 -0
- package/README.md +763 -0
- package/SECURITY.md +75 -0
- package/bin/threadshelf-mcp.js +12 -0
- package/bin/threadshelf.js +87 -0
- package/dist/mcp/server.js +388 -0
- package/dist/src/chunking.js +72 -0
- package/dist/src/cli.js +24 -0
- package/dist/src/embedding.js +59 -0
- package/dist/src/env.js +2 -0
- package/dist/src/generation/config.js +344 -0
- package/dist/src/generation/downloader.js +172 -0
- package/dist/src/generation/error-log.js +34 -0
- package/dist/src/generation/filesystem-browser.js +83 -0
- package/dist/src/generation/gguf-metadata.js +179 -0
- package/dist/src/generation/hardware.js +87 -0
- package/dist/src/generation/llama-install.js +563 -0
- package/dist/src/generation/llama-process.js +576 -0
- package/dist/src/generation/llama-profile.js +136 -0
- package/dist/src/generation/master-prompts.js +155 -0
- package/dist/src/generation/model-catalog.js +276 -0
- package/dist/src/generation/model-discovery.js +60 -0
- package/dist/src/generation/model-download.js +151 -0
- package/dist/src/generation/openai-compatible.js +231 -0
- package/dist/src/generation/providers/llama-cpp.js +97 -0
- package/dist/src/generation/providers/openrouter.js +106 -0
- package/dist/src/generation/quick-setup.js +215 -0
- package/dist/src/generation/registry.js +23 -0
- package/dist/src/generation/service.js +100 -0
- package/dist/src/generation/threads.js +311 -0
- package/dist/src/generation/types.js +1 -0
- package/dist/src/ingest-cli.js +95 -0
- package/dist/src/ingest.js +257 -0
- package/dist/src/load-env.js +17 -0
- package/dist/src/model-label.js +15 -0
- package/dist/src/parser.js +811 -0
- package/dist/src/paths.js +79 -0
- package/dist/src/routes/collections.js +97 -0
- package/dist/src/routes/files.js +136 -0
- package/dist/src/routes/generation.js +536 -0
- package/dist/src/routes/health.js +6 -0
- package/dist/src/routes/index.js +21 -0
- package/dist/src/routes/ingest.js +300 -0
- package/dist/src/routes/insights.js +24 -0
- package/dist/src/routes/loopback.js +15 -0
- package/dist/src/routes/model-catalog.js +178 -0
- package/dist/src/routes/search.js +57 -0
- package/dist/src/routes/stream-abort.js +23 -0
- package/dist/src/routes/thread.js +43 -0
- package/dist/src/search-cli.js +93 -0
- package/dist/src/server.js +78 -0
- package/dist/src/services/collections.js +58 -0
- package/dist/src/services/insights.js +111 -0
- package/dist/src/services/search.js +68 -0
- package/dist/src/services/stats.js +35 -0
- package/dist/src/services/thread.js +140 -0
- package/dist/src/store.js +1138 -0
- package/dist/src/validation.js +250 -0
- package/dist/src/watch.js +83 -0
- package/package.json +103 -0
- package/public/assets/index-CIm_Idqi.js +38 -0
- package/public/assets/index-Dv09K2vS.css +1 -0
- package/public/favicon.svg +6 -0
- package/public/index.html +28 -0
- package/scripts/openrouter-export-all.js +228 -0
- package/scripts/openrouter-export-browser.js +153 -0
package/README.md
ADDED
|
@@ -0,0 +1,763 @@
|
|
|
1
|
+
# ThreadShelf
|
|
2
|
+
|
|
3
|
+
**Local-first archive, semantic search, and continuation for your AI conversations.**
|
|
4
|
+
|
|
5
|
+
[](https://github.com/ChrystianSchutz/ThreadShelf/actions/workflows/ci.yml)
|
|
6
|
+
[](package.json)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](#privacy-boundary)
|
|
9
|
+
|
|
10
|
+
One private workspace across **ChatGPT, Claude, Google AI Studio, OpenRouter,
|
|
11
|
+
LM Studio, and Grok**. Search old conversations by meaning, reopen the complete
|
|
12
|
+
thread, and continue it with a local GGUF model through `llama.cpp` or the
|
|
13
|
+
explicitly external OpenRouter provider.
|
|
14
|
+
|
|
15
|
+
The archive pipeline—parsing, embeddings, LanceDB storage, search, HTTP API, and
|
|
16
|
+
MCP—runs locally. Conversation generation is an **Experimental Beta**:
|
|
17
|
+
`llama.cpp` stays loopback-only; switching to the clearly marked
|
|
18
|
+
**OpenRouter · external** provider sends the selected user/assistant context and
|
|
19
|
+
new prompt off-device.
|
|
20
|
+
|
|
21
|
+

|
|
22
|
+
|
|
23
|
+
| Capability | What ThreadShelf provides |
|
|
24
|
+
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
25
|
+
| **Archive & retrieval** | Multi-provider normalization, local multilingual embeddings, semantic and exact search, complete thread reconstruction |
|
|
26
|
+
| **Continue & create** | Local GGUF inference through managed `llama.cpp`, plus optional OpenRouter streaming |
|
|
27
|
+
| **Use it anywhere** | React UI, command-line ingest/search, HTTP API, and an MCP stdio server over the same index |
|
|
28
|
+
| **Keep control** | Loopback defaults, isolated local storage, explicit off-device labeling, synthetic test data |
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## What it does (in 30 seconds)
|
|
33
|
+
|
|
34
|
+
1. You export/copy your chats as JSON (see [Get your data](#get-your-data)).
|
|
35
|
+
2. ThreadShelf parses them into a common format, embeds them **locally**, and
|
|
36
|
+
stores them in a local vector database (LanceDB).
|
|
37
|
+
3. You search by meaning in the web UI, open the full original thread, and export
|
|
38
|
+
any conversation to Markdown — or query the same index from an MCP client.
|
|
39
|
+
4. Optionally start a new chat or continue an archived thread through local
|
|
40
|
+
`llama.cpp` or explicitly external OpenRouter.
|
|
41
|
+
|
|
42
|
+
## Architecture at a glance
|
|
43
|
+
|
|
44
|
+
```mermaid
|
|
45
|
+
flowchart LR
|
|
46
|
+
exports["AI chat exports"] --> parser["Provider parsers"]
|
|
47
|
+
parser --> turns["Normalized turns"]
|
|
48
|
+
turns --> chunks["Chunks"]
|
|
49
|
+
chunks --> embed["Local embeddings"]
|
|
50
|
+
embed --> db[("LanceDB + thread store")]
|
|
51
|
+
db --> ui["React UI"]
|
|
52
|
+
db --> api["HTTP API"]
|
|
53
|
+
db --> mcp["MCP stdio"]
|
|
54
|
+
db --> generation["Generation registry"]
|
|
55
|
+
generation --> llama["llama.cpp<br/>local GGUF"]
|
|
56
|
+
generation -. "explicit off-device provider" .-> openrouter["OpenRouter"]
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## How is this different from other history viewers?
|
|
60
|
+
|
|
61
|
+
- **One normalized archive across six providers.** It includes sources whose
|
|
62
|
+
history is otherwise split between exports, Drive files, browser pages, and
|
|
63
|
+
local application data.
|
|
64
|
+
- **Semantic, not just keyword.** Find a conversation by topic even when you don't
|
|
65
|
+
remember the exact words — plus an exact-match mode for when you do (error
|
|
66
|
+
strings, identifiers, code).
|
|
67
|
+
- **A local archive with an optional generation layer.** Search remains useful
|
|
68
|
+
without configuring any LLM. When generation is wanted, the primary engine is
|
|
69
|
+
a loopback-only `llama.cpp` server; OpenRouter is a separately marked external
|
|
70
|
+
choice.
|
|
71
|
+
- **One index, three front-ends.** The same archive is searchable from the web UI,
|
|
72
|
+
the HTTP API, and any MCP-capable tool.
|
|
73
|
+
|
|
74
|
+
## Supported sources
|
|
75
|
+
|
|
76
|
+
| Source | How export works | Format stability |
|
|
77
|
+
| ---------------------- | ---------------------------------------------- | ------------------------------------ |
|
|
78
|
+
| **Google AI Studio** | Download your Drive "Google AI Studio" folder | ⚠️ undocumented, tested July 2026 |
|
|
79
|
+
| **OpenRouter** | Browser-console export script (this repo) | ⚠️ undocumented |
|
|
80
|
+
| **LM Studio** | Copy local conversation files | ⚠️ undocumented, tested on **0.4.x** |
|
|
81
|
+
| **Grok / xAI** | Account data export (`prod-grok-backend.json`) | ⚠️ undocumented |
|
|
82
|
+
| **ChatGPT / OpenAI** | Official data export (`conversations.json`) | semi-stable |
|
|
83
|
+
| **Claude / Anthropic** | Official data export | semi-stable |
|
|
84
|
+
|
|
85
|
+
> ⚠️ **Format note.** Google AI Studio, OpenRouter, LM Studio, and Grok have no
|
|
86
|
+
> documented, stable export schema — their vendors can change it without warning.
|
|
87
|
+
> ThreadShelf is tested against snapshots of specific versions (e.g. LM Studio
|
|
88
|
+
> 0.4.x). If a newer app version changes the shape, parsing may break; please open
|
|
89
|
+
> an issue with an anonymized sample.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Get your data
|
|
94
|
+
|
|
95
|
+
You only need the providers you actually use. Put exported files anywhere, then
|
|
96
|
+
point ThreadShelf at that folder.
|
|
97
|
+
|
|
98
|
+
### Google AI Studio
|
|
99
|
+
|
|
100
|
+
Your prompts are saved to **Google Drive** in a folder named **`Google AI Studio`**.
|
|
101
|
+
|
|
102
|
+
1. Open [Google Drive](https://drive.google.com), find the `Google AI Studio` folder.
|
|
103
|
+
2. Right-click → **Download** (Drive zips it). Unzip somewhere local.
|
|
104
|
+
3. Index that folder. (Files have no `.json` extension — that's fine, they're
|
|
105
|
+
detected by content.)
|
|
106
|
+
|
|
107
|
+
Text chats and Imagen prompt histories from the July 2026 Drive export shape are
|
|
108
|
+
supported. The format is undocumented and may change.
|
|
109
|
+
|
|
110
|
+
### OpenRouter
|
|
111
|
+
|
|
112
|
+
OpenRouter has no bulk export, so this repo ships two browser-console scripts:
|
|
113
|
+
|
|
114
|
+
- **All chats:** [`scripts/openrouter-export-all.js`](scripts/openrouter-export-all.js)
|
|
115
|
+
— walks every chat in your sidebar and downloads one JSON per chat.
|
|
116
|
+
- **Single chat:** [`scripts/openrouter-export-browser.js`](scripts/openrouter-export-browser.js)
|
|
117
|
+
— exports just the chat currently open.
|
|
118
|
+
|
|
119
|
+
To export everything:
|
|
120
|
+
|
|
121
|
+
1. Open [openrouter.ai](https://openrouter.ai/) signed in, with your chat list
|
|
122
|
+
(sidebar) visible.
|
|
123
|
+
2. Open DevTools → Console (`F12` → _Console_ tab).
|
|
124
|
+
3. Copy the entire contents of `scripts/openrouter-export-all.js`, paste into the
|
|
125
|
+
console, press Enter.
|
|
126
|
+
4. The script clicks each chat, scrolls to load full history, and downloads one
|
|
127
|
+
JSON per chat. Allow "**multiple downloads**" if the browser asks.
|
|
128
|
+
5. Move the downloaded files into a folder and index that folder.
|
|
129
|
+
|
|
130
|
+
> The selectors are pinned to OpenRouter's current chat UI and covered by
|
|
131
|
+
> `test/playwright/openrouter-export.spec.js`. If OpenRouter changes its markup and
|
|
132
|
+
> the script finds no chats, that test is where to update the contract.
|
|
133
|
+
|
|
134
|
+
You'll get a file shaped like this (this is what the parser reads):
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"platform": "openrouter",
|
|
139
|
+
"exportedAt": "2026-05-24T12:00:00.000Z",
|
|
140
|
+
"pageTitle": "OpenRouter chat title",
|
|
141
|
+
"sourceUrl": "https://openrouter.ai/chat/...",
|
|
142
|
+
"turns": [
|
|
143
|
+
{ "role": "user", "content": "User message text" },
|
|
144
|
+
{ "role": "assistant", "content": "Assistant reply", "model": "optional/model-name" }
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Verify a download before indexing:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
npm run parse -- path/to/openrouter-export.json
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
See [docs/OPENROUTER.md](docs/OPENROUTER.md) for details and limitations.
|
|
156
|
+
|
|
157
|
+
### LM Studio
|
|
158
|
+
|
|
159
|
+
LM Studio stores **one JSON file per conversation** locally — no export step
|
|
160
|
+
needed, just point ThreadShelf at the folder (paths below):
|
|
161
|
+
|
|
162
|
+
| OS | Path |
|
|
163
|
+
| ------------- | ---------------------------------------------------- |
|
|
164
|
+
| Windows | `%USERPROFILE%\.lmstudio\conversations\` |
|
|
165
|
+
| macOS / Linux | `~/.lmstudio/conversations/` |
|
|
166
|
+
| Older builds | `~/.cache/lm-studio/conversations/` (check here too) |
|
|
167
|
+
|
|
168
|
+
Paste that path straight into the **manual folder path** box and index it — or copy
|
|
169
|
+
it somewhere first:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# macOS / Linux
|
|
173
|
+
cp -r ~/.lmstudio/conversations ~/lmstudio-export
|
|
174
|
+
|
|
175
|
+
# Windows (PowerShell)
|
|
176
|
+
Copy-Item "$env:USERPROFILE\.lmstudio\conversations" "$env:USERPROFILE\lmstudio-export" -Recurse
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Files are named `<id>.conversation.json` and look like this (trimmed — the parser
|
|
180
|
+
reads `messages[].versions[currentlySelected]`, splitting `thinking` steps from the
|
|
181
|
+
answer):
|
|
182
|
+
|
|
183
|
+
```json
|
|
184
|
+
{
|
|
185
|
+
"name": "My chat",
|
|
186
|
+
"createdAt": 1700000000000,
|
|
187
|
+
"lastUsedModel": { "identifier": "gpt-oss-20b" },
|
|
188
|
+
"messages": [
|
|
189
|
+
{
|
|
190
|
+
"versions": [
|
|
191
|
+
{ "type": "singleStep", "role": "user", "content": [{ "type": "text", "text": "Hi" }] }
|
|
192
|
+
],
|
|
193
|
+
"currentlySelected": 0
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
"versions": [
|
|
197
|
+
{
|
|
198
|
+
"type": "multiStep",
|
|
199
|
+
"role": "assistant",
|
|
200
|
+
"steps": [
|
|
201
|
+
{
|
|
202
|
+
"type": "contentBlock",
|
|
203
|
+
"style": { "type": "thinking" },
|
|
204
|
+
"content": [{ "type": "text", "text": "reasoning…" }]
|
|
205
|
+
},
|
|
206
|
+
{ "type": "contentBlock", "content": [{ "type": "text", "text": "Hello!" }] }
|
|
207
|
+
]
|
|
208
|
+
}
|
|
209
|
+
],
|
|
210
|
+
"currentlySelected": 0
|
|
211
|
+
}
|
|
212
|
+
]
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Conversation folders/subfolders are walked recursively. _Tested on LM Studio 0.4.x
|
|
217
|
+
(0.4.16); the format is undocumented and may change._
|
|
218
|
+
|
|
219
|
+
> **Note — LM Studio's local server ("router").** LM Studio can also run an
|
|
220
|
+
> OpenAI-compatible local server (Developer tab, default `http://localhost:1234`)
|
|
221
|
+
> that routes requests across loaded models. That's an _inference_ endpoint, not an
|
|
222
|
+
> export — ThreadShelf indexes the on-disk `conversations/` files above, so you
|
|
223
|
+
> don't need the server running to import or search your history.
|
|
224
|
+
|
|
225
|
+
### ChatGPT / OpenAI
|
|
226
|
+
|
|
227
|
+
ChatGPT → **Settings → Data controls → Export data**. You'll get an email with a
|
|
228
|
+
zip; index the `conversations.json` inside it.
|
|
229
|
+
|
|
230
|
+
### Claude / Anthropic
|
|
231
|
+
|
|
232
|
+
Claude → **Settings → export your data**. Index the exported conversation JSON.
|
|
233
|
+
|
|
234
|
+
### Grok / xAI
|
|
235
|
+
|
|
236
|
+
Grok → request your **account data export** (xAI account/privacy settings). You'll
|
|
237
|
+
get a download containing a `prod-grok-backend.json` somewhere under an
|
|
238
|
+
`export_data/.../` folder. Index the folder that contains it — ThreadShelf
|
|
239
|
+
detects the file by content, so the surrounding directory names don't matter.
|
|
240
|
+
|
|
241
|
+
The file holds every conversation in one document, shaped roughly like this (the
|
|
242
|
+
parser reads `conversations[].responses[].response`, splitting the model's
|
|
243
|
+
`agent_thinking_traces` reasoning from its `message` answer):
|
|
244
|
+
|
|
245
|
+
```json
|
|
246
|
+
{
|
|
247
|
+
"conversations": [
|
|
248
|
+
{
|
|
249
|
+
"conversation": {
|
|
250
|
+
"id": "…",
|
|
251
|
+
"title": "My chat",
|
|
252
|
+
"create_time": { "$date": { "$numberLong": "1771000000000" } }
|
|
253
|
+
},
|
|
254
|
+
"responses": [
|
|
255
|
+
{
|
|
256
|
+
"response": {
|
|
257
|
+
"sender": "human",
|
|
258
|
+
"message": "Hi",
|
|
259
|
+
"create_time": { "$date": { "$numberLong": "1771000000000" } }
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
"response": {
|
|
264
|
+
"sender": "assistant",
|
|
265
|
+
"message": "Hello!",
|
|
266
|
+
"model": "grok-3",
|
|
267
|
+
"agent_thinking_traces": [{ "thinking_trace": "reasoning…" }]
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
]
|
|
271
|
+
}
|
|
272
|
+
]
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
_Timestamps are MongoDB extended JSON (`{ "$date": { "$numberLong": … } }`). The
|
|
277
|
+
format is undocumented and may change._
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Quick Start
|
|
282
|
+
|
|
283
|
+
Requires **Node.js 20.19+** and npm.
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
npx threadshelf # downloads, starts, opens on http://localhost:3000
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
That is the whole install. The package ships the prebuilt web UI, so there is
|
|
290
|
+
nothing to compile and no repository to clone. Pick another port with
|
|
291
|
+
`npx threadshelf 3001`, and see `npx threadshelf --help` for the rest.
|
|
292
|
+
|
|
293
|
+
Your archive is **never** stored inside the npm package: an `npx` install
|
|
294
|
+
directory is disposable and npm may wipe it at any time. Persistent data lives
|
|
295
|
+
in a per-user directory instead, so upgrading or clearing the npm cache leaves
|
|
296
|
+
your index untouched.
|
|
297
|
+
|
|
298
|
+
| Platform | Default data directory |
|
|
299
|
+
| ------------- | -------------------------------------------- |
|
|
300
|
+
| Windows | `%LOCALAPPDATA%\ThreadShelf` |
|
|
301
|
+
| macOS / Linux | `~/.threadshelf` |
|
|
302
|
+
|
|
303
|
+
Override it with `--data-dir <path>` or `THREADSHELF_DATA_DIR`, and print the
|
|
304
|
+
resolved locations with `npx threadshelf --where`. The narrower overrides
|
|
305
|
+
(`LANCEDB_PATH`, `UPLOADS_DIR`, …) still win over the defaults.
|
|
306
|
+
|
|
307
|
+
### Run from a clone instead
|
|
308
|
+
|
|
309
|
+
Developing on ThreadShelf, or just prefer the source?
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
npm install # installs server + client (npm workspaces)
|
|
313
|
+
npm run build:client # builds the React UI into public/
|
|
314
|
+
npm start # serves on http://localhost:3000
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
A repository checkout keeps its data in the repo (`.lancedb/`, `.threadshelf/`,
|
|
318
|
+
`.collections.json`) exactly as before, so a clone and an `npx` install never
|
|
319
|
+
fight over the same files.
|
|
320
|
+
|
|
321
|
+
### Use ThreadShelf from an MCP client
|
|
322
|
+
|
|
323
|
+
ThreadShelf includes a local stdio MCP server over the same archive used by the
|
|
324
|
+
web UI and HTTP API.
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
npm run mcp
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
It exposes five tools for listing indexed data, inspecting statistics, semantic
|
|
331
|
+
or exact search, and retrieving complete conversations.
|
|
332
|
+
|
|
333
|
+
See [MCP setup](docs/MCP.md) for clean-clone installation, client configuration,
|
|
334
|
+
and security notes.
|
|
335
|
+
|
|
336
|
+
Then in the browser:
|
|
337
|
+
|
|
338
|
+
1. **Create or select a collection** (think of it as a folder/project, e.g.
|
|
339
|
+
`ai_studio`, `chatgpt`, `work_2026`). Start with a throwaway one.
|
|
340
|
+
2. **Select a folder** of exports, or paste an absolute folder path.
|
|
341
|
+
3. Click **Index folder** and watch live progress.
|
|
342
|
+
4. **Search** in natural language; use role filters (user / thinking / response).
|
|
343
|
+
Switch **Semantic → Exact** for case-insensitive substring matches — best for
|
|
344
|
+
identifiers, error messages, and code fragments the embedding model blurs.
|
|
345
|
+
5. **Click a result** to open the full source thread; copy or export to
|
|
346
|
+
Markdown or JSON.
|
|
347
|
+
|
|
348
|
+
Use **Stop indexing** to cancel an active run. The current embedding batch is
|
|
349
|
+
allowed to finish safely; completed files stay indexed and an uncommitted file
|
|
350
|
+
keeps its previous index rows.
|
|
351
|
+
|
|
352
|
+
> First indexing is slow while the local embedding model downloads once, then it's
|
|
353
|
+
> cached. Working on the UI? `npm run dev` (backend) + `npm run dev:client` (Vite
|
|
354
|
+
> hot reload on :5173, API proxied to :3000).
|
|
355
|
+
|
|
356
|
+
For scheduled local backups or headless indexing:
|
|
357
|
+
|
|
358
|
+
```bash
|
|
359
|
+
npm run ingest -- path/to/exports work_2026
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Omit the collection to use `chunks`. Add `--clear` only when you want to wipe the
|
|
363
|
+
collection before indexing.
|
|
364
|
+
|
|
365
|
+
To keep a folder indexed as it changes (LM Studio rewrites its conversation
|
|
366
|
+
files as you chat; AI Studio folders grow), add `--watch`:
|
|
367
|
+
|
|
368
|
+
```bash
|
|
369
|
+
npm run ingest -- ~/.lmstudio/conversations lmstudio -- --watch
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
After the initial pass the process stays running, watches the folder
|
|
373
|
+
recursively, and re-indexes just the files that changed (debounced; tune with
|
|
374
|
+
`--debounce <ms>`). Deleting a source file never removes it from the index —
|
|
375
|
+
ThreadShelf is an archive, and indexed conversations outlive their files.
|
|
376
|
+
|
|
377
|
+
You can also search straight from the terminal:
|
|
378
|
+
|
|
379
|
+
```bash
|
|
380
|
+
npm run search -- "that regex for polish postal codes"
|
|
381
|
+
npm run search -- "ECONNREFUSED 127.0.0.1" -- --mode keyword --n 5
|
|
382
|
+
npm run search -- "prompt injection" -- --collection work_2026 --json
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
The second `--` before CLI options is intentional: current npm versions can
|
|
386
|
+
otherwise consume option names that appear after positional arguments.
|
|
387
|
+
|
|
388
|
+
Full walkthrough: [docs/GETTING_STARTED.md](docs/GETTING_STARTED.md).
|
|
389
|
+
|
|
390
|
+
## Browse complete conversations
|
|
391
|
+
|
|
392
|
+

|
|
393
|
+
|
|
394
|
+
Click any result to open the full source thread, jump to the matched turn,
|
|
395
|
+
filter user/reasoning/response roles, copy text, or export the conversation to
|
|
396
|
+
Markdown or JSON. With no query, the search page lists all indexed
|
|
397
|
+
conversations with provider badges — sort them by recency, length, or title,
|
|
398
|
+
and narrow the list with the filter box.
|
|
399
|
+
|
|
400
|
+
- **More like this** — one click on any search result or thread turn runs a
|
|
401
|
+
semantic search seeded with that passage, for "I know I discussed this
|
|
402
|
+
somewhere else" moments.
|
|
403
|
+
- **Saved searches & pins** — star a query (with its filters and mode) to rerun
|
|
404
|
+
it later, and pin conversations to keep them at the top of the browse list.
|
|
405
|
+
Both are stored in your browser's localStorage; nothing leaves the machine.
|
|
406
|
+
|
|
407
|
+
## Experimental generation: llama.cpp + OpenRouter
|
|
408
|
+
|
|
409
|
+

|
|
410
|
+
|
|
411
|
+
> **Experimental Beta.** The archive/search path is the stable release scope;
|
|
412
|
+
> generation interfaces and model compatibility may still change. Original
|
|
413
|
+
> export files are never modified.
|
|
414
|
+
|
|
415
|
+
Use **New chat** to start a locally saved ThreadShelf conversation, or open an
|
|
416
|
+
imported thread and choose **Continue this conversation**:
|
|
417
|
+
|
|
418
|
+
- **llama.cpp · local** — the primary engine. ThreadShelf discovers GGUF files,
|
|
419
|
+
launches a loopback-only `llama-server`, streams tokens, reports CPU/GPU/hybrid
|
|
420
|
+
placement, and can eject the model from memory without deleting it.
|
|
421
|
+
- **OpenRouter · external** — an optional provider with a live model catalog.
|
|
422
|
+
Selecting this tab is the off-device choice: the model button carries an
|
|
423
|
+
`off-device` chip and the composer says that the request is sent through
|
|
424
|
+
OpenRouter. Archived `thinking` turns are excluded. Optional ZDR-only and
|
|
425
|
+
data-collection-denial routing can reduce eligible providers.
|
|
426
|
+
|
|
427
|
+
Completed chats and archive continuations are stored locally, embedded, and
|
|
428
|
+
searchable. A ghost-icon **Private conversation** is tab-scoped and is never
|
|
429
|
+
written to the thread store or semantic index. Failed or stopped streams remain
|
|
430
|
+
in a clearly marked, unsaved recovery card for the current tab.
|
|
431
|
+
|
|
432
|
+
The model menu discovers common LM Studio, llama.cpp, and Hugging Face model
|
|
433
|
+
directories plus custom roots. It separates model selection from context/output
|
|
434
|
+
settings, supports favorites, and exposes detailed runtime logs only on demand.
|
|
435
|
+
Active streams hold a model lease so concurrent eject or configuration changes
|
|
436
|
+
cannot unload a model mid-response.
|
|
437
|
+
|
|
438
|
+
### Guided setup: runtime and model in one confirmation
|
|
439
|
+
|
|
440
|
+
**Settings → Conversation generation → Set up local generation** resolves one
|
|
441
|
+
plan for the whole first run: the official `llama.cpp` build for this machine,
|
|
442
|
+
plus a GGUF model sized for the detected accelerator. The plan is shown before
|
|
443
|
+
anything is fetched — every URL, SHA-256 digest, size, and destination — and one
|
|
444
|
+
confirmation runs it. Nothing downloads until you confirm, and Cancel stops an
|
|
445
|
+
in-flight transfer immediately.
|
|
446
|
+
|
|
447
|
+
The plan is re-resolved on the server when you confirm; a client can never hand
|
|
448
|
+
the server a URL to fetch and execute. If the resolved plan no longer matches the
|
|
449
|
+
one you approved — a nightly build moved, the catalog changed, free VRAM shifted
|
|
450
|
+
the recommendation — the run stops and shows the new plan for a fresh
|
|
451
|
+
confirmation instead of downloading something you never agreed to.
|
|
452
|
+
|
|
453
|
+
### Browse and download models from Hugging Face
|
|
454
|
+
|
|
455
|
+
**Settings → Conversation generation → Download a model…** — or the same action
|
|
456
|
+
in the chat's model menu — opens a read-only browser over the public Hugging
|
|
457
|
+
Face API. It needs no account or token: search
|
|
458
|
+
or sort by downloads, likes, trending, or recency, inspect a repository's
|
|
459
|
+
quantizations, and see a per-quantization verdict for your hardware — **fits**
|
|
460
|
+
inside the accelerator budget, **tight** (partly on CPU), or **too large**.
|
|
461
|
+
Builds from known publishers (`unsloth`, `lmstudio-community`, `bartowski`,
|
|
462
|
+
`ggml-org`, `Qwen`, `google`, `mistralai`) are marked.
|
|
463
|
+
|
|
464
|
+
Downloads land in `.threadshelf/models` (override with
|
|
465
|
+
`THREADSHELF_MODELS_PATH`), which is gitignored and always searched for models,
|
|
466
|
+
so a downloaded model appears in the model menu without further configuration
|
|
467
|
+
and is selected automatically when the download finishes.
|
|
468
|
+
Every file is verified against the repository's LFS `oid` (its SHA-256) before
|
|
469
|
+
it is moved into place. A cancelled transfer keeps its `.part` file and the next
|
|
470
|
+
attempt resumes from there; any other failure deletes it.
|
|
471
|
+
|
|
472
|
+
Gated repositories are detected up front and marked in the UI. To use one, accept
|
|
473
|
+
its licence on Hugging Face and set `HF_TOKEN` in the gitignored root `.env`. A
|
|
474
|
+
configured token is sent with every Hugging Face request; without one, public
|
|
475
|
+
repositories still work. No conversation content is ever sent to Hugging Face —
|
|
476
|
+
only catalog metadata requests and file downloads.
|
|
477
|
+
|
|
478
|
+
### Find or install llama.cpp safely
|
|
479
|
+
|
|
480
|
+
The guided setup above covers the common case. The CLI installer remains the
|
|
481
|
+
scripted, offline, and custom-build path, and stays the only way to install an
|
|
482
|
+
archive ThreadShelf did not resolve itself.
|
|
483
|
+
|
|
484
|
+
The setup command is cross-platform (Windows x64/ARM64, macOS x64/Apple Silicon,
|
|
485
|
+
Linux x64/ARM64 where official release assets exist). With no arguments it only
|
|
486
|
+
looks for `llama-server`; it does not make a network request, download, install,
|
|
487
|
+
or execute it:
|
|
488
|
+
|
|
489
|
+
```bash
|
|
490
|
+
npm run setup:llama
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
Inspect the newest compatible official release without downloading an archive:
|
|
494
|
+
|
|
495
|
+
```bash
|
|
496
|
+
npm run setup:llama -- -- --check
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
Install the current official `ggml-org/llama.cpp` release. The interactive form
|
|
500
|
+
requires typing `install`; automation requires the explicit `--yes` flag. The
|
|
501
|
+
official GitHub SHA-256 digest is verified and the MIT license/source metadata is
|
|
502
|
+
kept beside the installed files:
|
|
503
|
+
|
|
504
|
+
```bash
|
|
505
|
+
npm run setup:llama -- -- --install
|
|
506
|
+
npm run setup:llama -- -- --install --yes
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
Upstream's `/releases/latest` points at a semver release that carries no
|
|
510
|
+
binaries, so ThreadShelf follows its `nightly-tag.txt` pointer to the real
|
|
511
|
+
`bNNNNN` build. Pin an exact upstream build with `--release`, and set
|
|
512
|
+
`GITHUB_TOKEN` if you hit the anonymous API rate limit:
|
|
513
|
+
|
|
514
|
+
```bash
|
|
515
|
+
npm run setup:llama -- -- --install --release b10088
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
Default builds are portable CPU builds (Metal is automatic on macOS). Accelerated
|
|
519
|
+
official variants may be selected with `--variant vulkan|cuda|rocm|sycl`; their
|
|
520
|
+
driver/runtime requirements still apply. Official variants are installed
|
|
521
|
+
side-by-side under release-and-variant folders,
|
|
522
|
+
so adding a GPU build never overwrites a working CPU build. Paste the desired
|
|
523
|
+
variant's printed `llama-server` path into Settings when more than one is present.
|
|
524
|
+
Autodiscovery prefers the newest managed release and an accelerator variant over
|
|
525
|
+
CPU for the same release; an explicitly configured executable still wins.
|
|
526
|
+
|
|
527
|
+
Official Windows CUDA releases split the server and CUDA runtime DLLs across two
|
|
528
|
+
archives. The CUDA install command downloads and SHA-256 verifies both. Re-running
|
|
529
|
+
the same explicitly approved command repairs an older ThreadShelf CUDA directory
|
|
530
|
+
that is missing the companion runtime without touching GGUF models:
|
|
531
|
+
|
|
532
|
+
```bash
|
|
533
|
+
npm run setup:llama -- -- --install --variant cuda
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
For Alpine, an unsupported architecture, or a custom build, provide your own
|
|
537
|
+
archive URL. Supplying `--url` is explicit download consent; provide a trusted
|
|
538
|
+
SHA-256 whenever possible:
|
|
539
|
+
|
|
540
|
+
```bash
|
|
541
|
+
npm run setup:llama -- -- --url https://example.invalid/llama-build.tar.gz --sha256 64_HEX_DIGEST --tag custom
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
The default destination is `.threadshelf/tools/`, which is gitignored. No model is
|
|
545
|
+
downloaded by this installer. Add existing GGUF directories and an optional
|
|
546
|
+
`llama-server` path in **Settings → Conversation generation**.
|
|
547
|
+
|
|
548
|
+
OpenRouter keys should preferably be supplied as `OPENROUTER_API_KEY` in the
|
|
549
|
+
gitignored root `.env` file (copy `.env.example`, then restart the server). A key
|
|
550
|
+
entered in the UI exists only in server memory for the current process and is
|
|
551
|
+
never written to `.threadshelf/generation.json` or returned by the API.
|
|
552
|
+
|
|
553
|
+
Generation configuration, created-chat storage, eject, and chat endpoints are
|
|
554
|
+
loopback-only even when the read/search UI is exposed with `HOST` and
|
|
555
|
+
`ALLOWED_HOSTS`. Full setup, persistence semantics, routing controls, runtime
|
|
556
|
+
diagnostics, and API examples are documented in
|
|
557
|
+
[Experimental Generation](docs/GENERATION_BETA.md).
|
|
558
|
+
|
|
559
|
+
## Archive insights
|
|
560
|
+
|
|
561
|
+
The **Insights** view charts your whole archive from data captured at ingest
|
|
562
|
+
time: activity over time, top models, turns per provider, and your longest
|
|
563
|
+
conversations — scoped to one collection or all of them.
|
|
564
|
+
|
|
565
|
+
## Dark theme
|
|
566
|
+
|
|
567
|
+

|
|
568
|
+
|
|
569
|
+

|
|
570
|
+
|
|
571
|
+
## Use it from MCP
|
|
572
|
+
|
|
573
|
+

|
|
574
|
+
|
|
575
|
+
ThreadShelf exposes your local index to MCP clients (e.g. Claude Desktop, or any
|
|
576
|
+
MCP-capable agent) over stdio — so a model can search your past chats as a tool.
|
|
577
|
+
|
|
578
|
+
```bash
|
|
579
|
+
npx threadshelf-mcp # installed package
|
|
580
|
+
npm run mcp # from a clone
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
Example Claude Desktop config (`claude_desktop_config.json`) using the published
|
|
584
|
+
package:
|
|
585
|
+
|
|
586
|
+
```json
|
|
587
|
+
{
|
|
588
|
+
"mcpServers": {
|
|
589
|
+
"threadshelf": {
|
|
590
|
+
"command": "npx",
|
|
591
|
+
"args": ["-y", "threadshelf-mcp"]
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
From a clone instead:
|
|
598
|
+
|
|
599
|
+
```json
|
|
600
|
+
{
|
|
601
|
+
"mcpServers": {
|
|
602
|
+
"threadshelf": {
|
|
603
|
+
"command": "npm",
|
|
604
|
+
"args": ["run", "mcp", "--silent"],
|
|
605
|
+
"cwd": "/absolute/path/to/this/repo"
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
It reads the same local LanceDB the UI uses — no extra setup. See
|
|
612
|
+
[docs/MCP.md](docs/MCP.md) for the exposed tools.
|
|
613
|
+
|
|
614
|
+
## How it works
|
|
615
|
+
|
|
616
|
+
- **Collections** are local LanceDB tables (use them like folders/projects).
|
|
617
|
+
- **Threads** are full conversations reconstructed from the parsed export; search
|
|
618
|
+
returns matching chunks, opening a result shows the whole thread.
|
|
619
|
+
- **Stored thread snapshots** keep indexed conversations readable after source
|
|
620
|
+
files are moved, rewritten, or deleted.
|
|
621
|
+
- **Generation providers** implement one streaming contract over managed
|
|
622
|
+
`llama.cpp` and OpenRouter, while per-thread leases and write locks protect
|
|
623
|
+
concurrent saves and model transitions.
|
|
624
|
+
|
|
625
|
+
Internals: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
|
|
626
|
+
|
|
627
|
+
## Engineering and test coverage
|
|
628
|
+
|
|
629
|
+
ThreadShelf is an npm-workspaces TypeScript/ESM project with a React 19 client
|
|
630
|
+
and an Express 5 server. The full gate is deliberately broader than unit tests:
|
|
631
|
+
|
|
632
|
+
| Layer | What is exercised |
|
|
633
|
+
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
634
|
+
| **Unit/regression** | Provider parsing, Unicode, chunking, validation, generation config/runtime helpers, thread persistence, client utilities |
|
|
635
|
+
| **API + MCP E2E** | A real server, isolated temporary LanceDB/uploads/collections, ingest/search/thread/generation routes, and MCP stdio |
|
|
636
|
+
| **Browser E2E** | The built production UI in Chromium: search, routing, collections, uploads, generation streams, privacy labels, responsive layouts |
|
|
637
|
+
| **Repository gate** | Git/privacy hygiene, Markdown links, ESLint, TypeScript, client build, and all tests above |
|
|
638
|
+
|
|
639
|
+
Every provider has small synthetic fixtures. Real exports are used only to learn
|
|
640
|
+
the JSON shape; private conversation content is never copied into the repository.
|
|
641
|
+
CI runs the full gate on Linux and lightweight core checks on Windows.
|
|
642
|
+
|
|
643
|
+
## Release status and important limitations
|
|
644
|
+
|
|
645
|
+
- Conversations indexed with the current version keep working (search, listing,
|
|
646
|
+
and full thread view) even if the original export file is later moved,
|
|
647
|
+
rewritten, or deleted — normalized turns are stored alongside the vectors at
|
|
648
|
+
ingest time. Collections indexed with older versions still read threads from
|
|
649
|
+
the original file path until you re-index them.
|
|
650
|
+
- Very large archives are supported in normal use, but >100k chunk collections
|
|
651
|
+
should still be validated against your own data before relying on exact stats.
|
|
652
|
+
- Undocumented provider formats can change without notice; keep small
|
|
653
|
+
anonymized fixtures for any real export shape that breaks parsing.
|
|
654
|
+
- Conversation generation is **Experimental Beta**; archive indexing and search
|
|
655
|
+
do not depend on it.
|
|
656
|
+
- ThreadShelf is a single-user local application. The HTTP API has no user
|
|
657
|
+
accounts or authentication and should remain bound to loopback unless it is
|
|
658
|
+
placed on a trusted network with deliberate host configuration.
|
|
659
|
+
|
|
660
|
+
## Scripts
|
|
661
|
+
|
|
662
|
+
| Command | Description |
|
|
663
|
+
| ---------------------------------------------------- | ----------------------------------------------------------------------------------- |
|
|
664
|
+
| `npm start` | Start server on port 3000 (`npm start -- 3001` for another port). |
|
|
665
|
+
| `npm run dev` | Server with file watch. |
|
|
666
|
+
| `npm run dev:client` | Vite dev server (UI hot reload on :5173). |
|
|
667
|
+
| `npm run build:client` | Build the UI into `public/`. |
|
|
668
|
+
| `npm run check` | Full gate: repo hygiene, lint, typecheck, unit + API/MCP E2E, build, Playwright. |
|
|
669
|
+
| `npm run check:repo` | Reject tracked private artifacts/secrets before commit. |
|
|
670
|
+
| `npm test` | Fast unit/regression tests. |
|
|
671
|
+
| `npm run test:e2e` | API + MCP E2E with a temporary server and LanceDB. |
|
|
672
|
+
| `npm run test:playwright` | Browser E2E (needs `npm run build:client` + Playwright). |
|
|
673
|
+
| `npm run mcp` | Start the MCP stdio server. |
|
|
674
|
+
| `npm run parse -- <file> -- [flags]` | Parse one export file (`--no-user`, `--no-thinking`, `--no-ai`). |
|
|
675
|
+
| `npm run ingest -- <folder> [collection] -- [flags]` | Ingest a folder (`--clear`, `--watch`, `--debounce <ms>`). |
|
|
676
|
+
| `npm run search -- "<query>" -- [flags]` | Search from the CLI (`--mode keyword`, `--collection`, `--roles`, `--n`, `--json`). |
|
|
677
|
+
| `npm run setup:llama` | Discover local `llama-server`; add `-- -- --check` or explicit install flags. |
|
|
678
|
+
| `npm run build:package` | Build the publishable package (client UI + compiled server into `dist/`). |
|
|
679
|
+
| `npm run pack:verify` | Pack the tarball, install it in a temp dir, and boot it (`npx` smoke test). |
|
|
680
|
+
|
|
681
|
+
Missing Playwright browsers? `npx playwright install chromium`.
|
|
682
|
+
|
|
683
|
+
## Configuration (optional env vars)
|
|
684
|
+
|
|
685
|
+
| Variable | Default | Purpose |
|
|
686
|
+
| ----------------------------------------- | ---------------------------------- | --------------------------------------------------------------------- |
|
|
687
|
+
| `PORT` | `3000` | Server port. |
|
|
688
|
+
| `HOST` | `127.0.0.1` | Server host. Set explicitly only when trusted LAN access is required. |
|
|
689
|
+
| `ALLOWED_HOSTS` | _(empty)_ | Comma-separated extra Host/Origin names for trusted LAN access. |
|
|
690
|
+
| `THREADSHELF_DATA_DIR` | _(see Quick Start)_ | Root for all persistent data. Overrides the per-user default. |
|
|
691
|
+
| `LANCEDB_PATH` | _(data dir)_ | LanceDB directory. |
|
|
692
|
+
| `UPLOADS_DIR` | _(data dir)_ | Uploaded source files. |
|
|
693
|
+
| `COLLECTIONS_PATH` | _(data dir)_ | Manual-collections registry file. |
|
|
694
|
+
| `CHUNK_MAX_CHARS` | `2000` | Max characters per embedded chunk. |
|
|
695
|
+
| `CHUNK_OVERLAP_CHARS` | `100` | Overlap between long chunks. |
|
|
696
|
+
| `EMBED_BATCH_SIZE` | `25` | Embedding batch size during ingest. |
|
|
697
|
+
| `GENERATION_CONFIG_PATH` | _(data dir)_ | Non-secret Experimental Beta generation settings. |
|
|
698
|
+
| `MASTER_PROMPTS_PATH` | _(data dir)_ | Saved master (system) prompts. |
|
|
699
|
+
| `LLAMA_CPP_SERVER` | _(auto)_ | Absolute path to an existing `llama-server` executable. |
|
|
700
|
+
| `LLAMA_CPP_BASE_URL` | _(empty)_ | Existing loopback-only llama.cpp server URL. |
|
|
701
|
+
| `LLAMA_CPP_CONTEXT_SIZE` | `8192` | Managed local server context size. |
|
|
702
|
+
| `LLAMA_CPP_ACCELERATION` | `auto` | `auto`, `cpu`, `gpu`, `hybrid`, or `multi-gpu`. |
|
|
703
|
+
| `LLAMA_CPP_GPU_LAYERS` | `20` | Exact layer offload for the hybrid profile. |
|
|
704
|
+
| `LLAMA_CPP_SPLIT_MODE` | `layer` | Multi-GPU split: `layer` or `row`. |
|
|
705
|
+
| `LLAMA_CPP_MAIN_GPU` | `0` | Main GPU index for applicable profiles. |
|
|
706
|
+
| `LLAMA_CPP_TENSOR_SPLIT` | _(empty)_ | Optional multi-GPU proportions, e.g. `3,1`. |
|
|
707
|
+
| `LLAMA_CPP_THREADS` | `-1` | CPU generation threads; `-1` lets llama.cpp choose. |
|
|
708
|
+
| `LLAMA_CPP_FLASH_ATTENTION` | `auto` | Flash Attention: `auto`, `on`, or `off`. |
|
|
709
|
+
| `LLAMA_MODEL_PATHS` | _(defaults)_ | Extra model roots (`;` on Windows, `:` on macOS/Linux). |
|
|
710
|
+
| `THREADSHELF_TOOLS_PATH` | _(data dir)_ | llama.cpp discovery/installer root. |
|
|
711
|
+
| `THREADSHELF_MODELS_PATH` | _(data dir)_ | Catalog download root; always searched for models. |
|
|
712
|
+
| `THREADSHELF_MODEL_CACHE` | _(data dir)_ | Cache for the downloaded embedding model. |
|
|
713
|
+
| `THREADSHELF_DISABLE_DEFAULT_MODEL_PATHS` | `0` | Set `1` to scan only explicitly configured roots. |
|
|
714
|
+
| `HF_TOKEN` | _(empty)_ | Hugging Face token; required only for gated repositories. |
|
|
715
|
+
| `HUGGING_FACE_HUB_TOKEN` | _(empty)_ | Alternative name for `HF_TOKEN`. |
|
|
716
|
+
| `GITHUB_TOKEN` | _(empty)_ | Raises the GitHub API rate limit for llama.cpp releases. |
|
|
717
|
+
| `GH_TOKEN` | _(empty)_ | Alternative name for `GITHUB_TOKEN`. |
|
|
718
|
+
| `OPENROUTER_API_KEY` | _(empty)_ | OpenRouter key; may be set in `.env`, never exposed to the browser. |
|
|
719
|
+
| `OPENROUTER_BASE_URL` | `https://openrouter.ai/api/v1` | Override primarily intended for testing. |
|
|
720
|
+
|
|
721
|
+
Paths given as _(data dir)_ default to a location inside the data directory
|
|
722
|
+
described in [Quick Start](#quick-start); setting one explicitly still wins.
|
|
723
|
+
|
|
724
|
+
For LAN access, bind to the interface you need and allow the exact browser host,
|
|
725
|
+
for example `HOST=0.0.0.0 ALLOWED_HOSTS=192.168.1.50,my-pc.local`. Without
|
|
726
|
+
`ALLOWED_HOSTS`, API requests from other machines are rejected by Host/Origin
|
|
727
|
+
checks.
|
|
728
|
+
|
|
729
|
+
## Documentation
|
|
730
|
+
|
|
731
|
+
- [Getting Started](docs/GETTING_STARTED.md) — install, run, index, search.
|
|
732
|
+
- [Architecture](docs/ARCHITECTURE.md) — data flow, modules, storage, API.
|
|
733
|
+
- [MCP Setup](docs/MCP.md) — run the stdio MCP server and what it exposes.
|
|
734
|
+
- [OpenRouter Export](docs/OPENROUTER.md) — the browser export flow + limitations.
|
|
735
|
+
- [Experimental Generation](docs/GENERATION_BETA.md) — llama.cpp/OpenRouter setup, privacy, and API.
|
|
736
|
+
- [Real Data Testing](docs/REAL_DATA_TESTING.md) — validate private exports safely.
|
|
737
|
+
- [FAQ](docs/FAQ.md) — common questions.
|
|
738
|
+
- [Changelog](CHANGELOG.md) — release highlights.
|
|
739
|
+
- [AGENTS.md](AGENTS.md) — guidance for AI coding agents and contributors.
|
|
740
|
+
|
|
741
|
+
## Privacy boundary
|
|
742
|
+
|
|
743
|
+
Indexing, embeddings, storage, search, MCP, and llama.cpp inference are local.
|
|
744
|
+
The explicitly selected **Experimental Beta OpenRouter generation is not
|
|
745
|
+
local**: it sends the selected archive or ThreadShelf chat's user/assistant
|
|
746
|
+
history and prompt to OpenRouter and
|
|
747
|
+
the routed provider. Two other surfaces reach the network but never carry
|
|
748
|
+
conversation content: GitHub Releases for `llama.cpp` builds, and Hugging Face
|
|
749
|
+
for model catalog metadata and GGUF downloads — both only after you ask for
|
|
750
|
+
them. **Do not commit real chat exports, uploaded files, local
|
|
751
|
+
databases, `.threadshelf/`, logs, or temp folders** — `.gitignore` excludes them.
|
|
752
|
+
`npm run check:repo` also rejects these paths if they become commit candidates.
|
|
753
|
+
Fixtures in `test/fixtures/` are synthetic and anonymized. See
|
|
754
|
+
[SECURITY.md](SECURITY.md).
|
|
755
|
+
|
|
756
|
+
## Contributing
|
|
757
|
+
|
|
758
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md): add tests for parser/ingest/search changes,
|
|
759
|
+
keep real exports out of git, and run `npm run check` before opening a PR.
|
|
760
|
+
|
|
761
|
+
## License
|
|
762
|
+
|
|
763
|
+
MIT — see [LICENSE](LICENSE).
|