trantor 0.17.44 → 0.17.45

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.
@@ -6,14 +6,14 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Trantor — the hub-world for AI agent crews: live message bus, presence, project Kanban/flow board + context-handoff for independent AI coding agents (Claude, Codex, Gemini, …)",
9
- "version": "0.17.44"
9
+ "version": "0.17.45"
10
10
  },
11
11
  "plugins": [
12
12
  {
13
13
  "name": "trantor",
14
14
  "source": "./",
15
15
  "description": "The hub-world for AI agent crews. Say \"fire up the crew\" and Claude becomes the architect: a plan-aware Advisor routes the work (solo / cheap inline calls / live crew of Codex, GLM, Kimi & DeepSeek in their own terminal windows), a Kanban/flow command center with a testing gate tracks it, and an economics brain (Scrooge) keeps the receipts. Includes the relay MCP, a SessionStart auto-discovery hook, and a PreCompact context-handoff so a fresh session can take over a full window instead of compacting.",
16
- "version": "0.17.44",
16
+ "version": "0.17.45",
17
17
  "author": {
18
18
  "name": "Sasha Bogojevic"
19
19
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trantor",
3
- "version": "0.17.44",
3
+ "version": "0.17.45",
4
4
  "description": "Trantor — the hub-world for AI agent crews: live message bus, presence, project Kanban/flow board + crew orchestration for independent AI coding agents (Claude, Codex, Gemini, Kimi, DeepSeek)",
5
5
  "mcpServers": {
6
6
  "relay": {
package/bin/advise.mjs CHANGED
@@ -35,6 +35,14 @@ export const ROSTER = {
35
35
  kimi: { cli: "kimi", launch: "kimi", session: "kimi", provider: "kimi" },
36
36
  deepseek: { cli: "opencode", launch: "deepseek:deepseek", session: "deepseek", provider: "deepseek" },
37
37
  glm: { cli: "opencode", launch: "opencode:zai-coding-plan", session: "opencode", provider: "zai" },
38
+ // OpenRouter = the BYOM on-ramp: one key fronts hundreds of models (incl. vendors with no
39
+ // CLI of their own). Rides the opencode runner like glm/deepseek, but under its OWN bus label
40
+ // `openrouter` (distinct session, never collides with the glm `opencode` seat). The launcher
41
+ // live-selects the best OpenRouter model for the work's difficulty (or pin one:
42
+ // `openrouter:openrouter/<vendor>/<model>`). The model picked is what determines its strength,
43
+ // so it sits LAST in every CREW_PREF tier — a wildcard that fills once the proven native seats
44
+ // are taken, and the ONLY seat for a user who brought nothing but an OpenRouter key.
45
+ openrouter: { cli: "opencode", launch: "openrouter:openrouter", session: "openrouter", provider: "openrouter" },
38
46
  };
39
47
 
40
48
  export function loadWorld() {
@@ -42,12 +50,17 @@ export function loadWorld() {
42
50
  const registry = read(join(H, ".token-scrooge", "registry.json"), { models: {}, tasks: {} });
43
51
  const caps = read(join(H, ".token-scrooge", "capabilities.json"), {});
44
52
  const has = (c) => { try { execSync(`command -v ${c}`, { stdio: "ignore", shell: "/bin/sh" }); return true; } catch { return false; } };
45
- const opencodeKey = () => !!read(join(H, ".config", "opencode", "opencode.json"), {})?.provider?.["zai-coding-plan"]?.options?.apiKey;
53
+ const opencodeKey = (prov) => !!read(join(H, ".config", "opencode", "opencode.json"), {})?.provider?.[prov]?.options?.apiKey;
54
+ // a key the user already has for Scrooge counts too — the opencode runner sources these .env
55
+ // files, so OPENROUTER_API_KEY in ~/.token-scrooge/.env lights up the crew seat with no extra setup.
56
+ const envHasKey = (k) => !!process.env[k] || [join(H, ".token-scrooge", ".env"), join(H, ".agent-bus", ".env")]
57
+ .some(f => { try { return readFileSync(f, "utf8").includes(k); } catch { return false; } });
46
58
  // a seat is available only if its CLI exists AND its provider is actually set up — a present
47
- // binary with a dead/missing seat (gemini, or opencode with no zai key) must NOT be recommended.
59
+ // binary with a dead/missing seat (gemini, or opencode with no provider key) must NOT be recommended.
48
60
  const hasSeat = (tok) => {
49
61
  const r = ROSTER[tok]; if (!r || !has(r.cli)) return false;
50
- if (tok === "glm") return !!profile?.providers?.zai || opencodeKey();
62
+ if (tok === "glm") return !!profile?.providers?.zai || opencodeKey("zai-coding-plan");
63
+ if (tok === "openrouter") return !!profile?.providers?.openrouter || opencodeKey("openrouter") || envHasKey("OPENROUTER_API_KEY");
51
64
  return true;
52
65
  };
53
66
  const agents = Object.keys(ROSTER).filter(hasSeat);
@@ -70,7 +83,7 @@ export function scroogeModelFor(registry, caps, kind = "code", difficulty = "eas
70
83
  const FORECAST = { easy: 0.3e6, medium: 1.5e6, hard: 6e6 }; // tokens
71
84
  // crew agent preference per difficulty: frontier subs take hard, cheap takes easy.
72
85
  // (gemini retired → its slot goes to glm, a strong coding-plan seat on $0 marginal quota.)
73
- const CREW_PREF = { hard: ["codex", "glm", "kimi", "deepseek"], medium: ["kimi", "glm", "codex", "deepseek"], easy: ["deepseek", "kimi", "glm", "codex"] };
86
+ const CREW_PREF = { hard: ["codex", "glm", "kimi", "deepseek", "openrouter"], medium: ["kimi", "glm", "codex", "deepseek", "openrouter"], easy: ["deepseek", "kimi", "glm", "codex", "openrouter"] };
74
87
 
75
88
  export function advise(input, world = loadWorld()) {
76
89
  const { profile, registry, caps, agents, scrooge } = world;
@@ -117,11 +130,15 @@ export function advise(input, world = loadWorld()) {
117
130
  const m = registry.models?.["deepseek-v4-flash"] || { cost_in: 0.14, cost_out: 0.28 };
118
131
  est = +((FORECAST[p.difficulty] * 0.85 * m.cost_in + FORECAST[p.difficulty] * 0.15 * m.cost_out) / 1e6).toFixed(2);
119
132
  }
120
- const why_r = p.difficulty === "hard"
133
+ let why_r = p.difficulty === "hard"
121
134
  ? `hard → strongest available coder (${agent}); its ${pool} pool means ${pool === "api" ? "real $ but cheapest capable" : "$0 marginal on existing quota"}`
122
135
  : p.difficulty === "medium"
123
136
  ? `medium → solid mid-tier (${agent}) keeps frontier seats free for hard work; ${pool === "api" ? "metered" : "quota"} pool`
124
137
  : `easy → cheapest seat (${agent})`;
138
+ // OpenRouter live-select ranks by COST only (the 335-model catalog has no capability scores
139
+ // yet — that's the capability-ingestion follow-up), so for HARD work it can land a cheap model.
140
+ // Flag it: pin a strong model explicitly (openrouter:openrouter/<vendor>/<model>) for hard work.
141
+ if (agent === "openrouter" && p.difficulty === "hard") why_r += ` — ⚠️ live-select ranks by cost; PIN a strong model (e.g. openrouter:openrouter/anthropic/claude-opus-latest) for hard work until capability data lands`;
125
142
  return { ...p, executor: agent, pool, est_cost_usd: est, reason: why_r };
126
143
  });
127
144
  // crew-size rationale: seats are EMERGENT from the work, and we say so
@@ -69,6 +69,12 @@ const CLI = {
69
69
  next: `opencode run -c{M} "$(cat {P})"`, mflag: " -m ", env: join(homedir(), ".token-scrooge", ".env") },
70
70
  opencode: { first: `opencode run{M} "$(cat {P})"`,
71
71
  next: `opencode run -c{M} "$(cat {P})"`, mflag: " -m ", env: join(homedir(), ".token-scrooge", ".env") },
72
+ // OpenRouter rides the opencode CLI exactly like deepseek/glm, but under its OWN agent label so
73
+ // its bus identity is `openrouter:<project>` (RELAY_AGENT is set per-spawn) — never colliding with
74
+ // the glm `opencode` seat. Model ids come pre-qualified (`openrouter/<vendor>/<model>`). Sources
75
+ // the token-scrooge .env so an existing OPENROUTER_API_KEY authenticates with no extra wiring.
76
+ openrouter: { first: `opencode run{M} "$(cat {P})"`,
77
+ next: `opencode run -c{M} "$(cat {P})"`, mflag: " -m ", env: join(homedir(), ".token-scrooge", ".env") },
72
78
  claude: { first: `claude{M} -p "$(cat {P})" --dangerously-skip-permissions`,
73
79
  next: `claude -c{M} -p "$(cat {P})" --dangerously-skip-permissions`, mflag: " --model " },
74
80
  };
package/bin/crew.sh CHANGED
@@ -75,7 +75,7 @@ SCROOGE="$BUS_DIR/engine/bin/scrooge"
75
75
  resolve_model() {
76
76
  local agent="$1" provider="$2" task="$3" diff="$4" cands="" out=""
77
77
  case "$agent" in
78
- opencode|deepseek)
78
+ opencode|deepseek|openrouter)
79
79
  cands="$(opencode models "$provider" 2>/dev/null | tr '\n' ' ')"
80
80
  [ -n "$cands" ] || { echo "[crew] no live models via 'opencode models $provider' — CLI default" >&2; return 0; }
81
81
  out="$(python3 "$SCROOGE" route --candidates "$cands" -t "$task" -d "$diff" --json 2>/dev/null)" ;;
@@ -133,6 +133,9 @@ spawn_grid() { # $@ = agents — (re)computes the grid for THIS batch and spawn
133
133
  for SPEC in "$@"; do
134
134
  AGENT="${SPEC%%:*}" # agent[:provider[/model]] — model rides in as CREW_MODEL
135
135
  FIELD=""; [ "$SPEC" != "$AGENT" ] && FIELD="${SPEC#*:}"
136
+ # `trantor up openrouter` (no provider) → default the opencode provider to openrouter so it
137
+ # live-selects from the OpenRouter catalog instead of falling back to opencode's default model.
138
+ [ "$AGENT" = "openrouter" ] && [ -z "$FIELD" ] && FIELD="openrouter"
136
139
  MODEL=""
137
140
  if [ -n "$FIELD" ]; then
138
141
  case "$FIELD" in
package/bin/doctor.mjs CHANGED
@@ -61,6 +61,10 @@ const CLIS = [
61
61
  { name: "kimi", bin: "kimi", wired: () => !!read(join(H, ".kimi", "mcp.json"))?.mcpServers?.relay, auth: () => existsSync(join(H, ".kimi", "credentials")), login: "kimi → /login (Kimi account or Moonshot API key)" },
62
62
  { name: "deepseek (via opencode)", bin: "opencode", wired: () => !!read(join(H, ".config", "opencode", "opencode.json"))?.mcp?.relay, auth: () => !!process.env.DEEPSEEK_API_KEY || (existsSync(join(H, ".agent-bus", ".env")) && readFileSync(join(H, ".agent-bus", ".env"), "utf8").includes("DEEPSEEK_API_KEY")) || !!read(join(H, ".local", "share", "opencode", "auth.json")), login: `get a key at platform.deepseek.com, then: echo 'DEEPSEEK_API_KEY=sk-…' >> ~/.agent-bus/.env` },
63
63
  { name: "glm (via opencode · coding plan)", bin: "opencode", wired: () => !!read(join(H, ".config", "opencode", "opencode.json"))?.mcp?.relay, auth: () => !!read(join(H, ".config", "opencode", "opencode.json"))?.provider?.["zai-coding-plan"]?.options?.apiKey, login: `put your Z.ai coding-plan key at ~/.config/opencode/opencode.json → provider["zai-coding-plan"].options.apiKey, then seat: trantor up opencode:zai-coding-plan/glm-5.1` },
64
+ // OpenRouter — the BYOM on-ramp: ONE key fronts hundreds of models. Rides opencode; the same
65
+ // OPENROUTER_API_KEY Scrooge already uses authenticates the crew seat (the runner sources the
66
+ // .env files). Available the moment the key exists in env/opencode + declared `openrouter=api`.
67
+ { name: "openrouter (via opencode · BYOM, hundreds of models)", bin: "opencode", wired: () => !!read(join(H, ".config", "opencode", "opencode.json"))?.mcp?.relay, auth: () => !!process.env.OPENROUTER_API_KEY || !!read(join(H, ".config", "opencode", "opencode.json"))?.provider?.openrouter?.options?.apiKey || [join(H, ".token-scrooge", ".env"), join(H, ".agent-bus", ".env")].some(f => { try { return readFileSync(f, "utf8").includes("OPENROUTER_API_KEY"); } catch { return false; } }), login: `get a key at openrouter.ai/keys, then: echo 'OPENROUTER_API_KEY=sk-or-…' >> ~/.agent-bus/.env && trantor profile set openrouter=api. Seat: trantor up openrouter (live-selects) or pin trantor up openrouter:openrouter/<vendor>/<model>` },
64
68
  ];
65
69
  let installed = 0;
66
70
  for (const c of CLIS) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trantor",
3
- "version": "0.17.44",
3
+ "version": "0.17.45",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "trantor": "bin/cli.mjs"
@@ -50,9 +50,15 @@ EXACT launch spec per provider (do not improvise these):
50
50
  | Kimi | `kimi` | Moonshot coding-plan |
51
51
  | DeepSeek | `deepseek:deepseek` | runs via opencode; `deepseek` alone = CLI default |
52
52
  | **GLM (Z.ai)** | **`opencode:zai-coding-plan`** | **runs via opencode, NOT a bare `glm`/`zai` terminal command.** `opencode:zai-coding-plan/glm-5.2` pins a model; `opencode:zai-coding-plan` live-selects. |
53
+ | **OpenRouter (BYOM)** | **`openrouter`** | **the bring-your-own-model on-ramp — one key fronts hundreds of vendors (incl. ones with no CLI).** Bare `openrouter` live-selects the best OpenRouter model for the difficulty; pin one with `openrouter:openrouter/<vendor>/<model>`. Its own bus identity `openrouter:<project>` (never collides with the GLM `opencode` seat). |
53
54
 
54
55
  `agent:provider` live-selects the best model now; `agent:provider/model` pins one. Example:
55
56
  `trantor up codex kimi deepseek:deepseek opencode:zai-coding-plan --task code --difficulty hard`.
57
+ **Whatever the advisor's `launch` field says, run that verbatim** — the roster above is the menu,
58
+ but the advisor already picked the right seats and specs for THIS work (a user who's only brought
59
+ an OpenRouter key, for instance, gets `openrouter` for everything; one who brought five providers
60
+ gets a load-balanced spread). New providers a user brings (OpenRouter today; any opencode-supported
61
+ vendor next) appear automatically once declared via `trantor profile set <name>=api` + a key.
56
62
 
57
63
  ⚠️ **Gemini CLI is RETIRED (Google killed the free seat 2026-06-18).** The advisor no longer
58
64
  offers it and you must NOT fire up `gemini` — `gemini --yolo` exits 1 and crash-loops on the