@mrrlin-dev/external-agents 0.2.7 → 0.2.9
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 +59 -18
- package/package.json +1 -1
- package/ui.js +17 -6
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
**The core value is economic.** You have separate free-tier quotas at Google + Groq + Cerebras + OpenRouter + Z.ai. `external-agents` treats them as **one pool of tokens** and dispatches to the next healthy bucket. What used to be one 30-req/min Gemini limit is now ~150 req/min across five providers, all at $0 — enough to run entire agentic loops (implementations, reviews, refactors) that would burn $10-100/day on a single paid model.
|
|
8
8
|
|
|
9
|
-
**It's also the substrate for LLM-consensus**
|
|
9
|
+
**It's also the substrate for LLM-consensus** — the "ask N different models the same question, adjudicate" pattern popularized in [LLM-as-a-Judge](https://arxiv.org/abs/2306.05685) work and echoed frequently by [Andrej Karpathy](https://x.com/karpathy) as an "LLM Council" idea: an ensemble of frontier models routinely beats any single one. `pick_agents` gives you N distinct-provider picks in one call, so a "fleet of subagents deliberating in parallel" is one primitive away. **[Mrrlin](https://mrrlin.com) uses exactly this** — its consensus gate resolves two dynamic terminal reviewers from this pool every round, so every design and every diff gets stress-tested by different model families before it merges.
|
|
10
10
|
|
|
11
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
12
|
|
|
@@ -32,9 +32,9 @@ The net effect for us has been **10-100x reduction** in per-task cost for the at
|
|
|
32
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
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
34
|
|
|
35
|
-
### LLM-consensus
|
|
35
|
+
### LLM-consensus — the multi-model panel, made trivial
|
|
36
36
|
|
|
37
|
-
|
|
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
38
|
|
|
39
39
|
```
|
|
40
40
|
ids = pick_agents({ n: 3, min_distinct_providers: 3, exclude_ids: [primary] })
|
|
@@ -42,7 +42,7 @@ outs = Promise.all(ids.map(id => dispatch({ agent_id: id, prompt })))
|
|
|
42
42
|
// three distinct-provider verdicts, ~$0, in one wall-clock round.
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
**Mrrlin's
|
|
45
|
+
**Mrrlin's consensus gate does exactly this.** Every design/spec and every PR diff goes through a 4-reviewer panel — GPT + Gemini (over MCP) + two dynamic terminal reviewers resolved from this pool (`external-agents pick --n 2 --min-distinct-providers 2 --exclude-providers openai,google`). The primary coding agent commits a blind verdict first, then adjudicates the panel. Free-tier terminals mean the gate is essentially free to run on every substantial change.
|
|
46
46
|
|
|
47
47
|
You don't need Mrrlin's gate to use the pattern — the primitives are unopinionated. Build your own reviewer panel, self-consistency check, jury-of-N verifier, whatever fits.
|
|
48
48
|
|
|
@@ -76,9 +76,7 @@ external-agents dispatch "$id" "Summarize this in one line: <paste any text>"
|
|
|
76
76
|
external-agents stats
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
-
Open the local dashboard with `external-agents ui`
|
|
80
|
-
|
|
81
|
-

|
|
79
|
+
Open the local dashboard with `external-agents ui` (see the [UI section](#local-ui) below).
|
|
82
80
|
|
|
83
81
|
---
|
|
84
82
|
|
|
@@ -88,6 +86,8 @@ Once installed, add one block to your MCP client config.
|
|
|
88
86
|
|
|
89
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.
|
|
90
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
91
|
### Claude Code
|
|
92
92
|
|
|
93
93
|
```bash
|
|
@@ -150,7 +150,7 @@ Your primary agent now has these low-level tools (build your own exec/review flo
|
|
|
150
150
|
ids = pick_agents({ n: 3, min_distinct_providers: 3, exclude_ids: [primary] })
|
|
151
151
|
outs = Promise.all(ids.map(id => dispatch({ agent_id: id, prompt })))
|
|
152
152
|
```
|
|
153
|
-
The package is deliberately unopinionated about *what* you compose. Mrrlin uses these primitives for its own
|
|
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.
|
|
154
154
|
|
|
155
155
|
---
|
|
156
156
|
|
|
@@ -220,21 +220,62 @@ Full schema documented in [docs/registry-schema.md](docs/registry-schema.md).
|
|
|
220
220
|
|
|
221
221
|
---
|
|
222
222
|
|
|
223
|
-
## Local UI
|
|
223
|
+
## Local UI — setting up API keys the easy way
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
external-agents ui
|
|
227
|
+
# → external-agents ui: http://127.0.0.1:4711
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Loopback dashboard (never exposed to the network). Two things you want it for:
|
|
231
|
+
|
|
232
|
+
### 1) Golden banner — sign up + paste key inline
|
|
233
|
+
|
|
234
|
+
For every free-tier provider that's currently `needs_auth` (no env var set), a golden row appears at the top with:
|
|
235
|
+
|
|
236
|
+
- What the provider gives you ("Groq: ~500-800 tok/s — fastest on the market"; "Cerebras: ~2000 tok/s, 30 rpm free"; "OpenRouter: 50+ models tagged `:free`"; …)
|
|
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.
|
|
239
|
+
|
|
240
|
+
Once saved, restart your MCP client (Codex / Claude Code) so `external-agents-mcp` re-reads `keys.env` at startup. The provider drops from the banner and joins the pool.
|
|
224
241
|
|
|
225
|
-
|
|
242
|
+
### 2) Registry table + Missing-your-model form
|
|
226
243
|
|
|
227
|
-
|
|
244
|
+
- Every registry entry with a live status badge (healthy / needs_auth / quota_exhausted / rate_limited / not_installed)
|
|
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)
|
|
228
247
|
|
|
248
|
+
### CLI equivalent (if you prefer scripting)
|
|
229
249
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
-
|
|
235
|
-
|
|
250
|
+
Every UI action has a CLI equivalent. To set a key without opening the browser:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
# arg form (bash-history exposed — fine for scripts)
|
|
254
|
+
external-agents set-credential CEREBRAS_API_KEY csk-…
|
|
255
|
+
|
|
256
|
+
# stdin form (nothing in bash-history)
|
|
257
|
+
pbpaste | external-agents set-credential CEREBRAS_API_KEY -
|
|
258
|
+
|
|
259
|
+
# interactive form (typed prompt)
|
|
260
|
+
external-agents set-credential CEREBRAS_API_KEY
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
All three paths write to the same `~/.local/state/external-agents/keys.env` — pick whichever is comfortable.
|
|
264
|
+
|
|
265
|
+
### One-minute walkthrough
|
|
266
|
+
|
|
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
|
+
```
|
|
236
277
|
|
|
237
|
-
|
|
278
|
+
_(TODO: an animated GIF walkthrough belongs here — see [issue #<TBD>](https://github.com/mrrlin-dev/external-agents/issues) for the recording task.)_
|
|
238
279
|
|
|
239
280
|
---
|
|
240
281
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrrlin-dev/external-agents",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"description": "One MCP server for every LLM you talk to — direct-API dispatcher across Gemini, DeepSeek, Groq, OpenRouter, Cerebras, and more. Part of mrrlin.com.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server.js",
|
package/ui.js
CHANGED
|
@@ -242,8 +242,8 @@ async function submitSuggest() {
|
|
|
242
242
|
url ? "**Docs / setup URL:** " + url : "**Docs / setup URL:** _(none provided)_",
|
|
243
243
|
"",
|
|
244
244
|
"---",
|
|
245
|
-
|
|
246
|
-
].join(
|
|
245
|
+
'_Submitted via \\'external-agents ui\\' — the local dashboard\\'s Missing-your-model form._',
|
|
246
|
+
].join('\\n');
|
|
247
247
|
const issueUrl =
|
|
248
248
|
"https://github.com/mrrlin-dev/external-agents/issues/new?" +
|
|
249
249
|
"labels=missing-model" +
|
|
@@ -310,12 +310,23 @@ function renderUnlock(agents) {
|
|
|
310
310
|
const rows = providers.map(p => {
|
|
311
311
|
const m = PROVIDER_META[p] || { label: p, pitch: "", signup: "#", env: "?" };
|
|
312
312
|
const count = missing.filter(a => a.provider === p).length;
|
|
313
|
-
|
|
313
|
+
// Ollama-cloud uses its own CLI (`ollama` — no API key input needed),
|
|
314
|
+
// so its row skips the input + Save and just shows the signup/download link.
|
|
315
|
+
const hasEnvInput = !!m.env && !m.env.startsWith("(");
|
|
316
|
+
const keyInput = hasEnvInput
|
|
317
|
+
? '<input id="k-' + m.env + '" type="password" placeholder="paste ' + m.env + ' here" ' +
|
|
318
|
+
'style="flex:1;min-width:180px;padding:5px 8px;border:1px solid #d4b96b;border-radius:4px;font:inherit;" ' +
|
|
319
|
+
'onkeydown="if(event.key===\\'Enter\\')saveKey(\\'' + m.env + '\\')">' +
|
|
320
|
+
'<button class="primary" onclick="saveKey(\\'' + m.env + '\\')">Save</button>' +
|
|
321
|
+
'<span id="s-' + m.env + '" class="status" style="font-size:12px;"></span>'
|
|
322
|
+
: '<span style="color:#8a7532;font-size:12px;">' + m.env + '</span>';
|
|
323
|
+
return '<div class="row" style="display:grid;grid-template-columns:180px 1fr auto;gap:10px 16px;align-items:start;padding:12px 0;border-top:1px solid #e6d78e;">' +
|
|
314
324
|
'<div><div class="prov">' + m.label + '</div>' +
|
|
315
325
|
'<div style="font-size:11px;color:#8a7532">+' + count + ' model' + (count>1?"s":"") + ' waiting</div></div>' +
|
|
316
|
-
'<div class="desc">' + m.pitch + '</div>' +
|
|
317
|
-
'<div
|
|
318
|
-
'<a class="signup" href="' + m.signup + '" target="_blank" rel="noopener"
|
|
326
|
+
'<div><div class="desc" style="margin-bottom:6px;">' + m.pitch + '</div>' +
|
|
327
|
+
'<div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap;">' + keyInput + '</div></div>' +
|
|
328
|
+
'<a class="signup" href="' + m.signup + '" target="_blank" rel="noopener" ' +
|
|
329
|
+
'style="align-self:center;padding:6px 12px;background:#4a8;color:#fff;text-decoration:none;border-radius:4px;font-size:13px;white-space:nowrap;">Get free key ↗</a>' +
|
|
319
330
|
'</div>';
|
|
320
331
|
}).join("");
|
|
321
332
|
box.innerHTML =
|