# SmartChats > SmartChats is an open-source, voice-first personal AI assistant: an agent you talk to that keeps your logs, metrics, todos and knowledge graph in your own database. Self-host it with one command (`curl -fsSL https://smartchats.ai/install | bash`, or the Docker image sheunaluko/smartchats) or use the hosted app at smartchats.ai. Apache-2.0 licensed, code at https://github.com/sheunaluko/smartchats. Every page below is plain markdown. For agents installing or operating SmartChats for a user, start with "For AI agents". For agents working on the code, read AGENTS.md in the repository. --- Source: https://smartchats.ai/docs # SmartChats SmartChats is a voice-first AI agent. You talk to it, and it can remember what you said, search the web, write and run code, and draw charts from your data. It reads and writes your data over the [Model Context Protocol](https://modelcontextprotocol.io) too, so other tools can use the same data. The same agent runs two ways: - **Cloud:** sign up at [smartchats.ai](https://smartchats.ai). There is no server to run. - **Self-hosted:** install with one command, then run `smartchats setup` and `smartchats start` on your laptop or a VPS. Same app, same agent. Your data stays in a database on your machine. See [Quick Start](/docs/quickstart). Your data can move both ways. `smartchats data export` and `smartchats data import` (and the matching MCP tools) copy everything between cloud and local. --- ## Voice logging and recall The core use is simple. Through the day, talk to SmartChats from your phone or any device. Log thoughts, exercise, ideas, anything, in plain speech. It stores them as logs, metrics, todos and knowledge-graph facts. Later, ask the built-in agent to recall it. Summarize the week. Find recurring themes. Compare what you logged on Tuesday with what you said on Friday. Draft a journal entry from your raw logs. If you prefer another tool for the recall side, point Claude (or any MCP client) or the `smartchats` CLI at your account. It is the same data. ``` phone (voice in, all day) │ ▼ SmartChats stores what you said │ └─────────── your data (cloud or local) ─────────┐ │ │ ▼ ▼ speak to SmartChats' (optional) Claude agent for recall or any MCP client ``` ### The other direction: bring data in, then talk to it Any agent that speaks MCP can also write into your account. For example: point a coding agent at a folder of Markdown notes and have it write them into SmartChats as logs, metrics and knowledge-graph facts through the MCP server. Then ask SmartChats about them by voice. ``` your existing data agent reads and structures it (Markdown, PDFs, exports) │ │ ▼ ▼ MCP server writes into any agent that your SmartChats account speaks MCP │ ▼ speak with it (SmartChats' agent or any MCP client) ``` --- ## Who it is for #### Knowledge worker Notes, decisions and half-formed ideas end up scattered across apps. Speak them to SmartChats as they happen, then ask its agent to recall them. It can pull patterns across weeks, list unfinished threads and connect last month's logs to this week's. #### Developer SmartChats gives an agent a typed, open data layer: - **MCP both ways.** Read and write a user's logs, metrics, todos and knowledge graph from any MCP client. - **One contract.** The `DataAPI` interface is the same in the web app, the CLI and the MCP server. Add a query builder once and every consumer can use it. - **Public source.** The CLI works against any deployment: cloud, local or your own fork. - **Code execution.** The agent writes and runs JavaScript in a sandbox in the browser, with workspace state that lasts across turns. - **Live agent messaging.** A coding agent can talk with you through the app with the [hub](/docs/hub) or the [MCP bridge](/docs/relay). See [Architecture](/docs/architecture) for how the pieces fit. #### Quantified self Log workouts, sleep, mood, water or weight by voice. "20 pull-ups, 30 push-ups, swam 30 minutes" becomes three structured metrics. - **Charts on request.** Ask for weekly running distance or a month of macros and the chart renders in the chat. - **Your own categories.** Search is full-text, vector and structured. - **Export any time.** `smartchats data export` writes every log, metric and entity to a JSON bundle. #### MCP client If you build an MCP client, or use one such as Claude Desktop, SmartChats is a data source: - **33 tools.** Read, search, insert, update and delete logs, metrics, todos and knowledge-graph entries. Vector search on logs, entities and relations. A read-only `run_query` for raw SurrealQL. Bundle import and export. - **Authenticated.** The cloud target acts as you. The local target is your single-user install. - **Same data layer as the app and the CLI.** See [MCP Server](/docs/mcp) for setup and the full tool list. #### Researcher SmartChats is a working system for studying voice interaction, agent memory and knowledge graphs over time. - **Inspectable.** The source is public. Fork and change any of it. - **Self-hostable.** Run the whole stack on your machine. - **Testable.** The schema is versioned, the layered runner (`smartchats-test`) covers lint, build, unit, integration and e2e, and data round-trips through export and import. - **Measurable voice.** The vie voice engine logs the evidence it tunes from, and has a replay harness. See [Voice engine](/docs/voice-engine). --- ## Get started - [Quick Start](/docs/quickstart) - [Self-Hosting](/docs/self-host) - [Architecture](/docs/architecture) - [MCP Server](/docs/mcp) --- ## What's in the stack - **The app.** A Next.js app in TypeScript that runs in your browser, with desktop and mobile layouts. The agent loop (cortex) streams structured output, runs code in a sandbox and keeps a knowledge graph. - **Voice.** Speech recognition and voice activity detection in the browser (tivi), and streaming text-to-speech from the server. The vie voice engine is the default turn-taking engine. Before the main model answers, a small server-side turn judge decides whether you have finished speaking. See [Voice engine](/docs/voice-engine). - **Model providers.** OpenAI, Anthropic, Gemini, xAI and Together AI for chat, OpenAI and Azure for speech. Bring your own keys. See [Providers](/docs/providers). - **The local server** (`smartchats-local-server`). Serves the app and the `/local-api` routes against SurrealDB, including media upload and storage through any S3-compatible bucket such as SeaweedFS. See [Local server](/docs/local-server). - **The CLI** (`smartchats`). Sets up, starts and stops the local stack, signs in to the cloud, and imports and exports data. See [CLI Reference](/docs/cli). - **The MCP server** (`smartchats-mcp`). Gives any MCP client your data over stdio. See [MCP Server](/docs/mcp). - **The hub and relay** (`smartchats-hub`, `smartchats-mcp-bridge`). Let coding agents on your machines talk with you through the app. See [Hub](/docs/hub) and [Relay](/docs/relay). The source for all of it is in the public repo, under the Apache License 2.0. See [Architecture](/docs/architecture#open-and-closed) for what is open and what is closed. --- ## See also - [GitHub issues](https://github.com/sheunaluko/smartchats/issues) for bugs and feature requests. - [LinkedIn (maintainer)](https://www.linkedin.com/in/sheun-aluko/) for design discussions. - [Contributing](/docs/contributing) for conventions and the dev workflow. --- Source: https://smartchats.ai/docs/quickstart # Quick Start Get SmartChats running on your machine in a few minutes. ## Installation #### macOS & Linux ```bash curl -fsSL https://smartchats.ai/install | bash ``` The installer downloads a release for macOS (Apple Silicon) or Linux (x64 or arm64) into `~/.smartchats` and then runs `smartchats setup` (shown under [Quick start](#quick-start)). The release bundles the CLI, the server, SurrealDB and the media storage (SeaweedFS). You don't need Node, Bun or Docker. > **Note:** **Linux users.** The installer needs `tar` and either `curl` or `wget` (preinstalled on most distros). If `tar` is missing: `sudo apt install tar` on Debian/Ubuntu, `sudo dnf install tar` on Fedora. > **Warning:** **Intel Macs.** There is no binary release for Intel Macs, and the installer exits with a message. Use the npm package instead (see the Package Managers tab). It needs Node 24+. To check that smartchats was installed successfully, open a new terminal window and run: ```bash smartchats --version # Output: 0.x.y ``` If you see `command not found`, add the install directory to your `PATH`. The installer symlinks the CLI into `/usr/local/bin` when that directory is writable. Otherwise it adds `~/.smartchats/bin` to `~/.zshrc`, `~/.bashrc` or your fish config. ```bash export PATH="$HOME/.smartchats/bin:$PATH" ``` Installer flags go after `bash -s --`: ```bash curl -fsSL https://smartchats.ai/install | bash -s -- --version v0.4.0 --non-interactive ``` | Flag | Default | Notes | |---|---|---| | `--version ` | `latest` | Install a specific release tag | | `--prefix ` | `~/.smartchats` | Install location | | `--non-interactive` | off | Don't run `smartchats setup` after installing | | `--no-path` | off | Don't touch `/usr/local/bin` or shell rc files | #### Windows ```powershell iwr -useb https://smartchats.ai/install.ps1 | iex ``` > **Warning:** **Windows support is experimental.** The installer and the `windows-x64` release exist, but they have not yet been tested on real Windows machines. Expect problems, and please report them on [GitHub](https://github.com/sheunaluko/smartchats/issues). If it doesn't work for you, use WSL2 with the macOS & Linux install line. The installer needs PowerShell 5 or later and Windows 10 1803 or later (for `tar.exe`). Only x64 is supported. Windows on ARM exits with an error because SurrealDB does not ship a Windows ARM build. It installs to `%USERPROFILE%\.smartchats`, adds `%USERPROFILE%\.smartchats\bin` to your user `PATH`, and then runs `smartchats setup`. To check that smartchats was installed successfully, open a new PowerShell window and run: ```powershell smartchats --version # Output: 0.x.y ``` To pass parameters, download the script first: ```powershell iwr -useb https://smartchats.ai/install.ps1 -o install.ps1 .\install.ps1 -Version v0.4.0 -NonInteractive ``` | Parameter | Default | Notes | |---|---|---| | `-Version ` | `latest` | Also read from `SMARTCHATS_VERSION` | | `-Prefix ` | `$HOME\.smartchats` | Also read from `SMARTCHATS_PREFIX` | | `-NoPath` | off | Don't change the user `PATH` | | `-NonInteractive` | off | Don't run `smartchats setup` after installing | #### Package Managers If you have Node 24 or later: ```bash npm install -g smartchats-ai ``` This installs the CLI only. It doesn't include the Bun runtime or SurrealDB. `smartchats setup` checks for both and prints the install command for anything missing. It also needs `git`, because it clones the SmartChats source to `~/.smartchats/cli/source` and builds it on first start. Try without installing: ```bash npx smartchats-ai setup ``` For most users the `curl` installer is simpler. #### Docker Each release publishes an all-in-one image (SurrealDB, media storage and the server) for `linux/amd64` and `linux/arm64`: ```bash docker pull sheunaluko/smartchats:latest # or :v0.4.0 ``` The same image is on GitHub's registry as `ghcr.io/sheunaluko/smartchats-aio`. To build it yourself instead, from a clone: `docker build -f Dockerfile.aio -t smartchats-aio .` (the build installs a published release, not your checkout. Pin one with `--build-arg SMARTCHATS_VERSION=v0.4.0`). Put your keys in `~/.smartchats/.env`, the same file a native install reads: ```bash mkdir -p ~/.smartchats cat > ~/.smartchats/.env < **Warning:** **You need at least one LLM provider key.** The container starts without one, but the agent cannot reply. `docker logs smartchats` shows a `NO API KEYS CONFIGURED` box when no key is found. See [Required environment](/docs/self-host#required-environment). Ways to supply keys: | Method | When to use | |---|---| | `-v ~/.smartchats/.env:/root/.smartchats/.env:ro` | Same file as a native install. Edit it, then `docker restart smartchats` | | `--env-file ~/.smartchats/.env` | Read once when the container is created | | `-e OPENAI_API_KEY=$OPENAI_API_KEY` | One-off runs, CI | From a clone, `bin/aio -d` builds the image if it is missing and runs it the same way, forwarding provider keys from your shell. For more, see [Self-Hosting](/docs/self-host#docker-alternative). ## Upgrading Once installed, smartchats can upgrade itself: ```bash smartchats upgrade ``` This re-runs the installer in place. Your data directory, `.env` and `config.json` are kept. Restart the stack afterwards with `smartchats restart`. | Flag | Default | Notes | |---|---|---| | `--version ` | `latest` | Install a specific release, for example `v0.4.0` | | `-y, --yes` | off | Skip the confirmation prompt | **npm users:** run `npm install -g smartchats-ai@latest` instead. `smartchats upgrade` prints this and exits 1. **Source checkouts:** run `git pull` in the repo. **Docker:** `docker pull sheunaluko/smartchats:latest` (or rebuild with `docker build --no-cache -f Dockerfile.aio -t smartchats-aio .`), then remove and re-run the container. Data in the `/data` volume is kept. ## Uninstalling There is no uninstall command. To remove a `curl` install, stop the stack and delete the install directory: ```bash smartchats stop rm -rf ~/.smartchats rm -f /usr/local/bin/smartchats ``` `~/.smartchats` holds your data (`data/`), keys (`.env`), config and logs. Export your data first if you want to keep it (see [Backup + restore](/docs/self-host#backup--restore)). If the installer edited your shell rc file, delete the `# Added by SmartChats installer` line and the `PATH` line after it. If you signed in to the cloud, also delete `~/.smartchats-mcp/credentials.json`. **Windows:** delete `%USERPROFILE%\.smartchats` and remove `%USERPROFILE%\.smartchats\bin` from your user `PATH`. **npm:** `npm uninstall -g smartchats-ai`. The clone at `~/.smartchats/cli/source` stays until you delete it. **Docker:** `docker rm -f smartchats` and `docker rmi sheunaluko/smartchats:latest`. Delete `~/.smartchats/data` to remove your data too. ## Quick start After installing, run the guided setup (the installers run it for you): ```bash smartchats setup ``` The wizard will: 1. Check your system. A binary install checks free disk only. A source or npm install also checks for `bun` and `surreal`. 2. Ask for each provider key in [Providers & API Keys](/docs/providers), starting with Together and Azure, the two the default setup uses (see [Which keys matter](#which-keys-matter)). Press enter to skip any of them. 3. Write the `.env` file, start the stack and print [http://localhost:3000](http://localhost:3000). To use it from your phone, open the same address with your computer's network name or IP and pair once with the code from `smartchats pair`. ```bash # Check what's running: smartchats status # Stop the stack: smartchats stop # Tail logs: smartchats logs -f # Add or change provider API keys later (interactive): smartchats env # Then `smartchats restart` to pick up the changes. ``` ### Which keys matter `setup` and `env` ask for every key in [Providers & API Keys](/docs/providers#providers), starting with the two the default setup uses: Together (the default chat model and the voice turn judge) and Azure Speech (the default voice). Every key is optional, but the agent needs at least one chat provider to reply. - **Without Together**, another provider's model is used if it has a key, and the server skips the turn judge, so a pause mid-sentence ends the turn. - **Without Azure**, replies are spoken with an OpenAI voice if OpenAI has a key. - **Without OpenAI**, semantic search over your logs, knowledge graph and memory is off: OpenAI's embeddings are currently the only ones SmartChats uses. Entries saved without a key are not embedded later, so add it early if you want them searchable. To change keys later, run `smartchats env`, or edit `~/.smartchats/.env` (`/.env` for a source install), then `smartchats restart`. That's it. Talk to the agent. ## What's next - **[CLI Reference](/docs/cli)**: every command and flag. - **[Self-Hosting](/docs/self-host)**: env-var management, running on a VPS, Docker. - **[Architecture](/docs/architecture)**: how voice, agent runtime, knowledge graph and sandboxed execution fit together. - **[MCP](/docs/mcp)**: expose smartchats to Claude Desktop and other MCP-aware tools. ## Use the hosted SaaS instead Don't want to run infrastructure? Sign up at [smartchats.ai](https://smartchats.ai). Same stack, managed billing, no install. Free tier included. --- Source: https://smartchats.ai/docs/self-host # Self-Hosting This page is for running SmartChats yourself: on your laptop, a VPS, a Raspberry Pi, anywhere with a shell. For most people, [Quick Start](/docs/quickstart) already covers it. This page goes further: env-var management, running on a VPS, Docker, and what runs inside the stack. ## TL;DR ```bash curl -fsSL https://smartchats.ai/install | bash smartchats setup ``` That gives you a self-hosted SmartChats on your machine. Everything below is for cases where you need more control. ## What you need - **macOS (Apple Silicon) or Linux** with a shell. Intel Macs use the npm package. Windows is experimental (see [Quick Start](/docs/quickstart)). - **An OpenAI API key.** `setup` treats it as the required key. It covers chat models, embeddings and OpenAI voices. - Optional: Anthropic, Google (Gemini), xAI, Together AI, Azure Speech and Serper keys for more models, the turn verdict, Azure voices and web search. - **3 GB free disk.** `setup` and `doctor` warn below that. The installer bundles the CLI, the server (both compiled with Bun, runtime included) and the SurrealDB binary. No Node, no Docker, no system-level deps required. ## The stack, briefly `smartchats start` launches **two** processes: ``` ┌──────────────────────────────────────┐ │ smartchats-server (port 3000) │ │ │ │ • serves the SPA at / │ │ • handles /local-api/* (health, │ │ llm, tts, embeddings, data, │ │ usage, keys, tools, insights, │ │ storage) │ └─────────────────┬────────────────────┘ │ ws://127.0.0.1:8000/rpc ▼ ┌──────────────────────────────────────┐ │ surreal (port 8000, loopback only) │ │ ~/.smartchats/data/surreal.db │ └──────────────────────────────────────┘ ▲ │ browser at :3000 ``` The browser only needs port 3000. SurrealDB binds to `127.0.0.1`. The server serves the static export of the Next.js app directly, so there is no Next.js runtime. ## Where things live | Path | Purpose | |---|---| | `~/.smartchats/bin/` | Installed binaries (`smartchats`, `smartchats-server`, `surreal`) | | `~/.smartchats/app/out/` | The static SPA the server serves | | `~/.smartchats/data/surreal.db` | SurrealDB storage (rocksdb). Change the directory with `start --data-dir` | | `~/.smartchats/run/pids.json` | Live process IDs, ports and start times | | `~/.smartchats/logs/` | Per-process logs (`surreal.log`, `server.log`) | | `~/.smartchats/.env` | API keys and config for a binary install. A source install uses `/.env` | | `~/.smartchats/config.json` | CLI preferences (last port, resolved source root). `$XDG_CONFIG_HOME/smartchats/config.json` if that variable is set | `smartchats home` prints the install root (binary install) or the resolved source root. ## Required environment The stack boots without any keys, but **the agent cannot reply** until at least one LLM provider key is configured. `smartchats start` prints a `NO API KEYS CONFIGURED` box when it can't find any. | Variable | Required? | Effect if missing | |---|---|---| | `OPENAI_API_KEY` | **Strongly recommended** | OpenAI models, embeddings and OpenAI voices unavailable | | `ANTHROPIC_API_KEY` | Optional | Claude models can't be selected | | `GOOGLE_API_KEY` *(or `GEMINI_API_KEY`)* | Optional | Gemini models can't be selected | | `XAI_API_KEY` | Optional | Grok models can't be selected | | `TOGETHER_API_KEY` *(or `TOGETHERAI_API_KEY`)* | Recommended for voice | The default turn verdict is skipped and every voice turn is answered | | `AZURE_SPEECH_KEY` + `AZURE_SPEECH_REGION` | Recommended for voice | The default voice provider (Azure) fails. Pick an OpenAI voice instead | | `SERPER_API_KEY` | Optional | Agent's web-search tool is a no-op | | `SMARTCHATS_API_KEY` | Optional | Bearer token for scripts and agents on other machines ([Other devices and scripts](#other-devices-and-scripts)) | | `SMARTCHATS_AUTH` | Optional | `off` lets every device in without pairing | > **Note:** **`setup` and `env` only prompt for OpenAI, Anthropic, Google and Serper.** Add the others to the `.env` file by hand, then run `smartchats restart`. ## Environment configuration `smartchats start` reads the `.env` file at the install root (or repo root), merges in your shell environment, and passes the result to the server. A variable set in your shell wins over the same name in `.env`, so `OPENAI_API_KEY=sk-... smartchats start` overrides the file for one run. Each provider has a `SMARTCHATS_`-prefixed name and one or more plain names. The server checks them in the order below and uses the first one set. If none is set, it falls back to a key stored in SurrealDB with `POST /local-api/keys`. | Provider | Checked in this order | |---|---| | OpenAI | `SMARTCHATS_OPENAI_API_KEY`, `OPENAI_API_KEY` | | Anthropic | `SMARTCHATS_ANTHROPIC_API_KEY`, `ANTHROPIC_API_KEY` | | Google (Gemini) | `SMARTCHATS_GOOGLE_API_KEY`, `GOOGLE_API_KEY`, `GEMINI_API_KEY` | | xAI | `SMARTCHATS_XAI_API_KEY`, `XAI_API_KEY` | | Together AI | `SMARTCHATS_TOGETHER_API_KEY`, `TOGETHER_API_KEY`, `TOGETHERAI_API_KEY` | | Azure Speech key | `SMARTCHATS_AZURE_SPEECH_KEY`, `AZURE_SPEECH_KEY` | | Azure Speech region | `SMARTCHATS_AZURE_SPEECH_REGION`, `AZURE_SPEECH_REGION` | | Serper (web search) | `SMARTCHATS_SERPER_API_KEY`, `SERPER_API_KEY` | Azure Speech is read from the environment only. A database-stored Azure key is not used for voice. ### Server settings These are read by `smartchats-server`. `smartchats start` sets the `SURREAL_*`, `SMARTCHATS_HOST`, `SMARTCHATS_PORT` and `SMARTCHATS_STATIC_DIR` values itself, so setting them in `.env` has no effect under `start`. They matter when you run the server another way, such as `smartchats dev`. | Variable | Server default | Set by `start` to | |---|---|---| | `SMARTCHATS_HOST` | `127.0.0.1` | `0.0.0.0` | | `SMARTCHATS_PORT` | `4242` | `--port` (default `3000`) | | `SMARTCHATS_STATIC_DIR` | unset (API only) | `~/.smartchats/app/out` | | `SURREAL_URL` | `ws://127.0.0.1:8000/rpc` | `ws://127.0.0.1:/rpc` | | `SURREAL_NS` | `smartchats` | `smartchats` | | `SURREAL_DB` | `main` | `main` | | `SURREAL_USER` | `root` | `root` | | `SURREAL_PASSWORD` | `root` | `root` | Media storage. `smartchats start` runs SeaweedFS and sets these for the server, so you only set them to use your own S3-compatible bucket instead (all four are needed). See [Storage](/docs/local-server#storage) for the bind address and the browser-facing endpoint: | Variable | Default | Notes | |---|---|---| | `SMARTCHATS_S3_ENDPOINT` | set by `start` | e.g. `https://.r2.cloudflarestorage.com` | | `SMARTCHATS_S3_ACCESS_KEY_ID` | unset | | | `SMARTCHATS_S3_SECRET_ACCESS_KEY` | unset | | | `SMARTCHATS_S3_BUCKET` | unset | | | `SMARTCHATS_S3_REGION` | `us-east-1` | Signing region | | `SMARTCHATS_S3_LINKS` | `server` | `direct`: browsers get presigned bucket URLs instead of links through the server ([Storage](/docs/local-server#storage)) | ### Where the `.env` file lives | Install path | `.env` location | |---|---| | `curl \| bash` install | `~/.smartchats/.env` | | Source checkout, or npm install | `/.env` (npm installs clone to `~/.smartchats/cli/source`) | | Docker container | `/root/.smartchats/.env` (mount the host file there, see below) | Ways to write keys: ```bash smartchats env # interactive walkthrough (recommended) smartchats env --list # show what's configured (masked) vi ~/.smartchats/.env # direct edit, needed for xAI / Together / Azure ``` `smartchats setup` also writes the file on first run. After editing, run `smartchats restart` (or `docker restart smartchats`) to pick up the changes. To check which keys the CLI sees, run: ```bash smartchats env --list # Output: # ✓ OpenAI sk-V…vGiE (.env $OPENAI_API_KEY) # ✗ Anthropic (Claude) not set (optional) ``` ### Sharing one `.env` between bare-metal and Docker installs The Docker container reads `/root/.smartchats/.env` (its install root), and the bare-metal install reads `~/.smartchats/.env` on the host. **Mounting the host file into the container reuses the same config**: ```bash docker run -d --name smartchats \ -p 3000:3000 \ -v ~/.smartchats/.env:/root/.smartchats/.env:ro \ -v ~/.smartchats/data:/data \ sheunaluko/smartchats:latest ``` Edit `~/.smartchats/.env` later, then: ```bash docker restart smartchats ``` ### Adding keys after the stack is running - **`smartchats env` on the host** (recommended): interactive prompts, writes to `~/.smartchats/.env`. Then `smartchats restart`, or `docker restart smartchats` if you mounted the file into the container. - **Edit the file directly**: `vi ~/.smartchats/.env`, then restart. This is the only way to add xAI, Together and Azure keys. - **Store a key in SurrealDB** with the keys API. No restart needed, and an environment key for the same provider still wins: ```bash curl -X POST http://localhost:3000/local-api/keys \ -H 'content-type: application/json' \ -d '{"keys":{"together":"..."}}' ``` The API accepts `openai`, `anthropic`, `google`, `xai`, `together` and `azure`. `GET /local-api/keys/providers` shows which providers are available and where each key came from. ### Other devices and scripts The computer running SmartChats needs nothing. A phone or another computer pairs once: it shows **Pair this device**, and you enter the code from ```bash smartchats pair # Pairing code: 482 913 (valid 5 min, one device) ``` or from Settings → Devices → **Pair a new device**. `smartchats devices` lists paired devices and `smartchats devices revoke ` removes one. **In Docker** your own browser is another device to the container, so it pairs once too. While nothing is paired, the container prints a code at startup (`docker logs smartchats`, valid 15 minutes); for a new one run `docker exec smartchats smartchats pair`. Scripts and agents on other machines can send `Authorization: Bearer $SMARTCHATS_API_KEY` instead, when you set that variable. `SMARTCHATS_AUTH=off` turns all of this off. Details: [Security](/docs/local-server#security). ## Running on a VPS The same install and commands work on a VPS. For a public deploy, put a reverse proxy in front for TLS and access control. ### Install on the VPS ```bash ssh user@vps curl -fsSL https://smartchats.ai/install | bash ``` The installer runs the interactive `smartchats setup`. For a scripted install, write `~/.smartchats/.env` first and use the non-interactive form: ```bash curl -fsSL https://smartchats.ai/install | bash -s -- --non-interactive smartchats setup --no-prompt ``` ### Restrict access to port 3000 `smartchats start` always binds the server to `0.0.0.0`, so port 3000 is reachable on every interface. There is no flag to change this. Block the port at the firewall and let only the reverse proxy reach it: ```bash # ufw example: allow SSH and HTTPS, deny direct access to 3000 sudo ufw allow OpenSSH sudo ufw allow 443/tcp sudo ufw deny 3000/tcp sudo ufw enable ``` SurrealDB already binds to `127.0.0.1` only. ### Front it with nginx + Let's Encrypt Keep `Host $host` and `X-Forwarded-For` in the config: they are how the server tells visitors from this machine ([Security](/docs/local-server#security)). Visitors then pair like any other device; `auth_basic` adds a second lock if you want one. ```nginx server { listen 443 ssl http2; server_name smartchats.your-domain.com; ssl_certificate /etc/letsencrypt/live/.../fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/.../privkey.pem; auth_basic "SmartChats"; auth_basic_user_file /etc/nginx/.htpasswd; location / { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } ``` ### Auto-start on boot (systemd) Run the stack in the foreground so systemd supervises it. On SIGTERM, `start --foreground` stops both processes. ```ini # /etc/systemd/system/smartchats.service [Unit] Description=SmartChats local stack After=network.target [Service] Type=simple User=YOUR_USER WorkingDirectory=/home/YOUR_USER ExecStart=/home/YOUR_USER/.smartchats/bin/smartchats start --foreground --no-prompt Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target ``` ```bash sudo systemctl daemon-reload sudo systemctl enable --now smartchats sudo systemctl status smartchats ``` ### Periodic backup ```bash #!/bin/bash # /etc/cron.daily/smartchats-backup (runs as root, so use full paths) mkdir -p /home/YOUR_USER/backups /home/YOUR_USER/.smartchats/bin/smartchats data export \ /home/YOUR_USER/backups/$(date +%Y-%m-%d).json --target=local ``` ## Docker (alternative) There is no published image. Build one from `Dockerfile.aio` in a clone of the repo: ```bash git clone https://github.com/sheunaluko/smartchats.git cd smartchats docker build -f Dockerfile.aio -t smartchats-aio:latest . ``` Each release also publishes this image as `sheunaluko/smartchats:` and `:latest` on Docker Hub, and as `ghcr.io/sheunaluko/smartchats-aio` on GitHub's registry (linux/amd64 and linux/arm64), so building is optional. The Dockerfile runs `curl … install | bash` against `debian:bookworm-slim`, so the image contains a published release (pin one with `--build-arg SMARTCHATS_VERSION=v0.4.0`). Its entrypoint is `smartchats start --data-dir=/data --foreground --no-prompt`, and `/data` holds the database and media. Media storage runs inside the container and the browser reaches it through port 3000, so that is the only port to publish. ```bash docker run -d --name smartchats \ -p 3000:3000 \ -v ~/.smartchats/.env:/root/.smartchats/.env:ro \ -v ~/.smartchats/data:/data \ sheunaluko/smartchats:latest ``` The `.env` mount is the same file the bare-metal CLI reads (`~/.smartchats/.env`), so one config works for both install modes. See [Sharing one `.env` between bare-metal and Docker installs](#sharing-one-env-between-bare-metal-and-docker-installs). > **Warning:** **Don't run the container and a native stack on the same data directory at once.** Both store the database at `/surreal.db`, so `~/.smartchats/data` works for either, one at a time. **`-e` flags** also work for one-off runs: ```bash docker run --rm -p 3000:3000 \ -e TOGETHER_API_KEY=$TOGETHER_API_KEY \ -v ~/.smartchats/data:/data \ sheunaluko/smartchats:latest ``` `bin/aio` in a clone wraps the build and run steps (`--rebuild`, `-d`, `--port`, `--data-dir`, `--tag`). It names the container `smartchats`, which is the name `smartchats doctor` checks. Docker is useful when you need process isolation or container orchestration. For laptop self-hosting, the native install is simpler. ## Backup + restore ```bash # Snapshot (writes a single JSON file) smartchats data export ~/snapshot.json --target=local # Wipe and restore (the import upserts every row by id) smartchats stop rm -rf ~/.smartchats/data smartchats start smartchats data import ~/snapshot.json --target=local ``` The fresh stack boots with an empty database, and the import writes the rows back. `data --target=local` always connects to `ws://localhost:8000/rpc`, so it needs the default `--surreal-port`. ## Troubleshooting > **Warning:** **Port 3000 already in use** → another process is bound to it. Either stop it (`lsof -i:3000` to find it), or `smartchats start --port 3030`. > **Warning:** **`smartchats start` exits with "surreal binary not found"** → in a binary install, `~/.smartchats/bin/surreal` is missing and there is no `surreal` on `PATH`. Re-run the installer. In a source or npm install, install SurrealDB with the command `start` prints. > **Warning:** **The agent doesn't reply** → no provider API keys reachable. Run `smartchats env --list` to see what the CLI found, add keys to `.env`, then `smartchats restart`. > **Note:** **Diagnosing a running stack** → `smartchats status` for a health snapshot, `smartchats logs -f` to tail per-process output, `smartchats logs --proc server -n 200` for just the server's last 200 lines. > **Note:** **Iterating on the source?** → `smartchats dev` (Next.js dev server with hot reload, the local server and SurrealDB in Docker) instead of `smartchats start`. See [Contributing](/docs/contributing). ## See also - **[Quick Start](/docs/quickstart)**: install and first run. - **[CLI Reference](/docs/cli)**: every command and flag. - **[Architecture](/docs/architecture)**: the open / cloud boundary and what's inside the stack. - **[Contributing](/docs/contributing)**: modifying SmartChats itself. --- Source: https://smartchats.ai/docs/agents # For AI Agents This page is for AI agents that install, configure or operate SmartChats on behalf of a user. Everything here runs without prompts and reports in machine-readable form. Plain-markdown copies of every docs page are at `https://smartchats.ai/docs/.md`, indexed in [`/llms.txt`](https://smartchats.ai/llms.txt) and combined in [`/llms-full.txt`](https://smartchats.ai/llms-full.txt). If you are working on the SmartChats code itself, read [`AGENTS.md`](https://github.com/sheunaluko/smartchats/blob/main/AGENTS.md) in the repository instead. ## What you are installing A local stack of three processes, started and stopped together by the `smartchats` CLI: | Process | Port | Role | |---|---|---| | SurrealDB | `127.0.0.1:8000` | The user's data (logs, metrics, todos, knowledge graph, sessions) | | SeaweedFS | `127.0.0.1:8333` | Media storage (photos, videos, files), reached only through the server. Optional | | smartchats-server | `0.0.0.0:3000` | The web app at `/app` and the API at `/local-api/*` | The user talks to the agent in a browser at `http://localhost:3000/app`. The model provider keys are the user's own. ## Install without prompts macOS (Apple Silicon) and Linux (x64, arm64): ```bash curl -fsSL https://smartchats.ai/install | bash -s -- --non-interactive ``` This installs to `~/.smartchats` and links `smartchats` into `/usr/local/bin` when it is writable (otherwise it adds `~/.smartchats/bin` to the shell rc). Add `--no-path` to touch neither, then call `~/.smartchats/bin/smartchats` directly. Add `--version v0.4.0` to pin a release. Intel Macs have no binary release: use `npm install -g smartchats-ai` (Node 24+). Docker, anywhere: ```bash docker run -d --name smartchats -p 3000:3000 \ -v ~/.smartchats/.env:/root/.smartchats/.env:ro \ -v ~/.smartchats/data:/data \ sheunaluko/smartchats:latest ``` ## Configure keys Keys live in `~/.smartchats/.env` (mode 0600). Write them with the user's consent and never echo them back. With the keys in the environment, `setup --no-prompt` saves them and starts the stack: ```bash TOGETHER_API_KEY=... AZURE_SPEECH_KEY=... AZURE_SPEECH_REGION=eastus \ smartchats setup --no-prompt ``` Or write the file and start: ```bash umask 077 cat >> ~/.smartchats/.env <<'EOF' TOGETHER_API_KEY=... AZURE_SPEECH_KEY=... AZURE_SPEECH_REGION=eastus EOF smartchats start --no-prompt ``` | Key | Needed for | Without it | |---|---|---| | `TOGETHER_API_KEY` | The default chat model (DeepSeek V4.1 Flash) and the voice turn judge | Another provider's model is used if one has a key. Voice turns end on silence with no judge | | `AZURE_SPEECH_KEY` + `AZURE_SPEECH_REGION` | The default voice | An OpenAI voice is used if OpenAI has a key, otherwise no speech | | `OPENAI_API_KEY` | GPT models, OpenAI voices, embeddings (semantic search over the user's logs) | Semantic search is off | | `ANTHROPIC_API_KEY`, `GOOGLE_API_KEY`, `XAI_API_KEY` | Claude, Gemini, Grok models | Those models aren't offered | | `SERPER_API_KEY` | The agent's web search tool | Web search is a no-op | At least one chat provider key (Together, OpenAI, Anthropic, Google or xAI) is needed for the agent to reply. Restart after changing keys: `smartchats restart`. Every variable is listed in [Providers & API Keys](/docs/providers). ## Verify Each command exits 0 on success: ```bash smartchats status --json # {"up": true, "procs": [{"name": "surreal", "pid": 1234, "port": 8000, "pidAlive": true, "portHealthy": true, ...}, {"name": "seaweedfs", ...}, {"name": "server", ...}]} smartchats doctor --json # {"exit": 0, "results": [{"name": "LLM provider keys", "status": "pass", "severity": "warn", "note": "found: Together, Azure ..."}, ...], "port": 3000} curl -s http://localhost:3000/local-api/health # {"ok": true, "checks": {"data": {"ok": true, ...}, "providers": {"ok": true, ...}}} curl -s http://localhost:3000/local-api/keys/providers # {"llm": [{"provider": "together", "available": true, ...}, ...], "tts": [{"provider": "azure", "available": true, ...}, ...]} ``` From a clone of the repository, `node scripts/smoke-stack.mjs http://localhost:3000` exercises the stack the way a user would: the app loads, a database query, an LLM reply, and a media upload and download. It prints one line per check and exits non-zero if any fails. > **Note:** **From the machine running SmartChats** these need no credentials. From anywhere else `/local-api` answers `401 pairing_required`: use `Authorization: Bearer $SMARTCHATS_API_KEY` (set it on the server), or pair with a code from `smartchats pair`. In Docker, run commands inside the container: `docker exec smartchats smartchats status`. ## Operate | Task | Command | |---|---| | Start / stop / restart | `smartchats start --no-prompt`, `smartchats stop`, `smartchats restart` | | Pair a phone or another computer | `smartchats pair --json` → `{"code", "expires_at"}`; list and revoke with `smartchats devices [--json]`, `smartchats devices revoke ` | | Logs | `smartchats logs --proc server -n 100` (or `surreal`, `seaweedfs`), files in `~/.smartchats/logs/` | | Run in the foreground (systemd, containers) | `smartchats start --foreground --no-prompt` | | Other ports | `smartchats start --port 3100 --surreal-port 8100` (media: `SMARTCHATS_S3_PORT=8433`) | | Back up the user's data | `smartchats data export ~/smartchats-backup.json` | | Restore | `smartchats data import ~/smartchats-backup.json --dry-run`, then without `--dry-run` | | Upgrade | `smartchats upgrade -y` | Process IDs and ports are in `~/.smartchats/run/pids.json`. The database is in `~/.smartchats/data/surreal.db` and media in `~/.smartchats/data/seaweedfs/`. ## When something is wrong | Symptom | Likely cause | Fix | |---|---|---| | `status` shows `server` with `portHealthy: false` | The server failed to start | `smartchats logs --proc server -n 50`. Often a port in use: start with `--port` | | Health `providers.ok` is false, the agent never replies | No chat provider key | Add one to `~/.smartchats/.env`, then `smartchats restart` | | Replies work but there's no voice | No Azure key and no OpenAI key | Add `AZURE_SPEECH_KEY` + `AZURE_SPEECH_REGION` (or `OPENAI_API_KEY`) | | Uploads fail in the app, `createUpload` answers 501 | Media storage is off | `smartchats doctor` shows why (no `weed` binary, or `SMARTCHATS_STORAGE=off`) | | Every `/local-api` call returns 401 `pairing_required` | The caller is not the machine running SmartChats (another device, or the host of a Docker container) | Pair (`smartchats pair`, then enter the code in the app), or send `Authorization: Bearer $SMARTCHATS_API_KEY` | | The app shows **Pair this device** | Same | Give the user the code from `smartchats pair` (Docker: `docker exec smartchats smartchats pair`) | | `smartchats: command not found` after install | PATH not updated in this shell | Use `~/.smartchats/bin/smartchats`, or open a new shell | ## Give the user's agent their data SmartChats exposes the user's data to other agents (Claude Desktop, Claude Code, Cursor and others) through an MCP server with tools to read and write logs, metrics, todos and the knowledge graph. See [MCP Server](/docs/mcp). ## Ground rules - The data is the user's: logs, journal entries and transcripts are personal. Don't copy them anywhere the user didn't ask for. - Keys stay in `~/.smartchats/.env` with mode 0600. Don't print them, commit them, or pass them on the command line where other users can see them. - The server listens on all interfaces. Other devices must pair, but don't expose port 3000 to the internet without TLS and a reverse proxy that forwards `Host` and `X-Forwarded-For` ([Security](/docs/local-server#security)). Pair a device only when the user asks. ## See also - [Quick Start](/docs/quickstart) - [Providers & API Keys](/docs/providers) - [Local Server](/docs/local-server) - [CLI Reference](/docs/cli) --- Source: https://smartchats.ai/docs/providers # Providers & API keys A self-hosted SmartChats server calls model and service providers with your own API keys. This page lists every provider it can use, the environment variables it reads, and what each feature needs. ## Providers | Provider | Env vars (first match wins) | Used for | Needed by default? | |---|---|---|---| | Together AI | `SMARTCHATS_TOGETHER_API_KEY`, `TOGETHER_API_KEY`, `TOGETHERAI_API_KEY` | Open-weight chat models, including the default model. The default voice turn verdict. | **Yes**: default chat model and turn verdict | | Azure Speech | `SMARTCHATS_AZURE_SPEECH_KEY`, `AZURE_SPEECH_KEY` plus `SMARTCHATS_AZURE_SPEECH_REGION`, `AZURE_SPEECH_REGION` | Text to speech (the default voice) | **Yes**: default voice | | OpenAI | `SMARTCHATS_OPENAI_API_KEY`, `OPENAI_API_KEY` | GPT chat models, embeddings (semantic search), OpenAI text to speech | **For semantic search**: the only embeddings SmartChats uses today | | Anthropic | `SMARTCHATS_ANTHROPIC_API_KEY`, `ANTHROPIC_API_KEY` | Claude chat models | No | | Google (Gemini) | `SMARTCHATS_GOOGLE_API_KEY`, `GOOGLE_API_KEY`, `GEMINI_API_KEY` | Gemini chat and image models | No | | xAI | `SMARTCHATS_XAI_API_KEY`, `XAI_API_KEY` | Grok chat models | No | | Serper | `SMARTCHATS_SERPER_API_KEY`, `SERPER_API_KEY` | The agent's web search tool | No | | Your own S3-compatible bucket | `SMARTCHATS_S3_ENDPOINT`, `SMARTCHATS_S3_ACCESS_KEY_ID`, `SMARTCHATS_S3_SECRET_ACCESS_KEY`, `SMARTCHATS_S3_BUCKET`, optional `SMARTCHATS_S3_REGION` (default `us-east-1`) | Media storage, instead of the SeaweedFS that `smartchats start` runs | No | The variable names come from `packages/smartchats-local-server/src/config.ts`. For each provider the server checks, in order: 1. `SMARTCHATS__API_KEY` 2. the provider's usual name, such as `TOGETHER_API_KEY` (and any alias listed above) 3. a key stored in the database with `POST /keys` The `SMARTCHATS_` name always wins. Use it when another tool on the machine already sets the plain name to a different key. ### Chat models by provider Each model in the registry (`packages/cortex/src/model_registry.ts`) belongs to one provider, and needs that provider's key: | Provider | Models | |---|---| | Together AI | DeepSeek V4.1 Flash (the default), GLM, Kimi, Qwen, MiniMax, gpt-oss | | OpenAI | GPT-4.1, GPT-5 family, GPT-6 family, o4-mini | | Anthropic | Claude Haiku, Sonnet, Opus and Fable | | Google | Gemini Flash, Flash Lite and Pro, plus an image model | | xAI | Grok | The app's preset menu greys out choices whose provider has no key. It reads availability from `GET /local-api/keys/providers`. ## Setting keys ### With the CLI The CLI writes keys to a `.env` file that `smartchats start` loads on boot: - **Binary install** (the `curl … | bash` installer): `~/.smartchats/.env`. Docker mounts the same file. - **Source install** (a clone of the repo): `.env` at the repo root. ```bash smartchats env # walk the providers and set or update each key smartchats env --list # show what is configured, masked smartchats setup # first run: also prompts for keys smartchats restart # pick up changes on a running stack ``` `smartchats env` and `setup` prompt for OpenAI, Anthropic, Google and Serper. For any other provider, add the line to the `.env` file yourself. `smartchats start` passes every variable in that file to the server, and a variable already set in your shell wins over the file. ```ini # ~/.smartchats/.env TOGETHER_API_KEY=... AZURE_SPEECH_KEY=... AZURE_SPEECH_REGION=eastus XAI_API_KEY=... ``` ### With POST /keys Keys can also be stored in the database, through the BYO Keys section of the app's settings or directly: ```bash curl -X POST http://127.0.0.1:3000/local-api/keys \ -H 'Content-Type: application/json' \ -d '{"keys": {"together": "..."}}' ``` | Route | What it does | |---|---| | `POST /local-api/keys` | Stores keys for `openai`, `anthropic`, `google`, `xai`, `together` or `azure` | | `GET /local-api/keys` | Masked preview of stored keys only (not env vars) | | `DELETE /local-api/keys/:provider` | Removes a stored key | | `GET /local-api/keys/providers` | Which LLM and TTS providers have a usable key, and from where | These work as shown from the machine running SmartChats. From another machine, add `-H "Authorization: Bearer $SMARTCHATS_API_KEY"` (set that variable on the server first). > **Warning:** **Env vars shadow stored keys.** A key from the environment is used before a stored one. `POST /keys` saves the key anyway and returns a `warning` naming the providers it will ignore at call time. > **Warning:** **Some keys are env-only.** Text to speech reads the Azure and OpenAI keys from the environment only, so an Azure key stored with `POST /keys` does not enable the voice. The Serper key and the S3 settings also come only from the environment. ## What each feature needs | Feature | Needs | Without it | |---|---|---| | Chat with the default model | Together | The request fails with `no together API key configured`, naming `SMARTCHATS_TOGETHER_API_KEY` and `POST /keys`. Pick a model from a provider you have a key for. | | Chat with any other model | That model's provider | Same error, naming that provider | | Voice turn verdict (default judge) | The verdict model's provider. Together for the default `deepseek-v4.1-flash` | The server skips the verdict and answers the turn, as if it had said COMPLETE. Turns are never held, so a pause mid-sentence ends the turn. | | Default voice (Azure Emma) | Azure key **and** region | With an OpenAI key, the reply is spoken with an OpenAI voice instead. With neither, there is no speech. | | OpenAI voices | OpenAI | Falls back to Azure if Azure is configured | | Embeddings (`text-embedding-3-small`) | OpenAI | The embeddings route returns an error. Saved logs are written without a vector, and search by meaning does not work. Adding a key later does not embed entries saved before it. | | Web search | Serper | The search tool returns `search disabled: SMARTCHATS_SERPER_API_KEY or SERPER_API_KEY not set` | | Media storage | No key: `smartchats start` runs SeaweedFS (bundled with binary installs) | Without SeaweedFS and without your own `SMARTCHATS_S3_*` bucket, storage routes return 501 and uploads are off. | > **Note:** **The turn verdict needs token probabilities.** Judge v2 decides from the model's probability that the user is done, read from the first token's logprobs. In llm-service only the Together adapter returns them today. With a verdict model from another provider, the verdict is decided from the sampled word and its threshold has no effect. A verdict is one short call per voice turn, well under a cent with the default model. See [Voice engine](/docs/voice-engine). ## See also - [Voice engine](/docs/voice-engine): how the turn verdict uses these keys - [Self-Hosting](/docs/self-host): env-var management, bearer-token auth and Docker - [CLI Reference](/docs/cli): `smartchats env` and `smartchats setup` - [TURN_VERDICT.md](https://github.com/sheunaluko/smartchats/blob/main/packages/llm-service/TURN_VERDICT.md): the verdict's providers and self-hosting note --- Source: https://smartchats.ai/docs/cli # CLI Reference `smartchats` is the command-line interface for the SmartChats stack. One tool covers the whole lifecycle: install, configure, launch, observe, stop, upgrade, sign in to the cloud, and move data around. Cloud auth is shared with the MCP server. Log in once with `smartchats login` and any tool reading `~/.smartchats-mcp/credentials.json` is authenticated too. ## Install See [Quick Start](/docs/quickstart). The short version: ```bash curl -fsSL https://smartchats.ai/install | bash ``` ## Top-level ```bash smartchats [options] ``` If you run `smartchats` with no arguments: - **No config and no source reachable** → runs `setup` (guided first run). - **Otherwise** → runs `start` on the last-used port. If the first argument is a flag other than `--help` or `--version`, the CLI routes to the legacy `launch` command (for older `npx smartchats --no-prompt` scripts). To check the installed version, run: ```bash smartchats --version # Output: 0.x.y ``` `-v` and `-V` work too. Use `smartchats help ` or `smartchats --help` for per-command details. ## Lifecycle ### `smartchats setup` Guided first run. Detects the install, checks the system, prompts for API keys, writes `.env`, and starts the stack. ```bash smartchats setup [options] ``` | Flag | Default | Notes | |---|---|---| | `--no-prompt` | off | Use existing env / `.env` values. Skip all prompts | | `--no-start` | off | Collect config only. Don't start the stack | | `--repo-path ` | `~/.smartchats/cli/source` | Where to clone the source (source and npm installs only) | What it walks through, in order: 1. **Install**: a binary install uses `~/.smartchats`. Otherwise it locates a source checkout (`$SMARTCHATS_HOME`, a directory walk, `config.smartchatsHome`) or clones one to `~/.smartchats/cli/source` (`$XDG_DATA_HOME/smartchats/cli/source` if that variable is set). 2. **System check**: free disk (warns below 3 GB). Source and npm installs also need `bun` and `surreal`, and setup stops with an install command if either is missing. 3. **API keys**: walks OpenAI, Anthropic, Google and Serper. For each, it shows what it found in your environment or `.env` and lets you keep or replace it. 4. **Write + start**: writes `.env` and runs `start`. The stack is up at [http://localhost:3000](http://localhost:3000) when the wizard exits. ### `smartchats env` Interactively configure provider API keys. Same prompt loop as the key step of `setup`, with no system check and no start. Use it to rotate keys or enable a provider later. ```bash smartchats env [options] ``` | Flag | Default | Notes | |---|---|---| | `-l, --list` | off | Show what's configured (masked). Don't prompt | It walks OpenAI, Anthropic, Google (Gemini) and Serper. For each one it shows what's already set and lets you keep it, replace it, or remove it. It writes to `~/.smartchats/.env` for a binary install (the same file you mount into Docker at `/root/.smartchats/.env`) or `/.env` for a source install. > **Note:** **`env` doesn't prompt for xAI, Together AI or Azure Speech.** Add `XAI_API_KEY`, `TOGETHER_API_KEY`, `AZURE_SPEECH_KEY` and `AZURE_SPEECH_REGION` to the `.env` file by hand. See [Required environment](/docs/self-host#required-environment). If the stack is running, restart afterwards to pick up the new values: ```bash smartchats restart # bare-metal install docker restart smartchats # docker install ``` To check what's configured without prompts, run: ```bash smartchats env --list # Output: # ✓ OpenAI sk-V…vGiE (env $OPENAI_API_KEY) # ✓ Anthropic (Claude) sk-a…iQAA (.env $ANTHROPIC_API_KEY) # ✓ Google (Gemini) AIza…Uvbg (env $GEMINI_API_KEY) # ✗ Serper (web search) not set (optional) ``` ### `smartchats start` Launches the local stack as detached child processes. Re-running while the stack is up and healthy prints the URL and exits. ```bash smartchats start [options] ``` | Flag | Default | Notes | |---|---|---| | `--port ` | 3000 | App port (browser connects here) | | `--surreal-port ` | 8000 | SurrealDB port (binds to `127.0.0.1`) | | `--data-dir ` | `~/.smartchats/data` | SurrealDB storage. The database is `/surreal.db` | | `--rebuild` | off | Source mode only: run the workspace build before starting. Ignored in a binary install | | `-f, --foreground` | off | Stay attached. Ctrl-C or SIGTERM stops both processes | | `--no-prompt` | off | Skip the "configure keys now?" prompt that appears when no provider key is found. Use in scripts, CI and Docker | The stack is two processes: `surreal` and `smartchats-server`. The server hosts the static SPA and the API at `/local-api/*` on the same port. It always binds to `0.0.0.0`. `start` reads `.env` from the install root (or repo root) and your shell environment. A shell variable wins over the same name in `.env`. When detached (the default), `start` returns 0 once both processes report ready. PIDs are written to `~/.smartchats/run/pids.json`. ### `smartchats stop` Stops the running stack. Sends SIGTERM, waits, then sends SIGKILL. ```bash smartchats stop [--timeout ] ``` | Flag | Default | Notes | |---|---|---| | `--timeout ` | 5000 | How long to wait after SIGTERM before SIGKILL | ### `smartchats restart` `stop`, then `start`. All `start` flags are forwarded. ```bash smartchats restart [start-options...] ``` ### `smartchats status` Shows what's running, on what ports, with what health. Exits 0 if the stack is up and serving, 1 otherwise. ```bash smartchats status [--json] ``` To check the stack, run: ```bash smartchats status # Output: # surreal pid ✓ 12345 port ✓ 8000 up 2m 14s # server pid ✓ 12346 port ✓ 3000 up 2m 14s # # ✔ Stack healthy. Open: http://localhost:3000 ``` `--json` prints `{ "up": ..., "procs": [...] }`, or `{ "up": false, "reason": "no-pid-file" }` when nothing is running. ### `smartchats logs` Tails per-process log files (`~/.smartchats/logs/surreal.log`, `~/.smartchats/logs/server.log`). ```bash smartchats logs [options] ``` | Flag | Default | Notes | |---|---|---| | `-f, --follow` | off | Stream new output (Ctrl-C to stop) | | `--proc ` | all | Which process(es) to show | | `-n, --lines ` | 50 | Initial line count | ### `smartchats dev` Hot-reload development stack. Runs `bin/devserve` in the smartchats source, so it needs a source checkout. ```bash smartchats dev [-- ] ``` Anything after `--` goes to `bin/devserve` unchanged. It has two target modes: | `--target` | Notes | |---|---| | `surreal` (default) | SurrealDB in a Docker container on port 8000, plus the local server and the Next.js dev server with hot reload | | `aio` | Runs `bin/aio`: the all-in-one Docker image. Add `--rebuild` to rebuild it | Other `devserve` flags: `--port=` (Next.js port, default 3000), `--no-server`, `--no-app`, `--no-prebuild`, `--no-surreal`, `--clean`. Run `bin/devserve --help` for details. ```bash smartchats dev -- --target=aio --rebuild ``` See [Contributing](/docs/contributing) for the development workflow. ### `smartchats upgrade` Upgrades a binary install by re-running the installer in place. Your data directory, `.env` and `config.json` are kept. ```bash smartchats upgrade [options] ``` | Flag | Default | Notes | |---|---|---| | `--version ` | `latest` | Install a specific release, for example `v0.4.0` | | `-y, --yes` | off | Skip the confirmation prompt | It runs `curl -fsSL https://smartchats.ai/install | bash -s -- --non-interactive --version --prefix `. Run `smartchats restart` afterwards to use the new binaries. For an npm install it prints `npm install -g smartchats-ai@latest` and exits 1. For a source checkout it tells you to `git pull` and exits 1. ## Devices The computer running SmartChats needs no pairing. A phone or another computer shows **Pair this device** and asks for a code. See [Security](/docs/local-server#security). ### `smartchats pair` Prints a 6-digit code for one device, valid 5 minutes. ```bash smartchats pair [--json] [--port ] # Pairing code: 482 913 (valid 5 min, one device) docker exec smartchats smartchats pair # Docker ``` `--json` prints `{"code": "482913", "expires_at": 1760000000000}`. ### `smartchats devices` ```bash smartchats devices [--json] # id, name, paired, last used smartchats devices revoke # the device must pair again ``` ## Cloud ### `smartchats login` Browser sign-in to the SmartChats cloud. Clears any cached token first, then caches the new credentials at `~/.smartchats-mcp/credentials.json`, the same file the MCP server reads. ```bash smartchats login ``` ### `smartchats logout` Clears cached cloud credentials. ```bash smartchats logout ``` ### `smartchats whoami` Shows the signed-in cloud user (uid, email, project, credentials file). If no credentials are cached, it starts the browser sign-in. ```bash smartchats whoami ``` ### `smartchats data` Moves user data between deployments as a JSON bundle. Use it for backups, debugging, and moving between local and cloud. ```bash smartchats data export [options] smartchats data import [options] ``` | Flag | Default | Notes | |---|---|---| | `--target=` | `local` | `cloud` uses your `smartchats login` credentials. `local` connects to `ws://localhost:8000/rpc` as root | | `--file=` | none | Bundle path. The first positional argument works too | | `--tables=t1,t2` | see below | Export: tables to export. Import: subset of the bundle's tables to write | | `--include-sensitive` | off | Export only: also export `byo_api_keys` and `usage_records` | | `--page-size=` | 100 | Export only: rows per page | | `--dry-run` | off | Import only: read and count rows, don't write | Each flag also accepts a space instead of `=` (for example `--target cloud`). Without `--tables`, export writes `logs`, `metrics`, `user_entities`, `user_relations`, `user_data`, `app_data`, `sessions`, `cortex`, `cortex_dynamic_functions`, `smartchats_apps` and `smartchats_app_installs`. Telemetry tables are never exported. The bundle file is written with `0600` permissions. Import upserts every row by id, so it's safe to re-run. Rows are written as the user of the target deployment. ```bash smartchats data export ~/backup.json --target=cloud smartchats data import ~/backup.json --target=local --dry-run smartchats data import ~/backup.json --target=cloud --tables=logs,metrics ``` > **Note:** **`--target=local` always uses port 8000.** If you started the stack with a different `--surreal-port`, the local target can't reach it. ## Meta ### `smartchats doctor` Environment health check. Diagnoses what's installed, what's running, and what's reachable. ```bash smartchats doctor [--port ] [--json] ``` | Flag | Default | Notes | |---|---|---| | `--port ` | 3000 | Port to probe for the running stack | | `--json` | off | Print the full result set as JSON | Checks (in order): 1. **SmartChats context**: where is the source root? 2. **bun installed**: critical. 3. **surreal installed**: critical. 4. **Free disk ≥ 3 GB**: warning only. 5. **Docker installed**: informational. 6. **Image `smartchats-aio:latest` present**: informational. 7. **Container `smartchats` running**: informational. 8. **`http://localhost:` responds**: critical when the `smartchats` container is running. 9. **Response is SmartChats**: sanity check (catches port collisions). 10. **LLM provider keys**: warning if no OpenAI, Anthropic or Google key is found. Exits 0 if every critical check passes, 1 otherwise. > **Warning:** **`doctor` looks for `bun` and `surreal` on `PATH`, even in a binary install.** A binary install doesn't need a separate `bun`, so this check can fail on a working install. `doctor` also reads `.env` from the source root only, so use `smartchats env --list` to check keys in a binary install. ### `smartchats home` Prints where smartchats lives and how the CLI found it. ```bash smartchats home [--path-only] ``` In a binary install it prints the install root and the server, surreal and SPA paths. `--path-only` prints just one path (the install root in a binary install, otherwise the source root), or exits 1 with no output if neither is found: ```bash cd "$(smartchats home --path-only)" ``` Source root resolution order: `$SMARTCHATS_HOME` → walking up from the current directory looking for `Dockerfile.aio` → `config.smartchatsHome` (written after an auto-clone). ### `smartchats help ` Per-command help. Same as ` --help`. ## Legacy ### `smartchats launch` Hidden from `--help` but still callable. Builds and runs the Docker all-in-one image (`smartchats-aio:latest`) from the smartchats source, cloning it first if needed. Use `smartchats start` for new installs. ```bash smartchats launch [options] ``` Flags: `--no-prompt`, `--rebuild`, `-d, --detached`, `--port `, `--data-dir `, `--tag `, `--test`, `--repo-path `. Run `smartchats launch --help` for details. ## Environment | Variable | Default | Purpose | |---|---|---| | `SMARTCHATS_HOME` | unset | Path to a smartchats source checkout. Overrides the directory walk and config fallback | | `SMARTCHATS_CONFIG_FILE` | unset | Full path to the CLI config file | | `XDG_CONFIG_HOME` | unset | If set, CLI config lives at `$XDG_CONFIG_HOME/smartchats/config.json` (else `~/.smartchats/config.json`) | | `XDG_DATA_HOME` | unset | If set, auto-clones go to `$XDG_DATA_HOME/smartchats/cli/source` | | `SMARTCHATS_REPO_URL` | `https://github.com/sheunaluko/smartchats.git` | Source URL for the auto-clone | | `SMARTCHATS_CREDENTIALS_FILE` | `~/.smartchats-mcp/credentials.json` | Cloud credentials location | | `SMARTCHATS_API_KEY` | unset | Bearer token the server accepts from scripts and agents on other machines ([Security](/docs/local-server#security)) | | `SMARTCHATS_AUTH` | `pair` | `off`: no pairing, every device gets in | | `SMARTCHATS_LOCAL_HOST` | `127.0.0.1` | Dev mode only: host the Next.js `/local-api/*` rewrite forwards to | | `SMARTCHATS_LOCAL_PORT` | `4242` | Dev mode only: port for that rewrite | | `NO_COLOR` | unset | Disables CLI color output | Provider keys are read from your environment and the `.env` file. A shell variable wins over `.env`. For each provider, the first name set wins, in this order: | Provider | Names | |---|---| | OpenAI | `SMARTCHATS_OPENAI_API_KEY`, `OPENAI_API_KEY` | | Anthropic | `SMARTCHATS_ANTHROPIC_API_KEY`, `ANTHROPIC_API_KEY` | | Google (Gemini) | `SMARTCHATS_GOOGLE_API_KEY`, `GOOGLE_API_KEY`, `GEMINI_API_KEY` | | xAI | `SMARTCHATS_XAI_API_KEY`, `XAI_API_KEY` | | Together AI | `SMARTCHATS_TOGETHER_API_KEY`, `TOGETHER_API_KEY`, `TOGETHERAI_API_KEY` | | Azure Speech | `SMARTCHATS_AZURE_SPEECH_KEY`, `AZURE_SPEECH_KEY` (plus `SMARTCHATS_AZURE_SPEECH_REGION`, `AZURE_SPEECH_REGION`) | | Serper | `SMARTCHATS_SERPER_API_KEY`, `SERPER_API_KEY` | The CLI prompts for the OpenAI, Anthropic, Google and Serper keys. The server reads all of them. See [Self-Hosting](/docs/self-host#environment-configuration) for server settings. ## Files | Path | Purpose | |---|---| | `~/.smartchats/bin/` | Installed binaries | | `~/.smartchats/app/out/` | Static SPA bundle | | `~/.smartchats/data/surreal.db` | SurrealDB rocksdb store | | `~/.smartchats/run/pids.json` | Live process state for the running stack | | `~/.smartchats/logs/{surreal,server}.log` | Per-process append logs | | `~/.smartchats/config.json` | CLI preferences (last port, resolved source root) | | `~/.smartchats/.env` | Provider keys and config, binary install | | `/.env` | Provider keys and config, source or npm install | | `~/.smartchats/cli/source/` | Default auto-clone location when no source is found | | `~/.smartchats-mcp/credentials.json` | Cloud credentials, shared with the MCP server | ## See also - **[Quick Start](/docs/quickstart)**: install and first run. - **[Self-Hosting](/docs/self-host)**: long-form deployment guide. - **[Contributing](/docs/contributing)**: modifying SmartChats itself. - **[MCP](/docs/mcp)**: expose smartchats to Claude Desktop and other MCP-aware tools via the shared credential store. --- Source: https://smartchats.ai/docs/architecture # Architecture This page gives an overview of the whole system first, then goes into the data layer, which most contributions touch. ## Request flow The browser app does most of the work: it runs the agent loop, the voice engine and the code sandbox. A server (the local server when you self-host, the hosted cloud otherwise) holds the API keys, calls the model providers, and owns the database and media storage. ``` browser: SmartChats app ┌───────────────────────────────────────────────┐ │ mic ─► tivi (VAD, speech recognition) │ │ │ │ │ ▼ │ │ vie (turn-taking) ── turn ready ──┐ │ │ ▼ │ │ cortex (agent loop) ◄── typed text │ │ │ │ │ ▼ │ │ SmartChatsBackend (local or cloud) │ └──────────┬────────────────────────────────────┘ │ HTTP, NDJSON stream back ▼ server: smartchats-local-server (/local-api/*) ┌───────────────────────────────────────────────┐ │ /llm/streamWithTTS │ │ 1. turn verdict (small model): done talking?│ │ INCOMPLETE ─► end response, keep │ │ listening │ │ 2. main model ─► llm-service providers │ │ 3. sentences ─► TTS ─► audio frames │ │ /data ─► SurrealDB │ │ /storage ─► S3 bucket (e.g. SeaweedFS) │ │ /tools ─► web search, URL fetch │ └───────────────────────────────────────────────┘ ``` ## The pieces ### The app and its shells `apps/smartchats` is a Next.js app built as a static single-page app. Its UI is a set of shells that share one store: the default desktop shell and a mobile shell, picked in settings, plus shells mounted on their own routes, `/sail` and `/voicelab`. Built-in mini apps (log, metrics and knowledge-graph explorers, todos and others) live in `app/apps/`. The app talks to its server only through the `SmartChatsBackend` interface described [below](#the-facade-pattern-in-practice). ### cortex: the agent loop `packages/cortex` is the agent framework. It composes the system prompt, calls the model through the backend, parses structured output, runs the functions and code the model asks for in a sandbox, and keeps workspace state across turns. It runs in the browser and in Node. Hosts inject the sandbox and the transport. It also holds the model catalog, presets and voices. ### llm-service: providers, streaming and the turn verdict `packages/llm-service` is the provider layer used by both servers. It normalizes OpenAI, Anthropic, Gemini, xAI and Together AI into one request and stream shape, with structured output, effort levels and bring-your-own keys. Text-to-speech adapters cover OpenAI and Azure. The combined LLM and TTS handler streams NDJSON: text deltas, then audio for each sentence as soon as it is ready. When a voice request carries a `verdict` block, the handler first makes one small model call that judges whether the user has finished speaking, and writes that verdict frame before any delta. On INCOMPLETE it ends the response without starting the main call. See [Providers](/docs/providers). ### tivi and vie: voice in and turn-taking `packages/tivi` provides the voice interface as React hooks: voice activity detection, browser speech recognition, the TTS playback queue and calibration. `packages/vie` is the voice engine and the default turn-taking engine. It decides when a turn has ended from VAD and recognizer signals, asks a judge (the server verdict by default, or the local Smart Turn model), and tunes its timing per device from logged evidence. The older engine stays behind the `voiceEngine: 'legacy'` setting. See [Voice engine](/docs/voice-engine). ### smartchats-local-server `packages/smartchats-local-server` is the self-hosted Express server. It serves the app and the API under `/local-api/*`: `health`, `llm`, `tts`, `embeddings`, `data`, `usage`, `keys`, `tools`, `insights` and `storage`. Data lives in SurrealDB. Media uploads go straight from the browser to an S3-compatible bucket through signed links, with a `media` table tracking each object. `smartchats start` runs SeaweedFS as that bucket and sets the `SMARTCHATS_S3_*` variables, or you point them at your own. See [Local server](/docs/local-server). ### smartchats-hub, the relay and the MCP bridge These let coding agents talk with you through the app. `smartchats-relay-cf` is the WebSocket relay between agents and your browser, partitioned by user. `smartchats-relay-protocol` defines its message envelope and `smartchats-relay-connect` handles connect, reconnect and re-auth. `smartchats-mcp-bridge` is an MCP server one agent runs to reach the relay. `smartchats-hub` is a per-machine daemon that holds one relay connection for every local agent, refreshes the token once, keeps a durable inbox per agent and serves MCP on `127.0.0.1`. See [Hub](/docs/hub) and [Relay](/docs/relay). ### smartchats-tools `packages/smartchats-tools` holds the agent's I/O helpers, such as web search through Serper and URL fetch with readable-text extraction. They are pure functions with no host dependencies, so the local server's `/tools/*` routes and the cloud use the same code. ### MCP `packages/smartchats-mcp` exposes the data layer to MCP clients over stdio. See [MCP Server](/docs/mcp). ## The data layer The data layer follows three rules: 1. **One contract for data access.** Every consumer (browser app, CLI, MCP server) talks to data through the same `DataAPI` interface. 2. **Two implementations.** Cloud (your smartchats.ai account) and local (your own install). Both implement the same contract. 3. **One home for each kind of change.** Query builders, multi-step operations and schema changes each have a set place, and consumers pick them up. A new feature is usually one small change in one place. If one feature touches five files, look for the existing home first. ### Layers ``` smartchats web app what users see │ ▼ getBackend().data.query(...) │ ┌────────────────────┴──────────────────┐ │ │ smartchats-cli smartchats-mcp other consumers │ │ └────────┬──────────────────────────────┘ ▼ operations.importBundle / exportBundle multi-step ops ▼ queries.X(args) → { query, variables } pure builders (no I/O) ▼ DataAPI.query contract (smartchats-backend) ╱ ╲ makeCloudDataAPI makeLocalDataAPI factories (smartchats-database) │ │ cloud-client createClient (hosted cloud) (your local SurrealDB) ``` | Layer | Responsibility | Lives in | |---|---|---| | Web app | Voice and chat UI, agent loop, KG views | `apps/smartchats` | | CLI | Setup, start and stop, login, data import and export | `packages/smartchats-cli` | | MCP | Data tools for MCP clients | `packages/smartchats-mcp` | | Operations | Multi-step work: bundle import and export | `packages/smartchats-database/src/operations/` | | Query builders | Pure functions: args → `{ query, variables }`, no I/O | `packages/smartchats-database/src/queries/` | | `DataAPI` contract | Two methods: `query()` and `healthCheck()` | `packages/smartchats-backend/src/types.ts` | | Cloud factory | Wraps `smartchats-cloud-client` and cloud auth | `packages/smartchats-database/src/data_api.ts` | | Local factory | Wraps the SDK-direct `createClient` | `packages/smartchats-database/src/data_api.ts` | ### Adding a query the agent can run Three files, about 20 lines. 1. **Add a builder** in `packages/smartchats-database/src/queries/your_topic.ts`: ```ts export function findRecentByTag(args: { tag: string; limit?: number }): QuerySpec { return { query: 'SELECT * FROM logs WHERE tags CONTAINSALL [$tag] ORDER BY ts DESC LIMIT $limit', variables: { tag: args.tag, limit: args.limit ?? 20 }, }; } ``` 2. **Expose it to MCP** in `packages/smartchats-mcp/src/tools.ts`: ```ts server.tool( 'find_recent_by_tag', 'Find recent logs tagged with a given label', { tag: z.string(), limit: z.number().optional() }, async (args) => runAndFormat(queries.findRecentByTag(args), handle), ); ``` 3. **Expose it to the CLI** as a subcommand in `packages/smartchats-cli/src/commands/`: ```ts const result = await handle.data.query(queries.findRecentByTag(args)); console.log(JSON.stringify(result.rows, null, 2)); ``` The same builder works against the cloud (after `smartchats login`) and against a local install. The builder has no transport-specific code. ### Adding a multi-step operation If the work is more than one query, for example looping over a bundle and writing each row with progress and error reporting, put it in `operations/`: ```ts // packages/smartchats-database/src/operations/your_op.ts import type { DataAPI } from 'smartchats-backend'; export async function yourOp(data: DataAPI, args: YourOpArgs): Promise { // loops, error aggregation, progress callbacks await data.query(...); } ``` The CLI and MCP then both call `operations.yourOp(handle.data, args)`. See `import_bundle.ts` and `export_bundle.ts` for real examples. ## The facade pattern in practice Two interfaces wrap the backends. ### 1. `SmartChatsBackend` (in the web app) The web app uses the full `SmartChatsBackend` interface from `packages/smartchats-backend/src/types.ts`. Besides `data` it has `llm`, `tts`, `embeddings`, `usage`, `keys`, `billing`, `tools`, `integrations`, `insights` and `storage`, plus `capabilities`, `health()` and `providers()`. ```ts // apps/smartchats/src/lib/backend_facade.ts const backend = getBackendInstance(); await backend.llm.streamWithTTS(...); // chat completion with speech await backend.tts.stream(...); // text-to-speech await backend.embeddings.embed(...); // embedding vectors await backend.data.query(...); // SurrealDB read/write await backend.storage.createUpload(...); // media upload links await backend.insights.emit(events); // telemetry const health = await backend.health(); // aggregate health probe const report = await backend.providers(); // which LLM / TTS providers can serve ``` `BackendFacadeProvider` puts the active backend into a React context. Features check `capabilities`: a self-hosted backend reports `billing: false`, so the credit chip hides, the billing page redirects and the bring-your-own-key UI shows instead. `storage` is false until a bucket is configured. `providers()` tells the model menu which models and voices have a key behind them. ### 2. `DataAPIHandle` (in the CLI and MCP) The CLI and MCP only need data, so they use the smaller `DataAPIHandle`: ```ts const handle = await makeDataAPI(target); // 'cloud' or 'local' await handle.data.query({ query, variables }); const uid = await handle.getUid(); await handle.close(); ``` It is the same `DataAPI` contract in a lighter wrapper. The factories (`makeCloudDataAPI`, `makeLocalDataAPI`) absorb the difference between stateless cloud calls and a stateful WebSocket connection. ## Open and closed Everything you need to use, extend, self-host or contribute is in the public repo: - **Contracts** (`smartchats-backend`): the typed interface every consumer codes against. - **Local stack** (`smartchats-backend-local`, `smartchats-local-server`, `smartchats-database`, `smartchats-cli`, `smartchats-mcp`): the install-and-run path. No cloud account needed. - **Cloud client** (`smartchats-cloud-client`): the client for the hosted cloud. It defaults to smartchats.ai. The code that runs the hosted service is closed. Everything in the public repository, every package included, is licensed under the Apache License 2.0 (copyright Sattvic Systems LLC). ## Single direct importer of `surrealdb` Only `packages/smartchats-database/src/client.ts` imports the `surrealdb` npm package in runtime code. Everything else goes through the `Client` interface it exports (`createClient`, `createLazyClient`, `createUserClient`, and the client's `signupAsUser` and `signinAsUser`). This made the v2 to v3 SDK migration a one-file change. SDK quirks (named exports, the `connect()` change, the removal of `queryRaw`, the `Table` wrapper for `insert()`, DateTime normalization) are handled inside `client.ts`. ## Auth and credentials There is one credential store, `~/.smartchats-mcp/credentials.json` (mode `0600`), which holds the cloud refresh token. The CLI, the MCP server, the bridge and the hub all use it. Run `smartchats login` once. The ID token lasts one hour. `smartchats-cloud-client` refreshes it on demand with the stored refresh token over plain `fetch`. The browser sign-in flow uses a localhost callback. ## Data model: event time and row lifecycle Every event-time table has two kinds of timestamp. Keep them apart. | Field(s) | What it means | Set by | Migrated? | |---|---|---|---| | `created_at` / `updated_at` | When the row was written in *this* database | DB (`VALUE time::now()` / `READONLY`) | **Never.** Stripped on import. | | `ts` / `local_date` / `local_tz` | When the thing the row describes happened in the user's life | App (`nowEventTime()` in `app/modules/system.ts`) | **Always.** Carried through export and import. | The event-time fields answer "when did it happen?": - `ts: datetime`: the UTC instant. Use it to sort and filter. - `local_date: string`: `YYYY-MM-DD` in the user's time zone at the time, so `GROUP BY local_date` gives correct daily totals with no time zone logic in the query. - `local_tz: string`: the IANA zone (for example `America/Chicago`), so the local time of day can be rebuilt later. Bundles import without `created_at` and `updated_at`, so the destination database stamps fresh values while the event-time fields keep the original timeline. Without this split, a year-old bundle would look "created today" and sort wrongly. Schema change history is in `HISTORY.md` at the repo root. ## Verification The layered test runner is `packages/smartchats-test`. Each level runs in order and stops at the first failure unless you pass `--continue-on-failure`. | Level | Name | What it runs | |---|---|---| | L0 | `lint` | `turbo run lint` across the workspace | | L1 | `build` | `turbo run build`, which type-checks every package | | L2 | `unit` | `npm run test:unit` in each package that defines it | | L3 | `integration` | Integration suite against an in-memory SurrealDB the level starts and cleans up | | L4 | `e2e` | The Playwright Simi suite (`apps/smartchats/tests/e2e/simi.spec.ts`). Needs a running stack | ```bash npx smartchats-test # every level that needs no running stack (L0-L3) npx smartchats-test quick # lint + build npx smartchats-test build,unit # chosen levels npx smartchats-test --list # list levels ``` For e2e, use `bin/test-e2e`. It starts the no-Docker stack with `bin/test-bun-deploy`, waits for `/local-api/health`, runs `smartchats-test e2e`, and shuts the stack down. ```bash bin/test-e2e # full Simi suite bin/test-e2e --pick # choose workflows interactively bin/test-e2e -- --grep agent_input # pass flags through to Playwright ``` See the [Verification Workflow in CONTRIBUTING.md](https://github.com/sheunaluko/smartchats/blob/main/CONTRIBUTING.md#verification-workflow) for which level fits which change. ## See also - [Voice engine](/docs/voice-engine): vie, the turn judge and tuning. - [Providers](/docs/providers): model and speech providers and keys. - [Local server](/docs/local-server): routes, SurrealDB and storage. - [Hub](/docs/hub) and [Relay](/docs/relay): agent messaging. - [MCP Server](/docs/mcp): the data tools for MCP clients. - [CLI Reference](/docs/cli): every subcommand. - [Contributing](/docs/contributing): conventions and the dev workflow. --- Source: https://smartchats.ai/docs/voice-engine # Voice engine In a voice session, SmartChats has to decide when you have finished speaking and your words should go to the agent. The part that makes this decision is **vie**, the voice interface engine (`packages/vie`). It is the default engine. The older pipeline stays available as `voiceEngine: 'legacy'`. This page explains how vie works, what you can change, and where to look when a turn ends too early or too late. The full design is in the [vie spec](https://github.com/sheunaluko/smartchats/blob/main/packages/vie/SPEC.md). ## What the engine does vie reads four kinds of signal: - **Audio power**: is the room loud enough to start the speech recognizer? - **The speech detector** (VAD): when speech starts and stops. - **The browser speech recognizer**: the text, updated word by word. - **A judge**: does the text so far sound finished? From these it produces a fixed chain of events for each turn: ``` listen.wanted → speech.ended → transcript.settled → turn.sent → turn.ended → turn.shipped → (turn.amended) ``` | Event | Meaning | Decided by | |---|---|---| | `listen.wanted` | Sound is loud enough to start the recognizer | Audio power vs `powerThreshold` | | `speech.ended` | You paused | The speech detector, after `tailMs` of silence | | `transcript.settled` | The words have caught up | No word-changing update for `settleMs` after `speech.ended` | | `turn.sent` | The text left for the agent | At the settle for the server judge, at COMPLETE for the others | | `turn.ended` / `turn.shipped` | You are done and the turn is final | The judge | | `turn.amended` | Words arrived for a turn already sent | The recognizer delivering late | Around that chain, the turn lifecycle adds four more events: | Event | Meaning | |---|---| | `turn.held` | The judge said INCOMPLETE. The turn waits for more words. | | `turn.merged` | A held turn shipped with the new words added to it, as one message. | | `turn.forced` | A held turn shipped as it was, after silence or a commit keyword. | | `turn.reopened` | New speech reopened a turn that had already been sent. | Only one thing is ever undone. If speech starts again while vie waits for the settle, `speech.ended` is cancelled and the turn carries on as one. The turn states look like this: ``` listening ─ vad.end ─► ended ─ settleMs quiet ─► judging ─ COMPLETE ───► shipped ─ reply done ─► idle ▲ │ │ │ vad.start ────┘ (the one retraction) ├─ INCOMPLETE ─► held ─ silence / keyword ─► shipped (as is) │ │ │ └──────────── continuation ◄────────────────────┴─ abort ─► held ─┘ new words ─► merged into the turn ``` ## The three judges The speech detector and the settle only know that you paused. The **judge** decides whether the pause ended your thought. Choose one in Settings under "Who decides you're done talking" (tivi setting `vieJudge`). | Judge | Setting | How it decides | Use it when | |---|---|---|---| | Server verdict | `server` (default) | A small LLM call on the server reads your words before the main reply | You have a key for the verdict model. This is the most accurate option. | | Smart Turn | `smart_turn` | An audio model scores the end of your speech in the browser | You want no extra server call. It struggles on some voices (see the spec, section 7). | | None | `none` | Silence alone ends the turn | You speak in short, complete commands, or you want the lowest latency. | Every judge goes through the same `judging` state. They differ only in how they answer: - **Server**: vie sends the text with a verdict request at the settle (`turn.sent`). The verdict arrives before any reply text, usually 0.3 to 0.5 s later. If it never arrives, the turn is confirmed after 3 s. - **Smart Turn**: vie scores the audio. A probability at or above `threshold` is COMPLETE. Past `judgeTimeoutMs` (1.4 s) the turn ships anyway. - **None**: COMPLETE at once. Words that go to a tool waiting for input (dictation, voice memo) always use no judge, because they make no model call for the server to judge. ## The server turn verdict The server judge is one small LLM call made before the main reply. It answers COMPLETE (the reply proceeds) or INCOMPLETE (the turn is held). The code is in `packages/llm-service/src/streaming/verdict.ts`, and [TURN_VERDICT.md](https://github.com/sheunaluko/smartchats/blob/main/packages/llm-service/TURN_VERDICT.md) has the measurements. The app asks for a verdict only when all of these hold: - the call comes from a running voice session - the voice engine is vie and the judge is `server` - a verdict model is set (`verdictModel`, default `deepseek-v4.1-flash`) ### Judge v2 The app sends `judge: 'v2'`. Under judge v2: - **The score** is P(COMPLETE), the model's own probability that its one-word answer is COMPLETE. It is read from the first output token's probabilities, not written by the model. - **The threshold** defaults to 0.5. The server holds the turn when the score is below it. - **The input** is the last 2 conversational turns, quoted as a transcript ("User: … / Assistant: …") in one message. The judge reads the exchange as a third party. This stops a reply like "go ahead, I'm listening" from reading as an invitation to keep talking. Scores pile up at both ends. Fewer than 3% fall between 0.1 and 0.9, so the threshold matters little in practice. There is no tuner for it. > **Note:** **Failures never cut you off.** A verdict that fails, cannot be parsed or passes its cap (`verdictCapMs`, default 800 ms) counts as COMPLETE. The only failure you can hear is a false hold: the app waiting for more after you finished. ### Holds and merges When the verdict is INCOMPLETE: 1. The server ends the response without making the main model call. 2. The app plays the keep-going cue. This is a short tone, or a spoken "ok" / "mhm" when acks are on (`enableAcks`). 3. Whatever you say next is merged with the held text and sent as one message (`turn.merged`). 4. If you say nothing for `holdSilenceMs` (3 s), or say a commit keyword ("done", "finished", "listo", "terminado"), the held text ships as it is (`turn.forced`). A forced turn carries no verdict. ### Providers Judge v2 needs a provider that returns token probabilities. Today that is Together, so the default verdict model needs a Together key. - With a Together key, v2 decides from P(COMPLETE). - With a verdict model from another provider, v2 decides from the sampled word and the threshold has no effect. - With no key for the verdict model, the local server skips the verdict and answers the turn, as if the verdict had said COMPLETE. See [Providers & API keys](/docs/providers) for how to set the key. ## Parameters All timing lives in `packages/vie/src/core/params.ts`. | Parameter | Default | Bounds | How it is set | |---|---|---|---| | `powerThreshold` | 0.01 | | Manual | | `tailMs` | 200 ms | 150 to 1500 | Fixed. The speech detector's debounce. Never tuned, can be set by hand. | | `settleMs` | 400 ms | 100 to 1200 | Adapts both ways, every turn | | `prerollMs` | 0 ms | 0 to 400 | Ratchets up when a first word is clipped | | `threshold` | 0.15 | 0.05 to 0.6 | Smart Turn's COMPLETE threshold. Ratchets down on a wrong hold. | | `breathMs` | 300 ms | | Manual | | `continuationMs` | 1000 ms | | Manual. Speech within this of a ship belongs to that turn. | | `holdSilenceMs` | 3000 ms | | Fixed. A held turn ships after this much silence. | | `judgeTimeoutMs` | 1400 ms | | Fixed. Smart Turn only. | ### How tuning works vie learns its timing on each device from what happens in real turns: - **Settle eases down and jumps up.** On every settle, `settleMs` moves 25% of the way toward the largest gap between word updates in that settle window. These moves are quiet. When a word lands late after a ship, `settleMs` jumps straight to that word's gap. On a clean device settle drifts down toward the recognizer's real gaps, jumps when a word arrives late, then eases down again. - **The tail is fixed** at 200 ms. Deciding whether a pause ended your thought is the settle's and the judge's job. - **Pre-roll and the Smart Turn threshold ratchet.** Each moves at most one step per turn, and only in its safe direction: pre-roll up after a clipped first word, the threshold down after a wrong hold. They do not come back down on their own. - **Pins.** A pinned parameter never moves. Moving a slider in Settings pins it. - **Per device.** Parameters, pins, the evidence ledger and the tuner history are stored in the browser's localStorage under `vie.device.v1`. A phone and a laptop each learn their own values. > **Warning:** **Calibration pins settle.** Running calibration on `/voicelab` pins `settleMs` at the end of its descent, which stops it adapting. Unpin it if you want the per-turn easing back. ## Continuations, late words and amends Two things can happen after a turn has settled. vie tells them apart by whether the speech detector heard new speech. **Late words.** A word update that arrives after the settle **with no new speech start** is the recognizer finishing what it already heard. It belongs to the same utterance, in any phase: - While the judge runs, the words are added to the turn. For the server judge the request is sent again with the full text. - While the turn is held, they are added to the held text. - After the ship, the turn is amended (`turn.amended`). Before the reply's first token, the app cancels the reply and sends one complete message in place of the old one. After that, the words are handed to the agent as late words. A recognizer revision is not late words. If the recognizer only repeats the end of what was already sent (for example by dropping a word), the sent text is kept and only the new words after it count. **Continuations.** New speech after the settle, a speech start followed by its first word, is a continuation. vie acts at the first word, not the bare speech start, because coughs and background talk often start the detector without producing words. | When the first word arrives | What happens | |---|---| | The assistant is speaking | Barge-in: the audio stops at once, then cancel and add (below) | | The judge or model is running, nothing heard yet, no tool has run | **Abort and hold.** The run is stopped, the agent never sees it, and the held text plus the new words go as one message. | | A tool already ran in this turn | **Cancel and add.** The new words go as their own message after a cancel note, so a side effect is not repeated. | | A tool is waiting for input | The words go to the tool | | The reply is finished | A normal new turn | ## Settings you can change These are tivi settings (`packages/tivi/src/lib/settings.ts`), set in the Settings panel: | Setting | Default | What it does | |---|---|---| | `voiceEngine` | `vie` | `legacy` switches back to the old pipeline | | `vieJudge` | `server` | `server`, `smart_turn` or `none` | | `verdictModel` | `deepseek-v4.1-flash` | The server verdict model | | `verdictEffort` | `none` | Reasoning effort for the verdict call. `none` turns thinking off. | | `verdictCapMs` | 800 | Longest the server waits for the verdict before going ahead as COMPLETE | | `enableAcks` | `false` | Speak "ok" / "mhm" on a hold in place of the tone | The engine choice and the verdict settings are only in the Settings panel. The agent can change the rest by voice. Ask it, for example, "turn on backchannel acks" or "pin the settle time at 500 ms". Its tools: - `set_voice_setting` and `reset_voice_settings` for settings such as acks, pre-roll and playback speed - `get_voice_tuning` for current parameters, pins, recent tuner moves and device alerts - `pin_voice_setting`, `unpin_voice_setting`, `reset_voice_tuning` and `set_auto_tune` for vie's learned parameters - `report_speech_cutoff` when a transcript lost its first word (this raises pre-roll). A lost last word is recorded but does not move settle. `/voicelab` shows the engine's layers live and runs calibration. ## Telemetry vie writes these events to insights: | Event | What to look at | |---|---| | `voice_turn` | One row per turn: `params.settle_ms`, `params.tail_ms`, `settle_max_gap_ms`, `settle_update_offsets_ms` | | `vie_event` | Each event in the chain. `transcript.settled` carries `max_gap_ms`, `turn.amended` carries `gap_ms`, `turn.reopened` carries `phase` and `held_by` | | `vie_evidence` | Each evidence row, including `verdict.wrong_hold` (a hold ended by silence or a keyword) and `verdict.cut_off` (speech soon after a COMPLETE), with the score and threshold | | `voice_tuning_changed` | Each parameter move. `quiet: true` marks routine settle easing | | `turn_verdict` | Each server verdict as vie received it | | `smart_turn_decision` | Each Smart Turn score | A healthy device shows settle easing down, an occasional jump on a late word, and a low rate of late words. Each late word cancels a reply in progress. ## See also - [vie spec](https://github.com/sheunaluko/smartchats/blob/main/packages/vie/SPEC.md): states, tuning rules, invariants and open items - [TURN_VERDICT.md](https://github.com/sheunaluko/smartchats/blob/main/packages/llm-service/TURN_VERDICT.md): the server verdict, judge v2 and the replay script for measuring a prompt or model change - [Providers & API keys](/docs/providers) - [tivi](/docs/packages/tivi) --- Source: https://smartchats.ai/docs/local-server # Local server reference `smartchats-local-server` is the backend of a self-hosted SmartChats. It is an Express app that serves the API under `/local-api/*` and, when `SMARTCHATS_STATIC_DIR` is set, the static app at `/`. It stores data in SurrealDB, calls LLM and TTS providers with your own keys, and signs upload links for an S3-compatible bucket. `smartchats start` launches it for you. This page covers what it does underneath. ## How `smartchats start` runs it `start` launches up to three detached processes and records them in `~/.smartchats/run/pids.json`: 1. **SurrealDB** on `127.0.0.1:8000`, user `root`, password `root`, with RocksDB storage at `~/.smartchats/data/surreal.db`. `start` waits up to 30 s for its `/health`. 2. **SeaweedFS** for media storage on `127.0.0.1:8333` (see [Storage](#storage)). It is optional: if it is missing or doesn't come up within 30 s, the stack runs without media. 3. **The local server** on port 3000, bound to `0.0.0.0`, pointed at that SurrealDB (`ns=smartchats`, `db=main`), serving the bundled app. `start` waits up to 30 s for `/local-api/health`. The server's environment is your `.env` merged with the shell's environment. The shell wins, so `KEY=value smartchats start` overrides one launch. On boot the server connects to SurrealDB and applies the schema, and exits if either fails. | Flag | Default | Notes | |---|---|---| | `--port` | `3000` | App and API port | | `--surreal-port` | `8000` | SurrealDB port, loopback only | | `--data-dir` | `~/.smartchats/data` | SurrealDB storage, and media under `seaweedfs/` | | `--rebuild` | off | Run `turbo run build` before starting | | `-f`, `--foreground` | off | Stay attached. Ctrl-C stops the stack | | `--no-prompt` | off | Skip the "configure keys?" prompt when no keys are found | ## Logs | Process | Path | |---|---| | SurrealDB | `~/.smartchats/logs/surreal.log` | | SeaweedFS (media) | `~/.smartchats/logs/seaweedfs.log` | | Local server | `~/.smartchats/logs/server.log` | ```bash smartchats logs [-f] [--proc surreal|seaweedfs|server|all] [-n 50] ``` The server logs one line per request (`METHOD url -> status ms`), plus per-route lines for LLM streams with model, token counts and cost. ## Environment | Variable | Default | Notes | |---|---|---| | `SMARTCHATS_PORT` | `4242` | `smartchats start` sets `3000` | | `SMARTCHATS_HOST` | `127.0.0.1` | `smartchats start` sets `0.0.0.0` | | `SMARTCHATS_API_KEY` | unset | Bearer token for scripts and agents on other machines (see [Security](#security)) | | `SMARTCHATS_AUTH` | `pair` | `off` lets every device in without pairing | | `SMARTCHATS_DATA_DIR` | unset | Where `devices.json` (paired devices) is kept; `smartchats start` sets the data directory. Unset means `~/.smartchats` | | `SMARTCHATS_STATIC_DIR` | unset | Directory of the static app export to serve at `/`. Unset means API only | | `SURREAL_URL` | `ws://127.0.0.1:8000/rpc` | | | `SURREAL_NS` | `smartchats` | | | `SURREAL_DB` | `main` | | | `SURREAL_USER` | `root` | | | `SURREAL_PASSWORD` | `root` | | | `SMARTCHATS_OPENAI_API_KEY` | then `OPENAI_API_KEY` | Chat, embeddings, OpenAI TTS | | `SMARTCHATS_ANTHROPIC_API_KEY` | then `ANTHROPIC_API_KEY` | | | `SMARTCHATS_GOOGLE_API_KEY` | then `GOOGLE_API_KEY`, `GEMINI_API_KEY` | Gemini | | `SMARTCHATS_XAI_API_KEY` | then `XAI_API_KEY` | | | `SMARTCHATS_TOGETHER_API_KEY` | then `TOGETHER_API_KEY`, `TOGETHERAI_API_KEY` | | | `SMARTCHATS_AZURE_SPEECH_KEY` | then `AZURE_SPEECH_KEY` | Azure TTS | | `SMARTCHATS_AZURE_SPEECH_REGION` | then `AZURE_SPEECH_REGION` | Azure TTS | | `SMARTCHATS_SERPER_API_KEY` | then `SERPER_API_KEY` | Web search | | `SMARTCHATS_S3_ENDPOINT` | set by `start` (SeaweedFS) | Media bucket. All four S3 values are needed to enable storage | | `SMARTCHATS_S3_ACCESS_KEY_ID` | unset | | | `SMARTCHATS_S3_SECRET_ACCESS_KEY` | unset | | | `SMARTCHATS_S3_BUCKET` | unset | | | `SMARTCHATS_S3_REGION` | `us-east-1` | Signing region | | `SMARTCHATS_S3_LINKS` | `server` | `direct` hands browsers presigned bucket URLs (see Storage) | Provider keys resolve in this order: the `SMARTCHATS_` variable, the plain variable, a key saved in the database with `POST /keys`, then none. ## Routes All routes are under `/local-api`. JSON request bodies are limited to 10 MB. | Route | Notes | |---|---| | `GET /health` | Readiness. `{ ok, checks: { data, providers, tts } }`. 503 only when SurrealDB is unreachable. Missing keys are reported but don't fail it | | `POST /llm/stream` | Text-only NDJSON stream | | `POST /llm/streamWithTTS` | LLM plus TTS NDJSON stream, with the optional turn verdict | | `POST /tts/stream` | NDJSON TTS stream, PCM16 24 kHz mono | | `POST /embeddings/embed` | OpenAI `text-embedding-3-small` | | `POST /data/query` | `{ query, variables? }` runs SurrealQL. Returns `{ statements: [{ status, result, time }] }` | | `GET /data/health` | Per-table check of the required tables | | `GET /usage/records` | Paginated usage rows | | `GET /usage/summary?since=` | Aggregate for a time window | | `GET /keys/providers` | Which LLM and TTS providers are available | | `GET /keys` | Masked previews of keys saved in the database (env keys are not shown) | | `POST /keys` | `{ keys: { openai?, anthropic?, google?, xai?, together?, azure? } }`. Warns when an env var shadows a saved key | | `DELETE /keys/:provider` | Remove a saved key | | `POST /tools/search` | Serper web search. Needs a Serper key | | `POST /tools/fetchUrl` | Fetch a page and extract readable text | | `POST /insights/batch` | Telemetry events, stored as sent in `insights_events` | | `POST /storage/createUpload` | Start an upload and get its upload links | | `POST /storage/resumeUpload` | Resume a multipart upload | | `POST /storage/completeUpload` | Finish an upload | | `POST /storage/getMediaUrl` | Fresh download link | | `POST /storage/listMedia` | List files | | `POST /storage/deleteMedia` | Delete a file | Self-hosted mode has no billing. Usage rows are written for every LLM, TTS, embedding and tool call with `creditsCharged: 0`. ### `POST /llm/stream` ```json { "model": "gpt-5.5", "input": [{ "role": "user", "content": "Hello" }], "max_tokens": 16384, "temperature": 0.7, "effort": "low", "stop": ["\n\n"], "session_id": "optional, recorded on the usage row" } ``` `model` and a non-empty `input` are required. `text_format` (`{ type: "json_schema", name, strict, schema }`) asks for structured output. `max_tokens` defaults to 16384 and is capped at 32768. `{ "warmup": true }` returns at once without calling a model. An unknown model or a missing key for its provider is a 400 before the stream starts. The response is NDJSON, one object per line: ```json {"t":"delta","d":"Hel"} {"t":"delta","d":"lo!"} {"t":"done","data":{"success":true,"output_text":"Hello!","usage":{"input_tokens":9,"output_tokens":3},"model":"gpt-5.5","provider":"openai","finish_reason":"stop","latency_ms":640}} ``` A failure mid-stream writes `{"t":"error","error":"..."}`. ### `POST /llm/streamWithTTS` Same body as `/llm/stream`, plus TTS fields and an optional `verdict` block: ```json { "model": "gpt-5.5", "input": [{ "role": "user", "content": "so I was thinking about" }], "tts": true, "tts_provider": "azure", "tts_voice": "en-US-AvaMultilingualNeural", "tts_speed": 1, "tts_instructions": "warm, brief", "verdict": { "model": "deepseek-v4.1-flash", "effort": "none", "cap_ms": 800, "judge": "v2", "threshold": 0.5 } } ``` The verdict is a small model call that runs before the main model on voice turns and decides whether the user finished speaking. Only `verdict.model` is required. `judge` is `v1` (the default) or `v2`, and `threshold` (0 to 1, default 0.5) applies to `v2`. When a verdict runs, its frame comes first: ```json {"t":"verdict","complete":false,"source":"model","model":"deepseek-v4.1-flash","cap_ms":800,"ms":{"queue":2,"model":310,"total":312},"utterance_words":5,"context_turns":0} ``` `source` is `model`, `cap` (timed out, treated as complete), `error`, or `skipped`. If `complete` is false the turn ends there: no main call is made, and a `done` frame follows with `finish_reason: "held"`. Otherwise the stream continues: | Frame | Fields | |---|---| | `text` | `d`: a text delta | | `audio_start` | `s` chunk index, `text`, `ms` | | `audio` | `s`, `c` sequence, `b64` PCM16 24 kHz mono | | `audio_end` | `s`, `ms` | | `audio_error` | `s`, `error`. The text stream is unaffected | | `server_timing` | Timing phases | | `llm_done` | The full text and usage, before trailing audio finishes | | `done` | Final stats, with `verdict` when one ran | | `error` | LLM stream failure | > **Note:** **A missing verdict key doesn't fail the turn.** If the server has no key for the verdict model's provider, it logs `no key for verdict model ...` and runs the turn without a verdict. A missing key for the main model is still a 400. ## Storage Photos, videos and files go to an S3-compatible bucket. `smartchats start` runs one for you: SeaweedFS (`weed mini`, bundled with binary installs) on `127.0.0.1:8333`, with its data and generated credentials under `/seaweedfs` (`media/`, `seaweedfs.env`, `s3.json`, all 0600). The server gets the `SMARTCHATS_S3_*` values from `start`. `smartchats status` and `smartchats logs --proc seaweedfs` show it. Only the server talks to the bucket. Upload and download links point at the server itself (`/local-api/storage/blob/`), and it streams the bytes to and from the bucket. So media works from any device that can open the app (a phone on your network, a container's published port, a reverse proxy) with nothing else exposed. Each link is signed (HMAC, from the bucket secret) for one operation on one file and expires within minutes, like a presigned URL. That is why these routes don't need the bearer token: an `` can't send one. | Env | Default | Notes | |---|---|---| | `SMARTCHATS_S3_PORT` | `8333` | S3 port (loopback) | | `SMARTCHATS_S3_LINKS` | `server` | `direct`: hand browsers presigned bucket URLs instead, so bytes skip the server. The bucket must then be reachable from the browser | | `SMARTCHATS_S3_BIND` | `127.0.0.1` | Direct links only: address SeaweedFS listens on, e.g. `0.0.0.0` | | `SMARTCHATS_S3_PUBLIC_ENDPOINT` | the endpoint | Direct links only: address browsers reach the bucket at, e.g. `http://192.168.1.20:8333`. Links are signed for it | | `SMARTCHATS_S3_ALLOWED_ORIGINS` | `http://localhost:`, `http://127.0.0.1:` | Direct links only: extra app origins the bucket allows by CORS, comma separated | | `SMARTCHATS_WEED_BIN` | bundled `weed`, else `weed` on PATH | | | `SMARTCHATS_STORAGE` | on | `off` runs the stack without media storage | To use your own bucket instead (S3, R2, MinIO), set `SMARTCHATS_S3_ENDPOINT`, `SMARTCHATS_S3_ACCESS_KEY_ID`, `SMARTCHATS_S3_SECRET_ACCESS_KEY` and `SMARTCHATS_S3_BUCKET`. `start` then skips SeaweedFS. The server streams through to it the same way; add `SMARTCHATS_S3_LINKS=direct` to send browsers to the bucket instead (configure the bucket's CORS for the app's origin). Without a bucket the storage routes answer 501 `not_supported`. From a clone, `bin/seaweedfs` runs the same local bucket for development, and `bin/devserve` picks it up. Storage errors return `{ error, code }`: 404 `not_found`, 400 `invalid_argument`, 409 `failed_precondition`, 501 `not_supported`. ## Security `smartchats start` binds the server to `0.0.0.0` so a phone on your network can use it. Who may call `/local-api`: | Caller | How | |---|---| | The computer running SmartChats | Trusted, nothing to set up: the browser at `http://localhost:3000`, the CLI, MCP servers, scripts | | A phone or another computer | Pairs once. It sees a **Pair this device** screen and enters a 6-digit code from `smartchats pair` (or Settings → Devices). It then keeps a cookie until you revoke it (`smartchats devices`, or Settings → Devices) | | A script or agent on another machine | `Authorization: Bearer $SMARTCHATS_API_KEY`, when that variable is set | | Anyone, with `SMARTCHATS_AUTH=off` | No checks, as before 0.4.0 | Everything else gets `401 {"error": "unauthorized", "code": "pairing_required"}`. The page itself (`/app`) and signed media links stay public: they hold no data. What counts as this machine: the connection comes from a loopback address, the `Host` is `localhost`, `127.0.0.1` or `[::1]`, any `Origin` is one of those too, and any `X-Forwarded-For` / `Forwarded` addresses are loopback. So a website whose domain resolves to `127.0.0.1` (DNS rebinding), or a page on another site calling `localhost:3000`, is not trusted. > **Warning:** **A proxy or tunnel on the same machine must forward the visitor's `Host` or address.** With neither, every visitor looks like this machine. The [nginx example](/docs/self-host#running-on-a-vps) sets `Host $host` and `X-Forwarded-For`; Caddy forwards both by default. Pairing codes are 6 digits, exist only after you ask for one (or once at startup while nothing is paired, for Docker), last 5 minutes (15 at startup), serve one device, and die after 5 wrong tries. A device's token is 32 random bytes in an `HttpOnly`, `SameSite=Strict` cookie; `/devices.json` (0600) stores only its SHA-256. | Route | Open to | Does | |---|---|---| | `GET /auth/status` | anyone | `{mode, authenticated, via, device?}` | | `POST /auth/pair` | anyone | `{code, name?}` → sets the device cookie | | `POST /auth/pair/start` | trusted | `{code, expires_at}` for another device | | `GET /auth/devices` | trusted | Paired devices | | `POST /auth/devices/revoke` | trusted | `{id}` | | `POST /auth/logout` | trusted | Unpairs the calling device | - SurrealDB binds to `127.0.0.1` with `root`/`root` credentials and SeaweedFS to `127.0.0.1`. Don't expose ports 8000 or 8333. - On a public server, keep the reverse proxy with TLS in front, and block direct access to port 3000. ## See also - [Self-Hosting](/docs/self-host) - [CLI](/docs/cli) - [Architecture](/docs/architecture) --- Source: https://smartchats.ai/docs/hub # Hub `smartchats-hub` is a local daemon that lets coding agents on your computer (Claude Code, Codex, opencode, or any other process) talk to you through SmartChats and to each other. Run one per computer. ``` SmartChats (browser, phone) <-> relay (public) <-> hub (127.0.0.1) <-> agents (local processes) | dashboard (127.0.0.1) ``` The hub is the only process on the machine that talks to the relay and refreshes your sign-in token. Each agent gets a durable inbox in SQLite, so messages sent while an agent is busy wait until it reads them. Agents reach the hub over MCP (streamable HTTP), plain HTTP, or the `smartchats-hub` CLI. > **Note:** **Hub or bridge.** On a machine without a hub, an agent can still reach SmartChats through [`smartchats-mcp-bridge`](/docs/relay). The hub keeps the same tool names, so prompts written for the bridge keep working. ## Install The hub ships in the repo as `packages/smartchats-hub`. It needs Node 24 or later (it uses the built-in `node:sqlite`). ```bash git clone https://github.com/sheunaluko/smartchats.git cd smartchats npm install npm run build cd packages/smartchats-hub && npm link # puts smartchats-hub on PATH ``` Remote access through SmartChats needs a SmartChats cloud sign-in. The hub reads the same credentials file as the CLI and the MCP server (`~/.smartchats-mcp/credentials.json`): ```bash smartchats login ``` ## Start, stop, status ```bash smartchats-hub start [--port N] [--relay URL] # foreground, Ctrl-C stops it smartchats-hub stop # SIGTERM the running hub and wait smartchats-hub status [--json] # pid, url, agents, control session ``` `status` lists each agent with online or offline, unacked count, watcher health, work state, and `NOT LISTENING` when nothing will wake it. It exits 3 when the hub is not running. To run the hub at login on macOS, generate a launchd plist and load it yourself: ```bash smartchats-hub plist --out ~/Library/LaunchAgents/ai.smartchats.hub.plist launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/ai.smartchats.hub.plist # remove: launchctl bootout gui/$(id -u)/ai.smartchats.hub ``` The plist runs `start` with `RunAtLoad` and `KeepAlive`. Output goes to `hub.log` in the hub home. | Env | Default | Notes | |---|---|---| | `SMARTCHATS_HUB_HOME` | `~/.smartchats/hub` | Hub state directory (0700). `--home DIR` overrides it on any command | | `SMARTCHATS_HUB_PORT` | `47821` | Fixed so agent MCP configs survive restarts | | `SMARTCHATS_RELAY` | `wss://relay.kind.computer` | Relay endpoint | | `SMARTCHATS_CREDENTIALS_FILE` | `~/.smartchats-mcp/credentials.json` | Firebase refresh token | | `SMARTCHATS_CLOUD_DEV_TOKEN` | unset | Skip Firebase auth, for a local dev relay | ## Two ways onto the hub **Join** puts an agent session that is already running on the hub. Nothing is launched and the session keeps its own permissions. Tell any agent "join the hub as builder" and it can follow the printed join sheet. ```bash smartchats-hub join --label builder [--kind claude-code] [--model M] [--pid N] [--replace] [--json] smartchats-hub leave ``` `join` records the agent's pid and kind (the nearest `claude`, `codex` or `opencode` process above the shell), writes `agents/.mcp.json` (0600), and prints the join sheet: agent id, how to get woken, the commands to read, ack and send, and the rules. Labels are unique on a hub and matched case-insensitively. If the holder of a label is offline, a new `join` takes it over with the same agent id and inbox and a new token. If the holder is alive, `join` refuses with `label_in_use` unless you pass `--replace`. **Spawn** registers an agent, applies a permission profile, and launches its CLI in a new zellij tab. The hub owns its lifecycle. ```bash smartchats-hub spawn --label L [--target claude-code|codex|opencode | --launch 'CMD'] \ [--folder NAME | --path DIR] [--permissions trusted|ask] [--print] [-- ARGS...] smartchats-hub agents [--json] [--prune] # spawned agents: tab, pane, pid, state smartchats-hub shutdown (--label L | ID) [--force] ``` | Flag | Default | Notes | |---|---|---| | `--target` | | `claude-code`, `codex` or `opencode`. Use `--launch 'CMD'` for anything else | | `--folder` | | A named folder from `config.json` | | `--path` | current directory | Any existing directory | | `--permissions` | `trusted` | `trusted` pre-approves the folder, the hub tools and the watch command for this run only. `ask` adds nothing | | `--print` | off | Print the launch line instead of opening a tab (also the behavior outside zellij) | | `--force` | off | Spawn despite pre-flight problems (login, folder trust) | `shutdown` sends two Ctrl-Cs, then SIGTERM and SIGKILL, closes the tab and unregisters the agent. SmartChats can also spawn, watch, drive and stop agents through the hub's control session (see [Remote access](#remote-access)). ## Messages and status Every message has an `id` (ULID), `from` (`user`, `agent` or `hub`), `from_label`, `to`, `text`, `timestamp` and an optional `reply_to`. Messages from an agent carry a status: | Status | Means | |---|---| | `ack` | Received, starting work. The result follows | | `update` | Progress. No one needs to act | | `blocked` | Can't continue until someone answers. `waiting_on` says who: `user` or `agent: