Skip to content

Commit 731c1e1

Browse files
authored
feat: initial implementation (#63)
1 parent 2c4ed92 commit 731c1e1

3 files changed

Lines changed: 111 additions & 4 deletions

File tree

DEPLOYMENT.md

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Deployment and Threading
2+
3+
Cursor Chat Browser is a **local, single-user** tool for reading Cursor chat history. It binds to `127.0.0.1` by default and is not designed as a multi-tenant internet-facing service. This page documents supported WSGI configurations, threading guarantees, and path-configuration trust boundaries.
4+
5+
## Quick start (production WSGI)
6+
7+
Install runtime dependencies, then serve with gunicorn (Linux/macOS) or waitress (cross-platform):
8+
9+
```bash
10+
pip install -r requirements-lock.txt # or: pip install -e .
11+
pip install gunicorn # Linux / macOS
12+
# pip install waitress # Windows-friendly alternative (see below)
13+
14+
# Multi-process (recommended): one thread per worker avoids any per-worker state surprises.
15+
gunicorn --factory --bind 127.0.0.1:3000 --workers 2 --threads 1 app:create_app
16+
17+
# Single-process, multi-threaded: safe after the #43 lock; useful for lighter deployments.
18+
gunicorn --factory --bind 127.0.0.1:3000 --workers 1 --threads 4 app:create_app
19+
```
20+
21+
**Waitress** (works well on Windows):
22+
23+
```bash
24+
pip install waitress
25+
python -c "from waitress import serve; from app import create_app; serve(create_app(), host='127.0.0.1', port=3000, threads=4)"
26+
```
27+
28+
Set `WORKSPACE_PATH` (and optionally `CLI_CHATS_PATH`) in the environment **before** starting the server when the data directory is known at launch time — see [Path configuration](#path-configuration) below.
29+
30+
Do **not** enable Flask debug mode in production (`--debug` / `FLASK_DEBUG=1`). The Werkzeug debugger allows arbitrary code execution by anyone who can reach the port.
31+
32+
## Development server
33+
34+
```bash
35+
python app.py
36+
```
37+
38+
This uses Flask/Werkzeug's built-in server. It is **single-threaded by default** (`threaded=False`), which is appropriate for local development. Requests are handled one at a time; there is no concurrent access to in-process state.
39+
40+
Werkzeug's reloader is disabled on Windows to avoid socket conflicts; on other platforms it follows the debug flag.
41+
42+
## Threading guarantee
43+
44+
The web app keeps one piece of shared mutable state: the module-level workspace path override in `utils/workspace_path.py`, written by `POST /api/set-workspace` (or `--base-dir` at startup). Reads and writes are serialized with `threading.Lock` (issue #43), so **multi-threaded deployment within a single OS process is supported**. Regression tests in `tests/test_workspace_path_thread_safety.py` exercise concurrent set/resolve under thread pools.
45+
46+
| Concurrency model | Supported? | Notes |
47+
|-------------------|------------|-------|
48+
| Single-threaded (dev server, `--threads 1`) | Yes | Simplest; default for `python app.py`. |
49+
| Multi-threaded, single process (`--workers 1 --threads N`, waitress) | Yes | Override protected by lock; safe for concurrent API requests. |
50+
| Multi-process (`--workers N`, N > 1) | Yes, with caveats | Each worker is a separate process with **its own memory**. See [Multi-process deployments](#multi-process-deployments). |
51+
52+
SQLite reads are opened per request and closed via context managers; connections are not shared across threads.
53+
54+
## WSGI servers
55+
56+
| Server | Role | Threading notes |
57+
|--------|------|-----------------|
58+
| **Werkzeug** (via `python app.py`) | Local development | Single-threaded by default; safe without extra configuration. |
59+
| **gunicorn** | Production (Linux/macOS) | Use `--factory app:create_app`. Prefer `--workers N --threads 1` for multi-process, or `--workers 1 --threads N` for threaded single-process. |
60+
| **waitress** | Production (all platforms, especially Windows) | Multi-threaded by default; compatible with the workspace-path lock. |
61+
| **pywebview** (desktop `.exe`) | Desktop GUI | No HTTP server or port; calls the WSGI app in-process. Request handling is serialized by the embedded server — equivalent to single-threaded from the app's perspective. |
62+
63+
gunicorn and waitress are **not** runtime dependencies; install them only when deploying behind a production WSGI server.
64+
65+
## Multi-process deployments
66+
67+
When gunicorn runs with `--workers 2` (or more), each worker is an independent Python process:
68+
69+
- **`POST /api/set-workspace`** only updates the worker that handled that request. Other workers keep their previous override (usually `None`). Prefer setting `WORKSPACE_PATH` or passing `--base-dir` at process start so every worker sees the same path.
70+
- **Exclusion rules** (`EXCLUSION_RULES` in app config) are loaded once at worker startup from `--exclude-rules` or the default file. Changing the rules file requires restarting workers.
71+
72+
For a single-user localhost deployment with multiple workers, set the workspace path via environment variable rather than the Configuration page.
73+
74+
## Path configuration
75+
76+
Workspace path resolution order: **runtime override** (`POST /api/set-workspace` or `--base-dir`) → **`WORKSPACE_PATH` environment variable****OS auto-detection** (see README Configuration table).
77+
78+
### Trust boundaries
79+
80+
| Mechanism | Validation | Intended use |
81+
|-----------|------------|--------------|
82+
| `POST /api/set-workspace`, `POST /api/validate-path` | Canonical path (`realpath`), directory checks, Cursor workspace markers (`state.vscdb` in immediate subdirectories) | Web UI and API callers; safe for interactive use. |
83+
| `WORKSPACE_PATH` env var | Tilde expansion only (`~` → home directory) | **Trusted-operator** escape hatch for automation, systemd units, and containers where the path is already known good. Not a substitute for API validation when input may be untrusted. |
84+
| `--base-dir` CLI flag | None (passed through to the same override as set-workspace) | Startup override for operators who control the launch command. |
85+
| OS auto-detection | N/A | Default when no override or env var is set. |
86+
87+
`CLI_CHATS_PATH` follows the same “tilde-expanded env var, trusted operator” model for Cursor CLI agent sessions under `~/.cursor/chats/`.
88+
89+
## Desktop mode (pywebview)
90+
91+
The Windows desktop build (`cursor-browser.spec` / `CursorChatBrowser.exe`) embeds the Flask app via [pywebview](https://pywebview.flowrl.com/). The UI is rendered in a native window (Edge WebView2 on Windows); no HTTP port is opened. Threading semantics match a single-threaded in-process WSGI server.
92+
93+
Install the desktop extra when building locally: `pip install -e ".[desktop]"`.
94+
95+
## CLI export (no web server)
96+
97+
`cursor-chat-export` / `python scripts/export.py` does not start Flask and has no threading or WSGI concerns. It reads `WORKSPACE_PATH` from the environment (or platform defaults) in a single process.
98+
99+
## Known limitations
100+
101+
- **Localhost-first**: Default bind address is `127.0.0.1`. Exposing the app on `0.0.0.0` without additional access controls is discouraged.
102+
- **No authentication**: The API assumes a trusted local operator.
103+
- **Exclusion rules**: Loaded at app startup; restart required after editing the rules file.
104+
- **Read-only data access**: The app reads Cursor's SQLite databases; it does not write to Cursor storage.

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ Open <http://localhost:3000> in your browser.
101101

102102
The Werkzeug debugger is **off by default** and must be opted in explicitly via the `--debug` flag or by setting `FLASK_DEBUG=1`. (Note: `FLASK_ENV=development` is **not** consulted - only `FLASK_DEBUG` is. See issue #9 for the rationale.)
103103

104+
## Deployment
105+
106+
For production WSGI servers (gunicorn, waitress), threading constraints, multi-process caveats, and path-configuration trust boundaries, see **[DEPLOYMENT.md](DEPLOYMENT.md)**.
107+
104108
## Tests
105109

106110
Run the full suite from the repository root (install `requirements-lock.txt` or `requirements.txt` first):
@@ -166,9 +170,7 @@ The application automatically detects your Cursor workspace storage location:
166170
| Linux | `~/.config/Cursor/User/workspaceStorage` |
167171
| Linux (SSH) | `~/.cursor-server/data/User/workspaceStorage` |
168172

169-
To override, set the `WORKSPACE_PATH` environment variable or use the Configuration page in the web UI.
170-
171-
Paths submitted through **`POST /api/set-workspace`** (and **`POST /api/validate-path`**) are validated the same way: canonical resolution (`realpath`), directory checks, and Cursor workspace markers (`state.vscdb` under immediate subdirectories). The **`WORKSPACE_PATH`** environment variable is only tilde-expanded — it is a **trusted-operator** escape hatch for automation and known-good paths, not a substitute for those API checks when untrusted input matters.
173+
To override, set the `WORKSPACE_PATH` environment variable or use the Configuration page in the web UI. API-validated paths vs trusted env-var overrides are documented in **[DEPLOYMENT.md](DEPLOYMENT.md#path-configuration)**.
172174

173175
Cursor CLI agent sessions are read from `~/.cursor/chats/` (the default path used by the `cursor agent` CLI). Override with the `CLI_CHATS_PATH` environment variable.
174176

@@ -223,7 +225,7 @@ pyinstaller cursor-browser.spec
223225

224226
This produces `dist/CursorChatBrowser/` containing `CursorChatBrowser.exe` and its supporting files. Move the folder anywhere you like, then pin the `.exe` to Start or the taskbar.
225227

226-
The desktop app uses [pywebview](https://pywebview.flowrl.com/) to render the Flask UI inside a native window via Edge WebView2 (pre-installed on Windows 10/11). No HTTP server or port is opened - pywebview calls the WSGI app directly in-process.
228+
The desktop app uses [pywebview](https://pywebview.flowrl.com/) to render the Flask UI inside a native window via Edge WebView2 (pre-installed on Windows 10/11). No HTTP server or port is opened - pywebview calls the WSGI app directly in-process. See **[DEPLOYMENT.md](DEPLOYMENT.md#desktop-mode-pywebview)** for threading details.
227229

228230
## Technology Stack
229231

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ include = [
7878
"launcher.py",
7979
"requirements.txt",
8080
"README.md",
81+
"DEPLOYMENT.md",
8182
"LICENSE",
8283
"cursor-browser.spec",
8384
]

0 commit comments

Comments
 (0)