Skip to content

Repository files navigation

Cavendo
CAVENDO ENGINE

Open-source agent workflow platform for AI-human collaboration

cavendo.com · Product Page · Documentation

Beta / Early Release — Cavendo Engine is under active development. APIs, schema, and behavior may change between releases. Suitable for evaluation, development, and early adoption. Not yet recommended for mission-critical production workloads without testing.

Cavendo Engine provides the infrastructure for AI agents to receive tasks, submit deliverables, and get human feedback in a structured review loop.

Features

  • User Management - Create users with auto-linked human agents for task assignment
  • Agent Registration - Register agents with API keys and scoped permissions
  • Agent Profiles - Rich agent metadata with capabilities, specializations, and capacity limits
  • Task Routing - Automatic task assignment based on tags, priority, and agent capabilities
  • User API Keys - Personal cav_uk_ keys for MCP access as yourself
  • Agent Execution - Execute tasks via Anthropic, OpenAI, or any OpenAI-compatible endpoint (Ollama, LM Studio, vLLM)
  • Task Management - Create, assign, and track tasks for agents
  • Sprint Planning - Organize tasks into sprints/milestones with progress tracking
  • Task Claiming - Agents can self-assign tasks from a pool
  • Progress Logging - Track task progress with percentage and details
  • Deliverable Workflow - Submit → Review → Approve/Revise/Reject cycle
  • Delivery Routes - Auto-route approved content to webhooks/email (with pluggable destinations)
  • Token Usage Tracking - Track input/output tokens for AI-generated deliverables
  • Comments/Discussion - Threaded discussions on tasks and deliverables
  • Bulk Operations - Create, update, or delete up to 100 tasks at once
  • Agent Metrics - Performance analytics: approval rates, completion times
  • Outbound Webhooks - 20 event types with HMAC signing and retry logic
  • S3 Storage Routes - Auto-upload deliverables to S3-compatible storage (AWS, MinIO, Backblaze B2)
  • Email Notifications - Multi-provider (SMTP, SendGrid, Mailjet, Postmark, SES)
  • Universal Activity Log - Full audit trail for deliverable and task lifecycle events
  • External Employees - First-class Bring Your Own Agent runtimes with queue polling, lease claiming, heartbeat, and result submission
  • Context - Project context for agent task completion
  • Minimal Admin UI - React-based dashboard for human oversight

Quick Start

Prerequisites

  • Node.js 18.0 or higher
  • npm or yarn
  • Python 3.x with setuptools (for native module compilation)
    • macOS: brew install python-setuptools
    • Linux: pip install setuptools or install via package manager
    • Windows: Usually included with Python installer
git clone https://github.com/Cavendo/Engine.git Cavendo-Engine
cd Cavendo-Engine

# Install server and UI dependencies
npm install
cd ui && npm install && cd ..

# Start development server (API + UI with hot reload)
npm run dev

On first start, the server automatically:

  • Generates .env with secure random secrets (JWT, encryption key)
  • Creates the SQLite database and applies the schema
  • Seeds a default admin user

The API runs at http://localhost:3001 and the UI dev server at http://localhost:5173.

For production, build the UI first: npm run build, then npm start.

First Login

Development-only bootstrap credentials:

  • Email: admin@cavendo.local
  • Password: admin

They are forced through a password change before they can use the API. Production startup refuses this account and requires BOOTSTRAP_ADMIN_EMAIL plus BOOTSTRAP_ADMIN_PASSWORD (or BOOTSTRAP_ADMIN_PASSWORD_FILE) when no administrator exists.

API Overview

Authentication

Two key types are supported for API and MCP access.

You can send the key in any of these headers:

  • X-Agent-Key: <your-api-key>
  • X-API-Key: <your-api-key>
  • Authorization: Bearer <your-api-key>

Note: Browser-based API calls (POST, PATCH, PUT, DELETE) require the X-CSRF-Token header with the token returned from the login response. API key authentication bypasses CSRF checks.

