This document explains how to compile, package, and deploy ClioDeck for different platforms. ## Table of Contents - [Technical Stack](#technical-stack) - [Prerequisites](#prerequisites) - [Installation](#installation) - [Development](#development) - [Build and Packaging](#build-and-packaging) - [User Installation](#user-installation) - [Initial Configuration](#initial-configuration) - [Common Issues](#common-issues) - [Distribution and Releases](#distribution-and-releases) --- ## Technical Stack ### Frontend - **Electron 40** - Multi-platform desktop - **React 18** - UI components - **TypeScript 5** - Type safety - **CodeMirror 6** - Markdown editor with live rendering - **Zustand** - State management - **Vite** - Build tool ### Backend - **Node.js 20+** - JavaScript runtime - **better-sqlite3** - SQLite database (vector store) - **pdfjs-dist** - PDF extraction - **electron-store** - Config persistence - **Python 3.11+** - Analysis services (topic modeling) ### LLM & AI - A typed provider registry — Ollama, embedded (`node-llama-cpp`), Anthropic, OpenAI-compatible, Mistral, Gemini — for both generation and embeddings, selected independently - Ollama embedding model: `nomic-embed-text` (768 dimensions) or `mxbai-embed-large` (not an automatic fallback — an independent choice) - Ollama chat model: `gemma2:2b` (fast, multilingual, but not tool-capable) or any chat model Ollama can run - Embedded (no Ollama needed): Qwen2.5-0.5B/1.5B for generation, Nomic Embed Text v2 MoE for embeddings — a full offline RAG workflow needs neither Ollama nor a cloud key - **BERTopic** - Topic modeling and clustering (Python) ## Prerequisites ### Development - **Node.js** 20+ and npm 10+ - **Python** 3.11+ (for better-sqlite3 and Python services) - **Ollama** installed locally for testing (or the embedded model, or a cloud provider — see [Embedded LLM Guide](https://github.com/cliodeck/cliodeck-app/wiki/1.7-Embedded-LLM-Guide)) ### Platform-Specific **Linux:** ```bash sudo apt-get install build-essential python3-dev ``` **macOS:** ```bash xcode-select --install ``` **Windows:** - Visual Studio Build Tools or Visual Studio Community - Python 3.11+ with pip --- ## Installation ### Installing Dependencies ```bash npm install ``` This command installs all Node.js dependencies and automatically compiles native modules (better-sqlite3, hnswlib-node, etc.). ### Installing Python Dependencies (Topic Modeling) Python services are used for topic modeling. For the development environment: ```bash cd backend/python-services/topic-modeling python3 -m venv .venv source .venv/bin/activate # Linux/macOS # Or: .venv\Scripts\activate # Windows pip install -r requirements.txt ``` --- ## Development ### Development Mode with Hot Reload ```bash npm run dev ``` This command launches in parallel: - Main process TypeScript in watch mode - Preload script in watch mode - Renderer (React) with Vite hot reload ### Start the Application In another terminal: ```bash npm start ``` ### Full Development Mode (All-in-One) ```bash npm run dev:full ``` Launches build watch AND the application automatically after 3 seconds. ### Tests ```bash # Bare `vitest` — in CI (non-interactive) this runs once and exits, but in # an interactive terminal it enters watch mode, same as npm run test:watch npm test # Explicit watch mode npm run test:watch # With UI npm run test:ui # With coverage npm run test:coverage ``` To run the suite once from an interactive terminal, use `npx vitest run` (see [Common how-to in CLAUDE.md](https://github.com/cliodeck/cliodeck-app/blob/main/CLAUDE.md)) — plain `npm test` will not exit on its own there. ### Linting and Type Checking ```bash # Linter npm run lint # Type checking npm run typecheck ``` > **⚠️ `npm run lint` is currently broken, not just unconfigured-by-choice:** > the script runs `eslint . --ext .ts,.tsx`, but there is no ESLint > configuration file anywhere in the repo (no `.eslintrc*`, no > `eslint.config.*`) — running it failed immediately with "ESLint couldn't > find a configuration file", and CI deliberately left lint out for that > reason. **Fixed in RC3's follow-up work**: a configuration exists, `npm > run lint` works, and CI runs it. The bar is **zero errors**; warnings are > a known stock. > > `npm run typecheck` also covers less than what CI actually runs: the > script itself is just `tsc --noEmit` against the root `tsconfig.json`. > CI additionally runs `npx tsc -p tsconfig.node.json --noEmit` and > `npx tsc -p tsconfig.preload.json --noEmit` as separate steps — the main > and preload processes are separate composite TypeScript projects with > their own configs. Running only `npm run typecheck` locally will not > catch a type error that's only visible from one of those two other > configs; run all three commands to match what CI actually checks. --- ## Build and Packaging ### Build Without Packaging ```bash npm run build ``` Compiles: - Main process TypeScript → `dist/src/main/` - Preload script → `dist/src/preload/` - Renderer React → `dist/src/renderer/` ### Build with Packaging #### All Platforms ```bash npm run build:all ``` Despite the name, this runs `electron-builder` with no `--linux`/`--mac`/`--win` flag, which only builds for the **current host platform** — not literally all three at once. Cross-building for other platforms needs the Docker/Wine approach described under [Cross-Platform Packaging](#cross-platform-packaging) below, and macOS/Windows targets can't be cross-built from Linux without it. #### Specific Platforms ```bash # Linux (AppImage + deb) npm run build:linux # macOS (DMG for Intel and Apple Silicon) npm run build:mac # Windows (NSIS installer) npm run build:win ``` #### macOS Build by Architecture ```bash # Intel only npm run build:mac-intel # Apple Silicon only npm run build:mac-arm # Universal (Intel + Apple Silicon) npm run build:mac-universal ``` #### Build Without Installer (for Testing) ```bash npm run build:dir ``` Creates an unpackaged executable folder in `release/`. ### Build Structure ``` cliodeck-app/ ├── dist/ # Compiled code │ ├── src/ │ │ ├── main/ # Main process JS │ │ ├── preload/ # Preload script JS │ │ └── renderer/ # React build ├── release/ # Installers, named after the current package.json version │ ├── ClioDeck-.AppImage # Linux — see the note on names below │ ├── ClioDeck-.dmg # macOS │ └── cliodeck__amd64.deb # Linux └── build/ # Packaging assets ├── icon.png ├── icon.icns └── icon.ico ``` No Windows entry: `npm run build:win` exists but no Windows build has ever been published (untested — see [User Installation](#user-installation) below). **A note on those names — they are not fixed.** electron-builder only writes the architecture into the filename when it is *not* the default (x64). Build on an x86_64 machine and you get `ClioDeck-.AppImage`; build the same commit on an Apple Silicon Mac and you get `ClioDeck--arm64.AppImage`. The `linux` target in `package.json` declares no architecture, so it silently inherits the build machine's. This is how the rc.4 release first shipped Linux binaries that were **arm64 only**, built on a Mac, while the file names gave nothing away. Never infer an architecture from a filename — read the ELF header: ```bash readelf -h ClioDeck.AppImage | grep Machine # AppImages are ELF executables dpkg-deb --field cliodeck.deb Architecture # what dpkg will check ``` `.github/workflows/build-linux-x64.yml` builds the x86_64 artefacts on a `ubuntu-latest` runner (manual trigger) and runs exactly these checks before anything is attached to a release. ### Cross-Platform Packaging **Linux → all OS:** Possible with Docker: ```bash docker run --rm -v $(pwd):/project electronuserland/builder:wine \ bash -c "cd /project && npm install && npm run build:all" ``` **macOS → macOS/Linux/Windows:** macOS can build for all platforms natively. **Windows → Windows only:** Windows can only build for Windows. --- ## User Installation ### User Prerequisites 1. **Ollama installed** on the machine 2. **Models downloaded**: ```bash ollama pull nomic-embed-text ollama pull gemma2:2b ``` ### Linux **AppImage (recommended):** ```bash # Asset names carry a platform prefix since rc.4 (`Linux.AppImage.-.`, # `Mac.Silicon.-.` …). Building a URL by bumping the version number alone # returns 404 — check the Releases page for the exact name. # x86_64 below; on arm64 replace `-x86_64` with `-arm64` (`uname -m` tells you). wget https://github.com/cliodeck/cliodeck-app/releases/download/v1.0.0-rc.4/Linux.AppImage.-.ClioDeck-1.0.0-rc.4-x86_64.AppImage -O ClioDeck.AppImage # Make executable chmod +x ClioDeck.AppImage # Launch ./ClioDeck.AppImage ``` **Debian/Ubuntu (.deb):** ```bash # Debian calls x86_64 `amd64`; on arm64 the file is `..._arm64.deb`. sudo dpkg -i Linux.Debian.-.cliodeck_1.0.0-rc.4_amd64.deb sudo apt-get install -f # Fix dependencies if necessary cliodeck ``` ### macOS 1. Download the DMG file from GitHub Releases 2. Double-click to mount the disk image 3. Drag ClioDeck to the Applications folder 4. Launch from Launchpad or Applications **First launch:** If macOS displays "app cannot be opened because it is from an unidentified developer": ```bash xattr -cr /Applications/ClioDeck.app ``` Or: Right-click → Open → Confirm ### Windows **No Windows build is currently published.** `npm run build:win` exists and the code should work on Windows, but this is untested — there is no `ClioDeck-Setup-*.exe` on the Releases page as of v1.0.0-rc.4. Build from source (see [Build and Packaging](#build-and-packaging)) if you want to try it. --- ## Initial Configuration ### 1. Verify Ollama The first time you open Settings, ClioDeck checks the Ollama connection — this fires when the Settings panel mounts (`ConfigPanel.tsx`), not automatically at app launch. **If Ollama is not detected:** 1. Install Ollama: https://ollama.ai/download 2. Start the service: ```bash # Linux/macOS ollama serve # Windows: Ollama starts automatically as a service ``` 3. Verify the service is working: ```bash curl http://localhost:11434/api/tags ``` ### 2. Download Models ```bash # Embedding model (REQUIRED) ollama pull nomic-embed-text # Chat model (RECOMMENDED) ollama pull gemma2:2b # Chat alternatives ollama pull mistral:7b-instruct # More accurate but heavier # Tool-capable alternatives (needed if you want Brainstorm to search your # corpus on its own — gemma2:2b and the Llama 3.x/4.x families cannot): ollama pull qwen3:8b ``` ### 3. Zotero Configuration (optional) To sync with Zotero: 1. **Get an API key:** - Go to https://www.zotero.org/settings/keys/new - Permissions: Read library, Write library - Copy the generated key 2. **Configure in ClioDeck:** - Settings → Zotero Integration - User ID: your Zotero user ID (visible in your library URL) - API Key: paste the key - Test Connection to verify 3. **Sync:** - Select a Zotero collection - Click "Sync" - Wait for PDFs and BibTeX file download --- ## Common Issues ### Development #### "Incompatible architecture" error on macOS **Symptom:** ``` mach-o file, but is an incompatible architecture (have 'arm64', need 'x86_64') ``` **Cause:** Native modules (better-sqlite3, hnswlib-node) are compiled for the wrong architecture. **Solution:** ```bash # Rebuild for your architecture npm run rebuild:native # Or specifically: npm run rebuild:x64 # Intel npm run rebuild:arm64 # Apple Silicon ``` `npm run rebuild:native` runs `electron-builder install-app-deps`, which handles this — there is no separate `after-pack` script in the repo. #### better-sqlite3 doesn't compile ```bash npm rebuild better-sqlite3 --build-from-source ``` If the issue persists, verify that Python and build tools are installed. #### hnswlib-node doesn't compile ```bash # Install C++ build tools # macOS: xcode-select --install # Linux: sudo apt-get install build-essential # Then: npm rebuild hnswlib-node --build-from-source ``` #### Python topic modeling service doesn't start **Port already in use:** ```bash # Kill process on port 8001 lsof -ti:8001 | xargs kill -9 ``` **Missing Python dependencies:** ```bash cd backend/python-services/topic-modeling source .venv/bin/activate pip install -r requirements.txt ``` ### Production #### Ollama doesn't start **Linux:** ```bash # Check status systemctl status ollama # Start manually ollama serve ``` **macOS:** ```bash # Check if process is running ps aux | grep ollama # Start manually ollama serve ``` **Windows:** ```bash # Open services.msc # Check "Ollama" service ``` #### Slow embeddings 1. **Use CPU optimized config:** - Settings → RAG → Chunking: CPU Optimized 2. **Reduce topK:** - Settings → RAG → Top K: 5 (instead of 10) 3. **Use a lighter model:** ```bash ollama pull gemma2:2b # Instead of mistral:7b ``` #### Chat doesn't respond 1. **Check Ollama:** ```bash curl http://localhost:11434/api/tags ``` 2. **Verify chat model is installed:** ```bash ollama list ``` 3. **Change model:** - Settings → LLM → Chat Model - Select an installed model #### Poorly indexed PDFs **Empty extracted text:** - The PDF is image-only (no selectable text) - Solution: Use external OCR then reimport **Poor extraction quality:** - The PDF is poorly formatted or corrupted - Check the PDF in an external reader #### Zotero sync fails 1. Verify API key is valid 2. Verify User ID is correct 3. Check internet connection 4. Check logs for more details ### Debug Logs ClioDeck does not currently write logs to a file — output goes to the console only. Launch the app from a terminal (or `npm start` in development) to see it. See the [Logging System](https://github.com/cliodeck/cliodeck-app/wiki/2.3-Logging-System) page for the three logging mechanisms (global console filter, renderer logger, main-process logger) and their env-var behavior. --- ## Distribution and Releases ### Code Signing (Production) #### macOS 1. Get an Apple Developer certificate 2. Configure in `package.json`: ```json { "build": { "mac": { "identity": "Developer ID Application: Your Name (TEAM_ID)" } } } ``` 3. Build with signing: ```bash CSC_NAME="Developer ID Application" npm run build:mac ``` #### Windows 1. Get a code signing certificate 2. Configure and build: ```bash set CSC_LINK=path/to/cert.pfx set CSC_KEY_PASSWORD=your_password npm run build:win ``` ### GitHub Releases 1. **Create a version tag:** ```bash git tag v1.0.0 git push origin v1.0.0 ``` 2. **Build and publish:** ```bash GH_TOKEN=your_github_token npm run build:all ``` electron-builder can automatically publish to GitHub Releases if a `publish` block is configured — `package.json` does not currently have one; releases are attached to tags manually today. If you add one, point it at `cliodeck/cliodeck-app` (the current repository — the project moved from `inactinique/cliodeck`, now archived). ### Environment Variables **Build:** - `CSC_LINK`: Path to signing certificate - `CSC_KEY_PASSWORD`: Certificate password - `GH_TOKEN`: GitHub token for releases - `DEBUG`: Enable electron-builder debug logs **Runtime:** - `NODE_ENV`: `development` or `production` - `OLLAMA_HOST`: Ollama URL (default: http://localhost:11434) - `CLIODESK_DEBUG`, `CLIODESK_LOG_LEVEL`: toggle the global raw `console.*` filter (`src/shared/console-filter.ts`). In practice this is only observable in the **main process** console — the packaged renderer runs fully sandboxed (`contextIsolation: true`, `nodeIntegration: false`, `sandbox: true`), so it has no `process` global at all, meaning this file's own production check can never fire there; separately, Vite's `esbuild.pure` setting already strips `console.log`/`info`/`debug` calls out of the production renderer bundle regardless. It does **not** affect the renderer's own structured logger. See [Logging System](https://github.com/cliodeck/cliodeck-app/wiki/2.3-Logging-System) --- ## Performance and Optimization ### Recommended Hardware Configuration **Minimum:** - CPU: Dual-core - RAM: 4 GB - Disk: 5 GB free **Recommended:** - CPU: Quad-core - RAM: 8 GB - Disk: 10 GB free **Optimal:** - CPU: 8+ cores - RAM: 16 GB - Disk: 20 GB free (for Ollama models) ### Installer Sizes As of v1.0.0-rc.4 (measured on the published assets): - **Linux AppImage**: 570 MB (x86_64), 262 MB (arm64) - **Linux deb**: 340 MB (x86_64), 158 MB (arm64) - **macOS DMG**: 263 MB (Apple Silicon), 268 MB (Intel) - **Windows**: not built (no release published) **Why x86_64 is more than twice the size.** Not a packaging fault — the same ratio shows up in both formats, so it is the payload. `node-llama-cpp` ships its GPU backends as per-platform optional dependencies: on macOS arm64 npm installs only `mac-arm64-metal` (5 MB), while on linux-x64 it pulls `linux-x64`, `linux-x64-cuda`, `linux-x64-cuda-ext` and `linux-x64-vulkan`. The CUDA ones account for most of the difference. They are not dead weight: `EmbeddedLLMClient` calls `getLlama()` with no argument, so backend detection is automatic and a user with an NVIDIA card gets the acceleration. But every x86_64 user pays the download for it. Excluding the CUDA variants through the `files` field would roughly halve the artefact at the cost of that acceleration — a trade-off, not an obvious win. ### Disk Space per Project - **App**: ~200 MB - **Ollama models**: ~500 MB - 5 GB (depending on models) - **Vector database**: 50-500 MB per project (depending on PDF count) - **Research journal**: 1-5 MB per session, 50-200 MB for a long project --- ## Security and Privacy ### Local Data All data remains **local**: - PDFs and documents: stored in project folder - Embeddings and indexes: local SQLite (`.cliodeck/brain.db`) - LLM and models: local Ollama, the embedded model, or whichever cloud provider you configure (see [Technical Stack](#technical-stack) above) **No data is sent to external servers by default** (local Ollama or the embedded model, plus local storage) — but several *optional*, user-configured features do send data out, and this line's previous "except Zotero" wording undersold that, directly contradicting the "whichever cloud provider you configure" bullet just above: - **Zotero sync** — bibliography metadata and PDFs, if configured. - **A cloud LLM provider** (Anthropic, OpenAI-compatible, Mistral, Gemini) — if you pick one instead of Ollama/embedded, your prompts and retrieved context go to that provider's API. - **Archive connectors** (Gallica, HAL, Europeana) — any query you run through these MCP tools goes to the respective external API; Europeana additionally requires an API key. ### API Key Storage All sensitive keys (`src/main/services/secure-storage.ts`) — Zotero API key, Europeana API key, and every cloud LLM provider key (Anthropic, OpenAI, Mistral, Gemini) — go through the same mechanism: Electron's `safeStorage` API encrypts the value, and a separate `electron-store` instance persists the encrypted bytes. `safeStorage` itself is backed by: - Linux: GNOME Keyring / KWallet - macOS: Keychain - Windows: Credential Manager (DPAPI) **⚠️ Undocumented fallback:** if `safeStorage.isEncryptionAvailable()` returns false — e.g. on a Linux setup with no keyring service running — `secure-storage.ts` silently stores every one of those keys in **plain text** instead, logging only a `console.warn` to the main-process console. Nothing surfaces this in the UI, so a user on a keyring-less Linux install would have no way to know their API keys aren't encrypted unless they launched ClioDeck from a terminal and read the log. --- ## Useful Scripts ```bash # Full cleanup npm run clean # Reinstall all dependencies rm -rf node_modules package-lock.json npm install # Rebuild native modules npm run rebuild:native # Check types npm run typecheck # Linter npm run lint # Build preview npm run preview ``` --- ## Resources - [Electron Builder Documentation](https://www.electron.build/) - [Vite Documentation](https://vitejs.dev/) - [Ollama Documentation](https://ollama.ai/docs) - [TypeScript Documentation](https://www.typescriptlang.org/) - [Vitest Documentation](https://vitest.dev/) --- **Note for daily development:** Simply use `npm run dev` in one terminal and `npm start` in another.