tinywebui 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,18 +2,30 @@
2
2
 
3
3
  # TinyWebUI
4
4
 
5
+ <a href="https://tinysuite.dev">
6
+ <img src="assets/tinywebui-full-logo.png" alt="TinyWebUI" width="240" />
7
+ </a>
8
+
5
9
  **A small, local-first chat workspace for any OpenAI-compatible model, with MCP tools.**
6
10
 
7
11
  Part of [TinySuite](https://tinysuite.dev). Comes with [TinySearch](https://github.com/TinySuiteHQ/TinySearch) and [TinyContext](https://github.com/TinySuiteHQ/TinyContext) already connected.
8
12
 
13
+ [![Website](https://img.shields.io/badge/tinysuite.dev-home-000000?logo=googlechrome&logoColor=white)](https://tinysuite.dev)
14
+ [![npm version](https://img.shields.io/npm/v/tinywebui.svg)](https://www.npmjs.com/package/tinywebui)
15
+ [![npm downloads](https://img.shields.io/npm/dm/tinywebui.svg?label=downloads%2Fmonth)](https://www.npmjs.com/package/tinywebui)
16
+ [![Tests](https://github.com/TinySuiteHQ/TinyWebUI/actions/workflows/test.yml/badge.svg)](https://github.com/TinySuiteHQ/TinyWebUI/actions/workflows/test.yml)
9
17
  [![License: AGPL-3.0-or-later](https://img.shields.io/badge/license-AGPL--3.0--or--later-blue.svg)](LICENSE)
10
18
  [![Node ≥ 22.13](https://img.shields.io/badge/node-%E2%89%A5%2022.13-339933.svg)](https://nodejs.org)
19
+ [![Release](https://img.shields.io/github/v/release/TinySuiteHQ/TinyWebUI?label=release)](https://github.com/TinySuiteHQ/TinyWebUI/releases)
20
+ [![Last Commit](https://img.shields.io/github/last-commit/TinySuiteHQ/TinyWebUI)](https://github.com/TinySuiteHQ/TinyWebUI/commits/main)
21
+ [![Discord](https://img.shields.io/badge/Discord-Join%20community-5865F2?logo=discord&logoColor=white)](https://discord.gg/mFFKF9bf5e)
22
+ ![MCP Client](https://img.shields.io/badge/MCP-client-blue)
11
23
 
12
24
  <img src="docs/demo.gif" width="800" alt="TinyWebUI from a fresh start: a web research turn with a task list and a tool approval, questions about an attached document, the model asking which tone to use, then chat search, statistics, MCP servers, automations and settings">
13
25
 
14
26
  <sub>From a fresh start, using DeepSeek V4.1 Flash through OpenRouter. The wait during the research turn is shown at 4× speed. <a href="docs/demo.mp4">Full-resolution video</a>.</sub>
15
27
 
16
- [Quick start](#quick-start) · [Configuration](#configuration) · [MCP tools](#mcp-tools) · [Chats and attachments](#chats-and-attachments) · [Search](#search) · [Automations](#automations) · [Long chats](#long-chats-caching-and-compaction) · [Statistics](#usage-statistics) · [Access tiers](#access-three-tiers) · [Deployment as code](#deployment-as-code) · [CLI](#cli-reference) · [Security](#security-and-limits) · [Development](#development)
28
+ **[Read the documentation → tinysuite.dev/docs/tinywebui](https://tinysuite.dev/docs/tinywebui/)**
17
29
 
18
30
  </div>
19
31
 
@@ -23,539 +35,76 @@ TinyWebUI is the chat interface of [TinySuite](https://tinysuite.dev), a set of
23
35
 
24
36
  It comes pre-loaded with two TinySuite MCP servers, so a new install can already search the web and remember things:
25
37
 
26
- - **[TinySearch](https://github.com/TinySuiteHQ/TinySearch)** searches, reads and reranks the web locally, then gives the model only the evidence worth putting in its context. It doesn't need a search API key.
27
- - **[TinyContext](https://github.com/TinySuiteHQ/TinyContext)** is a local long-term memory. It saves short memories in SQLite and recalls only what fits the token budget.
28
-
29
- You can add any other MCP server next to them, or remove them (see [MCP tools](#mcp-tools)).
30
-
31
- Out of the box it is a personal workspace with no login. It can grow into a team deployment, with gateway sign-in, roles, per-user data and an admin panel, without changing anything else.
38
+ - **[TinySearch](https://github.com/TinySuiteHQ/TinySearch)** searches, reads and reranks the web locally. It doesn't need a search API key.
39
+ - **[TinyContext](https://github.com/TinySuiteHQ/TinyContext)** is a local long-term memory that recalls only what fits the token budget.
32
40
 
33
41
  ## Highlights
34
42
 
35
- - **Durable conversations.** Chats are stored in SQLite, with edit, retry, rewind, folders and full-text search. A running turn belongs to the server, not the browser tab, so you can reload, close the laptop and come back to it.
36
- - **Steer a turn while it runs.** Press Enter while the model is working and your message reaches it at the next safe point. Press Alt+Enter to hold the message as a follow-up instead.
37
- - **Web search and memory included.** [TinySearch](https://github.com/TinySuiteHQ/TinySearch) and [TinyContext](https://github.com/TinySuiteHQ/TinyContext) are connected from the first start.
38
- - **MCP over stdio, Streamable HTTP or SSE.** Calls that can change something wait for your approval. Read-only calls in the same round run in parallel.
39
- - **Attachments.** Text and source files, PDFs, DOCX and images. HEIC and AVIF photos are converted automatically. Files you attached before can be reattached from a library.
40
- - **Hybrid local search.** BM25 combined with a small on-device embedding model searches documents and past chats. Nothing leaves the machine.
41
- - **Long chats that stay affordable.** Prefixes are deterministic, so prompt caches get reused. Large tool results are compacted once, and the full output stays one `expand_context` call away. Old turns are summarised when the window has to move.
42
- - **You can see what you spend.** Every round shows its token use, cache reads and cache writes. The statistics view breaks each request down by where the tokens went.
43
- - **Automations.** Cron-scheduled prompts that run in a chat of your choice. The model can create them too.
44
- - **Scales up when needed.** You can add a password for remote access, or put it behind an SSO gateway for a team with roles and an admin panel.
45
- - **Managed from files.** Every setting can come from version-controlled config, which hot-reloads, refuses typos and has a fingerprint you can check against a running instance.
43
+ - **Durable conversations.** Edit, retry, rewind, folders and full-text search. A running turn belongs to the server, so you can reload and come back to it.
44
+ - **Steer a turn while it runs.** Press Enter to reach the model at the next safe point, or Alt+Enter to hold a follow-up.
45
+ - **MCP over stdio, Streamable HTTP or SSE.** Calls that can change something wait for your approval.
46
+ - **Attachments.** Text and source files, PDFs, DOCX and images.
47
+ - **Hybrid local search** (BM25 plus a small on-device embedding model) over documents and past chats.
48
+ - **Long chats that stay affordable.** Cache-friendly prefixes, one-time compaction of large tool results, and per-round token statistics.
49
+ - **Automations.** Cron-scheduled prompts that run in a chat of your choice.
50
+ - **Private by default.** Interactive first runs ask for an owner password; a team can use an SSO gateway with roles and an admin panel.
51
+ - **Managed from files.** Version-controlled config that hot-reloads, refuses typos and has a fingerprint you can check against a running instance.
46
52
  - **Themes.** Fall Fairy and Cyber Grid are included. See [theme authoring](public/themes/README.md) to write your own.
47
53
 
48
54
  ## Quick start
49
55
 
50
- Requires **Node.js 22.13** or later. The bundled TinySearch and TinyContext servers also need **[uv](https://docs.astral.sh/uv/)**. It fetches them, and Python 3.12 if necessary, the first time they start.
56
+ Requires **Node.js 22.13** or later. The bundled TinySearch and TinyContext servers also need **[uv](https://docs.astral.sh/uv/)**, which fetches them the first time they start.
51
57
 
52
58
  ```bash
53
59
  npx tinywebui
54
60
  ```
55
61
 
56
- Or from a clone of this repository:
57
-
58
- ```bash
59
- npm install
60
- npm start
61
- ```
62
-
63
- Open <http://127.0.0.1:7777>. Use `--port` and `--host` to change where it listens.
64
-
65
- > **Search model.** `npm start` first downloads the default embedding model (about 90 MB) into `models/`, once. `npx tinywebui` skips that step and uses keyword search until you run `npx tinywebui models pull fast`. See [Search](#search).
66
-
67
- ### Docker
68
-
69
- ```bash
70
- cp example.tinywebui.config.json tinywebui.config.json # add your API key
71
- docker compose up --build
72
- ```
73
-
74
- The default image is Debian slim and has hybrid search, with the embedding model baked in at build time. `Dockerfile.lexical` builds a small Alpine image with keyword search only. Both images keep data in a `/data` volume and have a `/readyz` health check. The images don't include uv, so mount an `mcp.json` that connects to TinySearch and TinyContext over HTTP, for example running in their own containers, or one that leaves them out. [docs/deploy.md](docs/deploy.md) walks through a full scripted deployment.
75
-
76
- ### Development mode
77
-
78
- `npm run dev` restarts the server when `src/` or `bin/` change. Files in `public/` are served as-is, so a browser refresh is enough for them.
79
-
80
- ## Configuration
81
-
82
- Start from the example:
83
-
84
- ```bash
85
- cp example.tinywebui.config.json tinywebui.config.json
86
- ```
87
-
88
- > [!WARNING]
89
- > `tinywebui.config.json` can hold your API key. It is gitignored, so keep it that way.
90
-
91
- The Settings panel can change the model, system prompt, generation settings, tool budget, context controls and enabled tools, and it writes every change back to this file. The API key is never sent to the browser.
92
-
93
- **OpenRouter:**
62
+ The first run asks you to create an owner password of at least 15 characters. Open <http://127.0.0.1:7777>, sign in, then connect a model. Put an endpoint and key in `tinywebui.config.json` (start from `example.tinywebui.config.json`) or use the Settings panel:
94
63
 
95
64
  ```json
96
65
  {
97
66
  "baseUrl": "https://openrouter.ai/api/v1",
98
67
  "apiKey": "sk-or-...",
99
- "model": "deepseek/deepseek-v4.1-flash",
100
- "systemPrompt": "You are a terse, precise assistant.",
101
- "maxToolRounds": 12
68
+ "model": "deepseek/deepseek-v4.1-flash"
102
69
  }
103
70
  ```
104
71
 
105
- **A local runtime** (Ollama, vLLM, LM Studio, llama.cpp…): point `baseUrl` at its API and leave out `apiKey`.
106
-
107
- ```json
108
- {
109
- "baseUrl": "http://localhost:8080/v1",
110
- "model": "qwen3-coder-30b-a3b-instruct"
111
- }
112
- ```
113
-
114
- ### Environment variables
115
-
116
- | Variable | Effect |
117
- | --- | --- |
118
- | `TINYWEBUI_CONFIG` | Path to the config file (`.json`, or `.js`/`.mjs`, see [Deployment as code](#deployment-as-code)) |
119
- | `TINYWEBUI_MCP` | Path to `mcp.json` (default: next to the config file) |
120
- | `TINYWEBUI_DB` | Path to the SQLite database (default: `tinywebui.db` next to the config file) |
121
- | `TINYWEBUI_API_KEY`, `OPENROUTER_API_KEY` | API key; overrides the file |
122
- | `TINYWEBUI_BASE_URL`, `TINYWEBUI_MODEL` | Endpoint and model; override the file |
123
- | `TINYWEBUI_RETRIEVAL_MODE` | `auto`, `lexical`, `dense` or `hybrid` (see [Search](#search)) |
124
- | `TINYWEBUI_MODELS_DIR` | Where embedding models are stored (default `models/`) |
125
- | `TINYWEBUI_PASSWORD` | Tier-2 password, for containers (see [Access](#access-three-tiers)) |
126
- | `TINYWEBUI_SESSION_SECRET` | Signs session cookies. Set it so that sessions survive a restart |
127
- | `TINYWEBUI_LOG_LEVEL` | `debug`, `info` (default), `warn`, `error` or `silent`. Audit events are always written |
128
-
129
- Every log line is tagged with its area, for example `[tinywebui:mcp]`.
130
-
131
- ### Settings reference
132
-
133
- | Setting | Default | What it does |
134
- | --- | --- | --- |
135
- | `maxToolRounds` | `12` | Tool-use budget for one user message. A model response that calls one or more tools uses one round. |
136
- | `askUserTimeoutSeconds` | `120` | How long an `ask_user` question waits before the run continues on the model's own assumptions. `0` waits until the question is answered or the run is stopped. Automation runs never wait. |
137
- | `toolApproval` | `"writes"` | Which tool calls need your approval: `"writes"`, `"all"` or `"off"`. See [Tool approval](#tool-approval). |
138
- | `cache` | `true` | Prompt-cache shaping where the provider supports it. |
139
- | `cacheMode` | `"auto"` | `auto`, `implicit` (stable prefix only), `explicit` (Anthropic `cache_control`), `rolling` (OpenRouter) or `off`. |
140
- | `cacheTtl` | `"5m"` | Anthropic cache lifetime. `"1h"` costs more to write and suits chats with long pauses between turns. |
141
- | `reasoningReplay` | `"auto"` | Which reasoning earlier assistant turns send back: `reasoning_details` (OpenRouter, Anthropic-style), `reasoning_content` on tool-call turns (direct DeepSeek), or `omit`. Set it for a gateway that `auto` doesn't recognise. |
142
- | `compactThreshold` | `60000` | History tokens (system prompt and tool definitions excluded) at which older tool results are compacted. `0` disables it. |
143
- | `keepTurns` | `2` | How many recent user turns keep their tool results in full. |
144
- | `compactMinSaved` | `20000` | The minimum number of characters a compaction must remove to be worth the cache miss it causes. |
145
- | `maxInlineChars` | `30000` | Tool results larger than this are stubbed for later turns as soon as they arrive. |
146
- | `maxTurnChars` | `120000` | Tool results larger than this are stubbed even within the turn that fetched them. |
147
- | `maxHistoryTokens` | `100000` | Hard window. Beyond it, the oldest turns stop being sent to the model, but they stay in the transcript. `0` disables it. |
148
- | `llmCompaction` | `true` | Before the window drops turns, the chat's model summarises them. This costs one extra request each time the window moves. If the summary fails, the turns are simply dropped. |
149
- | `compactionMaxTokens` | `8192` | Output limit for that summary. Reasoning models think within this limit, and a cut-off summary is rejected, so leave headroom. |
150
- | `expandCharBudget` | `8000` | The most one `expand_context` call can return. |
151
- | `timezone` | `""` | IANA zone used for today's date and for automations. Empty uses the server's zone. |
152
- | `extraBody` | `{}` | Merged into every completion request. Gateway-specific options go here. |
153
- | `dbPath` | `""` | Database location. Empty means `tinywebui.db` next to the config file. |
154
-
155
- The model is told its full tool budget and today's date. The last tool result of each round ends with a note of the rounds left, and that note becomes a warning for the final two rounds. When the budget is spent, TinyWebUI makes one last request with tools disabled, so the model answers from the evidence it already has.
156
-
157
- The full schema, including the access and retrieval settings, is in [docs/config.schema.json](docs/config.schema.json). You can also print it with `tinywebui schema`.
158
-
159
- ### Model catalog
160
-
161
- By default, any model id works. To fix the list of models people can use, and to name and tune each one, add a `models` catalog:
162
-
163
- ```json
164
- {
165
- "model": "quick",
166
- "models": [
167
- { "id": "quick", "label": "Quick", "description": "Everyday questions",
168
- "model": "deepseek/deepseek-v4.1-flash", "temperature": 0.3 },
169
- { "id": "deep", "label": "Thorough", "model": "anthropic/claude-sonnet-5",
170
- "systemPrompt": "You are a careful research assistant...", "maxToolRounds": 20,
171
- "extraBody": { "reasoning": { "effort": "high" } } }
172
- ],
173
- "access": { "roles": { "user": { "models": ["quick"] } } }
174
- }
175
- ```
176
-
177
- - **`id`** is the name everything else uses: the `model` setting, role model lists and each person's saved choice. **`model`** is the provider's id; if it is left out, the `id` is sent.
178
- - **People only see `label` and `description`.** They can't type in an id, and the provider id never reaches the browser.
179
- - **Per-model overrides.** An entry can set `systemPrompt`, `temperature`, `maxTokens`, `maxToolRounds`, `cacheMode`, `cacheTtl`, `reasoningReplay` and `extraBody`. These replace the global values for turns with that model, except `extraBody`, which is merged with the global one.
180
- - **`"enabled": false`** hides an entry but keeps its settings. Anyone who had picked it falls back to the default model. The default must be an enabled entry.
181
- - **The catalog is file-only.** Startup and reload refuse a catalog with an unknown key, a duplicate id, or a reference to an entry that doesn't exist.
182
-
183
- ## MCP tools
184
-
185
- MCP servers are defined in `mcp.json` next to the config file, in the same shape that MCP desktop clients use.
186
-
187
- ### Pre-loaded: TinySearch and TinyContext
188
-
189
- Until an `mcp.json` exists, TinyWebUI starts with these two TinySuite servers, launched by [uv](https://docs.astral.sh/uv/):
190
-
191
- ```json
192
- {
193
- "mcpServers": {
194
- "tinysearch": {
195
- "command": "uvx",
196
- "args": ["--python", "3.12", "--from", "tinysuite-search[server]", "tinysearch"]
197
- },
198
- "tinycontext": {
199
- "command": "uvx",
200
- "args": ["--python", "3.12", "--from", "tinysuite-context[server]", "tinycontext"]
201
- }
202
- }
203
- }
204
- ```
205
-
206
- | Server | What the model gets |
207
- | --- | --- |
208
- | [TinySearch](https://github.com/TinySuiteHQ/TinySearch) | Web search, clean Markdown from pages, and a browser for pages that need clicking. It runs locally and needs no search API key. [Docs](https://tinysuite.dev/docs/tinysearch/). |
209
- | [TinyContext](https://github.com/TinySuiteHQ/TinyContext) | Memory across chats: it saves short memories and recalls the relevant ones within a token budget. Stored locally in SQLite. |
210
-
211
- The first start takes a little longer while uv fetches the packages. TinyContext also downloads its embedding model once.
212
-
213
- **Without uv**, TinyWebUI still starts and everything else works. The two servers are just switched off, and it tells you why:
214
-
215
- - **On the command line**, startup ends with `tinycontext, tinysearch off until uv is installed: https://docs.astral.sh/uv/`, and `tinywebui doctor` reports both as failed for the same reason.
216
- - **In the app**, both servers show **needs uv** in the tools menu and the MCP panel, with an **Install uv** link.
217
-
218
- Install uv and restart TinyWebUI; no other setup is needed. The restart matters because the uv installer adds itself to your `PATH` only for programs started after it.
219
-
220
- Saving the MCP panel writes these servers to `mcp.json`. After that the file is in charge: edit it to change them, or remove them to leave them out. An empty `{ "mcpServers": {} }` means no servers at all.
221
-
222
- ### Adding servers
223
-
224
- `example.mcp.json` has the two TinySuite servers plus a disabled HTTP example. Copy it to start, then add servers like these:
225
-
226
- ```bash
227
- cp example.mcp.json mcp.json
228
- ```
229
-
230
- ```json
231
- {
232
- "mcpServers": {
233
- "files": {
234
- "command": "npx",
235
- "args": ["-y", "@modelcontextprotocol/server-filesystem", "."]
236
- },
237
- "research": {
238
- "url": "http://127.0.0.1:8000/mcp",
239
- "headers": { "Authorization": "Bearer ..." }
240
- }
241
- }
242
- }
243
- ```
244
-
245
- - **Stdio servers** take `command`, `args`, `env` and `cwd`.
246
- - **Remote servers** take `url`, optional `headers`, and `"transport": "sse"` for SSE. The default transport is Streamable HTTP.
247
- - **`"disabled": true`** keeps a server defined but disconnected.
248
-
249
- The MCP panel validates and saves `mcp.json`, then reconnects without a restart. You can switch off single tools without disconnecting their server. The model sees tools under stable `server__tool` names.
250
-
251
- ### Tool approval
252
-
253
- A tool call that can change something waits for you. It appears in the transcript with its arguments and three choices: **allow**, **always allow this tool** and **deny**.
254
-
255
- - A tool counts as read-only only when its server marks it with the MCP `readOnlyHint` annotation. Any tool without that mark is treated as one that can write.
256
- - `toolApproval` sets the policy: `"writes"` (default) asks before any call that isn't read-only, `"all"` asks before every call and `"off"` never asks. The tools panel can override it for a single tool (`confirmTools` / `autoApproveTools`).
257
- - TinyWebUI's built-in tools never ask.
258
- - Scheduled automation runs have no one to ask, so they refuse a call that would need approval and report it.
259
-
260
- Calls from one round that are marked both `readOnlyHint` and `idempotentHint` run in parallel. Other calls run one at a time. Either way, the transcript and the results come out in the order the model asked for them.
72
+ `tinywebui.config.json` holds your password hash and API keys and is gitignored. Keep it that way. Use `npx tinywebui set-password` to change the password later.
261
73
 
262
- ### Built-in tools
74
+ `npx tinywebui` uses keyword search until you run `npx tinywebui models pull fast` (about 90 MB) to enable hybrid search.
263
75
 
264
- | Tool | What it does |
265
- | --- | --- |
266
- | `read_document` | Searches or reads a document attached to the chat. |
267
- | `search_chats` | Searches your earlier conversations (never the current one) and returns the questions with the answers they got. |
268
- | `expand_context` | Searches or pages through the full output behind a compacted tool result. |
269
- | `ask_user` | Asks you a question mid-turn, optionally with choices, and waits for the answer. |
270
- | `manage_tasks` | Keeps a checklist for the current chat, shown in the right rail. |
271
- | `manage_automation` | Creates, edits and runs scheduled [automations](#automations). |
272
-
273
- ## Chats and attachments
274
-
275
- **Conversations** are stored in `tinywebui.db`. You can edit and resend a message, retry an answer, rewind, file chats into folders and search them all. The search covers your messages and the final answers, but not reasoning, tool calls or raw tool output, so the results stay relevant. An outline on the right edge lists the questions in a long chat, and clicking one jumps to it.
276
-
277
- **Turns run on the server.** Reloading or disconnecting doesn't cancel a turn: reopen the chat to rejoin its stream, or press **stop**. While a turn is running, press **Enter** to steer it, or **Alt+Enter** to hold a follow-up that you send when you're ready.
278
-
279
- **Documents.** Attach files with the paperclip, by dragging them onto the composer, or by pasting a long block of text. Text and source files, CSV, JSON, PDFs with extractable text and DOCX files are stored as documents for the chat. The model reads the relevant parts with `read_document` instead of getting the whole file in its context. Each document can be up to 5 MiB. Files you attached before can be reattached from the library without uploading them again.
280
-
281
- **Images** are sent to the model with the message: up to eight per message, each up to 5 MiB. JPEG, PNG, WebP and GIF are sent as they are, and HEIC and AVIF are converted to JPEG. This needs a model and endpoint that accept OpenAI-style image content.
282
-
283
- **Not supported:** OCR (scanned or image-only PDFs are refused with a clear error) and legacy `.doc` files (save them as `.docx` or `.pdf`).
284
-
285
- ## Search
286
-
287
- `read_document` and `search_chats` share one retrieval engine. A chat turn is indexed as the question together with the answer it finally got, without the narration or tool calls in between.
288
-
289
- Search is **hybrid** by default. SQLite full-text search (BM25) matches exact words, and a small local embedding model matches meaning, so a question about an "automobile" finds the paragraph about the "sedan". The two rankings are combined with Reciprocal Rank Fusion, using the same ONNX models and fusion as its TinySuite siblings [TinySearch](https://github.com/TinySuiteHQ/TinySearch) and [TinyContext](https://github.com/TinySuiteHQ/TinyContext). Everything runs locally.
290
-
291
- - **Install.** `npm install` brings the embedding runtime (`onnxruntime-node` and `@huggingface/tokenizers`, both optional dependencies). The first `npm start` downloads the `fast` model through `tinywebui models ensure`. The Docker image includes it.
292
- - **Fallback.** If the model or the runtime is missing (an offline first start, a platform where onnxruntime can't install, or `npm install --omit=optional`), the default `auto` mode runs BM25 only and logs why.
293
- - **Keyword search only.** To skip hybrid search and the download, run `TINYWEBUI_RETRIEVAL_MODE=lexical npm start` or set `{ "retrieval": { "mode": "lexical" } }`.
294
- - **Pinning the model.** `{ "retrieval": { "mode": "hybrid", "model": "fast", "modelSha256": "<sha256 from tinywebui models pull>" } }` refuses to start with any other model file.
295
- - **No downloads at runtime.** Models only arrive through `tinywebui models pull` / `models ensure` or the Docker build. ONNX Runtime telemetry is switched off.
296
-
297
- <details>
298
- <summary><b>How retrieval works, and how to tune it</b></summary>
299
-
300
- &nbsp;
301
-
302
- **Small-to-big retrieval.** Each document is split twice:
303
-
304
- | | Passage | Chunk |
305
- | --- | --- | --- |
306
- | What it is | What `read_document` returns to the model | What gets embedded |
307
- | Size | `passageSize` characters (default 1,800, about 400 tokens) | The embedding model's token limit: 256 for `fast`, 512 for the others |
308
- | Overlap | `passageOverlap` characters (default 200) | `chunkOverlap` tokens (default 32) |
309
- | Used for | BM25 (every mode) and the text the model reads | Dense matching (`dense`, `hybrid`) |
310
-
311
- The question is compared with every small chunk, each passage takes its best chunk's score, and the model gets whole passages. Small chunks keep the match precise, and large passages give the model enough context to answer. Chunk size follows the model, so every part of a passage is embedded, even one longer than the model can read at once.
312
-
313
- **Modes.**
314
- - `auto` (default): `hybrid` when the model is installed, `lexical` when it isn't.
315
- - `lexical`: BM25 only.
316
- - `dense`: embeddings only.
317
- - `hybrid`: both, fused.
318
-
319
- An explicit `dense` or `hybrid` refuses to start without the model instead of falling back. A checksum that doesn't match `modelSha256` stops startup in every mode. `tinywebui models verify` and `tinywebui doctor` show which mode will run and why.
320
-
321
- **Models.**
322
-
323
- | Preset | Model | Notes |
324
- | --- | --- | --- |
325
- | `fast` (default) | all-MiniLM-L6-v2 | English, about 90 MB |
326
- | `balanced` | bge-small-en-v1.5 | English |
327
- | `quality` | bge-base-en-v1.5 | English |
328
- | `multilingual` | granite-embedding-107m-multilingual (Apache-2.0) | About as fast as `fast`, 430 MB download |
329
-
330
- For questions and documents in different languages, keyword matches rarely help, so `dense` mode or a higher `denseWeight` usually ranks better. A custom bundle works with `modelDir`.
331
-
332
- **Embeddings are computed once.** Document chunks are embedded when the document is attached, and chat turns when each run ends. Older chats and documents are embedded in the background at startup. Vectors are stored in SQLite, and a query only embeds the question. Switching models re-embeds everything automatically. A turn whose answer changes is re-embedded, and deleting or rewinding removes its vectors.
333
-
334
- **Tuning:**
335
- - `passageSize` and `passageOverlap` set what the model gets back. Bigger passages give more context per hit, but fewer hits fit in one result.
336
- - `chunkOverlap` sets how much neighbouring chunks share.
337
- - `denseWeight` (default 0.5) and `rrfK` (default 60) control the fusion.
338
- - `queryPrefix` and `documentPrefix` are for models that expect instructions. For bge, set `queryPrefix` to `"Represent this sentence for searching relevant passages: "`.
339
-
340
- Changing these is safe: stored documents are re-split when the passage settings change, and re-embedded when the model or `chunkOverlap` changes.
341
-
342
- **Adding a corpus.** The engine in `src/retrieval/retrieval.js` can search any source that describes its units through a small adapter (`units`, `candidates`, `lexical`, `hydrate`…). Register it with `retrieval.register(name, corpus)` and it gets lexical, dense and hybrid search, storage and backfill.
343
-
344
- </details>
345
-
346
- ## Automations
347
-
348
- An automation posts its instructions into a chat you choose, on a five-field cron schedule in an IANA timezone. You can create automations in the **automations** view, or ask the model to manage them with `manage_automation`.
349
-
350
- - Runs use the current model and enabled tools, and appear both in the chat and in the automation's run history.
351
- - **Run now** triggers one immediately. It waits for the chat's current turn to finish.
352
- - Automations only run while TinyWebUI is running. A run that was missed, or that falls while the chat is busy, is skipped.
353
- - No external notifications are sent.
354
-
355
- ## Long chats: caching and compaction
356
-
357
- Tool results can be much larger than the conversation around them. TinyWebUI keeps every result in full in SQLite, but avoids sending it again and again:
358
-
359
- 1. **Cache-friendly prefixes.** Between compactions, history is append-only and serialized deterministically, so providers can reuse the matching prompt prefix.
360
- 2. **One-time compaction.** When history crosses `compactThreshold`, older tool results are replaced, once, with compact stubs. A stub holds an artifact ID, a hint about the structure, and the first and last lines verbatim. Older images are replaced with a short note.
361
- 3. **Nothing is lost.** The model can get the exact content back through `expand_context`.
362
- 4. **A moving window as a last resort.** If history is still over `maxHistoryTokens`, which usually happens in a very long chat with few tool calls, the oldest turns stop being sent. The cut falls on a user message and leaves a note that earlier conversation was omitted. With `llmCompaction`, those turns are first summarised (together with any earlier summary) into the system prompt. Rewinding to a point before the cut removes the summary.
363
-
364
- **Provider specifics.** On OpenRouter, TinyWebUI sends a stable per-chat `session_id` so sticky routing keeps caches warm, and Claude gets the automatic rolling cache directive. Local and other endpoints get no OpenRouter-only fields. Set `cacheMode` only if your gateway needs a different cache dialect, or set `cache: false` to turn cache shaping off.
365
-
366
- ## Usage statistics
367
-
368
- Every round in the transcript shows its token use, including cache reads and writes when the provider reports them. The **Statistics** view totals usage over time and by model, and can drill down from all time to a single day. Extra summary requests made by `llmCompaction` show up there too.
369
-
370
- **Token attribution.** Each assistant request stores a counts-only breakdown of where its tokens went. The input side covers the operator prompt, harness, MCP guidance, tool schemas per server, conversation, reasoning sent back, tool history and results, and images. The output side covers reasoning, tool calls, intermediate text and the final answer.
371
-
372
- - The provider's totals and reported reasoning counts are authoritative. Everything else is estimated at about four characters per token, with images counted as 1,500 tokens, and is marked `~`.
373
- - The gap to the provider's total is shown as a signed difference and never rescaled away.
374
- - Cache reads are part of the input, not an extra component.
375
- - Requests made before attribution existed are not reconstructed.
376
-
377
- ## Access: three tiers
378
-
379
- Choose a tier with `authMode`. Each tier keeps everything the previous one has, and switching tiers never hides existing data.
380
-
381
- | Tier | `authMode` | For | Sign-in |
382
- | --- | --- | --- | --- |
383
- | 1. Just you | `"none"` (default) | You, on your own machine | None |
384
- | 2. Just you, remotely | `"single"` | You, over a network | A password |
385
- | 3. Users and admins | `"trusted-header"` | A team or organization | Google, Apple, Microsoft, any SSO, through a sign-in gateway |
386
-
387
- ### Tier 1: just you
388
-
389
- There is no login, and every chat, setting and tool belongs to whoever can reach the page. Keep it bound to `127.0.0.1`.
390
-
391
- ### Tier 2: just you, remotely
76
+ From a clone, run `npm install && npm start`. With Docker, create the owner password in the persistent volume before starting the service:
392
77
 
393
78
  ```bash
394
- npx tinywebui set-password # prompts, then writes authMode "single" and a hash to your config
395
- npx tinywebui --host 0.0.0.0
396
- ```
397
-
398
- - **Only a hash is stored.** The config holds an scrypt hash, never the password. In a container, set `"authMode": "single"` and pass the password in `TINYWEBUI_PASSWORD`.
399
- - **Sessions** use HTTP-only signed cookies. Running `set-password` again ends every existing session.
400
- - **Lockout.** After five wrong passwords, that address is locked out for 15 minutes.
401
- - **HTTPS.** Serve it over HTTPS, for example behind a reverse proxy, whenever it is reachable from outside your machine.
402
-
403
- ### Tier 3: users and admins
404
-
405
- TinyWebUI sits behind a **sign-in gateway** that handles the login, such as Cloudflare Access, oauth2-proxy, Authelia or Authentik. Any identity provider the gateway supports works. The gateway verifies each person and passes their identity in request headers:
406
-
407
- ```json
408
- {
409
- "authMode": "trusted-header",
410
- "trustedProxyCidrs": ["172.20.0.0/16"],
411
- "trustedUserIdHeader": "x-tinysuite-user-id",
412
- "trustedEmailHeader": "x-tinysuite-email",
413
- "trustedNameHeader": "x-tinysuite-name",
414
- "trustedRoleHeader": "x-tinysuite-role",
415
- "logoutUrl": "https://<team>.cloudflareaccess.com/cdn-cgi/access/logout",
416
- "access": {
417
- "bootstrapAdmins": ["<your gateway user id>"],
418
- "newUsers": "approved"
419
- }
420
- }
421
- ```
422
-
423
- - **Accounts create themselves** on a person's first request. They are keyed on the gateway's stable user ID, not on email, so a changed address keeps the same account.
424
- - **Data is private to each user.** This covers chats, documents, folders, search, automations and usage. The server refuses any request for another user's data by ID, and a test matrix tries every route against another user's IDs.
425
- - **The first admin comes from the config.** Anyone listed in `access.bootstrapAdmins` is always an approved admin.
426
- - **Roles grant [features](#features).** For example, users can be limited to chatting while admins set up models and MCP servers. The gateway can send `admin` or `user` in the role header, but a decision recorded in the files outranks it. A browser can never promote itself.
427
- - **Admins** get an **Admin** item under Statistics, where they can:
428
- - approve pending users (set `access.newUsers: "pending"` to require approval);
429
- - disable an account, which returns 403 immediately and stops that user's automations;
430
- - change roles;
431
- - read any user's chats, documents and live turns. This view is read-only, and each view is recorded in the audit log.
432
- - **Settings belong to the operator.** Without the `settings` feature, the model, system prompt and approval rules are read-only, and credentials and the gateway address are hidden. Without `mcp`, the MCP servers are hidden. Each person picks their own model from their role's allowed list.
433
- - **Audit log.** New accounts, rejected requests, role and status changes, config changes and admin views are written to stdout as `[tinywebui:audit]` JSON lines. Messages and secrets are never logged.
434
-
435
- > [!IMPORTANT]
436
- > Identity headers are trusted **only** from addresses in `trustedProxyCidrs`, based on the real network connection. `X-Forwarded-For` is ignored, and TinyWebUI refuses to start in this mode without that list. The gateway must remove any identity headers a client sends, and TinyWebUI must be reachable only through the gateway, with no published port.
437
-
438
- ## Deployment as code
439
-
440
- Every part of a deployment, down to what each role can see, can be set up from files. An agent or a script can build it, commit it to git and hand it over, and the running app stays in step with the files.
441
-
442
- ### Configuring from JavaScript
443
-
444
- `start()` takes the same settings, so you can run an instance without any files:
445
-
446
- ```js
447
- import { start } from 'tinywebui';
448
-
449
- const server = await start({
450
- port: 7777,
451
- config: { apiKey: process.env.OPENROUTER_API_KEY, model: 'anthropic/claude-sonnet-5' },
452
- mcpServers: { files: { command: 'npx', args: ['-y', '@modelcontextprotocol/server-filesystem', '.'] } },
453
- configFile: false, // or a path; false reads and writes no config file
454
- dbPath: ':memory:',
455
- });
456
- // later: await server.shutdown();
79
+ docker compose build
80
+ docker compose run --rm -it tinywebui set-password
81
+ docker compose up -d
457
82
  ```
458
83
 
459
- The `tinywebui` command does the same when it finds `tinywebui.config.js` (or `.mjs`) in the working directory, or when `TINYWEBUI_CONFIG` points at one. The file's default export is the options object, or a function (async is fine) that returns it. `--port` and `--host` still override it.
460
-
461
- **Layers apply lowest first:** built-in defaults, the JSON config file, environment variables, then `config` from JavaScript. Keys set in JavaScript are locked: the Settings panel shows them read-only and the API refuses to change them. `tinywebui.config.json` is still read and written for everything else, unless the options set `configFile: false`. In that case changes last only as long as the process. Passing `mcpServers` replaces `mcp.json` and makes the MCP panel read-only.
462
-
463
- ### Three kinds of setting
464
-
465
- | Kind | Where it lives | Can the UI change it? |
466
- | --- | --- | --- |
467
- | **File-only:** `authMode`, passwords, `trusted*`, `logoutUrl`, `baseUrl`, `apiKey`, `dbPath`, `access`, `models` | `tinywebui.config.js` or `tinywebui.config.json` | Never. The API refuses, even for admins. |
468
- | **Locked:** anything set in `tinywebui.config.js`, including `mcpServers` | `tinywebui.config.js` | No. Shown read-only. |
469
- | **Editable:** everything else | `tinywebui.config.json`, `mcp.json` | Yes, and every change is **written back to the file**. |
470
-
471
- - **Freeze everything** with `frozen: true` in either file. Settings, tools and MCP servers then can't be changed from the UI or the API. You change the files and let them reload instead. User data and admins' decisions about users still work normally.
472
- - **Mistakes fail loudly.** An unknown key (usually a typo), a value of the wrong type or a value outside its allowed set stops startup, reload, a UI save and `tinywebui validate`, and every problem is named. Nothing silently falls back to a default.
473
- - **Admin decisions live in the files.** Approving, disabling or changing someone's role writes `access.users` in `tinywebui.config.json` before the database is touched. The files outrank the database: delete the database and restart, and every approval and ban is still in force.
474
- - **Edits apply without a restart.** Changes to `tinywebui.config.json` or `mcp.json` are picked up automatically, or on `kill -HUP`. A reload is all or nothing: a file that doesn't parse or validate is rejected and logged, and the running config stays as it was. Changes to `tinywebui.config.js` or `authMode` need a restart.
475
- - **Fingerprints.** The effective config has a hash that never includes secrets. It is printed at startup, shown in the Admin panel, returned by `/readyz` and included in config-related audit lines before and after each change. Running `tinywebui fingerprint` on the committed files gives the same value, so you can check that a running instance matches a given commit.
476
-
477
- ### A complete tier-3 setup
478
-
479
- ```js
480
- // tinywebui.config.js: committed to git, holds no secrets
481
- export default {
482
- config: {
483
- authMode: 'trusted-header',
484
- trustedProxyCidrs: ['172.20.0.0/16'],
485
- baseUrl: 'http://gateway:8080/v1', // your model gateway; users never see it
486
- apiKey: process.env.MODEL_GATEWAY_KEY,
487
- model: 'fast',
488
- systemPrompt: 'You are the team assistant…', // locked: set here, so read-only in the UI
489
- logoutUrl: 'https://team.cloudflareaccess.com/cdn-cgi/access/logout',
490
- access: {
491
- bootstrapAdmins: ['google-oauth2|1234567890'],
492
- newUsers: 'pending',
493
- roles: {
494
- user: { features: ['chat', 'attachments', 'images', 'search', 'folders', 'model-picker'], models: ['fast', 'smart'] },
495
- admin: { features: '*' }
496
- }
497
- }
498
- },
499
- mcpServers: { // locked: users can use these tools, nobody can edit them
500
- search: { url: 'http://tinysearch:8000/mcp' },
501
- context: { url: 'http://gateway:8080/context/mcp' }
502
- }
503
- };
504
- ```
84
+ The Compose example publishes port 7777 on the host's loopback interface. The Docker image also accepts `TINYWEBUI_PASSWORD` for a headless first start; use a password of at least 15 characters and keep the container's data volume. Without credentials, a headless start exits before opening a port.
505
85
 
506
- [docs/deploy.md](docs/deploy.md) takes this through a scripted container deployment: validate, migrate, doctor, start and verify the fingerprint.
86
+ The Compose example now stores config in its named data volume. If you used the previous bind-mounted `tinywebui.config.json`, copy its settings into the volume before starting, then run `set-password` there.
507
87
 
508
- ### Features
88
+ ## Documentation
509
89
 
510
- A role's `features` list can include any of these. Each one covers both the UI and the API routes behind it: the UI hides what a role doesn't have, and the server refuses it.
90
+ The full reference lives at **[tinysuite.dev/docs/tinywebui](https://tinysuite.dev/docs/tinywebui/)**:
511
91
 
512
- | Feature | Grants |
92
+ | Topic | Covers |
513
93
  | --- | --- |
514
- | `chat` | Chatting |
515
- | `attachments` | Attaching documents |
516
- | `images` | Attaching images |
517
- | `search` | Searching chats |
518
- | `folders` | Organizing chats into folders |
519
- | `automations` | Scheduled automations |
520
- | `statistics` | The usage view |
521
- | `model-picker` | Choosing a model from the role's list |
522
- | `tools` | Turning tools on and off |
523
- | `settings` | Changing settings |
524
- | `mcp` | Managing MCP servers |
525
- | `admin` | Managing users |
526
- | `oversight` | Reading other users' chats |
527
-
528
- **Defaults.** In tier 3, users get everything except `tools`, `settings`, `mcp`, `admin` and `oversight`, and admins get `*`. In tiers 1 and 2 you get everything except user management.
529
-
530
- ## CLI reference
531
-
532
- Output on stdout is JSON or a single line, and logs go to stderr, so scripts can parse the output.
533
-
534
- | Command | What it does |
535
- | --- | --- |
536
- | `tinywebui [start] [--port] [--host] [--config]` | Run the server |
537
- | `tinywebui set-password` | Turn on tier 2 and set the password |
538
- | `tinywebui validate [path]` | Prints `ok <fingerprint>`, or lists every problem and exits with code 1 |
539
- | `tinywebui effective [--role user\|admin]` | What is in effect, how each setting can change, and what a role can see and do |
540
- | `tinywebui fingerprint` | Hash of the effective config (never includes secrets) |
541
- | `tinywebui schema` | JSON Schema for the config (also in [docs/config.schema.json](docs/config.schema.json)) |
542
- | `tinywebui migrate [--check]` | Run schema migrations as an explicit step. `--check` exits with code 1 if the database is behind. Set `autoMigrate: false` to make this mandatory. |
543
- | `tinywebui doctor` | Checks Node, config, database, model endpoint and each MCP server |
544
- | `tinywebui users list` | Users, and the decisions pinned in the files |
545
- | `tinywebui users set <id> [--role] [--status] [--clear]` | Record a decision in the config. A running server reloads it. |
546
- | `tinywebui models pull <preset> [--dir]` | Download an embedding model (`fast`, `balanced`, `quality`, `multilingual`) |
547
- | `tinywebui models ensure` | Download the configured model only if it is missing |
548
- | `tinywebui models verify` | Load the configured model the same way startup does |
549
-
550
- For orchestrators, `GET /healthz` reports that the process is answering, and `GET /readyz` reports that it can serve requests (`{"ready":true,"version":"…","fingerprint":"…"}`).
551
-
552
- ## Security and limits
553
-
554
- - **Network exposure.** In tier 1, keep TinyWebUI bound to `127.0.0.1`. Use tier 2 or 3 before exposing it to a network.
555
- - **MCP servers** run with whatever access you give them. Treat each configured server and its credentials as trusted infrastructure, and use [tool approval](#tool-approval) for anything that writes.
556
- - **Model output is untrusted.** Model-written text reaches the page only as text or through a Markdown renderer that escapes first, and the server sends strict security headers.
557
- - **Deleting a chat** also deletes its documents, tool output and search vectors.
558
- - **Not included:** OCR, cloud sync and branching chat history.
94
+ | [Quick start](https://tinysuite.dev/docs/tinywebui/#quick-start) | npx, clone, Docker |
95
+ | [Configuration](https://tinysuite.dev/docs/tinywebui/#configuration) | Environment variables, every setting, the model catalog |
96
+ | [MCP tools](https://tinysuite.dev/docs/tinywebui/#mcp-tools) | Adding servers, tool approval, built-in tools |
97
+ | [Chats and attachments](https://tinysuite.dev/docs/tinywebui/#chats-and-attachments) | Documents, images, steering a turn |
98
+ | [Search](https://tinysuite.dev/docs/tinywebui/#search) | Hybrid retrieval, embedding models, tuning |
99
+ | [Automations](https://tinysuite.dev/docs/tinywebui/#automations) | Scheduled prompts |
100
+ | [Long chats](https://tinysuite.dev/docs/tinywebui/#long-chats-caching-and-compaction) | Caching and compaction |
101
+ | [Usage statistics](https://tinysuite.dev/docs/tinywebui/#usage-statistics) | Token use and attribution |
102
+ | [Access](https://tinysuite.dev/docs/tinywebui/#access) | Just you, a password, or a team behind SSO |
103
+ | [Deployment as code](https://tinysuite.dev/docs/tinywebui/#deployment-as-code) | JavaScript config, locked settings, roles and features |
104
+ | [CLI](https://tinysuite.dev/docs/tinywebui/#cli) | Every command |
105
+ | [Security and limits](https://tinysuite.dev/docs/tinywebui/#security-and-limits) | Exposure, MCP trust, what isn't included |
106
+
107
+ In this repository: [docs/deploy.md](docs/deploy.md) is a scripted container deployment, and [docs/config.schema.json](docs/config.schema.json) is the config schema (also `tinywebui schema`).
559
108
 
560
109
  ## Development
561
110
 
@@ -564,22 +113,7 @@ npm test # the whole suite, about 10 seconds
564
113
  npm run dev # restart on changes in src/ and bin/
565
114
  ```
566
115
 
567
- The tests cover message serialization, caching, compaction, the tool loop and approvals, retries, editing, search, streaming and rejoining, settings, password and gateway sign-in, config write-back and reload, and a tenant-isolation matrix. They also enforce the architecture: import directions, the harness boundary, and the stream event registry. [AGENTS.md](AGENTS.md) maps the codebase and says where new code goes.
568
-
569
- ### Evals
570
-
571
- `npm run eval` runs the tasks in `evals/tasks/` against the configured model and scores its behaviour. It checks whether the model uses tools instead of guessing, ignores instructions planted in tool output, reports failures honestly, respects a declined call, and more. Each task scripts its tools' results, so runs are comparable. The checks are deterministic (regex, call counts), except `judge`, which asks the model for a strict PASS or FAIL on one criterion.
572
-
573
- ```bash
574
- npm run eval -- --repeat 3 --save baseline # record a reference run
575
- # ...change the prompt or the harness...
576
- npm run eval -- --repeat 3 --compare baseline
577
- ```
578
-
579
- `--only name,words` runs a subset of tasks and `--model id` tries another model. Results go to `evals/results/`, which git ignores. To add a task, drop a JSON file into `evals/tasks/`; the existing tasks show every type of check.
580
-
581
- > [!NOTE]
582
- > Evals make real API calls to your configured endpoint, so they cost money.
116
+ [AGENTS.md](AGENTS.md) maps the codebase and says where new code goes. `npm run eval` runs model-behaviour evals from `evals/tasks/` and makes real API calls, so it costs money.
583
117
 
584
118
  ## License
585
119