@mrrlin-dev/external-agents 0.2.10 → 0.3.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/README.md CHANGED
@@ -1,12 +1,17 @@
1
1
  # @mrrlin-dev/external-agents
2
2
 
3
+ [![Install to Claude Code](https://img.shields.io/badge/Install_to-Claude_Code-4a90e2?style=for-the-badge&logo=anthropic&logoColor=white)](#claude-code)
4
+ [![Install to Codex](https://img.shields.io/badge/Install_to-Codex-24292f?style=for-the-badge&logo=openai&logoColor=white)](#codex)
5
+ [![One-command install](https://img.shields.io/badge/curl_%7C_bash-one_command-4a8?style=for-the-badge)](#one-command-install)
6
+ [![npm](https://img.shields.io/npm/v/@mrrlin-dev/external-agents?style=for-the-badge)](https://www.npmjs.com/package/@mrrlin-dev/external-agents)
7
+
3
8
  **Cut your LLM bill by 10-100x by fanning work across free tiers of a dozen providers.**
4
9
 
5
10
  `external-agents` is an MCP server + CLI that routes work from your primary coding agent (Claude Code, Codex, Cursor) to a **pool of 20+ free-tier LLMs** — Gemini, Groq, Cerebras, OpenRouter :free, Z.ai, Ollama Cloud, and paid tiers of DeepSeek — via one clean surface: round-robin, cooldown-aware, auto-fallback on 429, with a local dashboard for setting keys and tracking usage.
6
11
 
7
12
  **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
13
 
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.
14
+ **It's also the substrate for LLM-consensus** — the "ask N different models the same question, adjudicate" pattern see [karpathy/llm-council](https://github.com/karpathy/llm-council) ("LLM Council works together to answer your hardest questions") and the broader [LLM-as-a-Judge](https://arxiv.org/abs/2306.05685) literature. 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
15
 
11
16
  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
17
 
@@ -34,7 +39,7 @@ The net effect for us has been **10-100x reduction** in per-task cost for the at
34
39
 
35
40
  ### LLM-consensus — the multi-model panel, made trivial
36
41
 
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:
42
+ 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 [karpathy/llm-council](https://github.com/karpathy/llm-council) (Karpathy's own experimental repo — LLM Council answering hard questions collectively), [LLM-as-a-Judge](https://arxiv.org/abs/2306.05685) benchmarks, and [self-consistency decoding](https://arxiv.org/abs/2203.11171). 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
43
 
39
44
  ```
40
45
  ids = pick_agents({ n: 3, min_distinct_providers: 3, exclude_ids: [primary] })
@@ -50,9 +55,19 @@ You don't need Mrrlin's gate to use the pattern — the primitives are unopinion
50
55
 
51
56
  ## Install
52
57
 
58
+ ### One-command install
59
+
60
+ ```bash
61
+ curl -fsSL https://raw.githubusercontent.com/mrrlin-dev/external-agents/main/install.sh | bash
62
+ ```
63
+
64
+ The script (a) `npm i -g`s the package, (b) registers the MCP server with every supported host on your PATH (Claude Code, Codex — missing hosts are skipped, not fatal), then (c) launches the local dashboard so you can paste free-tier API keys inline. Read [`install.sh`](./install.sh) before piping if that flow makes you nervous — it's ~35 lines.
65
+
66
+ ### Manual
67
+
53
68
  ```bash
54
69
  npm install -g @mrrlin-dev/external-agents
55
- external-agents --version
70
+ external-agents init # opens http://127.0.0.1:4711 in your browser
56
71
  ```
57
72
 
58
73
  Requires Node ≥ 20. Works on macOS and Linux. Windows via WSL.
@@ -223,10 +238,13 @@ Full schema documented in [docs/registry-schema.md](docs/registry-schema.md).
223
238
  ## Local UI — setting up API keys the easy way
224
239
 
225
240
  ```bash
226
- external-agents ui
227
- # → external-agents ui: http://127.0.0.1:4711
241
+ external-agents init # launches UI + opens the browser (recommended for first-time setup)
242
+ # or
243
+ external-agents ui # launches UI only; open http://127.0.0.1:4711 yourself
228
244
  ```
229
245
 
246
+ ![external-agents dashboard walkthrough — banner shows 3 free-tier providers waiting; paste key, click Save, get confirmation](docs/ui-walkthrough.gif)
247
+
230
248
  Loopback dashboard (never exposed to the network). Two things you want it for:
231
249
 
232
250
  ### 1) Golden banner — sign up + paste key inline
@@ -275,7 +293,6 @@ All three paths write to the same `~/.local/state/external-agents/keys.env` —
275
293
  Total time: ~3 minutes. Result: 7 more free-tier agents active in `pick`.
276
294
  ```
277
295
 
278
- _(TODO: an animated GIF walkthrough belongs here — see [issue #<TBD>](https://github.com/mrrlin-dev/external-agents/issues) for the recording task.)_
279
296
 
280
297
  ---
281
298
 
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
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.2.10",
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.",
3
+ "version": "0.3.0",
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
+ `);