Home / Docs / Configuration

Configuration

All Zubo configuration lives in a single file: ~/.zubo/config.json. You can edit this file directly with any text editor, or use the zubo config CLI commands for quick changes without opening a file. Changes to the configuration file are picked up on the next restart, while CLI changes via zubo config set take effect immediately if Zubo is running.

Copy-Paste Task Cards

Secure API Access

Enable API auth and create a key before exposing ports beyond localhost.

zubo config set auth.enabled true
zubo auth create-key my-app

Set Local Model Fallback

Keep responses available during provider outages or API quota issues.

zubo config set failover '["openai","ollama"]'
zubo config set providers.ollama.model llama3.3

Common Errors

401 Unauthorized / Missing Bearer token

If auth.enabled is true, all /api/* calls require Authorization: Bearer <key>. Create a new key if needed.

curl -H "Authorization: Bearer YOUR_KEY" http://localhost:3000/api/dashboard/status
Provider Timeout / Upstream unavailable

Use failover and switch temporarily to a responsive provider or smaller model. Check logs for repeated timeout patterns.

zubo model openai/gpt-4o-mini
zubo logs --follow
Missing local model (Ollama/LM Studio)

If local providers fail, ensure the runtime is running and a model is installed.

ollama serve
ollama pull llama3.3

Safe Config Workflow

  1. Inspect current values first: zubo config get.
  2. Apply one change at a time with zubo config set <key> <value>.
  3. Confirm behavior in the dashboard and with zubo status.
  4. Run zubo eval after important changes to validate reliability + safety paths.

For production usage, set auth.enabled, rateLimit.chatPerMinute, and budget limits before exposing any external channel.

Quick Config via CLI

The zubo config command provides a convenient way to read and write configuration values from the terminal:

# Set values
zubo config set activeProvider ollama
zubo config set model llama3.3
zubo config set heartbeatMinutes 60
zubo config set auth.enabled true
zubo config set rateLimit.chatPerMinute 30
zubo config set channels.webchat.port 8080
zubo config set providers.anthropic.model claude-sonnet-4-5-20250929

# Get values
zubo config get                    # Show entire configuration (pretty-printed)
zubo config get activeProvider     # Show a single value
zubo config get channels.telegram  # Show a nested object
zubo config get rateLimit          # Show all rate limit settings

Notes on the CLI:

LLM Providers

Zubo supports a multi-provider architecture. You configure one or more LLM providers under the providers object, set one as activeProvider, and optionally define a failover chain. If the active provider fails (network error, rate limit, authentication error), Zubo automatically tries each failover provider in order until one succeeds.

Supported Providers

Provider Key Default Base URL Notes
anthropic (uses Anthropic SDK directly) Claude models. Supports up to 200k token context. Native streaming. Recommended primary provider.
openai https://api.openai.com/v1 GPT-4o, GPT-4-turbo, and other OpenAI models. Streaming supported.
ollama http://localhost:11434/v1 Run models locally. No API key required. Great as a failover for offline operation.
groq https://api.groq.com/openai/v1 Extremely fast inference. Supports Llama, Mixtral, and other open models.
together https://api.together.xyz/v1 Wide selection of open-source models. Competitive pricing.
openrouter https://openrouter.ai/api/v1 Multi-model gateway. Access Claude, GPT-4, Llama, and more through a single API key.
lmstudio http://localhost:1234/v1 Local model GUI. Download and run models with a graphical interface. No API key required.
custom set explicitly Any OpenAI-compatible provider endpoint. Configure baseUrl, apiKey, and model.
deepseek https://api.deepseek.com/v1 DeepSeek models. OpenAI-compatible API with strong reasoning capabilities at competitive pricing.
xai https://api.x.ai/v1 xAI Grok models. OpenAI-compatible API with real-time knowledge.
fireworks https://api.fireworks.ai/inference/v1 Fireworks AI. Fast inference for open-source models with optimized serving.

Provider Configuration Fields

Each provider object under providers accepts the following fields:

Field Type Required Description
apiKey string Yes* API key for the provider. Not required for local providers (Ollama, LM Studio).
baseUrl string No Override the default base URL. Useful for proxies or self-hosted endpoints.
model string Yes Model name to use. Defaults vary by provider (e.g., claude-sonnet-4-5-20250929 for Anthropic).
streaming boolean No Enable or disable streaming responses. Defaults to true.
contextWindow number No Maximum context window size in tokens. Zubo uses this for context compaction decisions. Defaults to the provider's known maximum.

Local Models (Ollama & LM Studio)

Local models run entirely on your machine — no API keys, no usage fees, and your data never leaves your computer. They're ideal as a primary provider for privacy-focused setups, or as a failover when your internet is down.

Ollama

Ollama is a lightweight runtime for running open-source models locally. It provides an OpenAI-compatible API out of the box.

Installation:

# macOS (Homebrew)
brew install ollama

# Linux
curl -fsSL https://ollama.com/install.sh | sh

# Windows — download from https://ollama.com/download

Getting started:

# Start the Ollama server (runs on port 11434)
ollama serve

# Pull a model
ollama pull llama3.3       # Meta Llama 3.3 (good general-purpose)
ollama pull mistral        # Mistral 7B (fast, lightweight)
ollama pull qwen2.5        # Qwen 2.5 (strong multilingual)
ollama pull deepseek-r1    # DeepSeek R1 (strong reasoning)
ollama pull gemma2         # Google Gemma 2

# List downloaded models
ollama list

Zubo configuration:

"ollama": {
  "baseUrl": "http://localhost:11434/v1",
  "model": "llama3.3",
  "apiKey": "ollama"
}

The apiKey field is required by the OpenAI-compatible client but Ollama ignores it — any value works.

LM Studio

LM Studio provides a graphical interface for downloading, managing, and running local models. It includes a built-in server that exposes an OpenAI-compatible API.

Installation:

  1. Download LM Studio from lmstudio.ai (macOS, Windows, Linux)
  2. Open LM Studio and browse the model library to download a model
  3. Go to the Local Server tab in the left sidebar
  4. Select your model and click Start Server — it runs on port 1234 by default

Zubo configuration:

"lmstudio": {
  "baseUrl": "http://localhost:1234/v1",
  "model": "your-model-name",
  "apiKey": "lm-studio"
}

Tip: Local models work great as a failover. Set a cloud provider as primary and Ollama/LM Studio as the fallback — if your API key runs out or the network drops, Zubo seamlessly falls back to your local model:

"activeProvider": "anthropic",
"failover": ["ollama"]

CLI Providers (Claude Code & OpenAI Codex)

These providers use locally installed CLI tools that handle their own authentication — no API key configuration needed in Zubo.

ProviderCLI ToolInstallAuth
claude-code claude npm install -g @anthropic-ai/claude-code Run claude once to authenticate via browser
codex codex npm install -g @openai/codex Run codex login to authenticate
"claude-code": { "model": "claude-sonnet-4-5-20250929" }
"codex": { "model": "o4-mini" }

Multi-Provider Example with Failover

{
  "providers": {
    "anthropic": {
      "apiKey": "sk-ant-api03-...",
      "model": "claude-sonnet-4-5-20250929",
      "streaming": true,
      "contextWindow": 200000
    },
    "openai": {
      "apiKey": "sk-proj-...",
      "model": "gpt-4o",
      "streaming": true
    },
    "ollama": {
      "baseUrl": "http://localhost:11434/v1",
      "model": "llama3.3",
      "streaming": true
    }
  },
  "activeProvider": "anthropic",
  "failover": ["openai", "ollama"]
}

In this configuration, Zubo uses Claude as the primary model. If Anthropic returns an error (rate limit, network issue, etc.), it automatically tries OpenAI next, and finally Ollama as a local fallback. The failover is transparent to the user — they receive a response regardless of which provider handled it.

Smart Routing

Smart routing automatically selects the best provider for each query based on complexity. Simple questions (greetings, quick lookups, short follow-ups) are routed to fast, inexpensive models, while complex queries (multi-step reasoning, code generation, long-form writing) go to your most capable provider. This can significantly reduce costs without noticeable quality loss.

Field Type Default Description
smartRouting.enabled boolean false Enable automatic query routing between providers.
smartRouting.fastProvider string Provider key to use for simple queries (e.g., "groq", "deepseek").
smartRouting.fastModel string Model to use for the fast provider. Falls back to the provider's default model.
"smartRouting": {
  "enabled": true,
  "fastProvider": "groq",
  "fastModel": "llama-3.3-70b-versatile"
}

When smart routing is enabled, Zubo classifies each incoming message and routes it accordingly. You can always override the routing for a specific message by prefixing it with the provider name (e.g., @anthropic explain quantum computing).

Memory Retrieval Tuning

These settings control how much memory context is injected into each conversation turn.

FieldTypeDefaultDescription
memoryRetrieval.contextTopK number 3 Number of memory chunks considered for prompt context injection (1-10).
memoryRetrieval.minConfidence number 0 Minimum confidence threshold for memory chunks to be injected (0-1).
"memoryRetrieval": {
  "contextTopK": 4,
  "minConfidence": 0.25
}

Tool Scope Safety

Use tool scopes to enforce least-privilege behavior and safer default execution for risky tools.

FieldTypeDefaultDescription
toolScopes.allowed string[] [] (allow all) If non-empty, blocks any tool whose scopes are not in this allowlist.
toolScopes.dryRunByDefault boolean false When true, confirm-gated risky tools return a dry-run preview unless explicitly executed with _dryRun: false.
"toolScopes": {
  "allowed": ["memory", "network_read", "filesystem_read"],
  "dryRunByDefault": true
}

Tool Permission Overrides

You can override per-tool permission levels without changing code.

"toolPermissions": {
  "shell": "deny",
  "file_write": "confirm",
  "memory_search": "auto"
}

Valid values are auto, confirm, and deny. These overrides are used by both the dashboard and slash commands such as /permissions set <tool> <level>.

Channel Configuration

Channels define how users interact with Zubo. Each channel is independently configured under the channels object. All enabled channels share the same memory, personality, tools, and conversation history — a fact learned via Telegram is immediately available in Discord or the web dashboard.

Web Chat

Field Type Default Description
enabled boolean true Enable the built-in web chat dashboard.
port number 0 (auto) HTTP port for the web dashboard and API. Set to 0 for automatic port assignment (default).
"webchat": {
  "enabled": true,
  "port": 3000
}

Telegram

Field Type Default Description
enabled boolean false Enable the Telegram channel.
botToken string Bot token from @BotFather.
allowedUsers number[] [] Array of numeric Telegram user IDs allowed to interact. Empty array means no restriction.
"telegram": {
  "enabled": true,
  "botToken": "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11",
  "allowedUsers": [12345678, 87654321]
}

Discord

Field Type Default Description
enabled boolean false Enable the Discord channel.
botToken string Bot token from the Discord Developer Portal.
allowedUsers string[] [] Array of Discord user ID strings allowed to interact. Empty array means no restriction.
"discord": {
  "enabled": true,
  "botToken": "MTk4NjIyNDgzNDcxOTI1...",
  "allowedUsers": ["198622483471925248"]
}

Slack

Field Type Default Description
enabled boolean false Enable the Slack channel.
botToken string Bot User OAuth Token (starts with xoxb-).
appToken string App-Level Token (starts with xapp-). Required for Socket Mode.
allowedUsers string[] [] Array of Slack member ID strings allowed to interact. Empty array means no restriction.
"slack": {
  "enabled": true,
  "botToken": "xoxb-1234567890-abcdefghij",
  "appToken": "xapp-1-A0123456789-1234567890123-abcdef",
  "allowedUsers": ["U01ABCDEF"]
}

WhatsApp

Field Type Default Description
enabled boolean false Enable the WhatsApp channel.
authDir string ~/.zubo/whatsapp-auth Directory to store WhatsApp Web authentication data. On first run, a QR code is displayed for pairing.
allowedNumbers string[] [] Array of phone numbers (with country code, e.g., "14155551234") allowed to interact. Empty array means no restriction.
"whatsapp": {
  "enabled": true,
  "authDir": "/Users/you/.zubo/whatsapp-auth",
  "allowedNumbers": ["14155551234", "447700900000"]
}

Signal

Field Type Default Description
enabled boolean false Enable the Signal channel.
phoneNumber string The phone number registered with Signal for the bot (with country code, e.g., "+14155551234").
signalCliPath string signal-cli Path to the signal-cli binary. Defaults to looking on the system PATH.
allowedNumbers string[] [] Array of phone numbers allowed to interact. Empty array means no restriction.
"signal": {
  "enabled": true,
  "phoneNumber": "+14155551234",
  "signalCliPath": "/usr/local/bin/signal-cli",
  "allowedNumbers": ["+447700900000"]
}

Email

Field Type Required Description
enabled boolean No Enable the Email channel. Defaults to true when the email object is present.
imap.host string Yes Hostname of the IMAP server (e.g., "imap.gmail.com").
imap.port number No IMAP server port. Defaults to 993.
imap.user string Yes IMAP login username (typically your email address).
imap.password string Yes IMAP login password or app-specific password.
imap.tls boolean No Enable TLS for the IMAP connection. Defaults to true.
smtp.host string Yes Hostname of the SMTP server (e.g., "smtp.gmail.com").
smtp.port number No SMTP server port. Defaults to 587.
smtp.user string Yes SMTP login username (typically your email address).
smtp.password string Yes SMTP login password or app-specific password.
smtp.tls boolean No Enable TLS for the SMTP connection. Defaults to true.
pollIntervalSeconds number No How often (in seconds) to check for new emails. Minimum 10, defaults to 60.
allowedSenders string[] No Array of email addresses allowed to interact. If omitted, all senders are accepted.
fromName string No Display name used in the “From” header of outgoing replies.
"email": {
  "enabled": true,
  "imap": {
    "host": "imap.gmail.com",
    "port": 993,
    "user": "you@gmail.com",
    "password": "app-specific-password",
    "tls": true
  },
  "smtp": {
    "host": "smtp.gmail.com",
    "port": 587,
    "user": "you@gmail.com",
    "password": "app-specific-password",
    "tls": true
  },
  "pollIntervalSeconds": 60,
  "allowedSenders": ["friend@example.com", "boss@example.com"],
  "fromName": "Zubo"
}

Voice Configuration

Zubo supports speech-to-text (STT) for transcribing voice messages received on any channel, and text-to-speech (TTS) for generating spoken audio responses. Voice is configured under the voice object.

Speech-to-Text (STT)

Field Type Default Description
provider string "whisper" STT provider. Currently supported: whisper (OpenAI Whisper API).
apiKey string API key for the STT provider. For Whisper, this is your OpenAI API key.
model string "whisper-1" Model to use for transcription.

Text-to-Speech (TTS)

Field Type Default Description
provider string "openai" TTS provider. Supported: openai, elevenlabs.
apiKey string API key for the TTS provider.
voice string "nova" Voice to use for speech generation. OpenAI voices: alloy, echo, fable, onyx, nova, shimmer. ElevenLabs voices depend on your account.
"voice": {
  "stt": {
    "provider": "whisper",
    "apiKey": "sk-proj-...",
    "model": "whisper-1"
  },
  "tts": {
    "provider": "openai",
    "apiKey": "sk-proj-...",
    "voice": "nova"
  }
}

Agent Settings

These top-level settings control the core agent behavior.

Field Type Default Description
agentName string "Zubo" The name of your agent. Used in the system prompt and memory file. Set during setup or change via config_update.
maxTurns number 50 Maximum number of tool-call rounds per conversation turn. When the agent reaches this limit, it stops calling tools and produces a text response with whatever information it has gathered. This prevents runaway tool loops. Reasonable values range from 10 (tightly constrained) to 100 (complex multi-step tasks).
heartbeatMinutes number 30 Interval in minutes (1–1440) between heartbeat cycles. During each heartbeat, Zubo wakes up, checks for pending cron jobs, processes scheduled tasks, evaluates proactive memory triggers, and optionally sends notifications. Set to a low value (1–5) for near-real-time scheduling, or a high value (60–1440) to conserve resources.
{
  "agentName": "Zubo",
  "maxTurns": 50,
  "heartbeatMinutes": 30
}

Rate Limiting

Zubo includes built-in per-IP rate limiting to protect against abuse. Rate limits use a sliding window algorithm — each IP address gets an independent counter that resets on a rolling 60-second basis.

Field Type Default Description
rateLimit.chatPerMinute number 60 Maximum chat messages per minute per IP address. Applies to the /api/chat endpoint and WebSocket messages. When exceeded, the client receives a 429 Too Many Requests response.
rateLimit.uploadPerMinute number 10 Maximum file uploads per minute per IP address. Applies to the /api/upload endpoint. Uploads are more resource-intensive (document parsing, chunking, embedding), so the default is lower.
"rateLimit": {
  "chatPerMinute": 60,
  "uploadPerMinute": 10
}

For personal use on localhost, the defaults are generous. If you are exposing Zubo on a public network or sharing it with others, consider lowering these values (e.g., chatPerMinute: 30, uploadPerMinute: 5).

Budget Controls

Zubo tracks token usage and estimated costs for every LLM request. You can set spending limits to prevent surprise bills and view per-model cost breakdowns in the dashboard.

Field Type Default Description
budget.dailyLimitUsd number 0 Maximum daily spend in USD. Set to 0 for no limit. When the limit is reached, Zubo pauses LLM requests and notifies you.
budget.monthlyLimitUsd number 0 Maximum monthly spend in USD. Set to 0 for no limit.
budget.alertThreshold number 0.8 Percentage (0–1) of the budget at which Zubo sends a warning notification. Default is 80%.
"budget": {
  "dailyLimitUsd": 5,
  "monthlyLimitUsd": 50,
  "alertThreshold": 0.8
}

Cost tracking is always active even without spending limits. View your usage breakdown in the dashboard under Analytics → Costs, or ask Zubo directly: “How much have I spent today?”

Email Digest

Zubo can send periodic email digests summarizing conversation activity, usage statistics, and key interactions. Digests are delivered via the configured email channel (IMAP/SMTP), so the email channel must be configured for this feature to work.

Field Type Default Description
emailDigest.enabled boolean false Enable automated email digest delivery.
emailDigest.schedule string "0 8 * * 1-5" Cron expression for when to send digests. The default sends weekdays at 8:00 AM.
emailDigest.recipients string[] [] Array of email addresses to receive digests. If empty, the digest is sent to the email channel's SMTP user address.
emailDigest.includeStats boolean true Include usage statistics (messages, tokens, cost) in the digest.
emailDigest.includeTopConversations boolean true Include summaries of the most active conversations since the last digest.
emailDigest.maxConversations number 10 Maximum number of top conversations to include in the digest.
"emailDigest": {
  "enabled": true,
  "schedule": "0 8 * * 1-5",
  "recipients": ["you@example.com", "team@example.com"],
  "includeStats": true,
  "includeTopConversations": true,
  "maxConversations": 10
}

Digests are delivered using the same SMTP credentials configured in the email channel. The email's “From” name uses the email.fromName field (or defaults to the agent name). You can send a digest from the dashboard or via POST /api/dashboard/digests/send. See the Email Digest API endpoints for programmatic management.

Authentication

When auth.enabled is true, all HTTP API endpoints and WebSocket connections require a valid API key. This is strongly recommended if you are exposing Zubo's port beyond localhost.

Field Type Default Description
auth.enabled boolean false Enable API key authentication for all HTTP and WebSocket endpoints.

Managing API Keys

API keys can be created and managed via the CLI or the web dashboard:

# Create a new API key
zubo auth create-key my-app
# Output: API key created (id: N, label: my-app): ...

# List all API keys
zubo auth list-keys

# Delete an API key by numeric id
zubo auth delete-key 3

Using API Keys

Include the API key in the Authorization header as a Bearer token:

curl -X POST http://localhost:3000/api/chat \
  -H "Authorization: Bearer zb_k1_a1b2c3d4e5f6..." \
  -H "Content-Type: application/json" \
  -d '{"message": "Hello, Zubo!"}'

For WebSocket connections, pass the key as a query parameter:

ws://localhost:3000/ws?token=zb_k1_a1b2c3d4e5f6...

API keys are stored as hashed values in the SQLite database. The plain-text key is only shown once at creation time and cannot be retrieved later. If you lose a key, revoke it and create a new one.

Sandbox

The sandbox controls how user-installed skills (from ~/.zubo/workspace/skills/) are executed. Built-in tools always run in the main process, but user skills run in isolated subprocesses for safety.

Field Type Default Description
sandbox.enabled boolean true Enable subprocess sandboxing for user-installed skills. When enabled, each skill invocation runs in a separate Bun subprocess with restricted access. Disabling this is not recommended but may be necessary for skills that require direct main-process access.
sandbox.timeoutMs number 30000 Maximum execution time in milliseconds for a single skill invocation. If a skill exceeds this timeout, its subprocess is terminated and an error is returned to the agent. Set higher for skills that perform long-running operations (e.g., large file processing, slow API calls).
"sandbox": {
  "enabled": true,
  "timeoutMs": 30000
}

Full Example

Here is a complete ~/.zubo/config.json showing all configuration sections together. This represents a fully configured instance with Anthropic as the primary provider, Groq for smart routing, Ollama as a local failover, three channels enabled, voice support, budget controls, and production-ready security settings:

{
  "providers": {
    "anthropic": {
      "apiKey": "sk-ant-api03-...",
      "model": "claude-sonnet-4-5-20250929",
      "streaming": true,
      "contextWindow": 200000
    },
    "groq": {
      "apiKey": "gsk_...",
      "model": "llama-3.3-70b-versatile",
      "streaming": true
    },
    "ollama": {
      "baseUrl": "http://localhost:11434/v1",
      "model": "llama3.3",
      "streaming": true
    }
  },
  "activeProvider": "anthropic",
  "failover": ["groq", "ollama"],

  "smartRouting": {
    "enabled": true,
    "fastProvider": "groq",
    "fastModel": "llama-3.3-70b-versatile"
  },

  "channels": {
    "webchat": {
      "enabled": true,
      "port": 3000
    },
    "telegram": {
      "enabled": true,
      "botToken": "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11",
      "allowedUsers": [12345678]
    },
    "discord": {
      "enabled": true,
      "botToken": "MTk4NjIyNDgzNDcxOTI1...",
      "allowedUsers": []
    },
    "slack": {
      "enabled": false,
      "botToken": "",
      "appToken": "",
      "allowedUsers": []
    },
    "whatsapp": {
      "enabled": false,
      "authDir": "",
      "allowedNumbers": []
    },
    "signal": {
      "enabled": false,
      "phoneNumber": "",
      "signalCliPath": "signal-cli",
      "allowedNumbers": []
    },
    "email": {
      "enabled": false,
      "imap": {
        "host": "imap.gmail.com",
        "port": 993,
        "user": "",
        "password": "",
        "tls": true
      },
      "smtp": {
        "host": "smtp.gmail.com",
        "port": 587,
        "user": "",
        "password": "",
        "tls": true
      },
      "pollIntervalSeconds": 60,
      "allowedSenders": [],
      "fromName": "Zubo"
    }
  },

  "voice": {
    "stt": {
      "provider": "whisper",
      "apiKey": "sk-proj-...",
      "model": "whisper-1"
    },
    "tts": {
      "provider": "openai",
      "apiKey": "sk-proj-...",
      "voice": "nova"
    }
  },

  "agentName": "Zubo",
  "maxTurns": 50,
  "heartbeatMinutes": 30,

  "rateLimit": {
    "chatPerMinute": 60,
    "uploadPerMinute": 10
  },

  "budget": {
    "dailyLimitUsd": 5,
    "monthlyLimitUsd": 50,
    "alertThreshold": 0.8
  },

  "emailDigest": {
    "enabled": false,
    "schedule": "0 8 * * 1-5",
    "recipients": [],
    "includeStats": true,
    "includeTopConversations": true,
    "maxConversations": 10
  },

  "auth": {
    "enabled": false
  },

  "sandbox": {
    "enabled": true,
    "timeoutMs": 30000
  }
}

Tip: You do not need to include every field. Zubo applies sensible defaults for any omitted values. A minimal config only needs activeProvider and the corresponding provider entry with an API key.

Environment

By default, Zubo stores all data and configuration under ~/.zubo. You can override this by setting the ZUBO_HOME environment variable to an absolute path:

# Use a custom home directory
export ZUBO_HOME=/opt/zubo-data
zubo start

# Or inline for a single invocation
ZUBO_HOME=/opt/zubo-data zubo start --daemon

When ZUBO_HOME is set, all paths — config file, database, logs, sessions, models, and workspace — are resolved relative to that directory instead of ~/.zubo. This is useful for running multiple isolated Zubo instances on the same machine, or for placing the data directory on a specific volume or mount point.

Other environment variables recognized by Zubo:

Variable Description
ZUBO_HOME Override the default ~/.zubo data directory.
ZUBO_LOG_LEVEL Set the log level: debug, info, warn, error. Defaults to info.
ZUBO_PORT Override the web dashboard port (takes precedence over config file).
NODE_ENV When set to production, Zubo disables verbose logging and enables additional security hardening.