orbitchat
v3.6.5
Published
A standalone chat application for ORBIT that can be installed as an npm package and run as a CLI tool. All API communication is routed through a built-in Express proxy that maps adapter names to backend API keys, so **no secrets ever reach the browser**.
Readme
ORBIT Chat App
A standalone chat application for ORBIT that can be installed as an npm package and run as a CLI tool. All API communication is routed through a built-in Express proxy that maps adapter names to backend API keys, so no secrets ever reach the browser. Uses the open-source @schmitech/markdown-renderer for rich content display including math and charts.
Installation
As an npm Package (CLI Tool)
Install globally:
npm install -g orbitchatOr install locally:
npm install orbitchatInstalled CLI commands:
orbitchat— starts the ORBIT Chat server directlyorbitchat-daemon— shell wrapper with--start/--stop/--restart/--force-restart/--status
Quick Start
Define your adapter secrets via the
ORBIT_ADAPTER_KEYSorVITE_ADAPTER_KEYSenvironment variable:# Mapping of Adapter ID -> API Key export ORBIT_ADAPTER_KEYS='{"simple-chat":"my-secret-key"}'(Optional) Configure adapter URLs and metadata in
orbitchat.yaml:adapters: - id: "simple-chat" name: "Simple Chat" apiUrl: "http://localhost:3000" description: "Default conversational agent."Run the CLI:
orbitchat --config ./orbitchat.yaml --port 5173Open
http://localhost:5173— select an agent and start chatting.
Architecture
Browser ──X-Adapter-Name──▶ Express proxy ──X-API-Key──▶ ORBIT backend
(bin/orbitchat.js)The frontend never handles API keys. Instead:
- The browser sends an
X-Adapter-Nameheader with every API request. - The Express proxy looks up the adapter, injects the real
X-API-Key, and forwards the request to the configured backend URL. GET /api/adaptersreturns non-secret adapter metadata (name, description, notes, model) — never keys or backend URLs.
CLI Options
orbitchat [options]
Options:
--port PORT Server port (default: 5173)
--host HOST Server host (default: localhost)
--open Open browser automatically
--config PATH Path to orbitchat.yaml (default: ./orbitchat.yaml)
--api-only Run API proxy only (no UI serving)
--cors-origin URL Allowed CORS origin in api-only mode (default: *)
--help, -h Show help message
--version, -v Show version numberExamples
# Start with a custom config file
orbitchat --config /path/to/orbitchat.yaml
# Start with adapter keys defined inline
ORBIT_ADAPTER_KEYS='{"Chat":"mykey"}' orbitchat
# API proxy only — no UI, no build required
orbitchat --api-only --port 5174
# API proxy with restricted CORS origin
orbitchat --api-only --port 5174 --cors-origin http://localhost:3001API-Only Mode
Use --api-only to run the Express proxy without serving the built-in chat UI. This is useful when you are building your own frontend and only need the proxy layer to keep API keys off the browser.
orbitchat --api-only --port 5174In this mode:
- No
dist/directory ornpm run buildis required. - CORS headers are added automatically (default
Access-Control-Allow-Origin: *). Use--cors-originto restrict to a specific origin. - All
/api/*proxy routes andGET /api/adapterswork exactly the same as in full mode.
API contract for custom UIs
Your frontend needs to do two things:
Discover adapters —
GET /api/adaptersreturns:{ "adapters": [ { "name": "Simple Chat", "description": "...", "notes": "...", "model": "gpt-4o-mini" } ] }Send requests with
X-Adapter-Name— every call to/api/*must include the header:X-Adapter-Name: Simple ChatThe proxy resolves the adapter, injects the real
X-API-Key, and forwards the request to the backend URL configured for that adapter.
Endpoint reference
| Method | Path | Headers | Description |
|--------|------|---------|-------------|
| GET | /api/adapters | — | List available adapter metadata (name, description, notes, model) |
| POST | /api/v1/chat | X-Adapter-Name, X-Session-ID | Send a chat message (SSE streaming response) |
| POST | /api/files/upload | X-Adapter-Name | Upload a file (multipart/form-data) |
| GET | /api/files | X-Adapter-Name | List uploaded files |
| GET | /api/files/:id | X-Adapter-Name | Get file info |
| DELETE | /api/files/:id | X-Adapter-Name | Delete a file |
| GET | /api/v1/autocomplete?q=...&limit=5 | X-Adapter-Name | Autocomplete suggestions |
Configuring Adapters
Configuration is split between metadata/URLs (in orbitchat.yaml) and secrets (in environment variables).
1. Metadata in orbitchat.yaml
Define your adapters list in the YAML file:
adapters:
- id: "simple-chat"
name: "Simple Chat"
apiUrl: "http://localhost:3000"
description: "Basic chat interface using the default conversational agent."
- id: "document-qa"
name: "Document QA"
apiUrl: "http://localhost:3000"
description: "Chat with uploaded documents."
notes: "Supports PDF, DOCX, and plain text uploads."| Field | Description |
|-------|-------------|
| id | Required. Unique identifier used to match the API key in .env |
| name | Display name shown in the agent selector |
| apiUrl | Backend URL (defaults to api.url, then http://localhost:3000) |
| description | Short summary shown in dropdowns |
| notes | Markdown content shown in the chat empty state |
2. Secrets in .env
Provide the API keys via ORBIT_ADAPTER_KEYS (or VITE_ADAPTER_KEYS) as a JSON object keyed by adapter id:
VITE_ADAPTER_KEYS='{
"simple-chat": "secret-key-1",
"document-qa": "secret-key-2"
}'The system deep-merges these sources at runtime.
Configuration
Runtime Config File
Runtime settings are loaded from orbitchat.yaml (see orbitchat.yaml.example). The configuration uses a nested structure:
application:
name: "ORBIT Chat"
api:
url: "http://localhost:3000"
features:
enableUpload: true
server:
trustProxy: falseHeader logos (header.logoUrl, header.logoUrlLight, header.logoUrlDark) support:
- Remote URLs, for example
https://example.com/logo.png - Local file paths (absolute or relative to
orbitchat.yaml), for example./public/logo.png
Theme-aware logo selection order:
- Light theme:
header.logoUrlLight->header.logoUrl->header.logoUrlDark - Dark theme:
header.logoUrlDark->header.logoUrl->header.logoUrlLight
Default logo fallback behavior:
- If
header.logoUrlLightis empty or whitespace, ORBIT Chat uses/orbit-logo-light.png. - If
header.logoUrlDarkis empty or whitespace, ORBIT Chat uses/orbit-logo-dark.png. - These paths are resolved from the app root (Vite
public/directory), alongsidefavicon.svg. - Example:
With this config, light/dark themes automatically use the default files fromheader: logoUrlLight: "" logoUrlDark: ""public/.
Server/reverse proxy setting:
server.trustProxymaps directly to Expressapp.set('trust proxy', value).- Default is
false. - Set
server.trustProxy: 1when ORBIT Chat is behind a single trusted reverse proxy.
Environment Variables
Adapter secrets are provided via:
ORBIT_ADAPTER_KEYS(Preferred)VITE_ADAPTER_KEYS
Auth secrets are read from:
VITE_AUTH_DOMAINVITE_AUTH_CLIENT_IDVITE_AUTH_AUDIENCE
The CLI loads .env and .env.local from the current working directory on startup.
Development
Local Development Setup
Clone the repository and install dependencies:
npm install
npm run devBuilding for Production
npm run buildThe output is written to dist/. Serve it with:
orbitchat --port 8080public/ assets are copied into dist/ during build. Because the npm package publishes dist/, default logo assets such as orbit-logo-light.png and orbit-logo-dark.png are included in published builds.
Troubleshooting
No Adapters Available
If the agent selector shows no adapters:
- Ensure
VITE_ADAPTER_KEYSis set and contains valid JSON. - Verify that the adapter
idinorbitchat.yamlexactly matches the key used inVITE_ADAPTER_KEYS. - Check the CLI startup logs for "Available Adapters: ...".
Stale Configuration
If you've updated orbitchat.yaml but don't see changes:
- The CLI watches the YAML file and should restart automatically.
- Clear browser site data/localStorage for the app origin to ensure no stale session state is being used.
ERR_ERL_UNEXPECTED_X_FORWARDED_FOR (rate limit + reverse proxy)
If you see a log like:
ValidationError: The 'X-Forwarded-For' header is set but the Express 'trust proxy' setting is false (default).
code: 'ERR_ERL_UNEXPECTED_X_FORWARDED_FOR'this usually means ORBIT Chat is running behind a reverse proxy (Nginx, ingress controller, load balancer, Cloudflare, etc.) that adds X-Forwarded-For, but Express is still using the default trust proxy = false.
Why this matters:
- The app still runs (this is a validation warning), but guest rate limiting may identify client IPs incorrectly.
- In the worst case, many users can be rate-limited as if they were a single client.
How to prevent it:
- If you are behind a trusted reverse proxy, set this in
orbitchat.yaml:server: trustProxy: 1 - If you are not intentionally behind a proxy, remove unexpected
X-Forwarded-Forinjection in your network path. - As a temporary workaround, disable guest rate limiting in
orbitchat.yaml:guestLimits: rateLimit: enabled: false
Reference: https://express-rate-limit.github.io/ERR_ERL_UNEXPECTED_X_FORWARDED_FOR/
Security
- The browser never sees real API keys. The Express proxy maps adapter names to keys server-side.
GET /api/adaptersonly exposes non-secret metadata (name, description, notes, model) — never keys or backend URLs.- Keep
VITE_ADAPTER_KEYSout of source control. - Run the proxy behind HTTPS in production.
