orbitchat
v3.1.1
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 adapters (agents) via the
ORBIT_ADAPTERSorVITE_ADAPTERSenvironment variable:export ORBIT_ADAPTERS='[ {"name":"Simple Chat","apiKey":"my-key","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 only adapter names and descriptions — never keys or 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 adapters defined inline
ORBIT_ADAPTERS='[{"name":"Chat","apiKey":"mykey","apiUrl":"https://api.example.com"}]' 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": "..." } ] }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 names and descriptions |
| 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 |
Example: calling from a custom React app
// Discover adapters
const res = await fetch('http://localhost:5174/api/adapters');
const { adapters } = await res.json();
// Send a chat message (SSE stream)
const response = await fetch('http://localhost:5174/api/v1/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Adapter-Name': adapters[0].name,
'X-Session-ID': crypto.randomUUID(),
},
body: JSON.stringify({ message: 'Hello!' }),
});
// Read the SSE stream
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
console.log(decoder.decode(value));
}Configuring Adapters
Adapters map a user-visible name to a backend API key and URL. Configure them via the ORBIT_ADAPTERS (or VITE_ADAPTERS) environment variable as a JSON array:
export ORBIT_ADAPTERS='[
{
"name": "Simple Chat",
"apiKey": "default-key",
"apiUrl": "http://localhost:3000",
"description": "Basic chat interface using the default conversational agent."
},
{
"name": "Document QA",
"apiKey": "doc-qa-key",
"apiUrl": "http://localhost:3000",
"description": "Chat with uploaded documents.",
"notes": "Supports PDF, DOCX, and plain text uploads."
}
]'Each adapter object supports:
| Field | Required | Description |
|-------|----------|-------------|
| name | Yes | Display name shown in the agent selector |
| apiKey | Yes | Backend API key (never exposed to the browser) |
| apiUrl | No | Backend URL (defaults to api.url in orbitchat.yaml, then http://localhost:3000) |
| description | No | Short summary shown in dropdowns |
| notes | No | Markdown content shown in the chat empty state |
If api.defaultAdapter is not set (or left as default-key), the first adapter in the list is used.
Agent Selector UX
- When a conversation has no messages, the chat canvas shows a centered agent selector with the adapter's notes rendered beneath it.
- Once an adapter is selected, the input field unlocks.
- Sidebar cards display the agent assigned to each conversation.
- To change the adapter after messages exist, use the "Change agent" action in the sidebar.
Configuration
Runtime Config File
Runtime settings are loaded from orbitchat.yaml (see orbitchat.yaml.example).
Config lookup:
--config /path/to/orbitchat.yamlif provided./orbitchat.yaml(current working directory)
Header logo (header.logoUrl) supports:
- Remote URLs, for example
https://example.com/logo.png - Local file paths (absolute or relative to
orbitchat.yaml), for example./public/logo.png
When a local file path is used, the CLI serves it on an internal route (/__orbitchat_assets/...) at runtime.
Environment Variables
Adapter secrets are provided via environment variables:
ORBIT_ADAPTERS='[{"name":"Simple Chat","apiKey":"default-key","apiUrl":"http://localhost:3000"}]'ORBIT_ADAPTERSis preferred.VITE_ADAPTERSis also supported for compatibility.- If both are set,
ORBIT_ADAPTERStakes precedence.
Auth secrets are read from:
VITE_AUTH_DOMAINVITE_AUTH_CLIENT_IDVITE_AUTH_AUDIENCE
The CLI also 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 devDevelopment with Express Proxy
To run both the Express proxy and Vite dev server together:
node bin/dev-server.jsThis starts:
- Express proxy on port 5174 (handles
/api/*routes) - Vite dev server on port 5173 (proxies API requests to Express)
Building for Production
npm run buildThe output is written to dist/. Serve it with:
orbitchat --port 8080Running as a Daemon
For npm package installs, use:
orbitchat-daemon --start # Start in background
orbitchat-daemon --start 8080 # Start on custom port
orbitchat-daemon --stop # Stop
orbitchat-daemon --status # Check statusFrom a source checkout, you can also run:
./orbitchat.sh --startDaemon state files:
- Default PID/log directory:
$XDG_STATE_HOME/orbitchator~/.local/state/orbitchat - Override with:
ORBITCHAT_STATE_DIR=/path/to/state
Daemon examples with config:
orbitchat-daemon --config /home/ubuntu/orbitchat/orbitchat.yaml --start
orbitchat-daemon --config /home/ubuntu/orbitchat/orbitchat.yaml --force-restartUsing sudo:
sudomay drop environment variables (includingORBIT_ADAPTERS).- Preserve adapter env explicitly:
sudo --preserve-env=ORBIT_ADAPTERS orbitchat-daemon --config /home/ubuntu/orbitchat/orbitchat.yaml --start- If needed, set writable daemon state dir explicitly:
sudo ORBITCHAT_STATE_DIR=/var/tmp/orbitchat orbitchat-daemon --config /home/ubuntu/orbitchat/orbitchat.yaml --startAvailable Scripts
npm run dev— Start Vite dev servernpm run build— Build for productionnpm run preview— Preview production buildnpm run dev:local— Start dev server with local API buildnpm run dev:with-api— Build API from../node-apiand start dev servernpm run build:local— Build for production with local APInpm run build:api— Build and copy API from../node-api
Features
- Streaming Responses: Real-time streaming of AI responses via SSE
- Agent Selection: Choose from configured adapters per conversation
- File Upload: Upload and attach files (PDF, DOCX, TXT, CSV, JSON, HTML, images, audio) to conversations
- File Context: Query uploaded files — they are chunked, embedded, and included in chat context
- Autocomplete: Optional type-ahead suggestions via
/api/v1/autocomplete - Conversation Threads: Branch conversations into focused sub-threads
- Session Management: Automatic session ID generation and persistence
- Conversation Persistence: Chat history saved to localStorage
- Audio Output: Optional text-to-speech for AI responses
- Feedback Buttons: Optional thumbs-up/down per message
Security
- The browser never sees real API keys. The Express proxy maps adapter names to keys server-side.
GET /api/adaptersonly exposes names and descriptions — never keys or backend URLs.- Keep
ORBIT_ADAPTERS/VITE_ADAPTERSout of source control. - Run the proxy behind HTTPS (or another reverse proxy) in production so users cannot intercept traffic.
- Secure the host running the CLI — a compromised host can leak the adapters config or intercept proxied traffic.
File Upload
Supported File Types
| Type | Formats | Processing | |------|---------|------------| | Documents | PDF, DOCX, PPTX, XLSX | Text extraction, chunking, vector indexing | | Text | TXT, MD, HTML | Direct chunking and indexing | | Data | CSV, JSON | Chunking and indexing | | Code | PY, JS, TS, Java, Go, Rust, C/C++, and more | Direct indexing | | Images | PNG, JPEG, TIFF | OCR via vision service | | Audio | WAV, MP3, MP4, OGG, FLAC, WebM, M4A, AAC | ASR (Automatic Speech Recognition) | | Subtitles | VTT | Direct indexing |
Limits
- Maximum file size: 50 MB (configurable via
--max-file-size-mb) - Maximum files per conversation: 5 (configurable via
--max-files-per-conversation)
Processing Pipeline
- Upload — File uploaded via the Express proxy to
/api/files/upload - Validation — File type and size validated client-side and server-side
- Storage — File saved to filesystem (or S3 in production)
- Extraction — Text and metadata extracted using format-specific processors
- Chunking — Content chunked using configured strategy (fixed or semantic)
- Indexing — Chunks indexed in vector store for semantic search
- Status Polling — Client polls until processing completes
Integration Details
The application uses:
- Zustand for state management
- Express +
http-proxy-middlewarefor the API proxy layer - @schmitech/markdown-renderer (GitHub | NPM) for rich markdown rendering
- localStorage for persistent session and conversation storage
- TypeScript for type safety throughout
Troubleshooting
No Adapters Available
If the agent selector shows no adapters:
- Ensure
ORBIT_ADAPTERSorVITE_ADAPTERSis set and valid JSON - Check the CLI startup logs for "Available Adapters: ..."
- Verify each adapter has a
nameandapiKeyfield
If adapters load but descriptions/notes are missing in packaged installs (npm pack + install), while npm run dev works:
- Prefer
ORBIT_ADAPTERS(it takes precedence overVITE_ADAPTERSwhen both are set) - Ensure
orbitchat.yamlcontains adapter metadata and adapternamevalues exactly matchORBIT_ADAPTERS - Rebuild and repack from the updated source:
npm run build && npm pack - Reinstall the newly generated tarball
- Restart with a clean process/port:
orbitchat-daemon --force-restart(or./orbitchat.sh --force-restartin source checkout) - Verify runtime output:
- Startup log shows
Available Adapters: ... GET /api/adaptersreturnsdescription/notesfor each adapter
- Startup log shows
If logs show an adapter not in your current config (for example Cross Domain):
- Check startup log
Available Adapters: ...to confirm what the server actually loaded - Clear browser site data/localStorage for the app origin or open an incognito window
- Start a new conversation and reselect the agent
- Confirm requests no longer send stale
X-Adapter-Namevalues
File Upload Issues
- File size exceeded — Check file size against the configured limit
- Unsupported format — Verify file type is in the supported list above
- Upload fails — Check server logs and adapter configuration
- Processing fails — Ensure the file processing service is initialized on the backend
Debug Mode
Enable debug logging:
# in orbitchat.yaml
debug:
consoleDebug: trueThis enables detailed runtime logging from the CLI server.
Deployment Checklist
- Build the app:
npm run build - Set
ORBIT_ADAPTERSwith your production adapter configs (keep out of git) - Run behind HTTPS — use a reverse proxy like nginx or Caddy in front of
orbitchat - Bind to the right interface: use
--host 0.0.0.0to allow external access, or keep the defaultlocalhostfor local-only - Tune limits — set
--max-conversations,--max-message-length, etc. appropriate for your deployment - Monitor logs — use
orbitchat-daemon --startfor daemon mode with log file, or run directly and pipe to your log aggregator