Key Type Format Identity Use Case
User Key cav_uk_... Acts as the user Personal MCP access, direct user API access
Agent Key cav_ak_... Agent identity Automated bots, worker/MCP access
# User key - acts as you
curl -H "X-Agent-Key: cav_uk_..." http://localhost:3001/api/agents/me/tasks

# Agent key - acts as the agent
curl -H "X-Agent-Key: cav_ak_..." http://localhost:3001/api/agents/me/tasks

# Bearer auth is also supported
curl -H "Authorization: Bearer cav_uk_..." http://localhost:3001/api/agents/me

For internal service-to-service provisioning, use a bearer token instead of X-Agent-Key:

openssl rand -hex 32
INTERNAL_SERVICE_TOKEN=replace_with_random_secret
Authorization: Bearer <INTERNAL_SERVICE_TOKEN>
X-Internal-Service-Name: workflow_engine

External Employees and BYOA Runtimes

Cavendo supports first-class external employees that run outside the built-in executor. These workers:

  • poll for assigned work through the API
  • claim an execution lease before starting
  • fetch the full task context bundle
  • renew the lease with heartbeat calls while running
  • publish lifecycle state with external-status
  • submit results back into Cavendo for review

Recommended usage:

  • use agent keys for unattended workers
  • use user keys for user-owned local assistants and MCP sessions
  • use the HTTP API for long-running queue execution
  • use MCP for interactive, local, assistant-style access

See docs/external-agents.md for the full runtime contract.

Two important rules:

  • tasks are assigned to agents, not directly to users
  • some OAuth/callback-based connector handshakes can still require an interactive browser finish step

Core Endpoints

Method Endpoint Description
POST /api/users Create user (auto-creates linked agent)
PATCH /api/users/:id Update user (cascades to agent)
DELETE /api/users/:id Delete user and linked agent
POST /api/agents Register new agent
GET /api/agents/:id Get agent details
PATCH /api/agents/:id Update agent
DELETE /api/agents/:id Delete agent
POST /api/agents/:id/keys Generate agent API key
DELETE /api/agents/:id/keys/:keyId Revoke agent API key
GET /api/agents/:id/metrics Get agent performance metrics
GET /api/agents/providers List supported AI providers
POST /api/agents/match Match agents for a task payload
POST /api/agents/:id/execute Execute task via provider API
POST /api/agents/:id/test-connection Validate provider configuration
PATCH /api/agents/:id/execution Update execution/provider settings
GET /api/users/me/keys List personal API keys
POST /api/users/me/keys Generate personal API key
GET /api/agents/me/tasks List assigned tasks
GET /api/agents/me/tasks/next Get next task from queue
POST /api/agents/me/tasks/poll Poll assigned work for an external employee
POST /api/agents/me/tasks/:taskId/claim Claim an execution lease for assigned work
POST /api/agents/me/tasks/:taskId/heartbeat Renew a claimed external execution lease
POST /api/agents/me/tasks/:taskId/release Release a claimed external execution lease
POST /api/tasks/:id/claim Claim unassigned task
POST /api/tasks/:id/progress Log progress update
GET /api/tasks/:id/context Get full task context bundle
POST /api/tasks/:id/external-status Publish external execution lifecycle state
POST /api/tasks/:id/result Submit an external worker result
PATCH /api/tasks/:id Update task fields
POST /api/tasks/bulk Bulk create tasks (up to 50)
PATCH /api/tasks/bulk Bulk update tasks
DELETE /api/tasks/bulk Bulk delete tasks
DELETE /api/tasks/:id Delete task
POST /api/tasks/:id/execute Force task execution
POST /api/tasks/:id/retry Retry failed task execution
GET /api/sprints List sprints
POST /api/sprints Create sprint
GET /api/sprints/:id/tasks Get tasks in sprint
POST /api/deliverables Submit deliverable
PATCH /api/deliverables/:id/review Approve/revise/reject
GET /api/deliverables/:id/feedback Get revision feedback
POST /api/tasks/:id/comments Add comment to task
POST /api/deliverables/:id/comments Add comment to deliverable
POST /api/projects/:id/routes Create delivery route
POST /api/projects Create project
PATCH /api/projects/:id Update project
DELETE /api/projects/:id Delete project
GET /api/projects/:id/routing-rules Get project routing rules
PUT /api/projects/:id/routing-rules Update project routing rules
POST /api/projects/:id/routing-rules/test Test routing rules
GET /api/skills-runtime/catalog List runtime skills allowed for actor
POST /api/skills-runtime/invocations Invoke runtime skill
GET /api/skills-runtime/invocations/:id Get runtime skill invocation
POST /api/skills-runtime/invocations/:id/cancel Cancel runtime skill invocation
GET /api/routes/:id/logs View delivery log
POST /api/routes/:id/test Test route connection
GET /api/deliverables/:id/activity Deliverable activity timeline
GET /api/tasks/:id/activity Task activity timeline
GET /api/activity/entity/:type/:id Activity log by entity type
POST /api/internal/provisioning/projects/ensure Ensure project by external key (internal token)
POST /api/internal/provisioning/projects/:externalKey/routing-rules/ensure Ensure task routing config (internal token)

