From bcf46dafb9fd2121143359fe8afdec6fe0ec82ef Mon Sep 17 00:00:00 2001 From: pewdiepie-archdaemon Date: Mon, 15 Jun 2026 23:24:41 +0900 Subject: [PATCH] Refresh README presentation --- README.md | 501 +++---------------------------------- docs/odysseus-wordmark.png | Bin 0 -> 16877 bytes docs/odysseus.jpg | Bin 45964 -> 53198 bytes docs/setup.md | 425 +++++++++++++++++++++++++++++++ 4 files changed, 463 insertions(+), 463 deletions(-) create mode 100644 docs/odysseus-wordmark.png create mode 100644 docs/setup.md diff --git a/README.md b/README.md index 8eb85229b..dcf07f761 100644 --- a/README.md +++ b/README.md @@ -1,476 +1,65 @@ -# Odysseus +

+ Odysseus +

-> **Branch note:** `dev` is the default branch and contains the latest development changes, but it may be unstable. For the more stable curated branch, use [`main`](https://github.com/pewdiepie-archdaemon/odysseus/tree/main). +

+ A self-hosted AI workspace for chat, agents, research, documents, email, notes, calendar, and local model workflows. +

-``` -─────────────────────────────────────────────── - ⊹ ࣪ ˖ ૮( ˶ᵔ ᵕ ᵔ˶ )っ Odysseus vers. 1.0 -─────────────────────────────────────────────── -``` +

+ Quick Start · + Setup Guide · + Contributing · + Roadmap +

-![Odysseus](docs/odysseus.jpg) +

+ Packaging status +

-A self-hosted AI workspace -- meant to be the self-hosted version of the UI experience you get from ChatGPT and Claude. But with more jank and fun. Running on your own hardware, with your own data -- local-first, privacy-first, and no trojan. +

+ Odysseus interface +

-[![Packaging status](https://repology.org/badge/vertical-allrepos/odysseus-ai.svg)](https://repology.org/project/odysseus-ai/versions) - -## Features - - **Chat** -- chat with any local model or API; adding them is super simple.
 vLLM · llama.cpp · Ollama · OpenRouter · OpenAI · GitHub Copilot - - **Agent** -- hand it tools and let it run the whole task itself.
 built on [opencode](https://github.com/anomalyco/opencode) · MCP · web · files · shell · skills · memory - - **Cookbook** -- Scans your hardware, recommends models, click to download and serve.. easy!
 built on [llmfit](https://github.com/AlexsJones/llmfit) · VRAM-aware · GGUF / FP8 / AWQ · fit scoring · vLLM / llama.cpp serving - - **Deep Research** -- multi-step runs that gather, read, and synthesize sources into a nice visual report.
 adapted from [Tongyi DeepResearch](https://github.com/Alibaba-NLP/DeepResearch) - - **Compare** -- a fun tool to compare models side by side. Test completely blind, no bias!
 multi-model · blind test · synthesis - - **Documents** -- YOU write the text, AI is there to assist, not the opposite.
 multi-tab editor · markdown · HTML · CSV · syntax highlighting · AI edits · suggestions - - **Memory / Skills** -- Persistent memory and skills, your agent evolves over time as it better understands you and your tasks!
 ChromaDB · fastembed (ONNX) · vector + keyword retrieval · import/export - - **Email** -- IMAP/SMTP inbox with AI triage built in: urgency reminders, auto-tag, auto-summary, auto-reply drafts, auto-spam.
 IMAP · SMTP · per-account routing · CalDAV-aware - - **Notes & Tasks** -- Quick notes with reminders, a todo list, and scheduled tasks the agent can act on.
 note pings · checklist · cron-style tasks · ntfy / browser / email channels - - **Calendar** -- Local-first calendar with CalDAV sync to Radicale / Nextcloud / Apple / Fastmail.
 CalDAV pull · .ics import/export · per-calendar colors · agent-aware - - **Works on mobile** -- looks and runs great on your phone, not just desktop.
 responsive · installable (PWA) · touch gestures - - **Extras** -- more to explore, happy if you give it a go!
 image editor · theme editor · file uploads (vision + PDF) · web search · presets · sessions · 2FA - -## Demo -A full, hover-to-play tour lives on the landing page (`docs/index.html`). - -
-Screenshots / clips - -### Chat & Agents -![Chat & Agents](docs/chat.gif) -### Deep Research -![Deep Research](docs/research.gif) -### Compare -![Compare](docs/compare.gif) -### Documents -![Documents](docs/document.gif) -### Notes & Tasks -![Notes & Tasks](docs/notes.gif) - -
+--- ## Quick Start -Defaults work out of the box: clone, run, then configure models/search/email -inside **Settings**. Only edit `.env` for deployment-level overrides like -`APP_BIND`, `APP_PORT`, `AUTH_ENABLED`, `DATABASE_URL`, or a pre-seeded admin password. +> `dev` is the default branch and gets the newest changes first. Use [`main`](https://github.com/pewdiepie-archdaemon/odysseus/tree/main) if you want the more curated branch. -On first setup, Odysseus creates an admin account (`admin` unless -`ODYSSEUS_ADMIN_USER` is set) and prints a temporary password in the terminal. -For Docker installs, the same line is in `docker compose logs odysseus`. -Use that for the first login, then change it in **Settings**. - -Contributing? See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, testing, and -pull request guidelines. - -### Docker (recommended) ```bash git clone https://github.com/pewdiepie-archdaemon/odysseus.git cd odysseus -cp .env.example .env # optional, but recommended for explicit defaults +cp .env.example .env docker compose up -d --build ``` -To include optional extras in the image (PDF viewer, Office extraction; includes AGPL PyMuPDF), build with `docker compose build --build-arg INSTALL_OPTIONAL=true` before `up`. -Open `http://localhost:7000` when the containers are healthy. Docker Compose -binds the web UI to `127.0.0.1` by default. If the port is taken, set -`APP_PORT=7001` in `.env` and recreate the container. Set `APP_BIND=0.0.0.0` -only when you intentionally want LAN/reverse-proxy access. +Open `http://localhost:7000` when the containers are healthy. The first admin password is printed in `docker compose logs odysseus`. -> **On Apple Silicon (M-series) Macs:** Docker can't reach the Metal GPU, so -> Cookbook serves local models on CPU only. For GPU-accelerated model serving, -> run natively instead — see [Apple Silicon](#apple-silicon) below. +Native installs, GPU notes, Windows/macOS instructions, HTTPS, and configuration live in the [setup guide](docs/setup.md). -### Native Linux / macOS -```bash -git clone https://github.com/pewdiepie-archdaemon/odysseus.git -cd odysseus -python3 -m venv venv -source venv/bin/activate -pip install -r requirements.txt -python setup.py -python -m uvicorn app:app --host 127.0.0.1 --port 7000 -``` -Requirements: Python 3.11+. Cookbook also needs `tmux` for background model -downloads and serves. The app itself is lightweight; local model serving is the -heavy part and depends on the model, runtime, GPU, and VRAM, so small hosts can -connect to API or remote model servers instead. Use `--host 0.0.0.0` only when you intentionally want LAN/reverse-proxy access. +## Features -### Apple Silicon -Docker on macOS cannot use the Metal GPU. For GPU-accelerated Cookbook on an -M-series Mac, run Odysseus natively: +- **Chat + Agents** — local/API models, tools, MCP, files, shell, skills, and memory. +- **Cookbook** — hardware-aware model recommendations, downloads, and serving. +- **Deep Research** — multi-step web research with source reading and report generation. +- **Compare** — blind side-by-side model testing and synthesis. +- **Documents** — writing-first editor with AI edits, suggestions, Markdown, HTML, CSV, and syntax highlighting. +- **Email** — IMAP/SMTP inbox with triage, tags, summaries, reminders, and reply drafts. +- **Notes, Tasks + Calendar** — reminders, todos, scheduled agent tasks, and CalDAV sync. +- **Extras** — gallery/image editor, themes, uploads, web search, presets, sessions, and 2FA. -```bash -git clone https://github.com/pewdiepie-archdaemon/odysseus.git -cd odysseus -./start-macos.sh -``` +## Demo -It launches at `http://127.0.0.1:7860`. To expose it to your phone over a trusted LAN/VPN such as Tailscale, bind all interfaces: - -```bash -ODYSSEUS_HOST=0.0.0.0 ./start-macos.sh -# then open http://:7860 -``` - -The script also reads `.env` at startup, so `APP_BIND=0.0.0.0` and `APP_PORT` -set there are picked up automatically without a command-line override each run. - -Keep `AUTH_ENABLED=true` (the default) before binding outside loopback. Do not -expose this port directly to the public internet. To build a clickable app wrapper: - -```bash -./build-macos-app.sh -``` - -
-Cookbook, GPU, Ollama, and troubleshooting notes - -**Docker bundled services.** Compose starts Odysseus, ChromaDB, SearXNG, and -ntfy. Odysseus and the bundled service ports bind to `127.0.0.1` by default, so -they are reachable from the host but not exposed to your LAN/public internet -unless you opt in. - -**Cookbook storage in Docker.** Downloads live in `./data/huggingface` -(`~/.cache/huggingface` in the container). Cookbook-installed Python CLIs and -serve engines live in `./data/local` (`~/.local` in the container), so they -survive container recreation. - -**Remote servers.** In **Cookbook -> Settings -> Servers**, generate the -Odysseus SSH key and add the public key to the remote server's -`~/.ssh/authorized_keys`. From the host you can also run: - -```bash -ssh-copy-id -i data/ssh/id_ed25519.pub user@server -``` - -**Docker GPU overlays.** CPU-only users can skip this section. Cookbook can -only detect GPUs that Docker exposes to the container — if the host runtime or -device passthrough is not configured, Cookbook sees the iGPU, another card, or -CPU instead of your intended GPU. - -For NVIDIA, `scripts/check-docker-gpu.sh` diagnoses GPU passthrough and can -optionally install the host runtime or update `.env`. - -```bash -# Read-only diagnostic (default — installs nothing, never edits .env): -scripts/check-docker-gpu.sh - -# Print OS-specific install commands without running them: -scripts/check-docker-gpu.sh --print-install-commands - -# Install NVIDIA Container Toolkit on Ubuntu/Debian (requires sudo): -scripts/check-docker-gpu.sh --install-nvidia-toolkit - -# Write COMPOSE_FILE to .env (only when GPU passthrough is confirmed working): -scripts/check-docker-gpu.sh --enable-nvidia-overlay - -# Full assisted setup — install toolkit, then enable overlay if passthrough works: -scripts/check-docker-gpu.sh --install-nvidia-toolkit --enable-nvidia-overlay -``` - -Safety notes: -- The app never installs host GPU runtime automatically. -- The app never edits `.env` automatically. -- `.env` is only modified when `--enable-nvidia-overlay` is explicitly passed, - and only after GPU passthrough succeeds. `--yes` skips prompts but does not - bypass the passthrough gate. -- `.env.bak.*` backups created by `--enable-nvidia-overlay` are ignored by - Git and the Docker build context. - -To enable manually without the script, add this to `.env`: - -```bash -COMPOSE_FILE=docker-compose.yml:docker/gpu.nvidia.yml -``` - -**AMD / ROCm.** AMD setup is read-only diagnostic plus manual `.env` edit. Run: - -```bash -scripts/check-docker-amd-gpu.sh -``` - -Then add the reported values to `.env`, replacing `RENDER_GID` with your host's -numeric render group id: - -```bash -COMPOSE_FILE=docker-compose.yml:docker/gpu.amd.yml -RENDER_GID=989 -``` - -For NVIDIA/AMD GPU support, also read the comments in the selected overlay file: docker/gpu.nvidia.yml or docker/gpu.amd.yml. - -**Stack-management UIs (Portainer, Coolify, Dockhand, etc.).** These tools -often accept only a single Compose file and do not reliably honor `COMPOSE_FILE` -or multiple `-f` overlays. CLI users should keep using the `COMPOSE_FILE` -overlay workflow above. For stack UIs, point the stack at one of the standalone -files instead, which bundle the base stack plus the GPU settings: - -- `docker-compose.gpu-nvidia.yml` — still requires the NVIDIA Container Toolkit - on the host. -- `docker-compose.gpu-amd.yml` — still requires host ROCm/kfd/DRI setup, the - `video`/`render` group membership, and `RENDER_GID` when needed. - -The base `docker-compose.yml` plus the `docker/gpu.*.yml` overlays remain the -source of truth; the standalone files mirror them for single-file deployments. - -Verify after enabling either overlay: - -```bash -docker compose exec odysseus nvidia-smi -L # NVIDIA -docker compose exec odysseus sh -lc 'test -e /dev/kfd && test -d /dev/dri && ls -l /dev/kfd /dev/dri/renderD*' # AMD -``` - -> **GPU passthrough ≠ llama.cpp CUDA.** `nvidia-smi` passing inside the -> container confirms Docker GPU access, but llama.cpp also needs `cudart` and -> the CUDA Toolkit at runtime. If Cookbook logs show `Unable to find cudart -> library`, `Could NOT find CUDAToolkit`, `CUDA Toolkit not found`, or -> tensors/layers assigned to CPU, that is a Cookbook/llama.cpp build issue — -> not a Docker passthrough failure. Reinstall the serve engine via -> **Cookbook → Dependencies** to get a CUDA-enabled build. -> -> The same split applies to AMD/ROCm: seeing `/dev/kfd` and `/dev/dri` inside -> the container confirms device passthrough, not ROCm userspace or a -> ROCm-enabled vLLM/llama.cpp build. `rocm-smi` and `rocminfo` are not expected -> inside the slim Odysseus image. - -**Ollama with Docker.** If Ollama runs on the host, add this endpoint in -Settings: - -```text -http://host.docker.internal:11434/v1 -``` - -Ollama must listen outside its own loopback interface: - -```bash -OLLAMA_HOST=0.0.0.0:11434 ollama serve -``` - -This connects Odysseus in Docker to an Ollama server that is already running on -your host machine; it does not start Ollama inside the container. -`host.docker.internal` is Docker's hostname for the host machine from inside the -container. Cookbook **Serve** is a separate workflow for serving downloaded -models through Odysseus/llama.cpp, so Windows users with an existing Ollama -install usually only need to add the endpoint in Settings. - -**Useful checks.** - -```bash -docker compose ps -docker compose logs --tail=120 odysseus -docker compose logs odysseus | grep -E 'ChromaDB|MemoryVectorStore|DEGRADED' -``` - -**macOS details.** `start-macos.sh` installs Homebrew deps, creates the venv, -runs setup, and starts uvicorn on port `7860` because AirPlay often holds -`7000`. It uses llama.cpp/Ollama for Metal. vLLM/SGLang are CUDA/ROCm-only and -do not run on macOS. MLX-only models are not served by Odysseus. - -
- -### Native Windows - -**One-command launcher** (creates the venv, installs deps, runs setup, starts the -server; safe to re-run): - -```powershell -git clone https://github.com/pewdiepie-archdaemon/odysseus.git -cd odysseus -powershell -ExecutionPolicy Bypass -File .\launch-windows.ps1 -``` - -Or do it by hand: - -```powershell -git clone https://github.com/pewdiepie-archdaemon/odysseus.git -cd odysseus -py -3.11 -m venv venv -venv\Scripts\Activate.ps1 -pip install -r requirements.txt -python setup.py -python -m uvicorn app:app --host 127.0.0.1 --port 7000 -``` - -If `python` points at an older interpreter, use `py -3.12` (or another installed -3.11+ version) for the venv step. - -**Requirements:** Python 3.11+. The core app (chat, agent, memory, documents, -email, calendar, deep research) runs fully native. For full **Cookbook** background -model downloads and the agent shell tool, also install -[Git for Windows](https://git-scm.com/download/win) (provides `bash.exe`). -Local GPU *serving* of vLLM/SGLang needs Linux/WSL2; for a local model on Windows, -[Ollama](https://ollama.com/download) is the easiest path — point Odysseus at -`http://localhost:11434/v1` in Settings. - -Open `http://localhost:7000`, log in with the generated admin password, -and configure everything else inside **Settings**. - -## Troubleshooting & Advanced Setup - -### `chromadb-client` conflicts with embedded ChromaDB -If `chromadb-client` (the lightweight HTTP-only package) is installed alongside the full `chromadb` package, Odysseus starts but ChromaDB silently falls back to HTTP-only mode and fails. - -**Fix:** uninstall `chromadb-client` and force-reinstall the full package: -```bash -./venv/bin/pip uninstall chromadb-client -y -./venv/bin/pip install --force-reinstall chromadb -``` - -### HTTPS + LAN/Tailscale exposure -To expose Odysseus on a local network or Tailscale with HTTPS: -1. Change the bind address to `0.0.0.0` in `.env` (`APP_BIND=0.0.0.0` or `ODYSSEUS_HOST=0.0.0.0`). -2. Generate a locally-trusted cert for your LAN/Tailscale IPs using [mkcert](https://github.com/FiloSottile/mkcert): - ```bash - mkcert -install - mkcert -cert-file cert.pem -key-file key.pem 192.168.1.100 tailscale-ip - ``` -3. Run `uvicorn` with the generated certs: - ```bash - python -m uvicorn app:app --host 0.0.0.0 --port 7000 --ssl-certfile=cert.pem --ssl-keyfile=key.pem - ``` -4. Install the `mkcert` CA on any other device you want to access Odysseus from (e.g., for iOS, email the `rootCA.pem` to yourself, install the profile, and trust it in Certificate Trust Settings). - -### Optional Dependencies -`requirements-optional.txt` contains packages that unlock extra features. It is not installed by default. - -| Package | Feature unlocked | -|---------|-----------------| -| `faster-whisper` | Local speech-to-text (microphone -> text) via the "local" STT provider. | -| `ddgs` | DuckDuckGo as a search provider option. | -| `PyMuPDF` | PDF page rendering in the side viewer panel and form-filling. (Note: AGPL-3.0) | -| `markitdown` | Office/EPUB document text extraction (converts .docx/.xlsx/.pptx/.xls/.epub to Markdown). | - -### Faster, reproducible installs with uv (optional) -[uv](https://docs.astral.sh/uv/) works as a drop-in replacement for the -venv + pip steps in the native install guides, no project changes are needed but this change results in faster installs along with a lockfile for reproducible environments. After [installing `uv`](https://docs.astral.sh/uv/getting-started/installation/), use: - -```bash -uv venv venv --python 3.13 -uv pip install -r requirements.txt -# then continue as usual: python setup.py, uvicorn, ... -``` - -`requirements.txt` is intentionally unpinned, so two installs at different times can produce different package versions. If you want a reproducible environment (e.g. across your own machines, or to roll back after a bad upgrade), snapshot and restore exact versions with: - -```bash -uv pip compile requirements.txt -o requirements.lock # snapshot current resolution -uv pip sync requirements.lock # reproduce it exactly later -``` - -`requirements.lock` is gitignored and platform-specific (compile it on the OS you deploy to). Regenerate it deliberately when you want to take upgrades. The plain `uv pip install -r requirements.txt` keeps following the unpinned requirements like pip does. - -### Outlook / Office 365 email -Odysseus email accounts currently use IMAP/SMTP username-password auth. Outlook -and Microsoft 365 generally require OAuth instead, so normal Microsoft mailbox -passwords will fail. See [docs/email-outlook.md](docs/email-outlook.md) for the -current limitation and the planned integration direction. - -## Security Notes -Odysseus is a self-hosted workspace with powerful local tools: shell access, file uploads, model downloads, web research, email/calendar integrations, and API tokens. Treat it like an admin console. - -- Keep `AUTH_ENABLED=true` for any network-accessible deployment. -- Keep `LOCALHOST_BYPASS=false` outside local development. -- Use `SECURE_COOKIES=true` when Odysseus is served through HTTPS by a trusted reverse proxy or private access gateway. -- Do not expose it directly to the public internet without HTTPS and a trusted reverse proxy or private access layer. -- Keep `.env`, `data/`, `logs/`, databases, uploads, generated media, backups, auth/session files, API keys, and model/provider tokens out of Git and private shares. They are ignored by default. -- Review `data/auth.json` after first boot: disable open signup unless you intentionally want it, make only your own account admin, and keep demo/test accounts non-admin. -- Non-admin users do not get shell/Python/file read/write by default, and admin-only routes/tools such as MCP management, API tokens, webhooks, model/cookbook serving, backup/vault, and app settings are admin-gated. Other features are controlled by per-user privileges, so review each user's privileges before exposing a deployment. -- Rotate any API keys or tokens that were ever pasted into a shared chat, demo, screenshot, or log. -- If you enable API tokens or webhooks, create separate tokens per integration and delete unused ones. -- Prefer binding manual development runs to `127.0.0.1`; bind to `0.0.0.0` only when you intentionally want LAN/reverse-proxy access. -- Keep ChromaDB, SearXNG, ntfy, Ollama, vLLM, llama.cpp, databases, and raw model/provider APIs internal-only. Expose only the authenticated Odysseus web/API entrypoint through your trusted proxy or private access layer. -- Before publishing a fork, run `git status --short` and confirm no private files from `.env`, `data/`, `logs/`, uploads, backups, or local databases are staged. - -### Private or proxied deployments -Odysseus serves plain HTTP on its app port. Docker Compose binds Odysseus and the bundled services to `127.0.0.1` by default, so a typical production/private setup is: - -1. Keep Odysseus on localhost, for example `127.0.0.1:7000`. -2. Terminate HTTPS at a trusted reverse proxy or private access gateway. -3. Put the authenticated Odysseus web/API entrypoint behind that layer. -4. Keep raw service and model ports internal-only. - -Cloudflare Access, Tailscale, Caddy, nginx, and Traefik can all fit this pattern; none are required by Odysseus. If your access layer reaches Odysseus on the same host, proxy to `http://127.0.0.1:7000` and keep `AUTH_ENABLED=true`, `LOCALHOST_BYPASS=false`, and `SECURE_COOKIES=true`. -`ALLOWED_ORIGINS` lists exact permitted origins for cross-origin browser/API clients; ordinary same-origin reverse-proxy access usually does not need a special CORS entry. - -Common internal-only ports from the default docs/compose setup: - -| Port | Service | -|---|---| -| `7000` | Odysseus raw app port | -| `8080` | SearXNG | -| `8091` | ntfy | -| `8100` | ChromaDB host port for manual/compose access | -| `11434` | Ollama | -| `8000-8020` | Common local model/provider APIs | +A full hover-to-play tour lives on the landing page: [`docs/index.html`](docs/index.html). ## Contributing -Help is welcome. The best entry points are fresh-install testing, provider setup -bugs, mobile/editor polish, docs, and small focused refactors. See -[ROADMAP.md](ROADMAP.md) for the current help-wanted list. -## Configuration -Most setup is done inside the app with `/setup` or **Settings**. Use `.env` -for deployment-level defaults and secrets you want present before first boot. -Key settings: +Help is welcome. The best entry points are fresh-install testing, provider setup bugs, mobile/editor polish, docs, and small focused refactors. See [CONTRIBUTING.md](CONTRIBUTING.md) and [ROADMAP.md](ROADMAP.md). -| Variable | Default | Description | -|---|---|---| -| `LLM_HOST` | `localhost` | Your LLM server (e.g. `llm-host.local:8000`) | -| `LLM_HOSTS` | -- | Comma-separated list for model discovery | -| `OPENAI_API_KEY` | -- | Optional OpenAI key. Prefer adding providers in the app unless pre-seeding. | -| `SEARXNG_INSTANCE` | `http://localhost:8080` | SearXNG URL. Docker overrides this to `http://searxng:8080`. | -| `SEARXNG_SECRET` | generated on first Docker boot | Optional SearXNG cookie/CSRF secret. Leave blank unless you need to pin it. | -| `APP_BIND` | `127.0.0.1` | Docker Compose host bind address for the web UI. Use `0.0.0.0` only for intentional LAN/reverse-proxy access. | -| `APP_PORT` | `7000` | Docker Compose host port for the web UI. | -| `APP_DATA_DIR` | `./data` | Docker Compose host directory for application data volumes. | -| `APP_LOGS_DIR` | `./logs` | Docker Compose host directory for application logs. | -| `AUTH_ENABLED` | `true` | Enable/disable login | -| `LOCALHOST_BYPASS` | `false` | Development-only auth bypass for loopback requests. Keep false for shared/network deployments. | -| `ALLOWED_ORIGINS` | `http://localhost,http://127.0.0.1` | Comma-separated exact permitted origins for cross-origin browser/API clients. | -| `SECURE_COOKIES` | `false` | Set true when serving Odysseus through HTTPS at a trusted proxy or private access gateway. | -| `DATABASE_URL` | `sqlite:///./data/app.db` | Database connection string | -| `CHROMADB_HOST` | `localhost` | ChromaDB host for vector memory. Docker overrides this to `chromadb`. | -| `CHROMADB_PORT` | `8100` | ChromaDB port for manual host runs. Docker overrides this to `8000`. | -| `EMBEDDING_URL` | -- | OpenAI-compatible embeddings endpoint | -| `ODYSSEUS_CHAT_UPLOAD_MAX_BYTES` | `10485760` | Chat/agent attachment cap in bytes. Raise for larger local PDFs or text documents. | -| `ODYSSEUS_GALLERY_UPLOAD_MAX_BYTES` | `104857600` | Gallery image upload cap in bytes (100 MB). | -| `ODYSSEUS_GALLERY_TRANSFORM_UPLOAD_MAX_BYTES` | `26214400` | Gallery transform input cap in bytes (25 MB). | -| `ODYSSEUS_MEMORY_IMPORT_MAX_BYTES` | `10485760` | Memory import file cap in bytes (10 MB). | -| `ODYSSEUS_PERSONAL_UPLOAD_MAX_BYTES` | `26214400` | Personal document upload cap in bytes (25 MB). | -| `ODYSSEUS_EMAIL_COMPOSE_UPLOAD_MAX_BYTES` | `26214400` | Email compose attachment cap in bytes (25 MB). | -| `ODYSSEUS_STT_MAX_AUDIO_BYTES` | `26214400` | Speech-to-text audio cap in bytes (25 MB). | -| `ODYSSEUS_ICS_MAX_BYTES` | `10485760` | Calendar `.ics` import cap in bytes (10 MB). | +## Security -All upload-limit vars are validated (must be a positive integer) and optional; an invalid value fails fast at startup. - -### Built-in MCP servers (optional setup) - -Odysseus auto-registers a few built-in MCP servers at startup. The npx-based ones (currently the browser server, `@playwright/mcp`) only start when their npm package is already in the local npx cache. If a package isn't cached, that server is skipped with a startup log message explaining what to do, so a fresh install does not block on a multi-minute npm download or hang if Playwright system deps are missing. - -To enable the browser MCP (page navigation, screenshots, vision), run once: - -```bash -npx -y @playwright/mcp@latest --version -``` - -That installs `@playwright/mcp` plus Playwright (~300MB total). Restart Odysseus and the server will register at startup. - -## Architecture -``` -app.py # FastAPI entry point -core/ auth, database, middleware, constants -src/ llm_core, agent_loop, agent_tools, chat_processor, search/ -routes/ chat, session, document, memory, model … endpoints -services/ docs, memory, search, hwfit (Cookbook) … -static/ index.html + app.js + style.css + js/ (modular front-end) -docs/ landing page (index.html) + preview clips -``` - -## Data -All user data lives in `data/` (gitignored): `app.db` (sessions, messages, documents), -`memory.json`, `presets.json`, `uploads/`, `personal_docs/`, `chroma/`, `settings.json`. - -To back up or restore everything in `data/`, see the -[Backup & Restore guide](docs/backup-restore.md). +Odysseus is a self-hosted workspace with powerful local tools. Keep auth enabled, keep private data out of Git, and do not expose raw model/service ports publicly. Deployment details are in the [setup guide](docs/setup.md#security-notes). ## Star History @@ -483,19 +72,5 @@ To back up or restore everything in `data/`, see the ## License -AGPL-3.0-or-later -- see [LICENSE](LICENSE) and [ACKNOWLEDGMENTS.md](ACKNOWLEDGMENTS.md). -``` - | - ||| - ||||| - | | | ||||||| - )_) )_) )_) ~|~ - )___))___))___)\ | - )____)____)_____)\\| - _____|____|____|_____\\\__ - \ / - ~^~^~~^~^~~^~^~~^~^~~^~^~~^~^~~^~^~~^~^~ - ~^~ all aboard! ~^~ - ~^~^~~^~^~~^~^~~^~^~~^~^~~^~^~~^~^~~^~^~ -``` +AGPL-3.0-or-later -- see [LICENSE](LICENSE) and [ACKNOWLEDGMENTS.md](ACKNOWLEDGMENTS.md). diff --git a/docs/odysseus-wordmark.png b/docs/odysseus-wordmark.png new file mode 100644 index 0000000000000000000000000000000000000000..dce21eb660d1a77868c4872dda95a021645f958c GIT binary patch literal 16877 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 literal 0 HcmV?d00001 diff --git a/docs/odysseus.jpg b/docs/odysseus.jpg index 982a00f77df6ad3e9bc2f703a74eda677d3bd9b8..7a70bc5fc5c126f1242acb65bb7956c877152ada 100644 GIT binary patch literal 53198 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 qX!;pVKZB`7FtXe-TKR%|0V#;foA0Z diff --git a/docs/setup.md b/docs/setup.md new file mode 100644 index 000000000..c809dc66f --- /dev/null +++ b/docs/setup.md @@ -0,0 +1,425 @@ +# Odysseus Setup Guide + +This page keeps the detailed install, deployment, troubleshooting, and configuration notes out of the front README. + +## Quick Start + +> **Branch note:** `dev` is the default branch and contains the latest development changes, but it may be unstable. For the more stable curated branch, use [`main`](https://github.com/pewdiepie-archdaemon/odysseus/tree/main). + +Defaults work out of the box: clone, run, then configure models/search/email +inside **Settings**. Only edit `.env` for deployment-level overrides like +`APP_BIND`, `APP_PORT`, `AUTH_ENABLED`, `DATABASE_URL`, or a pre-seeded admin password. + +On first setup, Odysseus creates an admin account (`admin` unless +`ODYSSEUS_ADMIN_USER` is set) and prints a temporary password in the terminal. +For Docker installs, the same line is in `docker compose logs odysseus`. +Use that for the first login, then change it in **Settings**. + +Contributing? See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, testing, and +pull request guidelines. + +### Docker (recommended) +```bash +git clone https://github.com/pewdiepie-archdaemon/odysseus.git +cd odysseus +cp .env.example .env # optional, but recommended for explicit defaults +docker compose up -d --build +``` +To include optional extras in the image (PDF viewer, Office extraction; includes AGPL PyMuPDF), build with `docker compose build --build-arg INSTALL_OPTIONAL=true` before `up`. + +Open `http://localhost:7000` when the containers are healthy. Docker Compose +binds the web UI to `127.0.0.1` by default. If the port is taken, set +`APP_PORT=7001` in `.env` and recreate the container. Set `APP_BIND=0.0.0.0` +only when you intentionally want LAN/reverse-proxy access. + +> **On Apple Silicon (M-series) Macs:** Docker can't reach the Metal GPU, so +> Cookbook serves local models on CPU only. For GPU-accelerated model serving, +> run natively instead — see [Apple Silicon](#apple-silicon) below. + +### Native Linux / macOS +```bash +git clone https://github.com/pewdiepie-archdaemon/odysseus.git +cd odysseus +python3 -m venv venv +source venv/bin/activate +pip install -r requirements.txt +python setup.py +python -m uvicorn app:app --host 127.0.0.1 --port 7000 +``` +Requirements: Python 3.11+. Cookbook also needs `tmux` for background model +downloads and serves. The app itself is lightweight; local model serving is the +heavy part and depends on the model, runtime, GPU, and VRAM, so small hosts can +connect to API or remote model servers instead. Use `--host 0.0.0.0` only when you intentionally want LAN/reverse-proxy access. + +### Apple Silicon +Docker on macOS cannot use the Metal GPU. For GPU-accelerated Cookbook on an +M-series Mac, run Odysseus natively: + +```bash +git clone https://github.com/pewdiepie-archdaemon/odysseus.git +cd odysseus +./start-macos.sh +``` + +It launches at `http://127.0.0.1:7860`. To expose it to your phone over a trusted LAN/VPN such as Tailscale, bind all interfaces: + +```bash +ODYSSEUS_HOST=0.0.0.0 ./start-macos.sh +# then open http://:7860 +``` + +The script also reads `.env` at startup, so `APP_BIND=0.0.0.0` and `APP_PORT` +set there are picked up automatically without a command-line override each run. + +Keep `AUTH_ENABLED=true` (the default) before binding outside loopback. Do not +expose this port directly to the public internet. To build a clickable app wrapper: + +```bash +./build-macos-app.sh +``` + +
+Cookbook, GPU, Ollama, and troubleshooting notes + +**Docker bundled services.** Compose starts Odysseus, ChromaDB, SearXNG, and +ntfy. Odysseus and the bundled service ports bind to `127.0.0.1` by default, so +they are reachable from the host but not exposed to your LAN/public internet +unless you opt in. + +**Cookbook storage in Docker.** Downloads live in `./data/huggingface` +(`~/.cache/huggingface` in the container). Cookbook-installed Python CLIs and +serve engines live in `./data/local` (`~/.local` in the container), so they +survive container recreation. + +**Remote servers.** In **Cookbook -> Settings -> Servers**, generate the +Odysseus SSH key and add the public key to the remote server's +`~/.ssh/authorized_keys`. From the host you can also run: + +```bash +ssh-copy-id -i data/ssh/id_ed25519.pub user@server +``` + +**Docker GPU overlays.** CPU-only users can skip this section. Cookbook can +only detect GPUs that Docker exposes to the container — if the host runtime or +device passthrough is not configured, Cookbook sees the iGPU, another card, or +CPU instead of your intended GPU. + +For NVIDIA, `scripts/check-docker-gpu.sh` diagnoses GPU passthrough and can +optionally install the host runtime or update `.env`. + +```bash +# Read-only diagnostic (default — installs nothing, never edits .env): +scripts/check-docker-gpu.sh + +# Print OS-specific install commands without running them: +scripts/check-docker-gpu.sh --print-install-commands + +# Install NVIDIA Container Toolkit on Ubuntu/Debian (requires sudo): +scripts/check-docker-gpu.sh --install-nvidia-toolkit + +# Write COMPOSE_FILE to .env (only when GPU passthrough is confirmed working): +scripts/check-docker-gpu.sh --enable-nvidia-overlay + +# Full assisted setup — install toolkit, then enable overlay if passthrough works: +scripts/check-docker-gpu.sh --install-nvidia-toolkit --enable-nvidia-overlay +``` + +Safety notes: +- The app never installs host GPU runtime automatically. +- The app never edits `.env` automatically. +- `.env` is only modified when `--enable-nvidia-overlay` is explicitly passed, + and only after GPU passthrough succeeds. `--yes` skips prompts but does not + bypass the passthrough gate. +- `.env.bak.*` backups created by `--enable-nvidia-overlay` are ignored by + Git and the Docker build context. + +To enable manually without the script, add this to `.env`: + +```bash +COMPOSE_FILE=docker-compose.yml:docker/gpu.nvidia.yml +``` + +**AMD / ROCm.** AMD setup is read-only diagnostic plus manual `.env` edit. Run: + +```bash +scripts/check-docker-amd-gpu.sh +``` + +Then add the reported values to `.env`, replacing `RENDER_GID` with your host's +numeric render group id: + +```bash +COMPOSE_FILE=docker-compose.yml:docker/gpu.amd.yml +RENDER_GID=989 +``` + +For NVIDIA/AMD GPU support, also read the comments in the selected overlay file: docker/gpu.nvidia.yml or docker/gpu.amd.yml. + +**Stack-management UIs (Portainer, Coolify, Dockhand, etc.).** These tools +often accept only a single Compose file and do not reliably honor `COMPOSE_FILE` +or multiple `-f` overlays. CLI users should keep using the `COMPOSE_FILE` +overlay workflow above. For stack UIs, point the stack at one of the standalone +files instead, which bundle the base stack plus the GPU settings: + +- `docker-compose.gpu-nvidia.yml` — still requires the NVIDIA Container Toolkit + on the host. +- `docker-compose.gpu-amd.yml` — still requires host ROCm/kfd/DRI setup, the + `video`/`render` group membership, and `RENDER_GID` when needed. + +The base `docker-compose.yml` plus the `docker/gpu.*.yml` overlays remain the +source of truth; the standalone files mirror them for single-file deployments. + +Verify after enabling either overlay: + +```bash +docker compose exec odysseus nvidia-smi -L # NVIDIA +docker compose exec odysseus sh -lc 'test -e /dev/kfd && test -d /dev/dri && ls -l /dev/kfd /dev/dri/renderD*' # AMD +``` + +> **GPU passthrough ≠ llama.cpp CUDA.** `nvidia-smi` passing inside the +> container confirms Docker GPU access, but llama.cpp also needs `cudart` and +> the CUDA Toolkit at runtime. If Cookbook logs show `Unable to find cudart +> library`, `Could NOT find CUDAToolkit`, `CUDA Toolkit not found`, or +> tensors/layers assigned to CPU, that is a Cookbook/llama.cpp build issue — +> not a Docker passthrough failure. Reinstall the serve engine via +> **Cookbook → Dependencies** to get a CUDA-enabled build. +> +> The same split applies to AMD/ROCm: seeing `/dev/kfd` and `/dev/dri` inside +> the container confirms device passthrough, not ROCm userspace or a +> ROCm-enabled vLLM/llama.cpp build. `rocm-smi` and `rocminfo` are not expected +> inside the slim Odysseus image. + +**Ollama with Docker.** If Ollama runs on the host, add this endpoint in +Settings: + +```text +http://host.docker.internal:11434/v1 +``` + +Ollama must listen outside its own loopback interface: + +```bash +OLLAMA_HOST=0.0.0.0:11434 ollama serve +``` + +This connects Odysseus in Docker to an Ollama server that is already running on +your host machine; it does not start Ollama inside the container. +`host.docker.internal` is Docker's hostname for the host machine from inside the +container. Cookbook **Serve** is a separate workflow for serving downloaded +models through Odysseus/llama.cpp, so Windows users with an existing Ollama +install usually only need to add the endpoint in Settings. + +**Useful checks.** + +```bash +docker compose ps +docker compose logs --tail=120 odysseus +docker compose logs odysseus | grep -E 'ChromaDB|MemoryVectorStore|DEGRADED' +``` + +**macOS details.** `start-macos.sh` installs Homebrew deps, creates the venv, +runs setup, and starts uvicorn on port `7860` because AirPlay often holds +`7000`. It uses llama.cpp/Ollama for Metal. vLLM/SGLang are CUDA/ROCm-only and +do not run on macOS. MLX-only models are not served by Odysseus. + +
+ +### Native Windows + +**One-command launcher** (creates the venv, installs deps, runs setup, starts the +server; safe to re-run): + +```powershell +git clone https://github.com/pewdiepie-archdaemon/odysseus.git +cd odysseus +powershell -ExecutionPolicy Bypass -File .\launch-windows.ps1 +``` + +Or do it by hand: + +```powershell +git clone https://github.com/pewdiepie-archdaemon/odysseus.git +cd odysseus +py -3.11 -m venv venv +venv\Scripts\Activate.ps1 +pip install -r requirements.txt +python setup.py +python -m uvicorn app:app --host 127.0.0.1 --port 7000 +``` + +If `python` points at an older interpreter, use `py -3.12` (or another installed +3.11+ version) for the venv step. + +**Requirements:** Python 3.11+. The core app (chat, agent, memory, documents, +email, calendar, deep research) runs fully native. For full **Cookbook** background +model downloads and the agent shell tool, also install +[Git for Windows](https://git-scm.com/download/win) (provides `bash.exe`). +Local GPU *serving* of vLLM/SGLang needs Linux/WSL2; for a local model on Windows, +[Ollama](https://ollama.com/download) is the easiest path — point Odysseus at +`http://localhost:11434/v1` in Settings. + +Open `http://localhost:7000`, log in with the generated admin password, +and configure everything else inside **Settings**. + +## Troubleshooting & Advanced Setup + +### `chromadb-client` conflicts with embedded ChromaDB +If `chromadb-client` (the lightweight HTTP-only package) is installed alongside the full `chromadb` package, Odysseus starts but ChromaDB silently falls back to HTTP-only mode and fails. + +**Fix:** uninstall `chromadb-client` and force-reinstall the full package: +```bash +./venv/bin/pip uninstall chromadb-client -y +./venv/bin/pip install --force-reinstall chromadb +``` + +### HTTPS + LAN/Tailscale exposure +To expose Odysseus on a local network or Tailscale with HTTPS: +1. Change the bind address to `0.0.0.0` in `.env` (`APP_BIND=0.0.0.0` or `ODYSSEUS_HOST=0.0.0.0`). +2. Generate a locally-trusted cert for your LAN/Tailscale IPs using [mkcert](https://github.com/FiloSottile/mkcert): + ```bash + mkcert -install + mkcert -cert-file cert.pem -key-file key.pem 192.168.1.100 tailscale-ip + ``` +3. Run `uvicorn` with the generated certs: + ```bash + python -m uvicorn app:app --host 0.0.0.0 --port 7000 --ssl-certfile=cert.pem --ssl-keyfile=key.pem + ``` +4. Install the `mkcert` CA on any other device you want to access Odysseus from (e.g., for iOS, email the `rootCA.pem` to yourself, install the profile, and trust it in Certificate Trust Settings). + +### Optional Dependencies +`requirements-optional.txt` contains packages that unlock extra features. It is not installed by default. + +| Package | Feature unlocked | +|---------|-----------------| +| `faster-whisper` | Local speech-to-text (microphone -> text) via the "local" STT provider. | +| `ddgs` | DuckDuckGo as a search provider option. | +| `PyMuPDF` | PDF page rendering in the side viewer panel and form-filling. (Note: AGPL-3.0) | +| `markitdown` | Office/EPUB document text extraction (converts .docx/.xlsx/.pptx/.xls/.epub to Markdown). | + +### Faster, reproducible installs with uv (optional) +[uv](https://docs.astral.sh/uv/) works as a drop-in replacement for the +venv + pip steps in the native install guides, no project changes are needed but this change results in faster installs along with a lockfile for reproducible environments. After [installing `uv`](https://docs.astral.sh/uv/getting-started/installation/), use: + +```bash +uv venv venv --python 3.13 +uv pip install -r requirements.txt +# then continue as usual: python setup.py, uvicorn, ... +``` + +`requirements.txt` is intentionally unpinned, so two installs at different times can produce different package versions. If you want a reproducible environment (e.g. across your own machines, or to roll back after a bad upgrade), snapshot and restore exact versions with: + +```bash +uv pip compile requirements.txt -o requirements.lock # snapshot current resolution +uv pip sync requirements.lock # reproduce it exactly later +``` + +`requirements.lock` is gitignored and platform-specific (compile it on the OS you deploy to). Regenerate it deliberately when you want to take upgrades. The plain `uv pip install -r requirements.txt` keeps following the unpinned requirements like pip does. + +### Outlook / Office 365 email +Odysseus email accounts currently use IMAP/SMTP username-password auth. Outlook +and Microsoft 365 generally require OAuth instead, so normal Microsoft mailbox +passwords will fail. See [docs/email-outlook.md](docs/email-outlook.md) for the +current limitation and the planned integration direction. + +## Security Notes +Odysseus is a self-hosted workspace with powerful local tools: shell access, file uploads, model downloads, web research, email/calendar integrations, and API tokens. Treat it like an admin console. + +- Keep `AUTH_ENABLED=true` for any network-accessible deployment. +- Keep `LOCALHOST_BYPASS=false` outside local development. +- Use `SECURE_COOKIES=true` when Odysseus is served through HTTPS by a trusted reverse proxy or private access gateway. +- Do not expose it directly to the public internet without HTTPS and a trusted reverse proxy or private access layer. +- Keep `.env`, `data/`, `logs/`, databases, uploads, generated media, backups, auth/session files, API keys, and model/provider tokens out of Git and private shares. They are ignored by default. +- Review `data/auth.json` after first boot: disable open signup unless you intentionally want it, make only your own account admin, and keep demo/test accounts non-admin. +- Non-admin users do not get shell/Python/file read/write by default, and admin-only routes/tools such as MCP management, API tokens, webhooks, model/cookbook serving, backup/vault, and app settings are admin-gated. Other features are controlled by per-user privileges, so review each user's privileges before exposing a deployment. +- Rotate any API keys or tokens that were ever pasted into a shared chat, demo, screenshot, or log. +- If you enable API tokens or webhooks, create separate tokens per integration and delete unused ones. +- Prefer binding manual development runs to `127.0.0.1`; bind to `0.0.0.0` only when you intentionally want LAN/reverse-proxy access. +- Keep ChromaDB, SearXNG, ntfy, Ollama, vLLM, llama.cpp, databases, and raw model/provider APIs internal-only. Expose only the authenticated Odysseus web/API entrypoint through your trusted proxy or private access layer. +- Before publishing a fork, run `git status --short` and confirm no private files from `.env`, `data/`, `logs/`, uploads, backups, or local databases are staged. + +### Private or proxied deployments +Odysseus serves plain HTTP on its app port. Docker Compose binds Odysseus and the bundled services to `127.0.0.1` by default, so a typical production/private setup is: + +1. Keep Odysseus on localhost, for example `127.0.0.1:7000`. +2. Terminate HTTPS at a trusted reverse proxy or private access gateway. +3. Put the authenticated Odysseus web/API entrypoint behind that layer. +4. Keep raw service and model ports internal-only. + +Cloudflare Access, Tailscale, Caddy, nginx, and Traefik can all fit this pattern; none are required by Odysseus. If your access layer reaches Odysseus on the same host, proxy to `http://127.0.0.1:7000` and keep `AUTH_ENABLED=true`, `LOCALHOST_BYPASS=false`, and `SECURE_COOKIES=true`. +`ALLOWED_ORIGINS` lists exact permitted origins for cross-origin browser/API clients; ordinary same-origin reverse-proxy access usually does not need a special CORS entry. + +Common internal-only ports from the default docs/compose setup: + +| Port | Service | +|---|---| +| `7000` | Odysseus raw app port | +| `8080` | SearXNG | +| `8091` | ntfy | +| `8100` | ChromaDB host port for manual/compose access | +| `11434` | Ollama | +| `8000-8020` | Common local model/provider APIs | + +## Configuration +Most setup is done inside the app with `/setup` or **Settings**. Use `.env` +for deployment-level defaults and secrets you want present before first boot. +Key settings: + +| Variable | Default | Description | +|---|---|---| +| `LLM_HOST` | `localhost` | Your LLM server (e.g. `llm-host.local:8000`) | +| `LLM_HOSTS` | -- | Comma-separated list for model discovery | +| `OPENAI_API_KEY` | -- | Optional OpenAI key. Prefer adding providers in the app unless pre-seeding. | +| `SEARXNG_INSTANCE` | `http://localhost:8080` | SearXNG URL. Docker overrides this to `http://searxng:8080`. | +| `SEARXNG_SECRET` | generated on first Docker boot | Optional SearXNG cookie/CSRF secret. Leave blank unless you need to pin it. | +| `APP_BIND` | `127.0.0.1` | Docker Compose host bind address for the web UI. Use `0.0.0.0` only for intentional LAN/reverse-proxy access. | +| `APP_PORT` | `7000` | Docker Compose host port for the web UI. | +| `APP_DATA_DIR` | `./data` | Docker Compose host directory for application data volumes. | +| `APP_LOGS_DIR` | `./logs` | Docker Compose host directory for application logs. | +| `AUTH_ENABLED` | `true` | Enable/disable login | +| `LOCALHOST_BYPASS` | `false` | Development-only auth bypass for loopback requests. Keep false for shared/network deployments. | +| `ALLOWED_ORIGINS` | `http://localhost,http://127.0.0.1` | Comma-separated exact permitted origins for cross-origin browser/API clients. | +| `SECURE_COOKIES` | `false` | Set true when serving Odysseus through HTTPS at a trusted proxy or private access gateway. | +| `DATABASE_URL` | `sqlite:///./data/app.db` | Database connection string | +| `CHROMADB_HOST` | `localhost` | ChromaDB host for vector memory. Docker overrides this to `chromadb`. | +| `CHROMADB_PORT` | `8100` | ChromaDB port for manual host runs. Docker overrides this to `8000`. | +| `EMBEDDING_URL` | -- | OpenAI-compatible embeddings endpoint | +| `ODYSSEUS_CHAT_UPLOAD_MAX_BYTES` | `10485760` | Chat/agent attachment cap in bytes. Raise for larger local PDFs or text documents. | +| `ODYSSEUS_GALLERY_UPLOAD_MAX_BYTES` | `104857600` | Gallery image upload cap in bytes (100 MB). | +| `ODYSSEUS_GALLERY_TRANSFORM_UPLOAD_MAX_BYTES` | `26214400` | Gallery transform input cap in bytes (25 MB). | +| `ODYSSEUS_MEMORY_IMPORT_MAX_BYTES` | `10485760` | Memory import file cap in bytes (10 MB). | +| `ODYSSEUS_PERSONAL_UPLOAD_MAX_BYTES` | `26214400` | Personal document upload cap in bytes (25 MB). | +| `ODYSSEUS_EMAIL_COMPOSE_UPLOAD_MAX_BYTES` | `26214400` | Email compose attachment cap in bytes (25 MB). | +| `ODYSSEUS_STT_MAX_AUDIO_BYTES` | `26214400` | Speech-to-text audio cap in bytes (25 MB). | +| `ODYSSEUS_ICS_MAX_BYTES` | `10485760` | Calendar `.ics` import cap in bytes (10 MB). | + +All upload-limit vars are validated (must be a positive integer) and optional; an invalid value fails fast at startup. + +### Built-in MCP servers (optional setup) + +Odysseus auto-registers a few built-in MCP servers at startup. The npx-based ones (currently the browser server, `@playwright/mcp`) only start when their npm package is already in the local npx cache. If a package isn't cached, that server is skipped with a startup log message explaining what to do, so a fresh install does not block on a multi-minute npm download or hang if Playwright system deps are missing. + +To enable the browser MCP (page navigation, screenshots, vision), run once: + +```bash +npx -y @playwright/mcp@latest --version +``` + +That installs `@playwright/mcp` plus Playwright (~300MB total). Restart Odysseus and the server will register at startup. + +## Architecture +``` +app.py # FastAPI entry point +core/ auth, database, middleware, constants +src/ llm_core, agent_loop, agent_tools, chat_processor, search/ +routes/ chat, session, document, memory, model … endpoints +services/ docs, memory, search, hwfit (Cookbook) … +static/ index.html + app.js + style.css + js/ (modular front-end) +docs/ landing page (index.html) + preview clips +``` + +## Data +All user data lives in `data/` (gitignored): `app.db` (sessions, messages, documents), +`memory.json`, `presets.json`, `uploads/`, `personal_docs/`, `chroma/`, `settings.json`. + +To back up or restore everything in `data/`, see the +[Backup & Restore guide](docs/backup-restore.md).