@dylanrussell/agent-router 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +211 -0
- package/assets/logo.svg +45 -0
- package/dist/cli.d.ts +23 -0
- package/dist/cli.js +1259 -0
- package/dist/cli.js.map +1 -0
- package/dist/plugin.d.ts +30 -0
- package/dist/plugin.js +787 -0
- package/dist/plugin.js.map +1 -0
- package/dist/tui.d.ts +99 -0
- package/dist/tui.js +1048 -0
- package/dist/tui.js.map +1 -0
- package/package.json +95 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dylan Russell
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="./assets/logo.svg" width="128" alt="agent-router logo" />
|
|
4
|
+
|
|
5
|
+
# agent-router
|
|
6
|
+
|
|
7
|
+
**Switch the models assigned to your opencode agents.**
|
|
8
|
+
Named stacks applied to agent frontmatter · one command, one restart, new model crew.
|
|
9
|
+
|
|
10
|
+
[](https://www.npmjs.com/package/@dylanrussell/agent-router)
|
|
11
|
+
[](https://www.npmjs.com/package/@dylanrussell/agent-router)
|
|
12
|
+
[](./LICENSE)
|
|
13
|
+
[](https://nodejs.org)
|
|
14
|
+
[](https://github.com/dylanrussellmd/agent-router/actions/workflows/ci.yml)
|
|
15
|
+
[](https://www.typescriptlang.org)
|
|
16
|
+
[](https://vitest.dev)
|
|
17
|
+
|
|
18
|
+
[Install](#install) · [Quickstart](#quickstart) · [Stacks](#stacks) · [In the TUI](#in-the-tui) · [FAQ](#faq) · [Issues](https://github.com/dylanrussellmd/agent-router/issues)
|
|
19
|
+
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## What it does
|
|
25
|
+
|
|
26
|
+
opencode agents are markdown files (`~/.config/opencode/agents/*.md`) whose YAML frontmatter carries a `model:` line:
|
|
27
|
+
|
|
28
|
+
```markdown
|
|
29
|
+
---
|
|
30
|
+
description: High-reasoning review, debugging, and architecture counsel
|
|
31
|
+
mode: subagent
|
|
32
|
+
model: anthropic/claude-opus-4-8 # ← the ONLY line agent-router touches
|
|
33
|
+
temperature: 0.1
|
|
34
|
+
tools: { write: false, edit: false }
|
|
35
|
+
---
|
|
36
|
+
<prompt body — owned by you, never touched by agent-router>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`agent-router` keeps named **stacks** — JSON files mapping agent names to models — and applies them to those `model:` lines on demand. Premium models for the workday, cheap ones for bulk chores, one command to swap the whole crew:
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"agents": {
|
|
44
|
+
"Omni": { "model": "anthropic/claude-fable-5" },
|
|
45
|
+
"oracle": { "model": "openai/gpt-5.5" },
|
|
46
|
+
"explorer": { "model": "openai/gpt-5.4-mini" },
|
|
47
|
+
"librarian": { "model": "openai/gpt-5.4-mini" },
|
|
48
|
+
"fixer": { "model": "openai/gpt-5.5" }
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The prompt body and every other frontmatter key are yours; agent-router rewrites exactly one line per agent, atomically, through symlinks (dotfile-manager setups survive intact).
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
~/.config/opencode/
|
|
57
|
+
├── agents/ ← your agent .md files (the live target)
|
|
58
|
+
└── agent-router/
|
|
59
|
+
├── state.json ← {active, previousActive, …} (machine state)
|
|
60
|
+
├── stacks/
|
|
61
|
+
│ ├── premium.json ← named stacks (your config)
|
|
62
|
+
│ └── cheap.json
|
|
63
|
+
└── history/ ← rolling 20 most-recent switches
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Stacks are config, history is state — point `stacksDir` somewhere dotfile-managed if you version your setup (see [Configuration](#configuration)).
|
|
67
|
+
|
|
68
|
+
## Install
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npx -y @dylanrussell/agent-router init
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`init` will:
|
|
75
|
+
|
|
76
|
+
1. Capture your agents' current models into a first stack (`default`) and mark it active.
|
|
77
|
+
2. Add `@dylanrussell/agent-router@latest` to the `plugin` array in `opencode.json` (backing it up first).
|
|
78
|
+
3. Add the same entry to `tui.json` — this loads the sidebar + `/agent-*` commands (opencode ≥ 1.17).
|
|
79
|
+
4. Remove the legacy `@dylanrussell/omo-router` plugin entry if present.
|
|
80
|
+
|
|
81
|
+
Then **restart opencode** so it picks up the plugin.
|
|
82
|
+
|
|
83
|
+
## Quickstart
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
agent-router capture my-mix # snapshot current frontmatter models as a stack
|
|
87
|
+
agent-router list # show stacks; * marks active
|
|
88
|
+
agent-router use cheap # apply a stack (validates first)
|
|
89
|
+
agent-router back # undo the most recent switch
|
|
90
|
+
agent-router current # print the agent → model mapping in frontmatter
|
|
91
|
+
agent-router status # print active stack name
|
|
92
|
+
agent-router show cheap # print a stack's JSON
|
|
93
|
+
agent-router edit cheap # open a stack in $EDITOR
|
|
94
|
+
agent-router validate --all # check every stack against `opencode models`
|
|
95
|
+
agent-router history # list recent switches
|
|
96
|
+
agent-router import my-mix <file> # import a stack from a file
|
|
97
|
+
agent-router export my-mix <file> # export a stack to a file
|
|
98
|
+
agent-router rm my-mix # remove a stack
|
|
99
|
+
agent-router path # print all paths used (debugging)
|
|
100
|
+
agent-router completion # install shell autocompletion
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The everyday loop: tune your agents until you like them → `capture <name>` → repeat with other models → `use <name>` to swap between the results.
|
|
104
|
+
|
|
105
|
+
## Stacks
|
|
106
|
+
|
|
107
|
+
A stack file needs one thing: an `agents` record whose entries carry a `model` string. Agent names are the `.md` basenames in your agents dir (`Omni` ↔ `Omni.md`). Unknown keys are preserved round-trip.
|
|
108
|
+
|
|
109
|
+
`use` is strict by design: if a stack references an agent file that doesn't exist, or one without a `model:` line, the switch fails **before anything is written** — your suite is never left half-switched. It also validates every model ID against `opencode models` first (skip with `--no-validate`, override with `--force-invalid`).
|
|
110
|
+
|
|
111
|
+
`capture` is the inverse: it reads the current `model:` line of every agent file (files without one are skipped) and writes a stack. There are no bundled seed stacks — your real setup is the seed.
|
|
112
|
+
|
|
113
|
+
## Inside opencode
|
|
114
|
+
|
|
115
|
+
The plugin exposes six tools the agent (or you, by asking it) can call:
|
|
116
|
+
|
|
117
|
+
| tool | what it does |
|
|
118
|
+
|---|---|
|
|
119
|
+
| `router_status` | active stack + current frontmatter mapping + available stacks |
|
|
120
|
+
| `router_list` | stack list with `isActive` flags |
|
|
121
|
+
| `router_use({name, validate?})` | apply a stack; pops a TUI toast |
|
|
122
|
+
| `router_capture({name, force?})` | snapshot current models into a stack |
|
|
123
|
+
| `router_back({n?})` | undo last N switches |
|
|
124
|
+
| `router_validate({name?, active?})` | check model IDs against current opencode auth |
|
|
125
|
+
|
|
126
|
+
> *"Switch to cheap"* — your agent calls `router_use`, the toast pops up, you restart opencode.
|
|
127
|
+
|
|
128
|
+
## In the TUI
|
|
129
|
+
|
|
130
|
+
On opencode ≥ 1.17 the plugin also ships a TUI half (loaded from `tui.json`, wired up by `init`):
|
|
131
|
+
|
|
132
|
+
- **Sidebar panel** — shows the active stack, the stack count, and a `⟳ restart required` badge after any switch. Updates live (≤1.5s) when the CLI or agent switches stacks underneath the TUI.
|
|
133
|
+
- **Commands** — type `/` or open the command palette:
|
|
134
|
+
|
|
135
|
+
| command | what it does |
|
|
136
|
+
|---|---|
|
|
137
|
+
| `/agent-switch` (alias `/ar`) | pick a stack, validate, apply |
|
|
138
|
+
| `/agent-view` | browse a stack's agent → model assignments |
|
|
139
|
+
| `/agent-edit` | reassign a model, picking from your reachable model catalog |
|
|
140
|
+
| `/agent-back` | confirm + revert to the previous stack |
|
|
141
|
+
| `/agent-validate` | check a stack's model IDs against current auth |
|
|
142
|
+
| `/agent-status` | toast the active stack + list |
|
|
143
|
+
|
|
144
|
+
Everything degrades gracefully: on older opencode versions (or if the TUI API changes) the sidebar and commands simply don't appear — the CLI and agent tools keep working.
|
|
145
|
+
|
|
146
|
+
Debugging the TUI half: `AGENT_ROUTER_TUI_DEBUG=/tmp/agent-router-tui.log opencode` writes a trace of the plugin's init steps.
|
|
147
|
+
|
|
148
|
+
## Configuration
|
|
149
|
+
|
|
150
|
+
Paths resolve in this order: explicit option → `config.json` → env var → default.
|
|
151
|
+
|
|
152
|
+
`~/.config/opencode/agent-router/config.json` (read identically by the CLI and the plugin — the recommended place):
|
|
153
|
+
|
|
154
|
+
```json
|
|
155
|
+
{
|
|
156
|
+
"agentsDir": "~/.agents/agents",
|
|
157
|
+
"stacksDir": "~/.agents/agent-router/stacks"
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
| setting | env var | default |
|
|
162
|
+
|---|---|---|
|
|
163
|
+
| agents dir | `AGENT_ROUTER_AGENTS_DIR` | `~/.config/opencode/agents` |
|
|
164
|
+
| stacks dir | `AGENT_ROUTER_STACKS_DIR` | `${routerHome}/stacks` |
|
|
165
|
+
| state home | `AGENT_ROUTER_HOME` (legacy `OMO_ROUTER_HOME`) | `~/.config/opencode/agent-router` |
|
|
166
|
+
|
|
167
|
+
## ⚠ Things to know
|
|
168
|
+
|
|
169
|
+
- **Restart required.** opencode reads agent files once at startup. After every `agent-router use`, restart opencode for the new models to take effect. The CLI reminds you.
|
|
170
|
+
- **Hand-edits to frontmatter are not auto-saved into stacks.** If you hand-tune a model and want to keep it, `capture` it (or `capture <active> --force`). The next `use` that touches that agent overwrites the hand-edit.
|
|
171
|
+
- **Validation is auth-state-dependent.** `agent-router validate` runs `opencode models`, which only lists models reachable through your current auth. If you revoke a key, previously-valid stacks may suddenly be invalid.
|
|
172
|
+
- **Dotfile-manager users:** applied switches change your agent `.md` files — re-add/commit them (e.g. `chezmoi add`) when you want the change versioned.
|
|
173
|
+
|
|
174
|
+
## FAQ
|
|
175
|
+
|
|
176
|
+
**Where did omo-router go?** This package is its successor. omo-router snapshotted `oh-my-openagent.json`; agent-router targets native opencode agent frontmatter instead — no third-party plugin required. `init` removes the old plugin entry; the old package is deprecated on npm.
|
|
177
|
+
|
|
178
|
+
**Why no variants/fallback models?** Native agent frontmatter has a single `model:` line. If you want a fallback story, make it a stack (`cheap`, `free`) and switch to it.
|
|
179
|
+
|
|
180
|
+
**Can I have per-project stacks?** Point `AGENT_ROUTER_STACKS_DIR` at a project-local directory in that project's shell env.
|
|
181
|
+
|
|
182
|
+
## Architecture in 60 seconds
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
┌──────────────────────────────────────────────┐
|
|
186
|
+
│ opencode (Bun) │
|
|
187
|
+
│ └─ agents loaded from agents/*.md ──────────┼── reads at startup ─┐
|
|
188
|
+
│ └─ plugin: agent-router (this package) ─────┼─ tools, toast │
|
|
189
|
+
└──────────────────────────────────────────────┘ ▼
|
|
190
|
+
~/.config/opencode/agents/*.md ◄── `model:` lines rewritten on `use`
|
|
191
|
+
~/.config/opencode/agent-router/
|
|
192
|
+
stacks/<name>.json ◄── source of truth for each stack
|
|
193
|
+
state.json ◄── pointer to active stack
|
|
194
|
+
history/<ts>__<from>-to-<to>.json ◄── displaced mappings, rolling 20
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Contributing
|
|
198
|
+
|
|
199
|
+
Issues and PRs welcome. Run locally:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
git clone https://github.com/dylanrussellmd/agent-router.git
|
|
203
|
+
cd agent-router
|
|
204
|
+
pnpm install
|
|
205
|
+
pnpm test
|
|
206
|
+
pnpm build
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## License
|
|
210
|
+
|
|
211
|
+
MIT — see [LICENSE](./LICENSE).
|
package/assets/logo.svg
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="omo-router logo">
|
|
2
|
+
<title>omo-router</title>
|
|
3
|
+
<desc>Three stacked rounded rectangles representing named model stacks; the top one is highlighted in cyan as the active stack. A small cyan dot on the left indicates the selector position. The plate is a soft slate background with rounded corners.</desc>
|
|
4
|
+
|
|
5
|
+
<!-- background plate -->
|
|
6
|
+
<rect width="256" height="256" rx="48" fill="#0F172A"/>
|
|
7
|
+
|
|
8
|
+
<!-- subtle inner highlight -->
|
|
9
|
+
<rect x="2" y="2" width="252" height="252" rx="46" fill="none" stroke="url(#sheen)" stroke-width="1" stroke-opacity="0.35"/>
|
|
10
|
+
|
|
11
|
+
<!-- 3 stacked cards (the 3 stacks) -->
|
|
12
|
+
<!-- bottom card: dim -->
|
|
13
|
+
<rect x="60" y="170" width="148" height="34" rx="9" fill="#334155"/>
|
|
14
|
+
<rect x="74" y="183" width="44" height="8" rx="4" fill="#0F172A" fill-opacity="0.55"/>
|
|
15
|
+
|
|
16
|
+
<!-- middle card: medium -->
|
|
17
|
+
<rect x="60" y="120" width="148" height="34" rx="9" fill="#475569"/>
|
|
18
|
+
<rect x="74" y="133" width="60" height="8" rx="4" fill="#0F172A" fill-opacity="0.55"/>
|
|
19
|
+
|
|
20
|
+
<!-- top card: active (cyan) -->
|
|
21
|
+
<rect x="60" y="70" width="148" height="34" rx="9" fill="url(#activeFill)"/>
|
|
22
|
+
<rect x="74" y="83" width="36" height="8" rx="4" fill="#0F172A" fill-opacity="0.7"/>
|
|
23
|
+
<!-- subtle glow on active -->
|
|
24
|
+
<rect x="60" y="70" width="148" height="34" rx="9" fill="none" stroke="#22D3EE" stroke-width="1.5" stroke-opacity="0.5"/>
|
|
25
|
+
|
|
26
|
+
<!-- selector lane on the left -->
|
|
27
|
+
<line x1="40" y1="64" x2="40" y2="210" stroke="#1E293B" stroke-width="4" stroke-linecap="round"/>
|
|
28
|
+
|
|
29
|
+
<!-- selector dot pointing at active row -->
|
|
30
|
+
<circle cx="40" cy="87" r="8" fill="#06B6D4"/>
|
|
31
|
+
<circle cx="40" cy="87" r="8" fill="none" stroke="#22D3EE" stroke-width="2" stroke-opacity="0.65"/>
|
|
32
|
+
|
|
33
|
+
<!-- gradient defs -->
|
|
34
|
+
<defs>
|
|
35
|
+
<linearGradient id="activeFill" x1="60" y1="70" x2="208" y2="104" gradientUnits="userSpaceOnUse">
|
|
36
|
+
<stop offset="0" stop-color="#0891B2"/>
|
|
37
|
+
<stop offset="1" stop-color="#06B6D4"/>
|
|
38
|
+
</linearGradient>
|
|
39
|
+
<linearGradient id="sheen" x1="0" y1="0" x2="256" y2="256" gradientUnits="userSpaceOnUse">
|
|
40
|
+
<stop offset="0" stop-color="#94A3B8"/>
|
|
41
|
+
<stop offset="0.5" stop-color="#94A3B8" stop-opacity="0"/>
|
|
42
|
+
<stop offset="1" stop-color="#94A3B8"/>
|
|
43
|
+
</linearGradient>
|
|
44
|
+
</defs>
|
|
45
|
+
</svg>
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agent-router` CLI entry point.
|
|
3
|
+
*
|
|
4
|
+
* One file, one tool — uses `cac` for arg parsing because it's tiny (~6kB
|
|
5
|
+
* minified) and we don't need the bells and whistles of yargs/commander.
|
|
6
|
+
*
|
|
7
|
+
* Architecture:
|
|
8
|
+
* - Each subcommand is a small async function that calls into `core/`.
|
|
9
|
+
* - All stdout/stderr formatting lives here (the core throws typed errors;
|
|
10
|
+
* the CLI maps them to exit codes + human-readable output).
|
|
11
|
+
* - We never call `process.exit()` directly — we throw and let `main()`
|
|
12
|
+
* handle it. This makes the CLI testable via dynamic import.
|
|
13
|
+
*
|
|
14
|
+
* Exit codes (see `errors.ts`):
|
|
15
|
+
* 0 success
|
|
16
|
+
* 1 user error (bad args, refused destructive op)
|
|
17
|
+
* 2 stack or agent file not found
|
|
18
|
+
* 3 IO error
|
|
19
|
+
* 4 schema or model-validation failed
|
|
20
|
+
*/
|
|
21
|
+
declare function main(argv: ReadonlyArray<string>): Promise<number>;
|
|
22
|
+
|
|
23
|
+
export { main };
|