-
Notifications
You must be signed in to change notification settings - Fork 0
2.1 Build and Deployment Guide
This document explains how to compile, package, and deploy ClioDeck for different platforms.
- Technical Stack
- Prerequisites
- Installation
- Development
- Build and Packaging
- User Installation
- Initial Configuration
- Common Issues
- Distribution and Releases
- 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
- 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)
- 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) ormxbai-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
- Ollama embedding model:
- BERTopic - Topic modeling and clustering (Python)
- 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)
Linux:
sudo apt-get install build-essential python3-devmacOS:
xcode-select --installWindows:
- Visual Studio Build Tools or Visual Studio Community
- Python 3.11+ with pip
npm installThis command installs all Node.js dependencies and automatically compiles native modules (better-sqlite3, hnswlib-node, etc.).
Python services are used for topic modeling. For the development environment:
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.txtnpm run devThis command launches in parallel:
- Main process TypeScript in watch mode
- Preload script in watch mode
- Renderer (React) with Vite hot reload
In another terminal:
npm startnpm run dev:fullLaunches build watch AND the application automatically after 3 seconds.
# 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:coverageTo run the suite once from an interactive terminal, use npx vitest run (see Common how-to in CLAUDE.md) — plain npm test will not exit on its own there.
# Linter
npm run lint
# Type checking
npm run typecheck
⚠️ npm run lintis currently broken, not just unconfigured-by-choice: the script runseslint . --ext .ts,.tsx, but there is no ESLint configuration file anywhere in the repo (no.eslintrc*, noeslint.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 lintworks, and CI runs it. The bar is zero errors; warnings are a known stock.
npm run typecheckalso covers less than what CI actually runs: the script itself is justtsc --noEmitagainst the roottsconfig.json. CI additionally runsnpx tsc -p tsconfig.node.json --noEmitandnpx tsc -p tsconfig.preload.json --noEmitas separate steps — the main and preload processes are separate composite TypeScript projects with their own configs. Running onlynpm run typechecklocally 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.
npm run buildCompiles:
- Main process TypeScript →
dist/src/main/ - Preload script →
dist/src/preload/ - Renderer React →
dist/src/renderer/
npm run build:allDespite 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 below, and macOS/Windows targets can't be cross-built from Linux without it.
# 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# Intel only
npm run build:mac-intel
# Apple Silicon only
npm run build:mac-arm
# Universal (Intel + Apple Silicon)
npm run build:mac-universalnpm run build:dirCreates an unpackaged executable folder in release/.
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-<version>.AppImage # Linux — see the note on names below
│ ├── ClioDeck-<version>.dmg # macOS
│ └── cliodeck_<version>_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 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-<version>.AppImage; build the same commit on an Apple Silicon Mac and you get ClioDeck-<version>-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:
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.
Linux → all OS: Possible with Docker:
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.
- Ollama installed on the machine
-
Models downloaded:
ollama pull nomic-embed-text ollama pull gemma2:2b
AppImage (recommended):
# 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.AppImageDebian/Ubuntu (.deb):
# 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- Download the DMG file from GitHub Releases
- Double-click to mount the disk image
- Drag ClioDeck to the Applications folder
- Launch from Launchpad or Applications
First launch: If macOS displays "app cannot be opened because it is from an unidentified developer":
xattr -cr /Applications/ClioDeck.appOr: Right-click → Open → Confirm
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) if you want to try it.
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:
-
Install Ollama: https://ollama.ai/download
-
Start the service:
# Linux/macOS ollama serve # Windows: Ollama starts automatically as a service
-
Verify the service is working:
curl http://localhost:11434/api/tags
# 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:8bTo sync with Zotero:
-
Get an API key:
- Go to https://www.zotero.org/settings/keys/new
- Permissions: Read library, Write library
- Copy the generated key
-
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
-
Sync:
- Select a Zotero collection
- Click "Sync"
- Wait for PDFs and BibTeX file download
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:
# Rebuild for your architecture
npm run rebuild:native
# Or specifically:
npm run rebuild:x64 # Intel
npm run rebuild:arm64 # Apple Siliconnpm run rebuild:native runs electron-builder install-app-deps, which handles this — there is no separate after-pack script in the repo.
npm rebuild better-sqlite3 --build-from-sourceIf the issue persists, verify that Python and build tools are installed.
# Install C++ build tools
# macOS:
xcode-select --install
# Linux:
sudo apt-get install build-essential
# Then:
npm rebuild hnswlib-node --build-from-sourcePort already in use:
# Kill process on port 8001
lsof -ti:8001 | xargs kill -9Missing Python dependencies:
cd backend/python-services/topic-modeling
source .venv/bin/activate
pip install -r requirements.txtLinux:
# Check status
systemctl status ollama
# Start manually
ollama servemacOS:
# Check if process is running
ps aux | grep ollama
# Start manually
ollama serveWindows:
# Open services.msc
# Check "Ollama" service-
Use CPU optimized config:
- Settings → RAG → Chunking: CPU Optimized
-
Reduce topK:
- Settings → RAG → Top K: 5 (instead of 10)
-
Use a lighter model:
ollama pull gemma2:2b # Instead of mistral:7b
-
Check Ollama:
curl http://localhost:11434/api/tags
-
Verify chat model is installed:
ollama list
-
Change model:
- Settings → LLM → Chat Model
- Select an installed model
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
- Verify API key is valid
- Verify User ID is correct
- Check internet connection
- Check logs for more details
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 page for the three logging mechanisms (global console filter, renderer logger, main-process logger) and their env-var behavior.
- Get an Apple Developer certificate
- Configure in
package.json:
{
"build": {
"mac": {
"identity": "Developer ID Application: Your Name (TEAM_ID)"
}
}
}- Build with signing:
CSC_NAME="Developer ID Application" npm run build:mac- Get a code signing certificate
- Configure and build:
set CSC_LINK=path/to/cert.pfx
set CSC_KEY_PASSWORD=your_password
npm run build:win- Create a version tag:
git tag v1.0.0
git push origin v1.0.0- Build and publish:
GH_TOKEN=your_github_token npm run build:allelectron-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).
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:developmentorproduction -
OLLAMA_HOST: Ollama URL (default: http://localhost:11434) -
CLIODESK_DEBUG,CLIODESK_LOG_LEVEL: toggle the global rawconsole.*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 noprocessglobal at all, meaning this file's own production check can never fire there; separately, Vite'sesbuild.puresetting already stripsconsole.log/info/debugcalls out of the production renderer bundle regardless. It does not affect the renderer's own structured logger. See Logging System
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)
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.
- 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
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 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.
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)
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.
# 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- Electron Builder Documentation
- Vite Documentation
- Ollama Documentation
- TypeScript Documentation
- Vitest Documentation
Note for daily development: Simply use npm run dev in one terminal and npm start in another.