agent-router
by splitintech
README.md
<p align="center">
<img src="media/icon.png" alt="Agent Router" width="64" height="64">
</p>
<h1 align="center">vscode-agent-router</h1>
<p align="center">
<strong>Cursor / VS Code dispatcher:</strong> any local model talks to Claude, Codex, Slack, and catalog peers over MCP, CLI, or API.
</p>
<p align="center">
<a href="https://marketplace.visualstudio.com/items?itemName=splitin.agent-router"><img src="https://img.shields.io/visual-studio-marketplace/v/splitin.agent-router?label=VS%20Marketplace" alt="Visual Studio Marketplace"></a>
<a href="https://open-vsx.org/extension/splitin/agent-router"><img src="https://img.shields.io/open-vsx/v/splitin/agent-router?label=Open%20VSX" alt="Open VSX"></a>
<a href="https://github.com/splitintech/vscode-agent-router/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT license"></a>
<a href="https://github.com/splitintech/vscode-agent-router"><img src="https://img.shields.io/badge/product-open--internal-tools-orange" alt="Open Internal Tools"></a>
</p>
<p align="center">
<a href="https://github.com/splitintech/open-internal-tools">Hub</a>
·
<a href="#getting-started">Docs</a>
·
<a href="#use-cases">Use cases</a>
·
<a href="#mcp-tools">MCP</a>
·
<a href="https://www.splitin.net/careers-requests">Careers</a>
</p>
<p align="center">
<a href="https://www.splitin.net/tech-stack/open-internal-tools/vscode-agent-router">www.splitin.net/tech-stack/open-internal-tools/vscode-agent-router</a>
</p>
<p align="center">
<img src="docs/banner.webp" alt="vscode-agent-router by SplitInTech — Route developer tasks across agents inside VS Code">
</p>
# One Cursor agent. Official Claude, Codex, and Slack. No scraped UIs.
A Cursor (or VS Code) extension that **routes** work. The agent in this window — any model — calls small MCP tools. For **Claude / Codex / ChatGPT**, the router **opens the official extension** (`vscode://splitin.agent-router/launch`). It does **not** run `claude -p`, `codex exec`, or those vendors’ HTTP APIs. GitHub / Railway / Slack CLI stay as tools. Cursor Cloud API is available only when you pass `runtime=cloud`.
Built internally at SplitIn as the tech side of a product, then packaged so others can install the same dispatcher. A tool specialist owns this folder end to end. It can stay MIT and later also power a hosted SplitIn service. Every contributor would have **% equity** in that hosted service.
- **Compose, do not rebuild**: Slack CLI, Claude Code / Codex / ChatGPT **extensions**, Cursor Cloud Agents API (opt-in), `@modelcontextprotocol/sdk`, `tsup`, vitest, `@vscode/vsce`.
- **Indie-cheap**: no extra daemon, no extra cloud, no extra seat. Secrets in env / `~/.agent-router`, never in git.
- **Out of the box**: Marketplace / Open VSX install, F5, or a VSIX. Sidebar Peers + Jobs, Command Palette, editor/explorer handoff menus, URI handler.
- **Version by version**: catalog JSON adds peers without a new adapter class.
This does **not** merge Composer, Claude’s sidebar, and Codex into one transcript. It is a dispatcher, the same way [ideation-loop-system](https://github.com/splitintech/open-internal-tools/tree/main/ideation-loop-system) routes Slack threads instead of inventing a new bot per vendor.
```text
Cursor agent (any model)
→ MCP tools (list_peers, route, call_cli, slack_api, list_jobs)
→ AgentRouter
→ Claude: vscode://splitin.agent-router/launch → Claude Code URI (prefills, does not auto-submit)
→ Codex / ChatGPT: same URI → chatgpt.addToThread (openai.chatgpt)
→ Cursor: Composer command if present; otherwise you already are the agent
→ Slack: ~/.slack/bin/slack api | SLACK_BOT_TOKEN
→ GitHub / Railway / Vercel / Supabase / Stripe / Linear: allowlisted CLI or API
→ ideation-hq: POST /hooks/jobs (failed jobs stay on the Slack thread)
```
## Table of contents
- [How this folder is built](#how-this-folder-is-built)
- [Getting started](#getting-started)
- [Use cases](#use-cases)
- [Install](#install)
- [MCP tools](#mcp-tools)
- [Peers](#peers)
- [Slack](#slack)
- [Cloud jobs](#cloud-jobs)
- [IDE handoff](#ide-handoff)
- [Add a peer](#add-a-peer)
- [Commands](#commands)
- [Settings and secrets](#settings-and-secrets)
- [Layout](#layout)
- [Confirm it works](#confirm-it-works)
- [Careers](#careers)
## How this folder is built
[open-internal-tools](https://github.com/splitintech/open-internal-tools) is a **program repo**: each folder is the tech of one product, MIT, install from **that** folder, one specialist owns it. Stack is chosen per job, not one language for the hub.
| Folder | Language / runtime | How it is built |
| --- | --- | --- |
| [mac-unlock-notify](https://github.com/splitintech/open-internal-tools/tree/main/mac-unlock-notify) | zsh on macOS | LaunchAgent + `ioreg` + `curl` + Slack webhook. No Node. Secrets in `~/.config`. |
| [slack-agent-hq](https://github.com/splitintech/open-internal-tools/tree/main/slack-agent-hq) | TypeScript on Node 20+ | Slack Bolt + Slack CLI + YAML integrations. Official `@Cursor` / `@Claude` / `@Codex` apps, not a custom bot per vendor. |
| [in-app-otp](https://github.com/splitintech/open-internal-tools/tree/main/in-app-otp) | TypeScript | `tsup` + vitest. Core + adapters (React, Express, Django, Supabase). |
| [react-mobile-interactions](https://github.com/splitintech/open-internal-tools/tree/main/react-mobile-interactions) | TypeScript + React | Package-only primitives, vitest, tsup. |
| **vscode-agent-router** (this folder) | TypeScript on Node 20+ | Cursor/VS Code extension + MCP stdio. Catalog JSON + adapters. Same compose rule: official CLIs and APIs. |
Shared defaults across those folders, followed here:
1. **Internal, then package** — ship a VSIX / `dist/` others can load.
2. **Right tool for the job** — extension host for `executeCommand` / URI handoff; stdio MCP for any model; `catalog/peers.json` for generic CLIs.
3. **Pick existing OSS** — do not scrape Claude or Codex UIs; do not persist API keys in `settings.json`.
4. **One folder, one PR surface** — keep Agent Router work inside `vscode-agent-router/`.
5. **People and agents in sync** — the product *is* that sync: Cursor agents call Claude/Codex/Slack instead of working in silos.
## Getting started
```sh
git clone https://github.com/splitintech/vscode-agent-router.git
cd vscode-agent-router
npm install
npm test
npm run build
```
Then F5 (**Run Agent Router Extension**) or install the VSIX (see [Install](#install)). Work in sync with other contributors and agents. PRs stay in `vscode-agent-router/`.
## Use cases
Ten ways developers integrate Agent Router:
1. **Consult Claude from Cursor** — a Composer agent calls `route peer=claude action=consult runtime=ide` (default). Agent Router opens Claude Code with a **prefilled** prompt. It does **not** auto-submit and does **not** run `claude -p`.
2. **Fan-out a fix** — same prompt, `runtime=ide` on `claude`, `codex`, and `chatgpt`; Jobs sidebar holds ids; poller can `POST` ideation HQ `/hooks/jobs`.
3. **Handoff a selection to Claude Code** — editor context menu prefills `vscode://anthropic.claude-code/open?prompt=…` (does not auto-submit).
4. **Add the current file to Codex** — explorer or editor title uses `chatgpt.addFileToThread` when `openai.chatgpt` is installed.
5. **Post a cloud result to Slack** — `slack_api method=chat.postMessage` or **Agent Router: Post to Slack**, using the public Slack CLI (`~/.slack/bin/slack`).
6. **Search Slack docs from the agent** — `route peer=slack action=consult` runs `slack docs search`.
7. **List GitHub PRs without a GitHub MCP** — `call_cli peer=github argv=["pr","list","--limit","5"]` (`gh` allowlist).
8. **Add Docker as a peer** — **Add Peer from Template** writes `~/.agent-router/peers.json`; `call_cli` enforces `allow`.
9. **Probe the laptop** — **Probe Transports** / sidebar dots for which of `claude`, `codex`, `gh`, `railway`, Slack CLI, and API keys are actually present.
10. **Status a Railway or Vercel service** — catalog CLI rows, still one `call_cli` tool, no per-vendor MCP explosion.
## Install
**VS Code Marketplace** — search **SplitIn Agent Router**, or:
```sh
code --install-extension splitin.agent-router
```
<https://marketplace.visualstudio.com/items?itemName=splitin.agent-router>
**Cursor (Open VSX)** — Extensions panel → search **SplitIn Agent Router** (`splitin.agent-router`), or:
```sh
cursor --install-extension splitin.agent-router
```
<https://open-vsx.org/extension/splitin/agent-router>
**From this folder (VSIX / F5)**
```sh
npm run package
```
In Cursor: **Extensions: Install from VSIX…** → `agent-router-0.1.0.vsix`. Reload. Or F5 **Run Agent Router Extension** (`.vscode/launch.json`).
Publish a release (needs `VSCE_PAT` + `OVSX_PAT` in the environment):
```sh
npm run publish:marketplaces
```
On activate the extension registers MCP via Cursor `cursor.mcp.registerServer` when that API exists, otherwise it spawns `dist/mcp.js`. After install, a Cursor agent of any model can call the tools without a project `mcp.json`.
Keep the stdio binary for Claude Code or Codex MCP configs (and for the hub’s “reply as a peer” path):
```json
{
"mcpServers": {
"agent-router": {
"command": "node",
"args": ["dist/mcp.js"]
}
}
}
```
Claude Code and Codex extensions are **not** hard dependencies. If they are missing, handoff commands prompt you to install them.
## MCP tools
Transport preference is **MCP → CLI → API**. If Railway/Supabase/Linear is already on Cursor MCP, the agent should call those tools directly. `route` with `transport=mcp` tells you the same instead of proxying MCP inside MCP.
| Tool | What it does |
| --- | --- |
| `list_peers` | Catalog ids, kinds, runtimes, transports |
| `probe_peers` | CLIs on PATH, API env vars, `mcp.json`, Slack CLI fingerprint |
| `route` | Opens the VS Code/Cursor **extension** for `claude` / `codex` / `chatgpt` / local `cursor`. `params.promptId` loads a catalog prompt; `params.memoryPacket` appends shared MEMORY. Platform peers still CLI/API. |
| `call_cli` | Allowlisted argv for a catalog CLI |
| `call_api` | HTTP to that peer’s `baseUrl` using `authEnv` |
| `slack_api` | `slack api family.method key=value` with HTTP fallback |
| `list_jobs` | Cloud launches recorded by the router |
| `job_status` | Get or `refresh` one job |
```text
route peer=claude action=consult runtime=ide prompt="Summarize src/auth.ts"
route peer=chatgpt action=handoff params.promptId=chatgpt.plan params.memoryPacket="…"
route peer=codex action=consult runtime=ide prompt="Fix tests"
route peer=cursor action=consult runtime=ide prompt="Continue in Composer"
route peer=cursor action=launch runtime=cloud prompt="Add a README"
route peer=slack action=launch params.channel=C0123 text="Cloud job finished"
slack_api method=chat.postMessage channel=C0123 text="done"
call_cli peer=github argv=["pr","list","--limit","5"]
list_jobs
job_status jobId=ar-… refresh=true
```
`runtime=local` / `cloud` on `claude`, `codex`, and `chatgpt` is **rejected** (`claude -p` / `codex exec` / vendor HTTP). Default is `runtime=ide`. Local Cursor opens Composer if this build exposes a handoff command; otherwise you already are the agent. Cursor Cloud remains `runtime=cloud`.
## Peers
Shipped in [`catalog/peers.json`](catalog/peers.json). User overlay: `~/.agent-router/peers.json`.
| Peer | MCP | CLI | API | Cloud / IDE |
| --- | --- | --- | --- | --- |
| cursor | — | — | `POST /v1/agents` (opt-in `runtime=cloud`) | default **ide**: Composer / you are the agent |
| claude | — | **refused** | **refused** | `vscode://splitin.agent-router/launch` → Claude Code URI (no auto-submit) |
| codex | — | **refused** | **refused** | same URI → `chatgpt.addToThread` |
| chatgpt | — | **refused** | **refused** | same URI → `openai.chatgpt` |
| ideation-hq | — | — | `POST /hooks/jobs` | link jobs into the Slack thread |
| slack | optional | `slack api` / `docs` / `auth` | `slack.com/api` | — |
| github | optional | `gh` | api.github.com | — |
| railway | optional | `railway` | GraphQL | — |
| vercel | optional | `vercel` | api.vercel.com | — |
| supabase | optional | `supabase` | api.supabase.com | — |
| stripe | optional | `stripe` | api.stripe.com | — |
| linear | optional | — | GraphQL | — |
## Slack
Same rule as mac-unlock-notify and slack-agent-hq: use Slack’s own surface, not a homemade bot.
- Binary: `~/.slack/bin/slack` (public CLI). Fingerprint `d41d8cd98f00b204e9800998ecf8427e`.
- Install: `curl -fsSL https://downloads.slack-edge.com/slack-cli/install.sh | bash`
- Fallback: `SLACK_BOT_TOKEN` → `https://slack.com/api`
- Commands: **Slack Auth Status**, **Post to Slack**, selection → **Handoff Selection to Slack**
- Settings: `agentRouter.slackTeamId`, `agentRouter.slackChannel` (channel id, not a token)
```bash
slack api chat.postMessage channel=C0123456789 text="Hello from Agent Router"
```
## Cloud jobs
Launches (`action=launch`) write a JobStore row. The extension polls every `agentRouter.pollIntervalMs` (default 15s) and **POSTs ideation HQ** `POST {hqUrl}/hooks/jobs` with `x-agent-router-secret` (`AGENT_ROUTER_JOBS_SECRET`) so failed jobs stay on the Slack thread.
Cursor Cloud: `GET https://api.cursor.com/v1/agents/{id}` (`CURSOR_API_KEY`) when you explicitly pass `runtime=cloud`.
Set `agentRouter.hqUrl` / `AGENT_ROUTER_HQ_URL` and optional `agentRouter.projectId`. Set `agentRouter.notifySlackOnJobComplete` and a channel to post when a job hits succeeded/failed.
Unpushed local files are not on the cloud clone. Push (or pass a remote SHA) first.
## IDE handoff
MCP `route` (including the `node dist/mcp.js` child) cannot call `vscode.commands.executeCommand`. It **opens** `vscode://splitin.agent-router/launch?peer=&prompt=`. The extension host then:
- **Claude**: `vscode://anthropic.claude-code/open?prompt=` — prefills a **new tab**, does **not** auto-submit, does not return a result.
- **Codex / ChatGPT**: `chatgpt.addToThread` after `ensureExtension("openai.chatgpt")`.
- **Cursor local/ide**: Composer command from `probeCursorHandoff` if present; otherwise “you are already the agent”.
## Add a peer
**Agent Router: Add Peer from Template**, or edit `~/.agent-router/peers.json`. Example:
```json
{
"version": 1,
"transportPreference": ["mcp", "cli", "api"],
"peers": [
{
"id": "docker",
"title": "Docker",
"kind": "platform",
"runtimes": ["local"],
"capabilities": ["api"],
"transports": {
"cli": { "bin": "docker", "allow": ["ps", "compose"] }
}
}
]
}
```
`call_cli` rejects argv that is not on `allow`. No new TypeScript adapter required.
## Commands
| Command | What it does |
| --- | --- |
| Agent Router: Probe Transports | Refresh Peers tree + Output JSON |
| Agent Router: List Peers | Catalog dump |
| Agent Router: Handoff to Claude Code | Prefill Claude tab |
| Agent Router: Handoff to Codex | `chatgpt.addToThread` |
| Agent Router: Add File to Codex | `chatgpt.addFileToThread` |
| Agent Router: Slack Auth Status | `slack auth list` |
| Agent Router: Post to Slack | `chat.postMessage` |
| Agent Router: Add Peer from Template | Write `~/.agent-router/peers.json` |
| Agent Router: Open Job | Open job URL |
Editor context (selection): Claude, Codex, Slack. Explorer file: Codex add-file.
## Settings and secrets
Do **not** put tokens in `settings.json` or this repo.
| Setting / env | Purpose |
| --- | --- |
| `CURSOR_API_KEY` | Cursor Cloud Agents (`runtime=cloud` only) |
| `SLACK_BOT_TOKEN` | HTTP fallback if Slack CLI is missing |
| `AGENT_ROUTER_HQ_URL` / `agentRouter.hqUrl` | Ideation HQ base URL for `POST /hooks/jobs` |
| `AGENT_ROUTER_JOBS_SECRET` | Shared secret header `x-agent-router-secret` |
| `AGENT_ROUTER_PROJECT_ID` / `agentRouter.projectId` | Default HQ `project_id` on job posts |
| `AGENT_ROUTER_PROMPTS_DIR` / `agentRouter.promptsDir` | Prompt catalog (default: sibling `../ideation-loop-system/prompts` or `~/.agent-router/prompts`) |
| `CURSOR_CLOUD_REPO_URL` / `agentRouter.cursorCloudRepoUrl` | Git remote for Cursor cloud |
| `agentRouter.slackTeamId` / `slackChannel` | Slack `--team` and default channel |
| `agentRouter.notifySlackOnJobComplete` | Slack on terminal job status |
| `agentRouter.pollIntervalMs` | Job poll interval |
| `agentRouter.timeoutMs` | CLI/API timeout |
| `agentRouter.catalogPath` | Extra catalog file; user file still merges |
## Layout
```text
catalog/peers.json shipped peers (MCP / CLI / API / cloud / ide)
src/core/ vscode-free: router, registry, jobs, poll, probe
src/adapters/ cursor, claude, codex, slack, generic
src/transports/ spawn (no shell), Slack CLI resolve, HTTP
src/mcp/ createMcpServer + stdio bootstrap
src/ext/ VS Code: MCP host, trees, handoff, poller
src/extension.ts activate
tests/ vitest, no vscode
```
Core stays vscode-free so `npm test` and `node dist/mcp.js` never load the extension host.
## Confirm it works
```sh
npm test
npm run build
```
Then in Cursor:
- **Probe Transports** — sidebar Peers dots match CLIs on the machine
- Handoff a selection to Claude and Codex (those extensions installed)
- Slack Auth Status (or docs search if the CLI is present)
- `call_cli` github with `argv=["config"]` → allowlist error
- Cloud launch (if keys exist) shows a row under Jobs
## Careers
Own this dispatcher end to end — or explore SplitIn tech careers — at **[https://www.splitin.net/careers-requests](https://www.splitin.net/careers-requests)**.
<p align="center">
<img src="docs/banner.webp" alt="vscode-agent-router by SplitInTech — Route developer tasks across agents inside VS Code">
</p>
## License
MIT. See [LICENSE](LICENSE). Program rules live with the hub: [CONTRIBUTING.md](CONTRIBUTING.md).
## Standalone repository
This product is maintained at [vscode-agent-router](https://github.com/splitintech/vscode-agent-router). Clone this repository and run the documented setup from its root. The npm/package and API identities are preserved. See [migration provenance](docs/migration.md) and [contributing](CONTRIBUTING.md). See [release setup](docs/repository-release.md); publishing starts disabled pending external configuration.
## Standalone prompt catalog
The extension and MCP builds include a pinned Ideation prompt catalog, so an adjacent checkout is not required. Explicit `agentRouter.promptsDir` or `AGENT_ROUTER_PROMPTS_DIR` configuration takes precedence; existing sibling and personal catalogs remain supported. The bundled snapshot is a generated consumer of [Ideation Loop System](https://github.com/splitintech/ideation-loop-system), with per-file provenance in [prompt-catalog.json](docs/migration/prompt-catalog.json). Update `prompts/` and run `npm run sync:prompts` to regenerate `src/core/bundledPrompts.ts` when deliberately upgrading the catalog.
Run `npm ci`, `npm run typecheck`, `npm test`, and `npm run package`. The VSIX retains extension ID `splitin.agent-router`. Publishing to either marketplace remains a separate, credentialed release action.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues