Chorus is a local-first macOS agent core with a CLI, TUI onboarding, provider routing, a UI-independent chat gateway, tool gateway, sub-agent scheduler, persistent memory, operation logs, browser control, and skill discovery.
This repository currently implements the kernel, command-line workflow, onboarding TUI, and a lightweight chat TUI with slash commands.
- macOS
- Node.js 20 or newer
- pnpm
- Optional: OpenCode, if you want to use the
opencodetool - Optional: provider API keys for OpenAI, Anthropic, or Gemini
pnpm install
pnpm buildUse the development command while working in the repo:
pnpm dev status
pnpm dev tuiOr link the built CLI globally:
pnpm build
pnpm link --global
chorus statusRun the TUI onboarding flow:
pnpm dev onboardOpen the main bordered TUI:
pnpm dev tuiType in the bottom chat box and press Enter to talk. Provider responses stream into the conversation when the selected provider supports streaming, the status line shows a small spinner while the model is thinking, and basic Markdown from agent replies is rendered for headings, lists, quotes, code blocks, and links. Type / to open the command palette, use the up/down arrow keys, then press Enter. When the command palette is closed, use up/down, PageUp/PageDown, or terminal mouse wheel events to scroll the conversation. Mouse wheel support depends on the terminal sending SGR mouse events, and those mouse reports are filtered from the input box.
TUI slash commands are UI controls only. Agent tools are not exposed as user commands:
/status show runtime status
/clear clear conversation
/help show commands
/quit quit
You can also paste a local absolute path into normal chat and ask about its content. Absolute paths like /Users/... are treated as paths, not slash commands. For example:
/Users/luccazh/Documents/Programing☕️/Chorus/Plan_总结.md 这个文件有什么内容
Chorus sends that to the model in the normal chat loop. If the model requests read, the tool gateway executes it, returns the file data to the model, and the model answers. For obvious file-path prompts, Chorus also has a fallback that routes the read result back to the model instead of dumping raw file content directly into the chat.
The TUI talks to the model through the UI-independent ChatGateway, so the same event stream can later drive WebUI or IM frontends. When the model asks for a tool, Chorus executes it through the same ToolGateway, shows the exact tool name plus a redacted parameter summary and result status in the conversation, and sends the result back to the model for the final answer.
Each chat turn also recalls top-k matching long-term memory from SQLite and injects it into the provider context before the model answers.
When a TUI chat turn changes files inside a git repository, Chorus snapshots the repo before the turn and auto-commits new changes after the turn. Pre-existing dirty files are left out of that auto-commit so user edits are not silently swept in.
It saves settings to:
~/.chorus/config.json
The onboarding flow configures:
- agent name
- UI language
- tone/personality
- provider and default model
- custom provider name, URL, API key, model IDs, and call format
- provider API key
- optional OpenAI base URL
- OpenCode enable/disable
- addon review strictness
- addon cooldown duration
You can also configure providers with environment variables. Environment variables override saved settings when the runtime starts:
export CHORUS_PROVIDER=openai
export CHORUS_MODEL=gpt-4o-mini
export OPENAI_API_KEY=...
export OPENAI_BASE_URL=...
export CHORUS_PROVIDER=anthropic
export CHORUS_MODEL=claude-3-5-haiku-latest
export ANTHROPIC_API_KEY=...
export CHORUS_PROVIDER=gemini
export CHORUS_MODEL=gemini-2.0-flash-001
export GEMINI_API_KEY=...Custom providers are configured in onboarding or directly in ~/.chorus/config.json.
Each custom provider supports:
name: the provider name used byCHORUS_PROVIDERor--providerbaseUrl: the API endpoint base URLapiKey: optional API keymodels: one or more model IDscallFormat:openai_chat,anthropic_messages, orgemini_generate_contentallowInsecureTls: optional, only for trusted custom gateways with broken certificate chains
Example:
{
"provider": "local-openai",
"model": "qwen2.5-coder:32b",
"customProviders": [
{
"name": "local-openai",
"baseUrl": "http://localhost:11434/v1",
"apiKey": "not-needed",
"models": ["qwen2.5-coder:32b", "llama3.1:8b"],
"callFormat": "openai_chat",
"allowInsecureTls": false
}
]
}Then call it with:
pnpm dev ask --provider local-openai --model qwen2.5-coder:32b "hello"If a trusted custom HTTPS gateway fails with UNABLE_TO_GET_ISSUER_CERT_LOCALLY, either fix the server certificate chain or set "allowInsecureTls": true for that one custom provider. Keep it false for normal public API endpoints.
For local testing without network calls:
export CHORUS_PROVIDER=mock
pnpm dev ask pingRuntime state defaults to:
~/.chorus
Important files:
~/.chorus/config.jsonfor user settings~/.chorus/chorus.sqlitefor structured state and memory~/.chorus/logs/operations.jsonlfor tool execution logs~/.chorus/tasks/<task-id>.jsonlfor task timelines~/.chorus/skills/**/SKILL.mdfor local skills~/.chorus/workspaces/<workspace>/summary.mdfor human-readable notes
Use CHORUS_HOME to run Chorus against a different data directory:
CHORUS_HOME=/tmp/chorus-dev pnpm dev statusShow runtime status:
pnpm dev statusAsk through the chat gateway. This uses memory and can let the model request tools; tool events are printed on stderr and the final assistant text is printed on stdout:
pnpm dev ask "Say hello from Chorus"List tools:
pnpm dev tools listRun a tool with JSON parameters:
pnpm dev tool bash '{"command":"pwd"}'
pnpm dev tool read '{"path":"README.md"}'
pnpm dev tool search '{"path":".","query":"OpenCode"}'Dangerous shell commands are blocked by the gateway:
pnpm dev tool bash '{"command":"rm -rf ./danger"}'
pnpm dev tool bash '{"command":"sudo whoami"}'OpenCode must go through the dedicated tool. Chorus calls it as opencode run [message]:
pnpm dev tool opencode '{"message":"Explain this repository","cwd":"."}'HTTP and web tools:
pnpm dev tool http '{"method":"GET","url":"https://example.com"}'
pnpm dev tool web '{"action":"read","url":"https://example.com"}'
pnpm dev tool web '{"action":"search","query":"Chorus local agent"}'Persistent browser session tool:
pnpm dev tool browser '{"action":"open","url":"https://example.com","sessionId":"demo"}'
pnpm dev tool browser '{"action":"snapshot","sessionId":"demo"}'
pnpm dev tool browser '{"action":"click","sessionId":"demo","selector":"text=More information"}'
pnpm dev tool browser '{"action":"screenshot","sessionId":"demo"}'
pnpm dev tool browser '{"action":"close","sessionId":"demo"}'The browser tool first tries Playwright's bundled Chromium, then falls back to system Chrome, Chromium, or Edge if the bundled browser is not installed.
Git helper:
pnpm dev tool git '{"action":"status"}'
pnpm dev tool git '{"action":"diff"}'Memory:
pnpm dev memory add --kind world_fact --summary "Chorus stores structured memory in SQLite" --tags chorus,memory
pnpm dev memory search "SQLite memory"
pnpm dev memory pruneSub-agents:
pnpm dev tool open_subagent '{"goal":"Inspect README","workspace":"chorus","success_criteria":["Report findings"],"file_scope":["README.md"]}'
pnpm dev tool contact '{"recipientId":"<sub-agent-id>","type":"coordination","body":"Use src only."}'
pnpm dev tool read_inbox '{"recipientId":"<sub-agent-id>","markRead":true}'
pnpm dev subagents list
pnpm dev subagents stop <sub-agent-id> --scope agent --reason "manual stop"Omit recipientId in contact to send a sub-agent message back to the main agent inbox. Use recipientIds to send the same message to multiple sub-agents.
Skills:
pnpm dev tool skills '{"action":"list"}'
pnpm dev tool skills '{"action":"search","query":"browser"}'
pnpm dev tool skills '{"action":"read","name":"browser-helper"}'Skill discovery reads ~/.chorus/skills, paths from CHORUS_SKILL_PATHS, and ~/.codex/skills when present.
Addon review:
pnpm dev tool install_addon '{"source":"./some-addon","addonType":"plugin"}'Only a security_review actor can call allow or decline successfully through the tool gateway.
Current tools:
bashreadwriteeditlistsearchdelmemoryskillshttpwebbrowsergitscreenuiopencodemcpopen_subagentcontactread_inboxstoplist_subagentsinstall_addonallowdecline
Run the full local check:
pnpm checkExpected result:
8 test files passed
29 tests passed
Node may print an experimental warning for node:sqlite on Node 23. The warning is expected and does not indicate a failing check.