Metadata-Version: 2.4
Name: metalist
Version: 0.3.1
Summary: A minimalist single-user note-taking app with SSR, fast in-memory tree operations, and efficient sync.
Project-URL: Homepage, https://github.com/evolvingstuff/metalist
Project-URL: Repository, https://github.com/evolvingstuff/metalist
Project-URL: Issues, https://github.com/evolvingstuff/metalist/issues
Requires-Python: <3.14,>=3.10
Description-Content-Type: text/markdown
Requires-Dist: argon2-cffi==23.1.0
Requires-Dist: cryptography==42.0.5
Requires-Dist: fastapi==0.110.0
Requires-Dist: latex2mathml==3.81.0
Requires-Dist: loguru==0.7.2
Requires-Dist: Mako==1.3.2
Requires-Dist: mutagen==1.47.0
Requires-Dist: pydantic==2.9.2
Requires-Dist: python-multipart==0.0.9
Requires-Dist: starlette==0.36.3
Requires-Dist: tree-sitter==0.25.2
Requires-Dist: tree-sitter-javascript==0.25.0
Requires-Dist: uvicorn==0.27.1
Provides-Extra: dev
Requires-Dist: hypothesis==6.112.0; extra == "dev"
Requires-Dist: pytest==8.0.0; extra == "dev"
Requires-Dist: pytest-cov==4.1.0; extra == "dev"

# MetaList

A minimalist single-user note-taking app focused on server-side rendering (SSR), fast in-memory tree operations, and efficient sync/diff updates.

## Features
- Rich text editing (ContentEditable) with image support
- Drag-and-drop note reordering
- Real-time content saving
- Keyboard shortcuts (press `?` in the app)
- Linked-list ordering model for efficient reorders
- Optional password protection + encryption at rest (AES-GCM)
- Multi-tab search contexts with server-persisted scroll/search state (survives browser restarts)
- Manual namespace backups/restores to a user-selected backup folder with retention controls

## Technology Stack

### Backend
- FastAPI
- SQLite (via stdlib `sqlite3`) with a guard-aware wrapper (`SafeSession`)
- Mako templates for SSR

### Frontend
- Vanilla JavaScript (no framework)
- HTML5 Drag and Drop API
- ContentEditable for rich text editing
- CSS custom properties for theming

### Testing
- Python/unit tests plus manual regression passes

## Architecture (High Level)
- Server renders the base page via Mako templates.
- The browser client drives interaction via `/api2` JSON endpoints.
- Notes are loaded/decrypted into an in-memory store at startup; a post-startup DB read guard prevents accidental runtime SELECTs.

## Development

### Setup
For a published one-off run with uv:
```bash
uvx metalist
```

For a persistent uv tool install:
```bash
uv tool install metalist
metalist
```

For pip, users can run `pip install metalist`. For a non-editable local install from this checkout, use `uv pip install .` or `pip install .` instead of the editable command below.

```bash
python3 -m venv .venv
source .venv/bin/activate
uv pip install -e .[dev]

npm install
```

### Run
The installed entrypoint starts Uvicorn with the FastAPI app:
```bash
metalist
```
For source-checkout compatibility, `python main.py` still works. Plain `python main.py` is now an orchestration command: it restarts already-running namespaces from the current checkout, launches stopped namespaces, prints their URLs, and exits. Use `python main.py --namespace work` or `python main.py work` when you want one foreground namespace process.

`metalist` and explicit single-namespace source runs bind HTTP on `0.0.0.0:8000` by default, matching the old MetaList LAN-friendly behavior.
On first startup, MetaList also auto-generates a self-signed TLS pair at `~/MetaList/certs/metalist-cert.pem` and `~/MetaList/certs/metalist-key.pem`, then enables HTTPS on `0.0.0.0:8443`. If you already have real PEM files, point `METALIST_TLS_CERT` and `METALIST_TLS_KEY` at them instead. Set `METALIST_AUTO_GENERATE_TLS=0` only if you explicitly want HTTP-only startup.