Internal Provisioning

These endpoints are backend-only. They do not accept browser session auth or X-Agent-Key, and are intended for automation that needs stable idempotent project provisioning.

curl -X POST http://localhost:3001/api/internal/provisioning/projects/ensure \
  -H "Authorization: Bearer $INTERNAL_SERVICE_TOKEN" \
  -H "X-Internal-Service-Name: workflow_engine" \
  -H "Content-Type: application/json" \
  -d '{
    "externalKey": "project:acme",
    "name": "Acme",
    "description": "Provisioned by workflow automation",
    "status": "active"
  }'

Webhook Events

Configure webhooks per-agent to receive real-time notifications:

  • deliverable.approved - Deliverable approved
  • deliverable.submitted - New deliverable submitted
  • deliverable.revision_requested - Revision requested with feedback
  • deliverable.rejected - Deliverable rejected
  • task.created - New task created
  • task.assigned - Task assigned to agent
  • task.completed - Task completed
  • task.status_changed - Task status transitions
  • task.overdue - Task past due date
  • task.updated - Task details changed
  • task.claimed - Agent claimed a task
  • task.progress_updated - Progress logged on task
  • task.routing_failed - No matching agent for routing
  • task.execution_failed - Agent execution failed
  • review.completed - Review action taken on deliverable
  • agent.registered - New agent created
  • agent.status_changed - Agent status changed
  • project.created - New project created
  • project.knowledge_updated - Project knowledge base changed
  • knowledge.updated - Knowledge entry changed

Delivery Routes

Delivery routes automatically push approved content to external systems. Configure per-project routes that trigger on events like deliverable.approved:

# Create a webhook route
curl -X POST http://localhost:3001/api/projects/1/routes \
  -H "Content-Type: application/json" \
  -H "Cookie: session=..." \
  -H "X-CSRF-Token: ..." \
  -d '{
    "name": "Notify on approval",
    "trigger_event": "deliverable.approved",
    "destination_type": "webhook",
    "destination_config": {
      "url": "https://example.com/webhook",
      "signing_secret": "whsec_..."
    }
  }'

Destination Types:

  • webhook - POST to any URL with optional HMAC signing
  • email - Send via configured provider (SMTP, SendGrid, Mailjet, Postmark, SES)
  • storage - Upload deliverable files to S3-compatible storage (AWS S3, MinIO, Backblaze B2)

Email Configuration:

Email for delivery routes is configured via environment variables in .env. Set EMAIL_PROVIDER to one of the supported providers and add the corresponding credentials:

