Fixture: a TypeScript mod that imports the claude-code SDK surface.

<img src="doc_en/banner.svg" alt="OpenSquad" width="720" />
<strong>English</strong> | <a href="README_ZH.md">中文</a>
OpenSquad is a local-first multi-agent collaboration framework. Multiple autonomous agents (PM, Coder, QA, etc.) communicate via group chat to coordinate and complete complex tasks.
<img src="doc_en/screenshots/agent_web.png" alt="OpenSquad single-agent web workspace" width="900" />
<img src="doc_shared/screenshots/chat_group.png" alt="OpenSquad group chat" width="900" />
OpenSquad enables multiple AI agents to collaborate like a real team. Each agent runs as an independent process with its own LLM connection, tool set, and memory. Agents communicate through group chat, coordinated by a PM agent, to tackle complex tasks that a single agent cannot handle alone.
A single AI agent has clear limitations when dealing with complex projects:
OpenSquad solves these through multi-agent collaboration: PM handles task decomposition and coordination, Coder focuses on implementation, QA independently verifies — each with a clear responsibility.
flowchart TB
User["User / Web UI"] --> GW["Gateway :9555"]
GW --> LA["Launcher :9600"]
LA --> PM["PM Agent"]
LA --> Dev["Coder Agent"]
LA --> QA["QA Agent"]
PM & Dev & QA --> IM["Group chat / IM"]
PM & Dev & QA --> Plugins["Plugins / MCP / Skills"]
Plugins --> Svc["Service plugins e.g. websearch :9001"]
Details: Architecture · Documentation hub
uv sync (or install.bat / install.sh).api_key in src/model_cards/ (see model cards guide).uv run opensquad init (creates ~/.opensquad/workspace by default).uv run opensquad start.http://127.0.0.1:5173 (or Gateway port from system_config).module.register, and transpiles their .ts / .tsx with a vendored sucrase — so no Node-version-dependent type stripping)Windows
git clone https://github.com/opensquad-ai/opensquad.git && cd opensquad && install.bat
Linux / macOS
git clone https://github.com/opensquad-ai/opensquad.git && cd opensquad && bash install.sh
The script automatically: checks prerequisites → installs dependencies → initializes workspace → starts all services.
Note: After first start, add your LLM API key to a model card under
src/model_cards/.
uv is a fast Python package manager. Recommended.
# Install uv (if not already)
pip install uv
# Clone the project
git clone https://github.com/opensquad-ai/opensquad.git
cd opensquad
# Install dependencies (uses uv.lock for reproducible builds)
uv sync
# Install frontend dependencies
cd src/opensquad/gateway/nexuschat-pro && npm install && cd ../../..
# Initialize and start
uv run opensquad init
uv run opensquad start
From PyPI — runtime, built web UI and the default resources:
pip install opensquad
opensquad init
opensquad start
Since 0.8.48 the wheel is a complete deployment. It ships the default
model_cards/,plugins/,agents/,pymcp/,collab_cards/,role_cards/andskills/next to theopensquadpackage, soopensquad initseeds a runnable workspace andopensquad startfinds every service. One gap remains: a fresh pip workspace has nosystem_config.json(the wheel carries no template), so ports stay on their defaults — gateway 9555, launcher 9600, registry 9720, frontend 5173 — until you save one from the Web UI's Settings.
From a checkout — full deployment, editable install:
git clone https://github.com/opensquad-ai/opensquad.git
cd opensquad
pip install -e .
# Install frontend dependencies
cd src/opensquad/gateway/nexuschat-pro && npm install && cd ../../..
# Initialize and start
opensquad init
opensquad start
The npm package is a thin bootstrap: on first run it installs the matching Python opensquad CLI, then forwards every command to it.
npm install -g opensquad-ai
opensquad init
opensquad start
Requires Node.js 18+ (Mods host: 20.6+) and Python 3.11+. npx opensquad-ai works too, with no global install.
git clone https://github.com/opensquad-ai/opensquad.git
cd opensquad
# Start (auto-builds image)
docker compose up -d
# View logs
docker compose logs -f
After start, visit http://localhost:9555. Data is persisted in Docker volumes.
Custom configuration:
# Edit config
cp src/system_config.example.json src/system_config.json
# Fill in your LLM API Key...
# Mount config and start
docker run -d \
-p 9555:9555 -p 9600:9600 -p 9720:9720 \
-v opensquad-data:/data \
-v ./src/system_config.json:/app/src/system_config.json:ro \
opensquad
Use the row that matches how you installed OpenSquad:
| Installed via | Update with | Notes |
|---|---|---|
| Desktop app (installer) | In-app Check for updates, or opensquad update | Fully automatic: it downloads the installer, installs it silently and relaunches the app. Builds older than v0.8.43 must download the installer once by hand — the relaunch-on-update logic shipped in that version. |
pip install opensquad | pip install --upgrade opensquad | |
npm install -g opensquad-ai | npm install -g opensquad-ai@latest | The wrapper re-installs the matching Python package when the versions differ. |
git clone (uv sync / pip install -e .) | git pull, then re-run the same install command | Re-run npm install under src/opensquad/gateway/nexuschat-pro if the frontend changed; npm run build there to serve the UI statically instead of through Vite. |
| Docker | git pull && docker compose up -d --build | The image is built from the local Dockerfile, not pulled from a registry. |
opensquad update only installs anything for the packaged desktop app. It cannot rewrite a pip / npm / source install, so on those it prints the command from the table and exits — without touching your environment or your git state.
opensquad start launches all 4 services:
| Service | Port | Description |
|---|---|---|
| Gateway Backend | 9555 | FastAPI backend (WebSocket + HTTP) |
| Plugin Registry | 9720 | Plugin store API |
| Frontend Dev | 5173 | Vite React frontend |
| Launcher | 9600 | Agent process manager |
Open http://127.0.0.1:5173 in your browser to create and configure agents via the Web UI.
| Command | Description |
|---|---|
opensquad init [--workspace <path>] | Initialize workspace (default: ~/.opensquad/workspace) |
opensquad start [--port <port>] | Start all services |
opensquad stop | Stop all OpenSquad services (frontend, gateway, launcher, adapters, agent processes) |
opensquad status | Show agent and service status |
opensquad plugin list | List installed plugins |
opensquad plugin install <id> | Install a plugin from the store or Git URL |
opensquad plugin uninstall <id> | Uninstall a plugin |
Run without installing:
python -m opensquad.cli start
Copy system_config.example.json to your workspace or src/system_config.json (see architecture-paths):
{
"hosts": { "gateway": "127.0.0.1" },
"ports": { "gateway": 9555, "launcher": 9600, "websearch": 9001 },
"auth": { "node_secret": "your-secret-here" }
}
Never commit real system_config.json, auth.json, or model cards with API keys.
LLM API keys live in model cards (src/model_cards/*.json). Agents are created via the Web UI at runtime.
Start here: Documentation hub → Getting started (EN)
| Document | Description |
|---|---|
| Architecture | System design and module map |
| Collaboration | Multi-agent workflows |
| Plugin ecosystem | Built-in plugins vs Registry |
| Contributing | How to contribute |
| Releasing | Maintainer release checklist |
Issues and Pull Requests are welcome! Please read the Contributing Guide and Code of Conduct first.
MIT License — see LICENSE for details.
Powered by OpenSquad Core
hooks/register.ts 29 lines1// Fixture for P6-B1: a TypeScript mod, run as-is on Node 24's type stripping,
2// importing the `claude-code` SDK surface that the host answers with a shim.
3//
4// Type-only imports are erased at runtime; the value import must resolve.
5import { atom, memberOf, read, update } from 'claude-code'
6import type { EngineInterface, Register } from 'claude-code'
7
8type Counter = { hits: number }
9
10const hits = atom({ plugin: 'ts-demo', key: 'hits' } as const, 0)
11const seen = atom({ plugin: 'ts-demo', key: 'seen' } as const, 0)
12
13export const register: Register = (on: (event: string, matcher: unknown, handler: unknown) => void) => {
14 on('tool.call', { tool: 'demo.typed' }, async ($: EngineInterface, e: { tool: string }, next: () => unknown) => {
15 const before: number = read(hits) ?? 0
16 update(hits, (value: number) => (value ?? 0) + 1)
17 const after: number = read(hits) ?? 0
18 // `memberOf` gives this member its own cell, reached with the `$`-first shape.
19 // `elsewhere` is a *different* member, read to prove the cells are not shared.
20 const member = memberOf(seen, e)
21 const mine: number = read($, member) ?? 0
22 update($, member, mine + 1)
23 const elsewhere: number = read($, memberOf(seen, { requestId: 'demo-elsewhere' })) ?? 0
24 return {
25 deny: `ts-demo: typed ts works, hits ${before} -> ${after}, seen ${mine} -> ${mine + 1}, elsewhere ${elsewhere}`,
26 }
27 })
28}
29