Database selection:
- No explicit namespace on a single-namespace launch: `~/MetaList/namespaces/default/default.metalist.db`
- `--namespace work` or `METALIST_NAMESPACE=work`: `~/MetaList/namespaces/work/work.metalist.db`
- The related files DB is derived automatically, so `namespaces/work/work.metalist.db` uses `namespaces/work/work.metalist.files.db`
- Remembered launch ports are stored as plaintext metadata inside each namespace's main `*.metalist.db`
- Launch precedence is: explicit CLI flags > env vars > saved namespace profile; if a namespace has no saved profile, launch it once with explicit ports or configure ports from the UI
- Backups stay beside the namespace data under `~/MetaList/namespaces/work/backups/` and use one archive per snapshot with filenames like `work-<timestamp>.metalist-backup.tar.gz`
- The Backup Settings modal targets one user-selected backup folder and can include multiple namespaces in a single run
- Restoring `work` into `work` is the normal overwrite path; importing a backup under a different namespace name requires a new target namespace and rejects saved launch-port conflicts.

Useful env flags:
- `CRASH_SERVER_ON_FAIL=1` (default): fail-fast on validation errors
- `API_PREFIX=/api2`: override API prefix (client assumes `/api2` by default)
- `METALIST_NAMESPACE=work`: select `~/MetaList/namespaces/work/work.metalist.db`
- `METALIST_HOST=0.0.0.0` (default): bind the main app to a different interface such as `127.0.0.1`
- `METALIST_PORT=8000` (default): bind the main app to a different port
- `METALIST_HTTPS_PORT=8443`: override the HTTPS port when TLS is enabled
- `MCP_AGENT_WEB_PORT=8765` (default): bind the MCP sidecar web UI to a different port
- `METALIST_TLS_CERT=/path/to/fullchain.pem` + `METALIST_TLS_KEY=/path/to/privkey.pem`: override TLS paths
- `METALIST_AUTO_GENERATE_TLS=0`: disable automatic creation of the default self-signed TLS pair
- default TLS paths: `~/MetaList/certs/metalist-cert.pem` and `~/MetaList/certs/metalist-key.pem`
- `METALIST_FORWARDED_ALLOW_IPS=127.0.0.1,::1` (default): trust proxy headers only from those reverse-proxy IPs
- `MCP_AGENT_PUBLIC_ORIGIN=https://notes.example.com:8765`: public origin for the MCP sidecar redirect when it is exposed behind HTTPS or a separate hostname/port

### Remote Access / HTTPS
Plain LAN or VPN HTTP works with a normal PyCharm run:
```bash
metalist
```
On a fresh machine, that first launch also creates the default TLS cert pair automatically. Then open either `http://<laptop-ip>:8000` or `https://<laptop-ip>:8443` from the other machine.

Namespaced launch example:
```bash
metalist --namespace work --port 8001 --mcp-port 8766
```
This starts a separate process backed by `~/MetaList/namespaces/work/work.metalist.db` on `http://127.0.0.1:8001`.
Its backup snapshots live under `~/MetaList/namespaces/work/backups/` with filenames like `work-<timestamp>.metalist-backup.tar.gz`. New backups are versioned `.tar.gz` workspace archives; legacy `.bak` backups remain restorable.

After you launch a namespace once with explicit ports, MetaList remembers them in that namespace's main DB, so later you can use the shorthand:
```bash
metalist work
```
and MetaList will reuse the saved HTTP / HTTPS / MCP sidecar ports for `work`. The same applies to the default namespace: `metalist` will reuse the saved default-namespace profile.

Equivalent explicit launch, if you want it:
```bash
METALIST_HOST=0.0.0.0 \
METALIST_PORT=8000 \
METALIST_HTTPS_PORT=8443 \
metalist
```
From the other machine, open `https://<laptop-ip>:8443`.

If you already have a real certificate and key, use the same dual-listener flow:
```bash
METALIST_HOST=0.0.0.0 \
METALIST_PORT=8000 \
METALIST_HTTPS_PORT=8443 \
METALIST_TLS_CERT=/path/to/fullchain.pem \
METALIST_TLS_KEY=/path/to/privkey.pem \
metalist
```

If you want to rotate or regenerate the default self-signed pair manually, the helper script is still available:
```bash
generate-lan-cert.sh
```

When HTTPS is enabled:
- remote HTTP requests to `http://<laptop-ip>:8000` are redirected to HTTPS
- localhost HTTP requests still stay on plain `http://127.0.0.1:8000` so the laptop can keep using the non-TLS port

If TLS is terminated by a reverse proxy on the same machine instead, keep MetaList on loopback and let the proxy forward to it:
```bash
METALIST_HOST=127.0.0.1 \
METALIST_PORT=8000 \
METALIST_FORWARDED_ALLOW_IPS=127.0.0.1,::1 \
metalist
```

If you do not need the MCP sidecar remotely, disable it:
```bash
MCP_AGENT_WEB_ENABLED=0 metalist
```

### MCP (Phase 1 Read-Only)
MCP is available automatically when you run:
```bash
metalist
```

`metalist` also auto-starts the agent web app sidecar and prints:
- `Agent web app: http://127.0.0.1:8765`
- The sidecar default MCP URL follows the resolved MetaList HTTP port for the current process.
- Use `--mcp-port` when you want multiple MetaList instances to auto-start sidecars without colliding on `8765`.
- On startup, local Ollama (`127.0.0.1`) is reset by default so a fresh runner is used.
- Sidecar Ollama auto-start uses `OLLAMA_CONTEXT_LENGTH=16384` by default.

Manual web mode (optional):
```bash
metalist-mcp web --port 8765
```
Then open `http://127.0.0.1:8765`.

Run direct MCP CLI calls:
```bash
metalist-mcp cli tools/list
metalist-mcp cli tools/call health_check '{}'
```

Compatibility shortcut (still works):
```bash
python mcp_client.py tools/list
```

Disable auto sidecar if needed:
```bash
MCP_AGENT_WEB_ENABLED=0 metalist
```

Control Ollama startup behavior:
```bash
# disable Ollama reset-on-start (default is enabled)
MCP_AGENT_RESET_OLLAMA_ON_START=0 metalist

# override auto-start context length (default 16384)
MCP_AGENT_OLLAMA_CONTEXT_LENGTH=32768 metalist
```

Optional: direct stdio transport (advanced/manual):
```bash
python -m app.mcp
```

Tool catalog and schemas:
- `docs/mcp_tools.md`

### Legacy Import
`convert-from-legacy.py` replaces the SQLite database referenced by `app.config.DATABASE_URL` and imports notes from a legacy JSON export.

This is destructive. It deletes the existing DB file before rebuilding it.

Example usage:
```bash
convert-from-legacy.py --input /path/to/legacy-export.json
```

Target a namespaced database during import:
```bash
convert-from-legacy.py --namespace work --input /path/to/legacy-export.json
```

If `--namespace`, `--port`, `--https-port`, or `--mcp-port` are omitted, the import script prompts for them and saves the resulting launch profile inside the target namespace DB. That means a one-time import into `work` can immediately seed later shorthand launches like `metalist work`.

### Publishing
For the real user-facing install flow:
```bash
uvx metalist
# or:
uv tool install metalist
metalist
```

This repo now packages itself under the PyPI distribution name `metalist`. Current releases support Python 3.10 through 3.13.

Recommended release path:
1. In the existing PyPI project `metalist`, configure GitHub Trusted Publishing for `evolvingstuff/metalist` and the workflow file `.github/workflows/publish-pypi.yml`.
2. Push a tag such as `v0.3.1`.
3. After the GitHub Actions workflow completes, users can run it with `uvx metalist`, install it persistently with `uv tool install metalist`, or install it with `pip install metalist`.

If `--input` is omitted, a file picker opens (when `tkinter` is available).
Notes tagged with `@implies` are converted into ontology rules and are not imported as notes.

### Run Tests

Python/unit test examples:
```bash
source .venv/bin/activate
.venv/bin/pytest
node --test tests/unit/*.mjs
.venv/bin/python -c "from pathlib import Path; import main; main._run_startup_sanity_gates(repo_root=Path.cwd())"
```

`TEST_MODE=1` and `POST /api2/test/reset` still exist for deterministic browser automation if we decide to add a new harness later, but Cypress is not part of the current workflow.

### Diagrams
Render Mermaid diagrams to PNGs:
```bash
npm run render-diagrams
```