Provider EMAIL_PROVIDER Required Variables
SMTP smtp (default) EMAIL_SMTP_HOST, EMAIL_SMTP_PORT (default 587), EMAIL_SMTP_SECURE (true/false), EMAIL_SMTP_USER, EMAIL_SMTP_PASS
SendGrid sendgrid EMAIL_SENDGRID_API_KEY
Mailjet mailjet EMAIL_MAILJET_API_KEY, EMAIL_MAILJET_SECRET_KEY
Postmark postmark EMAIL_POSTMARK_SERVER_TOKEN
AWS SES ses EMAIL_SES_REGION, EMAIL_SES_ACCESS_KEY_ID, EMAIL_SES_SECRET_ACCESS_KEY

Common variables (all providers):

  • EMAIL_FROM — Sender email address (default: notifications@cavendo.local)
  • EMAIL_FROM_NAME — Sender display name (default: Cavendo)

Example (SendGrid):

EMAIL_PROVIDER=sendgrid
EMAIL_SENDGRID_API_KEY=SG.xxx
EMAIL_FROM=notifications@example.com
EMAIL_FROM_NAME=Cavendo

Example (SMTP):

EMAIL_PROVIDER=smtp
EMAIL_SMTP_HOST=smtp.example.com
EMAIL_SMTP_PORT=587
EMAIL_SMTP_SECURE=false
EMAIL_SMTP_USER=user@example.com
EMAIL_SMTP_PASS=secret
EMAIL_FROM=notifications@example.com

Storage Route Configuration:

# Create an S3 storage route
curl -X POST http://localhost:3001/api/projects/1/routes \
  -H "Content-Type: application/json" \
  -H "Cookie: session=..." \
  -H "X-CSRF-Token: ..." \
  -d '{
    "name": "Archive approved deliverables",
    "trigger_event": "deliverable.approved",
    "destination_type": "storage",
    "destination_config": {
      "provider": "s3",
      "bucket": "my-deliverables",
      "region": "us-east-1",
      "access_key_id": "AKIA...",
      "secret_access_key": "...",
      "path_prefix": "cavendo/",
      "upload_content": true,
      "upload_files": true
    }
  }'

For MinIO or other S3-compatible services, add the endpoint field:

{
  "endpoint": "https://minio.example.com:9000"
}

See Routes API for full documentation.

Packages

Package Path Audience Stability Description
@cavendo/mcp-server packages/mcp-server MCP-native tools (Claude Code, Claude Desktop, etc.) Stable MCP server providing tools for task management, deliverable submission, and knowledge access
cavendo (Python SDK) packages/python-sdk Python developers building agents or automation Beta Typed Python client wrapping the full REST API
OpenClaw Skill packages/openclaw-skill OpenClaw agent framework users Beta CLI scripts (check_tasks, submit_work, get_context) for shell-based agent workflows

Each package is independently versioned. See individual CHANGELOG.md files in each package directory.

Integrations

MCP Server

npm install @cavendo/mcp-server

Configure in Claude Desktop with your personal API key:

{
  "mcpServers": {
    "cavendo": {
      "command": "npx",
      "args": ["@cavendo/mcp-server"],
      "env": {
        "CAVENDO_URL": "http://localhost:3001",
        "CAVENDO_AGENT_KEY": "cav_uk_..."
      }
    }
  }
}

Or use an agent key for bot identity:

{
  "mcpServers": {
    "cavendo": {
      "command": "npx",
      "args": ["@cavendo/mcp-server"],
      "env": {
        "CAVENDO_URL": "http://localhost:3001",
        "CAVENDO_AGENT_KEY": "cav_ak_..."
      }
    }
  }
}

Agent Execution

Agents can be configured to execute tasks via AI provider APIs (Anthropic, OpenAI, or any OpenAI-compatible endpoint). Provider API keys are stored encrypted (AES-256-GCM).

Execution Modes

Mode Behavior
Manual Admin triggers execution via UI or API
Auto Task Dispatcher automatically executes when tasks are assigned
Polling Agent periodically checks for new tasks (external agent)
Human Human worker — dispatcher skips, notifications via delivery routes

Task Dispatcher

The built-in Task Dispatcher is a background service that automatically executes tasks assigned to agents with execution_mode = 'auto'. It runs as part of the server process.

