@mrrlin-dev/external-agents 0.3.5 → 0.3.7
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/agents.yaml +18 -2
- package/install.sh +33 -0
- package/lib/state.js +63 -32
- package/package.json +1 -1
package/agents.yaml
CHANGED
|
@@ -366,6 +366,9 @@ agents:
|
|
|
366
366
|
# dispatch that failed. Which of the two is "primary" is decided by the
|
|
367
367
|
# consumer (mrrlin will make this operator-configurable in a later slice);
|
|
368
368
|
# both are registered here so either can serve.
|
|
369
|
+
# Codex family — subscription-backed, two tiers so `escalate_to_pro` has a
|
|
370
|
+
# target. `codex` (default weak-strong) works on every Codex plan; `codex-pro`
|
|
371
|
+
# picks the strongest model on your plan via `-m gpt-5.4-codex`.
|
|
369
372
|
- id: codex
|
|
370
373
|
provider: openai
|
|
371
374
|
model: gpt-5.2-codex
|
|
@@ -376,7 +379,20 @@ agents:
|
|
|
376
379
|
transports:
|
|
377
380
|
edit_exists: "codex exec"
|
|
378
381
|
|
|
379
|
-
- id:
|
|
382
|
+
- id: codex-pro
|
|
383
|
+
provider: openai
|
|
384
|
+
model: gpt-5.4-codex
|
|
385
|
+
tier: strong
|
|
386
|
+
tags: [reasoning]
|
|
387
|
+
auth: "cli:codex"
|
|
388
|
+
usage_url: "https://platform.openai.com/usage"
|
|
389
|
+
transports:
|
|
390
|
+
edit_exists: "codex exec -m gpt-5.4-codex"
|
|
391
|
+
|
|
392
|
+
# Claude family — subscription-backed, two tiers. `claude-opus` is the
|
|
393
|
+
# strongest, `claude-sonnet` (below) is weak-tier for the atomic-executor
|
|
394
|
+
# pool + `/consensus` terminal picks.
|
|
395
|
+
- id: claude-opus
|
|
380
396
|
provider: anthropic
|
|
381
397
|
model: claude-opus-4-7
|
|
382
398
|
tier: strong
|
|
@@ -384,7 +400,7 @@ agents:
|
|
|
384
400
|
auth: "cli:claude"
|
|
385
401
|
usage_url: "https://console.anthropic.com/settings/usage"
|
|
386
402
|
transports:
|
|
387
|
-
edit_exists: "claude --print"
|
|
403
|
+
edit_exists: "claude --print --model claude-opus-4-7"
|
|
388
404
|
|
|
389
405
|
# Weak-tier Sonnet — same Claude subscription, cheaper model. Included in
|
|
390
406
|
# default `pick_agents` runs (weak filter passes it), so `dispatch` /
|
package/install.sh
CHANGED
|
@@ -31,6 +31,39 @@ say "Installing @mrrlin-dev/external-agents globally via npm…"
|
|
|
31
31
|
npm install -g @mrrlin-dev/external-agents
|
|
32
32
|
ok "Installed. Binaries: external-agents, external-agents-mcp"
|
|
33
33
|
|
|
34
|
+
# 1b) aider (Python) — required for the `edit_exists` transport that gives
|
|
35
|
+
# real agentic file-editing behavior across ~100 providers via LiteLLM. It is
|
|
36
|
+
# a separate Python package because npm cannot install Python code. Best-
|
|
37
|
+
# effort: pipx first (isolated, cleanest), then pip3 --user, then a warning.
|
|
38
|
+
# We do NOT fail the install if none of those work — the operator can still
|
|
39
|
+
# use generate_new transport for free-tier providers, and install aider later.
|
|
40
|
+
say "Installing aider (Python — for agentic file-editing transport)…"
|
|
41
|
+
if command -v aider >/dev/null 2>&1; then
|
|
42
|
+
ok "aider already installed: $(aider --version 2>&1 | head -1)"
|
|
43
|
+
elif command -v pipx >/dev/null 2>&1; then
|
|
44
|
+
pipx install aider-chat >/dev/null 2>&1 && ok "aider installed via pipx." || warn "pipx install aider-chat failed. Install manually: pipx install aider-chat"
|
|
45
|
+
elif command -v pip3 >/dev/null 2>&1; then
|
|
46
|
+
# Some distros gate pip3 with PEP 668 externally-managed marker — try
|
|
47
|
+
# --user first, then --break-system-packages as a last resort.
|
|
48
|
+
if pip3 install --user aider-chat >/dev/null 2>&1; then
|
|
49
|
+
ok "aider installed via pip3 --user (add ~/.local/bin to PATH if 'aider' is not found)."
|
|
50
|
+
elif pip3 install --user --break-system-packages aider-chat >/dev/null 2>&1; then
|
|
51
|
+
ok "aider installed via pip3 --user --break-system-packages."
|
|
52
|
+
else
|
|
53
|
+
warn "pip3 install aider-chat failed. Install manually: pip install aider-chat"
|
|
54
|
+
fi
|
|
55
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
56
|
+
python3 -m ensurepip --user >/dev/null 2>&1 || true
|
|
57
|
+
if python3 -m pip install --user aider-chat >/dev/null 2>&1; then
|
|
58
|
+
ok "aider installed via python3 -m pip --user (add ~/.local/bin to PATH if 'aider' is not found)."
|
|
59
|
+
else
|
|
60
|
+
warn "python3 -m pip install aider-chat failed. Install manually: pip install aider-chat"
|
|
61
|
+
fi
|
|
62
|
+
else
|
|
63
|
+
warn "python3 not found on PATH — skipping aider. Install Python 3.10+ then run: pip install aider-chat"
|
|
64
|
+
warn " Free-tier providers (Groq / Cerebras / OpenRouter / Gemini / DeepSeek) still work through the 'generate_new' transport (native fetch — no aider required)."
|
|
65
|
+
fi
|
|
66
|
+
|
|
34
67
|
# 2) Register the MCP server with every host we can find. Errors are non-fatal
|
|
35
68
|
# because the operator may only use one of them.
|
|
36
69
|
if command -v claude >/dev/null 2>&1; then
|
package/lib/state.js
CHANGED
|
@@ -57,25 +57,28 @@ export function writeState(patch) {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
// Probe an agent's usability. Transport-aware — if EITHER transport is usable,
|
|
61
|
+
// the entry is healthy. This is important because most economy-flow entries
|
|
62
|
+
// (Groq, Cerebras, OpenRouter, Gemini, DeepSeek, etc.) work via generate_new
|
|
63
|
+
// (native fetch, no binary needed) and do NOT require aider. Previously the
|
|
64
|
+
// probe short-circuited on aider-missing and marked everything not_installed
|
|
65
|
+
// even when the operator only wanted native fetch — that broke the "just
|
|
66
|
+
// paste API keys and go" flow on any host without aider installed.
|
|
67
|
+
//
|
|
68
|
+
// Precedence:
|
|
69
|
+
// 1. generate_new usable (URL configured + env var set OR Ollama sentinel)
|
|
70
|
+
// → healthy. Aider is not required.
|
|
71
|
+
// 2. edit_exists usable (binary on PATH + auth satisfied) → healthy.
|
|
72
|
+
// 3. Neither → the more informative failure wins (needs_auth > not_installed).
|
|
60
73
|
export function probeInstalled(agentEntry) {
|
|
61
|
-
const cliCmd = agentEntry?.transports?.edit_exists;
|
|
62
|
-
if (!cliCmd || typeof cliCmd !== "string") {
|
|
63
|
-
return { state: "errored_transient", note: "no cli transport" };
|
|
64
|
-
}
|
|
65
|
-
const bin = cliCmd.trim().split(/\s+/)[0];
|
|
66
|
-
const r = spawnSync("command", ["-v", bin], { shell: "/bin/bash" });
|
|
67
|
-
if (r.status !== 0) {
|
|
68
|
-
return { state: "not_installed", note: `binary missing: ${bin}` };
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Binary present. Check auth prerequisites:
|
|
72
|
-
// - per-entry `env` override (with optional @file: refs) OR
|
|
73
|
-
// - `auth: "env:XXX"` — the var must be in process.env
|
|
74
|
-
// If a per-entry env override is present, it takes precedence: we verify
|
|
75
|
-
// every @file: reference resolves. Otherwise fall back to auth-field.
|
|
76
74
|
const auth = agentEntry.auth || "";
|
|
75
|
+
const gen = agentEntry.transports?.generate_new;
|
|
76
|
+
const cli = agentEntry.transports?.edit_exists;
|
|
77
|
+
if (!gen && !cli) return { state: "errored_transient", note: "no transport declared" };
|
|
77
78
|
|
|
78
|
-
|
|
79
|
+
// Resolve per-entry env overrides once (used by both transport checks).
|
|
80
|
+
const envOverrideReady = (() => {
|
|
81
|
+
if (!agentEntry.env || typeof agentEntry.env !== "object") return { ok: true, note: null };
|
|
79
82
|
for (const [k, v] of Object.entries(agentEntry.env)) {
|
|
80
83
|
if (typeof v === "string" && v.startsWith("@file:")) {
|
|
81
84
|
let p = v.slice("@file:".length);
|
|
@@ -84,30 +87,58 @@ export function probeInstalled(agentEntry) {
|
|
|
84
87
|
const s = fs.statSync(p);
|
|
85
88
|
if (!s.isFile()) throw new Error("not a regular file");
|
|
86
89
|
} catch (e) {
|
|
87
|
-
return {
|
|
90
|
+
return { ok: false, note: `env override ${k}: cannot read ${p}` };
|
|
88
91
|
}
|
|
89
92
|
}
|
|
90
93
|
}
|
|
91
|
-
return {
|
|
92
|
-
}
|
|
94
|
+
return { ok: true, note: null };
|
|
95
|
+
})();
|
|
93
96
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
// ---- Attempt (1): generate_new transport is usable ---------------------
|
|
98
|
+
if (gen && gen.url) {
|
|
99
|
+
const genEnv = gen.env;
|
|
100
|
+
const isOllama = genEnv === "OLLAMA_UNUSED_KEY";
|
|
101
|
+
const envSatisfied = isOllama
|
|
102
|
+
|| !genEnv // no env → no auth wall
|
|
103
|
+
|| (agentEntry.env && agentEntry.env[genEnv]) // per-entry override
|
|
104
|
+
|| !!process.env[genEnv]; // process env
|
|
105
|
+
if (envOverrideReady.ok && envSatisfied) {
|
|
106
|
+
return {
|
|
107
|
+
state: "healthy",
|
|
108
|
+
note: isOllama || !genEnv
|
|
109
|
+
? `generate_new ready (no api key required)`
|
|
110
|
+
: `generate_new ready (${genEnv} set)`,
|
|
111
|
+
};
|
|
98
112
|
}
|
|
99
113
|
}
|
|
100
114
|
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
115
|
+
// ---- Attempt (2): edit_exists transport is usable ----------------------
|
|
116
|
+
if (cli && typeof cli === "string") {
|
|
117
|
+
const bin = cli.trim().split(/\s+/)[0];
|
|
118
|
+
const r = spawnSync("command", ["-v", bin], { shell: "/bin/bash" });
|
|
119
|
+
if (r.status === 0) {
|
|
120
|
+
if (!envOverrideReady.ok) return { state: "needs_auth", note: envOverrideReady.note };
|
|
121
|
+
// Auth wall from the top-level `auth:` field (subscription CLI vs env var).
|
|
122
|
+
if (auth.startsWith("env:")) {
|
|
123
|
+
const varName = auth.slice("env:".length).split(/\s+/)[0];
|
|
124
|
+
if (!process.env[varName]) {
|
|
125
|
+
return { state: "needs_auth", note: `env var ${varName} not set (paste via UI or run: external-agents set-credential ${varName})` };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return { state: "healthy", note: `binary present: ${bin}` };
|
|
109
129
|
}
|
|
110
130
|
}
|
|
111
131
|
|
|
112
|
-
|
|
132
|
+
// ---- Both attempts failed — pick the most informative failure ---------
|
|
133
|
+
if (gen && gen.url && gen.env && gen.env !== "OLLAMA_UNUSED_KEY") {
|
|
134
|
+
return {
|
|
135
|
+
state: "needs_auth",
|
|
136
|
+
note: `env var ${gen.env} not set (paste via UI or run: external-agents set-credential ${gen.env})`,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
if (cli) {
|
|
140
|
+
const bin = cli.trim().split(/\s+/)[0];
|
|
141
|
+
return { state: "not_installed", note: `binary missing: ${bin}` };
|
|
142
|
+
}
|
|
143
|
+
return { state: "errored_transient", note: "no usable transport" };
|
|
113
144
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrrlin-dev/external-agents",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
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",
|