@mrrlin-dev/external-agents 0.2.10 → 0.3.1
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/README.md +70 -261
- package/cli.js +31 -0
- package/docs/01-dashboard.png +0 -0
- package/docs/02-typed.png +0 -0
- package/docs/03-saved.png +0 -0
- package/docs/hero.png +0 -0
- package/docs/ui-walkthrough.gif +0 -0
- package/install.sh +54 -0
- package/package.json +7 -4
- package/scripts/postinstall-banner.mjs +27 -0
package/README.md
CHANGED
|
@@ -1,326 +1,135 @@
|
|
|
1
1
|
# @mrrlin-dev/external-agents
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](#-2-minute-setup)
|
|
4
|
+
[](#-2-minute-setup)
|
|
5
|
+
[](#-2-minute-setup)
|
|
6
|
+
[](https://www.npmjs.com/package/@mrrlin-dev/external-agents)
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
**Route work from your coding agent across 20+ free-tier LLMs. Cut your bill 10-100×.**
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+

|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
Beyond savings and consensus, you also get the zoo-manager niceties: per-provider auth, cooldowns keyed to the *provider's* reset time (not a made-up default), quota tracking in a local JSONL, and a config-free `status` view of who is healthy right now.
|
|
12
|
-
|
|
13
|
-
> **Part of [mrrlin.com](https://mrrlin.com)** — the AI orchestration platform for developers. `external-agents` is the open-source layer we use internally for cost-efficient atomic execution and multi-model consensus. Ships MIT so anyone can adopt it standalone.
|
|
12
|
+
Your Google + Groq + Cerebras + OpenRouter + Z.ai + Ollama Cloud free tiers all have **separate quota buckets**. `external-agents` treats them as one pool: round-robin dispatch, cooldown-aware, auto-fallback on 429. Same agentic workload that used to cost $10-100/day on one paid model runs at effectively $0. Also the perfect substrate for [LLM-Council](https://github.com/karpathy/llm-council)-style multi-model panels — `pick_agents` gives you N distinct-provider picks in one call.
|
|
14
13
|
|
|
15
14
|
---
|
|
16
15
|
|
|
17
|
-
##
|
|
18
|
-
|
|
19
|
-
If you're paying for a single frontier model to do everything (planning, atomic edits, reviews, unit-test scaffolding, docstrings), you're leaving a **lot** of money on the table:
|
|
20
|
-
|
|
21
|
-
- **Free-tier quotas stack.** Google gives you generous Gemini quotas. Groq gives you 30 rpm Llama 3.3 70B at 500-800 tok/s. Cerebras gives you 30 rpm at ~2000 tok/s. OpenRouter gives you 20 rpd of `:free`-tagged frontier models with no card. Z.ai gives you GLM-4.7-flash. Ollama Cloud gives you gpt-oss:120b. Each of these has a *separate* bucket — `external-agents` treats them as one pool, so effective throughput is Σ(free-tier limits).
|
|
22
|
-
- **Round-robin, not "always pick the smartest".** Weak-tier atomic tasks (rename, refactor, write test, fix lint) don't need frontier reasoning; they need a competent model that's currently under quota. `pick --tier weak` finds one; escalation to strong-tier only fires when a task genuinely fails twice.
|
|
23
|
-
- **Fallback is automatic.** A 429 on Groq flips you to Cerebras without a retry loop in your code. The exhausted provider is marked with the reset time the provider itself reports in headers (not a 1-hour fallback), so you don't waste calls probing it.
|
|
24
|
-
- **Consensus is cheap.** Fan `pick_agents --n 4 --min-distinct-providers 4` and dispatch in parallel — four independent verdicts across four provider families, all on free-tier buckets, in the wall-time of the slowest one.
|
|
25
|
-
|
|
26
|
-
The net effect for us has been **10-100x reduction** in per-task cost for the atomic-executor workload that used to run on a single paid model. Your mileage varies with task mix, but the direction is the same for anyone who fans work out.
|
|
27
|
-
|
|
28
|
-
### What else you get (the zoo-management layer)
|
|
16
|
+
## 🚀 2-minute setup
|
|
29
17
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
- **Auth surfaces you actually have.** Subscription CLI (Codex, Claude), env-var API keys via [`aider`](https://aider.chat) → direct-to-provider through LiteLLM (100+ providers, no gateway proxy), direct CLI (cursor-agent, opencode, ollama).
|
|
33
|
-
- **Statistics that answer your questions.** How many dispatches went to Gemini this week? What did they cost? Which provider is failing the most? Local JSONL log + dashboard, no cloud required.
|
|
34
|
-
|
|
35
|
-
### LLM-consensus — the multi-model panel, made trivial
|
|
36
|
-
|
|
37
|
-
Ask several distinct LLMs the same thing and pick the majority answer — an ensemble of frontier models routinely beats any single one. This pattern shows up as [LLM-as-a-Judge](https://arxiv.org/abs/2306.05685) benchmarks, "LLM Council" experiments, [self-consistency decoding](https://arxiv.org/abs/2203.11171), and Karpathy's [recurring observation](https://x.com/karpathy) that mixed panels are strong. But it works only if you can (a) reach N different providers cheaply and (b) fan out in parallel. `external-agents` gives you both:
|
|
38
|
-
|
|
39
|
-
```
|
|
40
|
-
ids = pick_agents({ n: 3, min_distinct_providers: 3, exclude_ids: [primary] })
|
|
41
|
-
outs = Promise.all(ids.map(id => dispatch({ agent_id: id, prompt })))
|
|
42
|
-
// three distinct-provider verdicts, ~$0, in one wall-clock round.
|
|
18
|
+
```bash
|
|
19
|
+
curl -fsSL https://raw.githubusercontent.com/mrrlin-dev/external-agents/main/install.sh | bash
|
|
43
20
|
```
|
|
44
21
|
|
|
45
|
-
|
|
22
|
+
That's it. The script installs the package, registers the MCP server with Claude Code + Codex (whichever you have), and opens the local dashboard so you can paste free-tier API keys inline:
|
|
46
23
|
|
|
47
|
-
|
|
24
|
+

|
|
48
25
|
|
|
49
|
-
|
|
26
|
+
Sign up (60 sec, usually no card), paste, Save, restart your MCP client. Done. **Every new key adds a provider to the round-robin pool.**
|
|
50
27
|
|
|
51
|
-
|
|
28
|
+
<details>
|
|
29
|
+
<summary>Or wire it up manually (three commands)</summary>
|
|
52
30
|
|
|
53
31
|
```bash
|
|
54
32
|
npm install -g @mrrlin-dev/external-agents
|
|
55
|
-
external-agents --version
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
Requires Node ≥ 20. Works on macOS and Linux. Windows via WSL.
|
|
59
|
-
|
|
60
|
-
---
|
|
61
|
-
|
|
62
|
-
## Two-minute quickstart
|
|
63
33
|
|
|
64
|
-
|
|
65
|
-
# 1. See what's available (starts with zero-config defaults)
|
|
66
|
-
external-agents status
|
|
67
|
-
|
|
68
|
-
# 2. Wire an API-key provider (e.g. DeepSeek — sets DEEPSEEK_API_KEY for aider)
|
|
69
|
-
external-agents auth deepseek
|
|
70
|
-
|
|
71
|
-
# 3. Dispatch something (auto-pick + run)
|
|
72
|
-
id=$(external-agents pick -n 1)
|
|
73
|
-
external-agents dispatch "$id" "Summarize this in one line: <paste any text>"
|
|
74
|
-
|
|
75
|
-
# 4. See what just happened
|
|
76
|
-
external-agents stats
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
Open the local dashboard with `external-agents ui` (see the [UI section](#local-ui) below).
|
|
80
|
-
|
|
81
|
-
---
|
|
82
|
-
|
|
83
|
-
## Wire into your MCP client
|
|
84
|
-
|
|
85
|
-
Once installed, add one block to your MCP client config.
|
|
86
|
-
|
|
87
|
-
The package ships a dedicated `external-agents-mcp` binary — the MCP server entry — so client configs are plain command lines with no args. **Both Claude Code and Codex expose a one-liner** for this; you should never have to hand-edit config files unless you want to.
|
|
88
|
-
|
|
89
|
-
**How the client finds it.** `npm i -g @mrrlin-dev/external-agents` puts two symlinks on your global bin directory (`/opt/homebrew/bin`, `/usr/local/bin`, or wherever your Node global-bin lives) — `external-agents` (the CLI) and `external-agents-mcp` (the MCP server). Because that directory is on your `PATH`, running `external-agents-mcp` from any shell just works. `claude mcp add external-agents external-agents-mcp` stores the literal string `external-agents-mcp` in `~/.claude.json`; when Claude Code starts, it spawns that as a child process the same way your shell would, and shell PATH resolution finds the binary. No hosting, no registry lookup, no daemon. If you skipped the `npm i -g` step, `claude mcp add` succeeds but the server fails at startup with "command not found" — install first.
|
|
90
|
-
|
|
91
|
-
### Claude Code
|
|
92
|
-
|
|
93
|
-
```bash
|
|
34
|
+
# Register with whichever host(s) you use
|
|
94
35
|
claude mcp add external-agents external-agents-mcp
|
|
95
|
-
|
|
36
|
+
codex mcp add external-agents -- external-agents-mcp
|
|
96
37
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
### Codex
|
|
100
|
-
|
|
101
|
-
```bash
|
|
102
|
-
codex mcp add external-agents -- external-agents-mcp
|
|
38
|
+
# Set up keys
|
|
39
|
+
external-agents init # opens http://127.0.0.1:4711
|
|
103
40
|
```
|
|
104
41
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
### Cursor
|
|
108
|
-
|
|
109
|
-
Settings → MCP → Add server → command `external-agents-mcp`, no args.
|
|
42
|
+
Requires Node ≥ 20. Works on macOS and Linux; Windows via WSL.
|
|
110
43
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
If your MCP client doesn't have a one-liner, the config block is:
|
|
114
|
-
|
|
115
|
-
```json
|
|
116
|
-
{
|
|
117
|
-
"mcpServers": {
|
|
118
|
-
"external-agents": {
|
|
119
|
-
"command": "external-agents-mcp",
|
|
120
|
-
"args": []
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
or TOML equivalent:
|
|
127
|
-
|
|
128
|
-
```toml
|
|
129
|
-
[mcp_servers.external-agents]
|
|
130
|
-
command = "external-agents-mcp"
|
|
131
|
-
args = []
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
No publishing step — the MCP server is just `external-agents-mcp` on your PATH once you `npm i -g @mrrlin-dev/external-agents`. Nothing to host, nothing to expose over the network; it's a stdio-transport MCP server that runs locally, same as any other npm-installed CLI.
|
|
135
|
-
|
|
136
|
-
Your primary agent now has these low-level tools (build your own exec/review flows on top):
|
|
137
|
-
|
|
138
|
-
| Tool | What it does |
|
|
139
|
-
| --- | --- |
|
|
140
|
-
| `pick_agents` | Pick N distinct healthy candidates (round-robin, cooldown-aware). Optional `min_distinct_providers` for diverse fan-out. |
|
|
141
|
-
| `dispatch` | Run a specific agent with a prompt. `escalate_to_pro: true` retries on the same provider's strong tier. |
|
|
142
|
-
| `probe_agent` | Health-check one provider (cheap prompt). |
|
|
143
|
-
| `list_agents` | Registry with live state. |
|
|
144
|
-
| `get_state` | Full state file — for building your own dashboard. |
|
|
145
|
-
| `get_stats` | Aggregated dispatch metrics from local JSONL. |
|
|
146
|
-
| `set_credential` | Store a provider credential via the entry's auth surface. |
|
|
147
|
-
|
|
148
|
-
**Composition example** — build "review by a panel of 3 diverse providers" client-side:
|
|
149
|
-
```
|
|
150
|
-
ids = pick_agents({ n: 3, min_distinct_providers: 3, exclude_ids: [primary] })
|
|
151
|
-
outs = Promise.all(ids.map(id => dispatch({ agent_id: id, prompt })))
|
|
152
|
-
```
|
|
153
|
-
The package is deliberately unopinionated about *what* you compose. Mrrlin uses these primitives for its own consensus gate and atomic-executor loop; your workflow probably has its own vocabulary — that's the whole point.
|
|
44
|
+
</details>
|
|
154
45
|
|
|
155
46
|
---
|
|
156
47
|
|
|
157
|
-
##
|
|
158
|
-
|
|
159
|
-
### Free tier — no card required to get started
|
|
160
|
-
|
|
161
|
-
Most of the pool runs on generous free tiers. **20 agents across 8 providers, 17 of which cost $0** when the request is on the provider's free plan (all get their own quota bucket, so round-robin dramatically extends what you can do without paying).
|
|
162
|
-
|
|
163
|
-
| Provider | Models | Free tier |
|
|
164
|
-
| --- | --- | --- |
|
|
165
|
-
| **Google Gemini** (via AI Studio) | 3.5-flash-lite, 3.1-flash-lite, 3.6-flash, 3.5-flash, 3-flash-preview, 3/3.1-pro-preview | ✅ generous per-model quotas |
|
|
166
|
-
| **Groq** ⚡ | llama-3.3-70b-versatile, deepseek-r1-distill-llama-70b | ✅ 30 rpm — fastest inference on the market (~500-800 tok/s) |
|
|
167
|
-
| **OpenRouter** 🌐 | 50+ models tagged `:free` (deepseek-r1, qwen-coder-32b, llama-3.3-70b, ...) | ✅ 20 rpd free without card |
|
|
168
|
-
| **Cerebras** ⚡⚡ | llama3.3-70b, qwen-3-coder-480b | ✅ 30 rpm — ~2000 tok/s (fastest on the planet) |
|
|
169
|
-
| **Z.ai** (GLM) | glm-4.7-flash | ✅ free API tier |
|
|
170
|
-
| **Ollama Cloud** | gpt-oss:20b-cloud, gpt-oss:120b-cloud | ✅ free with Ollama account |
|
|
171
|
-
|
|
172
|
-
### Paid tier / per-token
|
|
48
|
+
## What you get
|
|
173
49
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
| **Codex** (subscription) | gpt-5.2-codex (primary orchestrator) | Uses your Codex Code subscription; not an API key |
|
|
50
|
+
- **`dispatch(agent_id, prompt)`** — an MCP tool your primary agent calls. Auto-picks a healthy provider, retries on a different one if the first is rate-limited, honors the provider's own reset time (not a made-up 1h default).
|
|
51
|
+
- **`pick_agents(n, min_distinct_providers)`** — the primitive for multi-model panels. Fan out 2-4 distinct-provider votes in parallel for jury-style review, self-consistency checks, or your own consensus loop.
|
|
52
|
+
- **Local dashboard** — `external-agents init` opens a loopback page where you paste keys inline, see live provider state, and check usage. Loopback only, never over the network, keys stored at `~/.local/state/external-agents/keys.env` (mode 0600).
|
|
178
53
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
- cursor-agent, opencode, Ollama (local, no quota)
|
|
182
|
-
|
|
183
|
-
Missing a provider? Adding one is a ~15-line YAML addition — see [docs/adding-a-provider.md](docs/adding-a-provider.md). aider (our downstream) supports 100+ providers via LiteLLM.
|
|
184
|
-
|
|
185
|
-
---
|
|
186
|
-
|
|
187
|
-
## Config reference
|
|
188
|
-
|
|
189
|
-
Registry file at `~/.config/external-agents/agents.yaml`:
|
|
190
|
-
|
|
191
|
-
```yaml
|
|
192
|
-
runtime:
|
|
193
|
-
primary_agent: codex # ignored when there's no orchestrator concept
|
|
194
|
-
review_diversity:
|
|
195
|
-
min_distinct_providers: 3
|
|
196
|
-
max_rounds_default: 3
|
|
197
|
-
|
|
198
|
-
telemetry_sink: # optional: pipe events to your own endpoint
|
|
199
|
-
url: https://your-backend.example/telemetry
|
|
200
|
-
headers:
|
|
201
|
-
Authorization: "Bearer {ENV_TOKEN}"
|
|
202
|
-
batch_size: 50
|
|
203
|
-
|
|
204
|
-
agents:
|
|
205
|
-
aider-gemini-flash:
|
|
206
|
-
provider: google
|
|
207
|
-
model: gemini-3-flash-preview
|
|
208
|
-
tier: weak
|
|
209
|
-
auth: "env:GEMINI_API_KEY"
|
|
210
|
-
transports:
|
|
211
|
-
generate_new:
|
|
212
|
-
url: "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions"
|
|
213
|
-
env: GEMINI_API_KEY
|
|
214
|
-
model: gemini-3-flash-preview
|
|
215
|
-
edit_exists: "aider --model gemini/gemini-3-flash-preview"
|
|
216
|
-
# ... more entries
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
Full schema documented in [docs/registry-schema.md](docs/registry-schema.md).
|
|
54
|
+
Your primary agent (Claude Code, Codex, Cursor) gets these as MCP tools automatically after the setup script above.
|
|
220
55
|
|
|
221
56
|
---
|
|
222
57
|
|
|
223
|
-
##
|
|
58
|
+
## Providers in the pool (out of the box, 25 agents)
|
|
224
59
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
60
|
+
| | | |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| **Gemini** (Google AI Studio) | Groq | Cerebras |
|
|
63
|
+
| 7 model variants, per-model quota | 30 rpm, ~500-800 tok/s | 30 rpm, ~2000 tok/s |
|
|
64
|
+
| **OpenRouter** :free | Z.ai (GLM) | Ollama Cloud |
|
|
65
|
+
| 50+ models, 20 rpd, no card | GLM-4.7-flash free | gpt-oss 20B/120B |
|
|
66
|
+
| **DeepSeek** | Anthropic Claude | Codex |
|
|
67
|
+
| Cheap direct API | Subscription (Opus + Sonnet) | Subscription (GPT-5) |
|
|
68
|
+
| **cursor-agent** | **opencode** | **kiro-cli** |
|
|
69
|
+
| CLI agentic reviewer | CLI agentic reviewer | AWS Kiro headless |
|
|
229
70
|
|
|
230
|
-
|
|
71
|
+
Missing a provider? [Suggest it](https://github.com/mrrlin-dev/external-agents/issues/new?labels=missing-model) — the built-in UI has a form that opens a pre-filled issue.
|
|
231
72
|
|
|
232
|
-
|
|
73
|
+
---
|
|
233
74
|
|
|
234
|
-
|
|
75
|
+
## Mrrlin uses this
|
|
235
76
|
|
|
236
|
-
|
|
237
|
-
- A green **"Get free key ↗"** link that opens the provider's signup page in a new tab. Signup is usually 60 seconds and does not ask for a card.
|
|
238
|
-
- **A password input + Save button** — paste the key here and click Save. It persists to `~/.local/state/external-agents/keys.env` (mode 0600, loopback only, never sent anywhere). Enter also submits.
|
|
77
|
+
[Mrrlin](https://mrrlin.com) is the platform this was extracted from. Its consensus gate — every design and every PR diff — runs a 4-reviewer panel: GPT + Gemini over MCP + **two dynamic terminal reviewers pulled from this exact pool** every round. Free-tier terminals mean the gate is essentially free to run on every substantial change, and cross-model diversity beats any single-model reviewer.
|
|
239
78
|
|
|
240
|
-
|
|
79
|
+
You don't need Mrrlin's gate to use the pattern. Build your own reviewer panel, self-consistency check, jury-of-N verifier — the primitives are unopinionated.
|
|
241
80
|
|
|
242
|
-
|
|
81
|
+
---
|
|
243
82
|
|
|
244
|
-
|
|
245
|
-
- Per-row Verify button (re-probes the entry) + Usage link (opens the provider's own billing dashboard for entries that publish one — Gemini, DeepSeek, Z.ai, Ollama Cloud, …)
|
|
246
|
-
- "Missing your model?" form at the bottom — submits a pre-filled GitHub issue on [`mrrlin-dev/external-agents/issues`](https://github.com/mrrlin-dev/external-agents/issues) with label `missing-model`, so requests are visible + trackable (and also logged locally as backup)
|
|
83
|
+
## FAQ
|
|
247
84
|
|
|
248
|
-
|
|
85
|
+
<details>
|
|
86
|
+
<summary><b>Do you send my API keys anywhere?</b></summary>
|
|
249
87
|
|
|
250
|
-
|
|
88
|
+
No. Keys live in `~/.local/state/external-agents/keys.env` (mode 0600, loopback-set) and are read into the MCP server's env at startup. Subscription tokens live where the subscription CLI puts them (`codex login`, `claude login`). Nothing is ever transmitted by `external-agents` itself.
|
|
251
89
|
|
|
252
|
-
|
|
253
|
-
# arg form (bash-history exposed — fine for scripts)
|
|
254
|
-
external-agents set-credential CEREBRAS_API_KEY csk-…
|
|
90
|
+
</details>
|
|
255
91
|
|
|
256
|
-
|
|
257
|
-
|
|
92
|
+
<details>
|
|
93
|
+
<summary><b>How does <code>claude mcp add</code> find <code>external-agents-mcp</code>?</b></summary>
|
|
258
94
|
|
|
259
|
-
|
|
260
|
-
external-agents set-credential CEREBRAS_API_KEY
|
|
261
|
-
```
|
|
95
|
+
`npm i -g` puts a symlink to `external-agents-mcp` on your global bin dir (usually `/opt/homebrew/bin` on macOS, `/usr/local/bin` on Linux). That dir is on your `PATH`. `claude mcp add` writes the literal string `external-agents-mcp` into `~/.claude.json`; when Claude Code starts, it spawns that as a child process — shell PATH resolution finds the binary. No hosting, no daemon, no registry lookup.
|
|
262
96
|
|
|
263
|
-
|
|
97
|
+
</details>
|
|
264
98
|
|
|
265
|
-
|
|
99
|
+
<details>
|
|
100
|
+
<summary><b>How does it handle 429s?</b></summary>
|
|
266
101
|
|
|
267
|
-
|
|
268
|
-
1. external-agents ui # opens http://127.0.0.1:4711
|
|
269
|
-
2. Golden banner shows 3 unlockable providers (Groq, OpenRouter, Cerebras)
|
|
270
|
-
3. Click "Get free key ↗" on Groq → console.groq.com opens, sign up, copy key
|
|
271
|
-
4. Paste key into the row's input, click Save → "✓ persisted to keys.env"
|
|
272
|
-
5. Repeat for OpenRouter, Cerebras
|
|
273
|
-
6. Restart your MCP client (Codex/Claude Code) — banner shrinks, pool grows
|
|
274
|
-
|
|
275
|
-
Total time: ~3 minutes. Result: 7 more free-tier agents active in `pick`.
|
|
276
|
-
```
|
|
102
|
+
Every real call updates state from response headers and error signals. Cooldown honors the provider's own reset time (parsed from `x-ratelimit-reset-*`, `Retry-After`, and error bodies). If Google says "resets in 42h", we wait 42h — not a 1h fallback.
|
|
277
103
|
|
|
278
|
-
|
|
104
|
+
</details>
|
|
279
105
|
|
|
280
|
-
|
|
106
|
+
<details>
|
|
107
|
+
<summary><b>Can I use this with just a subscription (no API keys)?</b></summary>
|
|
281
108
|
|
|
282
|
-
|
|
109
|
+
Yes — Codex subscription and Claude subscription are registered as `cli:*` entries. Zero API-key setup for those cases. Free-tier providers stack on top.
|
|
283
110
|
|
|
284
|
-
|
|
285
|
-
No. API-key credentials live in env vars only (read by aider at spawn time). Subscription tokens live where the subscription CLI keeps them (`codex login`, `claude login`, ...). `external-agents` never persists or transmits your credentials.
|
|
111
|
+
</details>
|
|
286
112
|
|
|
287
|
-
|
|
288
|
-
|
|
113
|
+
<details>
|
|
114
|
+
<summary><b>Is Mrrlin required?</b></summary>
|
|
289
115
|
|
|
290
|
-
**Is Mrrlin required?**
|
|
291
116
|
No. `external-agents` is standalone. Mrrlin uses it internally, but the package works for anyone building a multi-model workflow.
|
|
292
117
|
|
|
293
|
-
|
|
294
|
-
Yes — Codex subscription with `--model` overrides gives you access to cheaper models on the same plan. Same story with Claude when v0.2 lands. Zero API-key setup for those cases.
|
|
295
|
-
|
|
296
|
-
**How do you avoid rate-limit surprises?**
|
|
297
|
-
Every real call updates state from response headers and error signals. Cooldown honors the provider's own reset time (parsed from `x-ratelimit-reset-*`, `Retry-After`, and error bodies). If Google says "resets in 42h", we wait 42h — not a 1h default.
|
|
118
|
+
</details>
|
|
298
119
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
---
|
|
120
|
+
<details>
|
|
121
|
+
<summary><b>What about adding a new provider?</b></summary>
|
|
303
122
|
|
|
304
|
-
|
|
123
|
+
~15-line YAML addition — see [docs/adding-a-provider.md](docs/adding-a-provider.md). aider (used for `edit_exists` transport) supports 100+ providers via LiteLLM.
|
|
305
124
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
We use MIT + DCO (sign-off in commits), no CLA. Bug reports and provider additions are especially appreciated.
|
|
309
|
-
|
|
310
|
-
---
|
|
311
|
-
|
|
312
|
-
## License
|
|
313
|
-
|
|
314
|
-
MIT. See [LICENSE](LICENSE).
|
|
125
|
+
</details>
|
|
315
126
|
|
|
316
127
|
---
|
|
317
128
|
|
|
318
129
|
## About Mrrlin
|
|
319
130
|
|
|
320
|
-
`external-agents` is one piece of [**Mrrlin**](https://mrrlin.com) — an AI orchestration platform for solo developers and small teams.
|
|
321
|
-
|
|
322
|
-
If you like `external-agents`, you'll probably like the rest of Mrrlin.
|
|
323
|
-
|
|
324
|
-
---
|
|
131
|
+
`external-agents` is one piece of [**Mrrlin**](https://mrrlin.com) — an AI orchestration platform for solo developers and small teams. If you like this package, you'll probably like the rest of Mrrlin.
|
|
325
132
|
|
|
133
|
+
## License
|
|
326
134
|
|
|
135
|
+
MIT. Contributions welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).
|
package/cli.js
CHANGED
|
@@ -208,6 +208,35 @@ async function cmdSetCredential(args) {
|
|
|
208
208
|
}
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
+
// `external-agents init` — one-shot setup: launch the UI AND open the default
|
|
212
|
+
// browser to it. Meant for the "just installed the package, what now" moment.
|
|
213
|
+
// The UI process stays foregrounded (Ctrl-C to quit) so the operator can watch
|
|
214
|
+
// key-save events land in stderr.
|
|
215
|
+
function cmdInit(flags) {
|
|
216
|
+
const port = Number(flags.port) || 4711;
|
|
217
|
+
const host = String(flags.host || "127.0.0.1");
|
|
218
|
+
const url = `http://${host}:${port}/`;
|
|
219
|
+
const opener =
|
|
220
|
+
process.platform === "darwin" ? "open" :
|
|
221
|
+
process.platform === "win32" ? "cmd" :
|
|
222
|
+
"xdg-open";
|
|
223
|
+
const openerArgs = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
224
|
+
// Spawn UI first, then open browser after it starts listening.
|
|
225
|
+
const uiPath = path.join(path.dirname(new URL(import.meta.url).pathname), "ui.js");
|
|
226
|
+
const env = { ...process.env, EXTERNAL_AGENTS_UI_PORT: String(port), EXTERNAL_AGENTS_UI_HOST: host };
|
|
227
|
+
const child = spawn(process.execPath, [uiPath], { stdio: "inherit", env });
|
|
228
|
+
// Give the UI ~600ms to bind before opening the browser (loopback listen is
|
|
229
|
+
// usually instantaneous but we do not want the browser to open on a not-yet-
|
|
230
|
+
// bound port).
|
|
231
|
+
setTimeout(() => {
|
|
232
|
+
try { spawn(opener, openerArgs, { stdio: "ignore", detached: true }).unref(); }
|
|
233
|
+
catch { /* browser open is best-effort; UI is still up on ${url} */ }
|
|
234
|
+
}, 600);
|
|
235
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
236
|
+
process.on("SIGINT", () => child.kill("SIGINT"));
|
|
237
|
+
process.on("SIGTERM", () => child.kill("SIGTERM"));
|
|
238
|
+
}
|
|
239
|
+
|
|
211
240
|
// `external-agents ui` — spawn the loopback dashboard (ui.js) inline so the CLI
|
|
212
241
|
// stays the single entry point. ui.js runs its server at top level and blocks;
|
|
213
242
|
// we spawn it as a child so cli.js does not need to import server-lifecycle code
|
|
@@ -234,6 +263,7 @@ switch (subcmd) {
|
|
|
234
263
|
case "probe": cmdProbe(args); break;
|
|
235
264
|
case "stats": cmdStats(flags); break;
|
|
236
265
|
case "ui": cmdUi(flags); break;
|
|
266
|
+
case "init": cmdInit(flags); break;
|
|
237
267
|
case "set-credential": await cmdSetCredential(args); break;
|
|
238
268
|
case "help":
|
|
239
269
|
case "--help":
|
|
@@ -245,6 +275,7 @@ switch (subcmd) {
|
|
|
245
275
|
probe <agent-id>
|
|
246
276
|
stats [--since ISO] [--json]
|
|
247
277
|
ui [--port N] [--host H] # local dashboard for setting keys + inspecting state (default http://127.0.0.1:4711)
|
|
278
|
+
init [--port N] [--host H] # launch UI AND open it in the default browser — the "just installed" one-shot
|
|
248
279
|
set-credential <ENV_NAME> [<value> | -] # persist a key to ~/.local/state/external-agents/keys.env (0600); '-' or omitted = read from stdin`);
|
|
249
280
|
process.exit(subcmd ? 0 : 2);
|
|
250
281
|
default: die(`unknown subcommand: ${subcmd}`, 2);
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/docs/hero.png
ADDED
|
Binary file
|
|
Binary file
|
package/install.sh
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# @mrrlin-dev/external-agents — one-command installer.
|
|
3
|
+
#
|
|
4
|
+
# What it does:
|
|
5
|
+
# 1. Installs the package globally via `npm i -g @mrrlin-dev/external-agents`.
|
|
6
|
+
# 2. Registers the MCP server with EVERY supported host that is on your PATH
|
|
7
|
+
# (Claude Code + Codex). Missing hosts are skipped, not fatal.
|
|
8
|
+
# 3. Launches `external-agents init` — brings the local dashboard up on
|
|
9
|
+
# http://127.0.0.1:4711 and opens it in your default browser so you can
|
|
10
|
+
# paste API keys for the free-tier providers (Groq, Cerebras, OpenRouter).
|
|
11
|
+
#
|
|
12
|
+
# Usage:
|
|
13
|
+
# curl -fsSL https://raw.githubusercontent.com/mrrlin-dev/external-agents/main/install.sh | bash
|
|
14
|
+
#
|
|
15
|
+
# The script is idempotent: re-running it upgrades the npm package and re-runs
|
|
16
|
+
# the MCP-registration one-liners (both hosts' `mcp add` is a no-op when the
|
|
17
|
+
# entry already matches). Nothing is deleted, nothing is elevated.
|
|
18
|
+
set -euo pipefail
|
|
19
|
+
|
|
20
|
+
say() { printf '\033[1m→\033[0m %s\n' "$*" >&2; }
|
|
21
|
+
ok() { printf '\033[32m✓\033[0m %s\n' "$*" >&2; }
|
|
22
|
+
warn() { printf '\033[33m!\033[0m %s\n' "$*" >&2; }
|
|
23
|
+
|
|
24
|
+
# 1) npm install (global)
|
|
25
|
+
if ! command -v npm >/dev/null 2>&1; then
|
|
26
|
+
printf '\033[31m✗\033[0m npm is not on PATH. Install Node.js (>=20) first.\n' >&2
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
say "Installing @mrrlin-dev/external-agents globally via npm…"
|
|
31
|
+
npm install -g @mrrlin-dev/external-agents
|
|
32
|
+
ok "Installed. Binaries: external-agents, external-agents-mcp"
|
|
33
|
+
|
|
34
|
+
# 2) Register the MCP server with every host we can find. Errors are non-fatal
|
|
35
|
+
# because the operator may only use one of them.
|
|
36
|
+
if command -v claude >/dev/null 2>&1; then
|
|
37
|
+
say "Registering with Claude Code (claude mcp add)…"
|
|
38
|
+
claude mcp add external-agents external-agents-mcp 2>/dev/null && ok "Claude Code wired." || warn "claude mcp add returned non-zero (already registered? or CLI is too old). Manual: claude mcp add external-agents external-agents-mcp"
|
|
39
|
+
else
|
|
40
|
+
warn "Claude Code CLI not found on PATH — skipping. Run this yourself later: claude mcp add external-agents external-agents-mcp"
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
if command -v codex >/dev/null 2>&1; then
|
|
44
|
+
say "Registering with Codex CLI (codex mcp add)…"
|
|
45
|
+
codex mcp add external-agents -- external-agents-mcp 2>/dev/null && ok "Codex CLI wired." || warn "codex mcp add returned non-zero (already registered? or CLI is too old). Manual: codex mcp add external-agents -- external-agents-mcp"
|
|
46
|
+
else
|
|
47
|
+
warn "Codex CLI not found on PATH — skipping. Run this yourself later: codex mcp add external-agents -- external-agents-mcp"
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# 3) Launch the dashboard. `init` foregrounds the UI process AND opens the
|
|
51
|
+
# browser; the operator hits Ctrl-C when done.
|
|
52
|
+
say "Launching the local dashboard — paste your free-tier API keys inline…"
|
|
53
|
+
say "(Press Ctrl-C to quit the dashboard once you are done.)"
|
|
54
|
+
exec external-agents init
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrrlin-dev/external-agents",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "One MCP server for every LLM you talk to
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "One MCP server for every LLM you talk to \u2014 direct-API dispatcher across Gemini, DeepSeek, Groq, OpenRouter, Cerebras, and more. Part of mrrlin.com.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server.js",
|
|
7
7
|
"bin": {
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"scripts": {
|
|
12
12
|
"start": "node server.js",
|
|
13
13
|
"ui": "node ui.js",
|
|
14
|
-
"cli": "node cli.js"
|
|
14
|
+
"cli": "node cli.js",
|
|
15
|
+
"postinstall": "node scripts/postinstall-banner.mjs || true"
|
|
15
16
|
},
|
|
16
17
|
"keywords": [
|
|
17
18
|
"mcp",
|
|
@@ -49,7 +50,9 @@
|
|
|
49
50
|
"agents.yaml",
|
|
50
51
|
"docs/",
|
|
51
52
|
"README.md",
|
|
52
|
-
"LICENSE"
|
|
53
|
+
"LICENSE",
|
|
54
|
+
"scripts/",
|
|
55
|
+
"install.sh"
|
|
53
56
|
],
|
|
54
57
|
"dependencies": {
|
|
55
58
|
"@modelcontextprotocol/sdk": "latest",
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Prints a short banner on `npm i -g @mrrlin-dev/external-agents` completion.
|
|
3
|
+
// Skipped when npm runs in a CI or non-interactive context so we do not spam
|
|
4
|
+
// build logs. We stay well within npm etiquette — no auto-launch, no network
|
|
5
|
+
// calls, just a hint.
|
|
6
|
+
if (process.env.CI || process.env.npm_config_production === "true") process.exit(0);
|
|
7
|
+
if (!process.stderr.isTTY) process.exit(0);
|
|
8
|
+
|
|
9
|
+
const G = "\x1b[32m"; // green
|
|
10
|
+
const B = "\x1b[1m"; // bold
|
|
11
|
+
const D = "\x1b[2m"; // dim
|
|
12
|
+
const R = "\x1b[0m"; // reset
|
|
13
|
+
|
|
14
|
+
process.stderr.write(`
|
|
15
|
+
${G}${B}✓ @mrrlin-dev/external-agents installed.${R}
|
|
16
|
+
|
|
17
|
+
${B}Next step (one command):${R} ${G}external-agents init${R}
|
|
18
|
+
${D}↳ opens the local dashboard on http://127.0.0.1:4711${R}
|
|
19
|
+
${D}↳ paste API keys inline for Groq / Cerebras / OpenRouter (free tiers)${R}
|
|
20
|
+
${D}↳ each provider unlocks in ~30 seconds${R}
|
|
21
|
+
|
|
22
|
+
Wire the MCP server into your primary agent:
|
|
23
|
+
${G}claude mcp add external-agents external-agents-mcp${R}
|
|
24
|
+
${G}codex mcp add external-agents -- external-agents-mcp${R}
|
|
25
|
+
|
|
26
|
+
More at ${B}https://github.com/mrrlin-dev/external-agents${R}
|
|
27
|
+
`);
|