How it works:

  1. Polls for eligible tasks on a configurable interval (default: 30 seconds)
  2. Checks agent capacity (active_task_count < max_concurrent_tasks)
  3. Gathers full task context (project context, previous deliverables, feedback)
  4. Executes via the agent's configured AI provider
  5. Creates a deliverable from the response and sets task to review
  6. Handles revision chains automatically — when re-executing a task sent back for revision, links the new deliverable to the previous version via parent_id and marks the old one as revised

Configuration (.env):

DISPATCHER_INTERVAL_MS=30000   # Polling interval (default: 30 seconds)
DISPATCHER_BATCH_SIZE=5        # Max tasks per cycle (default: 5)

Error handling:

  • Errors are categorized with appropriate cooldowns: auth_error/config_error (5 min), timeout/overloaded (10 min), rate_limited (60 min), unknown (6 hours)
  • Updating an agent's API key automatically clears stuck task errors
  • Manual retry: POST /api/tasks/:id/retry clears a task's error cache
  • All executions are logged to the Activity Log for visibility

Manual execution:

# Execute a specific task now (requires session auth + CSRF token)
curl -X POST \
  -H "Cookie: session=YOUR_SESSION_ID" \
  -H "X-CSRF-Token: YOUR_CSRF_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"taskId": 123}' \
  http://localhost:3001/api/agents/1/execute

# Test provider connection
curl -X POST \
  -H "Cookie: session=YOUR_SESSION_ID" \
  -H "X-CSRF-Token: YOUR_CSRF_TOKEN" \
  http://localhost:3001/api/agents/1/test-connection

Local Model Support

Use the openai_compatible provider to run tasks against Ollama, LM Studio, vLLM, or any server exposing /v1/chat/completions:

# Configure an agent for local Ollama
curl -X PATCH \
  -H "Cookie: session=YOUR_SESSION_ID" \
  -H "X-CSRF-Token: YOUR_CSRF_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "openai_compatible",
    "providerModel": "llama3.2:latest",
    "providerBaseUrl": "http://localhost:11434",
    "providerLabel": "Ollama",
    "executionMode": "manual"
  }' \
  http://localhost:3001/api/agents/1/execution

Known gotcha: The model name must match the exact tag in your local runtime (e.g., ollama list). A typo like llama3.2 instead of llama3.2:latest will return a model-not-found error from the local server.

Endpoint security — By default, only local and allowlisted endpoints are permitted. Configure via .env:

# Allow remote HTTPS endpoints (default: false)
ALLOW_CUSTOM_PROVIDER_BASE_URLS=true

# Allowlist specific hosts (comma-separated, supports host:port)
PROVIDER_BASE_URL_ALLOWLIST=gpu-box.lan,myserver.local:11434

# Default base URL when agent has none set (default: http://localhost:11434)
OPENAI_COMPAT_DEFAULT_BASE_URL=http://localhost:11434

Deliverable Version Chain

When a deliverable is sent back for revision and re-executed (by the dispatcher or manually), the system maintains a version chain:

v1 (revised) ← v2 (revised) ← v3 (pending)
     ↑ parent_id     ↑ parent_id

Each deliverable has a parent_id linking to its previous version and an incrementing version number. The GET /api/deliverables/:id response includes a versions array showing the full history.

Python SDK

pip install cavendo-engine
from cavendo import CavendoClient

client = CavendoClient(
    url="http://localhost:3001",
    api_key="cav_ak_..."
)

task = client.tasks.next()
if task:
    client.deliverables.submit(
        task_id=task.id,  # Task is a typed object, use dot notation
        title="Research Report",
        content="## Findings\n..."
    )

License

AGPL-3.0 - See LICENSE for details.

For alternative licensing arrangements, contact sales@cavendo.com.

Database

Cavendo Engine uses SQLite for simplicity. The database is automatically created on first run.

# Initialize fresh database
npm run db:init

# Database location
data/cavendo.db

Schema Versioning

server/db/schema.sql is the canonical v0.1.0 baseline schema. On fresh installs, db:init runs this file directly — no migrations are needed. Future releases will include a migrations/ directory for post-v0.1.0 schema upgrades.

Environment Variables

All configuration is via environment variables in .env. See .env.example for a ready-to-copy template.

Production security: Always set strong, unique values for JWT_SECRET, SESSION_SECRET, SESSION_HASH_SECRET, and encryption key material (ENCRYPTION_KEY or ENCRYPTION_KEYRING). Set BOOTSTRAP_ADMIN_EMAIL and BOOTSTRAP_ADMIN_PASSWORD (or BOOTSTRAP_ADMIN_PASSWORD_FILE) for a fresh production deployment. If SESSION_SECRET is not set, the server falls back to JWT_SECRET for key derivation; session token hashing falls back through those secrets and then stable encryption key material. Set these explicitly in production to separate concerns.

Core

Variable Purpose Default Required
PORT Server listen port 3001 No
NODE_ENV Environment (development / production) development Yes (prod)
DATABASE_PATH SQLite database file path ./data/cavendo.db No
CORS_ORIGIN Allowed CORS origin for UI http://localhost:5173 No
TRUST_PROXY Set true or proxy count when behind reverse proxy No

Authentication & Security

Variable Purpose Default Required
JWT_SECRET Server secret used as fallback entropy for encryption key derivation Auto-generated on first run Yes (prod)
SESSION_SECRET Preferred secret for encryption key derivation (takes priority over JWT_SECRET) Falls back to JWT_SECRET Recommended (prod)
SESSION_HASH_SECRET Dedicated HMAC secret for stored session token hashes Falls back to session/JWT/encryption key material Recommended (prod)
BOOTSTRAP_ADMIN_EMAIL Initial administrator email when no admin exists Yes (fresh prod install)
BOOTSTRAP_ADMIN_PASSWORD / _FILE Initial administrator password (minimum 14 characters) Yes (fresh prod install)
API_KEY_PREFIX Prefix for agent API keys cav_ak No
RATE_LIMIT_API Pre-auth API requests per minute, keyed by resolved client IP 300 No
RATE_LIMIT_AUTHENTICATED Post-auth API requests per minute, keyed by verified user or agent identity 3000 No
RATE_LIMIT_API_ALLOWLIST Comma-separated client IPs or CIDR ranges that bypass the general API limiter No

RATE_LIMIT_API is intentionally pre-auth and IP-only. Authenticated routes also apply RATE_LIMIT_AUTHENTICATED after a user or agent key has been verified. RATE_LIMIT_API_ALLOWLIST uses the resolved client IP, so set TRUST_PROXY correctly when running behind nginx, Cloudflare, or another reverse proxy; otherwise all clients can appear as the proxy address and share one bucket. When proxy trust is enabled, Cavendo honors CF-Connecting-IP, True-Client-IP, and X-Forwarded-For in that order. Example: RATE_LIMIT_API_ALLOWLIST=203.0.113.10,198.51.100.0/24

Encryption

Variable Purpose Default Required
ENCRYPTION_KEY AES-256 key for provider API key encryption Derived from session secret (dev only) Yes (prod)
ENCRYPTION_SALT Salt for key derivation cavendo-dev-salt Yes (prod)
ENCRYPTION_KEYRING JSON keyring for key rotation (overrides above) No
ENCRYPTION_KEY_VERSION_CURRENT Active keyring version Latest in keyring No

Task Dispatcher

Variable Purpose Default Required
DISPATCHER_INTERVAL_MS Auto-execution polling interval 30000 (30s) No
DISPATCHER_BATCH_SIZE Max tasks to execute per cycle 5 No
EXECUTION_TIMEOUT_MS Provider API call timeout 120000 (2 min) No

Webhooks & Delivery Routes

Variable Purpose Default Required
WEBHOOK_TIMEOUT_MS Webhook delivery timeout 5000 (5s) No
WEBHOOK_MAX_RETRIES Max webhook retry attempts 3 No
ALLOW_PRIVATE_WEBHOOKS Allow localhost/private IP webhook URLs false No (dev only)
RETRY_SWEEP_INTERVAL_MS Failed delivery retry sweep interval 15000 (15s) No

Local Model Endpoints

Variable Purpose Default Required
ALLOW_CUSTOM_PROVIDER_BASE_URLS Allow remote HTTPS provider endpoints false No
PROVIDER_BASE_URL_ALLOWLIST Comma-separated trusted hosts (e.g., gpu-box.lan:11434) No
OPENAI_COMPAT_DEFAULT_BASE_URL Fallback base URL for openai_compatible agents http://localhost:11434 No

Email (Delivery Routes)

Variable Purpose Default Required
EMAIL_PROVIDER Provider: smtp, sendgrid, mailjet, postmark, ses smtp No
EMAIL_FROM Sender email address notifications@cavendo.local No
EMAIL_FROM_NAME Sender display name Cavendo No
EMAIL_SMTP_HOST SMTP server hostname If using SMTP
EMAIL_SMTP_PORT SMTP server port 587 No
EMAIL_SMTP_SECURE Use TLS (true/false) false No
EMAIL_SMTP_USER SMTP username If using SMTP auth
EMAIL_SMTP_PASS SMTP password If using SMTP auth
EMAIL_SENDGRID_API_KEY SendGrid API key If using SendGrid
EMAIL_MAILJET_API_KEY Mailjet API key If using Mailjet
EMAIL_MAILJET_SECRET_KEY Mailjet secret key If using Mailjet
EMAIL_POSTMARK_SERVER_TOKEN Postmark server token If using Postmark
EMAIL_SES_REGION AWS SES region If using SES
EMAIL_SES_ACCESS_KEY_ID AWS access key ID If using SES
EMAIL_SES_SECRET_ACCESS_KEY AWS secret access key If using SES

Maintenance note: When adding new process.env.* usage in server code, update both this table and .env.example.

Troubleshooting

Native Module Build Failures

If you see ModuleNotFoundError: No module named 'distutils' during npm install:

# macOS
brew install python-setuptools

# Linux/Windows
pip install setuptools

Then retry npm install.

Programmatic Usage

Cavendo Engine exports a createApp() factory from server/app.js for embedding the engine in a larger application (e.g., layering commercial routes on top).

import { createApp } from './server/app.js';

const { app, start, stop } = createApp({
  // Runs before engine routes are mounted
  async beforeRoutes(app) {
    app.use(myCustomMiddleware());
  },

  // Runs after engine routes but before SPA fallback and error handlers
  async afterRoutes(app) {
    app.get('/api/custom-endpoint', (req, res) => res.json({ ok: true }));
  },

  // Runs after DB init/migrations but before HTTP listen
  async beforeStart(app) {
    await seedExtraData();
  },

  // Runs after server is listening — fatal if it throws
  async onStarted({ app, server }) {
    console.log('Custom startup complete');
  },
});

await start({ port: 3002, host: '0.0.0.0' });

// Graceful shutdown: closes server, stops dispatcher/retry workers, closes DB
await stop();

All hooks are optional and support both sync and async functions.

Standalone modenode server/index.js (or npm start) uses the same factory internally with no hooks, preserving existing behavior.

Known limitation: stop() closes the SQLite connection, which is terminal. Calling start() after stop() in the same process will throw. To restart, create a new process.

Environment Bootstrap

server/env.js is imported at the top of app.js and runs at import time. It auto-generates .env from .env.example on first run (with random secrets) and loads existing .env values into process.env. This ensures env vars are available before any other module (DB, routes, services) resolves.

Roadmap

Future storage integrations planned:

  • Google Drive (OAuth2)
  • Microsoft OneDrive (OAuth2)
  • Dropbox (OAuth2)

Contributing

Contributions welcome! Please read our contributing guidelines before submitting PRs.

Links

About

Cavendo Engine

Resources

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages