trantor 0.17.47 → 0.17.48

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.47"
9
+ "version": "0.17.48"
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.47",
16
+ "version": "0.17.48",
17
17
  "author": {
18
18
  "name": "Sasha Bogojevic"
19
19
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trantor",
3
- "version": "0.17.47",
3
+ "version": "0.17.48",
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/README.md CHANGED
@@ -134,7 +134,7 @@ project takes over with a full window (and a PreCompact hook does this automatic
134
134
  (`solo | scrooge | crew | hybrid`), a routing table with a **reason per package**, why
135
135
  that many seats ("seats follow the work, not the install list"), and a real-money estimate
136
136
  with quota-pool accounting. You say go.
137
- 2. **Windows open.** `trantor up codex kimi deepseek:deepseek opencode:zai-coding-plan` spawns one titled
137
+ 2. **Windows open.** `trantor up codex kimi deepseek:deepseek glm:zai-coding-plan` spawns one titled
138
138
  terminal window per agent. `agent:model` pins a model; `agent:provider --difficulty hard`
139
139
  picks the **best live model** for the work at spawn (capability × cost), enumerated from the
140
140
  CLI itself — never a guessed endpoint. **Serialized and then verified on the bus** — the
@@ -236,7 +236,7 @@ trantor setup | doctor | connect | profile | up <agents…> | swap <old> <new> |
236
236
 
237
237
  `trantor up` notes: `agent:model` pins a model (`deepseek:deepseek-v4-pro`); `agent:provider
238
238
  --task <k> --difficulty <d>` picks the **best live model** for the work at spawn
239
- (`opencode:zai-coding-plan --difficulty hard`); spawns are verified on the bus with one retry;
239
+ (`glm:zai-coding-plan --difficulty hard`); spawns are verified on the bus with one retry;
240
240
  geometry auto-detects the screen you're working on (`CREW_RECT="X,Y,W,H"` to override). `trantor
241
241
  swap <oldAgent> <newSpec>` replaces an exhausted agent with a live-selected one. `trantor down`
242
242
  kills crew processes via their ttys and closes windows without macOS "Terminate?" dialogs.
package/bin/advise.mjs CHANGED
@@ -36,7 +36,7 @@ export const BUILTIN_ROSTER = {
36
36
  codex: { cli: "codex", launch: "codex", session: "codex", provider: "codex" },
37
37
  kimi: { cli: "kimi", launch: "kimi", session: "kimi", provider: "kimi" },
38
38
  deepseek: { cli: "opencode", launch: "deepseek:deepseek", session: "deepseek", provider: "deepseek", providerOc: "deepseek" },
39
- glm: { cli: "opencode", launch: "opencode:zai-coding-plan", session: "opencode", provider: "zai", providerOc: "zai-coding-plan" },
39
+ glm: { cli: "opencode", launch: "glm:zai-coding-plan", session: "glm", provider: "zai", providerOc: "zai-coding-plan" },
40
40
  openrouter: { cli: "opencode", launch: "openrouter:openrouter", session: "openrouter", provider: "openrouter", providerOc: "openrouter" },
41
41
  };
42
42
  // opencode provider ids already claimed by a built-in (so discovery never duplicates them) + the
@@ -193,12 +193,12 @@ export function advise(input, world = loadWorld()) {
193
193
  const seat = roster[r.executor];
194
194
  return {
195
195
  order: i + 1, title: r.title, difficulty: r.difficulty,
196
- // bus identity = the runner's session name (spec's agent part) glm rides the `opencode`
197
- // runner so its session/assignee is `opencode:<project>`, NOT `glm:<project>`.
196
+ // bus identity = the seat's session label (every opencode-driven seat has its OWN label, so
197
+ // glm is `glm:<project>`, openrouter `openrouter:<project>`, a brought provider `<name>:<project>`).
198
198
  assignee: r.executor === "scrooge" || r.executor === "orchestrator" ? undefined : `${seat?.session || r.executor}:<project>`,
199
199
  // launch = the EXACT `trantor up` spec to spawn this seat; the orchestrator runs
200
200
  // `trantor up <launch> --task <task> --difficulty <difficulty>`. Carrying it explicitly is
201
- // what teaches the orchestrator the GLM path (`opencode:zai-coding-plan`) instead of guessing.
201
+ // what teaches the orchestrator the GLM path (`glm:zai-coding-plan`) instead of guessing.
202
202
  launch: ["scrooge", "orchestrator"].includes(r.executor) ? undefined : (seat?.launch || r.executor),
203
203
  // "auto" = resolve a LIVE model at spawn (the launch spec already pins the provider; the
204
204
  // runner picks the best live model for it). Was `<cli>-default` — a stale default.
package/bin/cli.mjs CHANGED
@@ -53,7 +53,7 @@ switch (cmd) {
53
53
  trantor profile declare your plans: trantor profile set claude=max codex=plus deepseek=api
54
54
  trantor provider bring ANY model (BYOM): list seats · add <name> --key … · remove <name>
55
55
  trantor models browse live models behind each seat + the router's pick per difficulty
56
- trantor up … spawn a crew here: trantor up codex kimi deepseek:deepseek opencode:zai-coding-plan
56
+ trantor up … spawn a crew here: trantor up codex kimi deepseek:deepseek glm:zai-coding-plan
57
57
  trantor down tear the crew down (kills processes, closes windows, no dialogs)
58
58
  trantor ui open the live dashboard (board + flow views)
59
59
  trantor catchup "where are we?" — the continuous board + git, with a synthesized brief
package/bin/crew.sh CHANGED
@@ -52,8 +52,9 @@ down() {
52
52
  case "$CMD" in up|swap) ;; *) echo "usage: crew.sh up <agent...> | crew.sh swap <oldAgent> <newAgent[:provider[/model]]> | crew.sh down"; exit 1 ;; esac
53
53
 
54
54
  # --task/--difficulty drive LAZY live-model selection for provider-only specs (agent:provider).
55
- # An agent spec is one of: `codex` (CLI default) · `opencode:zai-coding-plan` (provider only →
56
- # pick the best live model now) · `opencode:zai-coding-plan/glm-5.2` (full pin, used as-is).
55
+ # An agent spec is one of: `codex` (CLI default) · `glm:zai-coding-plan` (provider only → pick the
56
+ # best live model now) · `glm:zai-coding-plan/glm-5.2` (full pin). The agent label is the bus
57
+ # identity; any non-native label runs via opencode (legacy `opencode:zai-coding-plan` still works).
57
58
  TASK="code"; DIFF="medium"; _ARGS=()
58
59
  while [ $# -gt 0 ]; do
59
60
  case "$1" in
package/bin/doctor.mjs CHANGED
@@ -60,7 +60,7 @@ const CLIS = [
60
60
  { name: "gemini (CLI retired 2026-06-18)", bin: "gemini", wired: () => !!read(join(H, ".gemini", "settings.json"))?.mcpServers?.relay, auth: () => existsSync(join(H, ".gemini", "oauth_creds.json")) || !!process.env.GEMINI_API_KEY || !!process.env.GOOGLE_API_KEY, login: "Gemini CLI retired 2026-06-18 (free/Pro/Ultra). Crew seat → GLM (opencode) or Antigravity `agy`. Gemini still serves as a Scrooge cheap-model via GEMINI_API_KEY." },
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
- { 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` },
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 glm:zai-coding-plan/glm-5.1` },
64
64
  // OpenRouter — the BYOM on-ramp: ONE key fronts hundreds of models. Rides opencode; the same
65
65
  // OPENROUTER_API_KEY Scrooge already uses authenticates the crew seat (the runner sources the
66
66
  // .env files). Available the moment the key exists in env/opencode + declared `openrouter=api`.
package/bin/provider.mjs CHANGED
@@ -4,11 +4,14 @@
4
4
  //
5
5
  // trantor provider # list seats: built-in + discovered, with availability + tier
6
6
  // trantor provider add <name> [--key sk-…] [--plan api|coding-plan|max] [--label <bus-name>]
7
+ // [--base-url <url> [--models m1,m2]] # wire a CUSTOM OpenAI-compatible endpoint
7
8
  // trantor provider remove <name> # drop it from your profile (leaves the key in place)
8
9
  //
9
10
  // `add` writes <NAME>_API_KEY to ~/.agent-bus/.env (if --key given), declares the plan in your
10
- // quota profile, verifies opencode can see the provider's models, and prints the seat spec. The
11
- // Advisor then routes to it automatically; run `scrooge-capabilities` so it routes well by difficulty.
11
+ // quota profile, verifies opencode can see the provider's models, and prints the seat spec. For a
12
+ // provider opencode already knows (openrouter, groq, …) the key is enough; for a CUSTOM endpoint,
13
+ // pass --base-url and it writes the opencode.json provider block for you. The Advisor then routes
14
+ // to it automatically; run `scrooge-capabilities` so it routes well by difficulty.
12
15
  import { readFileSync, writeFileSync, existsSync, mkdirSync, chmodSync, appendFileSync } from "node:fs";
13
16
  import { join, dirname } from "node:path";
14
17
  import { homedir } from "node:os";
@@ -23,6 +26,26 @@ const has = (c) => { try { execSync(`command -v ${c}`, { stdio: "ignore", shell:
23
26
  const C = { dim: "\x1b[2m", grn: "\x1b[32m", red: "\x1b[31m", yel: "\x1b[33m", gold: "\x1b[38;5;208m", off: "\x1b[0m" };
24
27
  const envKeyName = (p) => `${String(p).toUpperCase().replace(/[^A-Z0-9]/g, "_")}_API_KEY`;
25
28
 
29
+ const OC_CONFIG = join(H, ".config", "opencode", "opencode.json");
30
+ // Wire a CUSTOM OpenAI-compatible provider into opencode.json (matching opencode's schema +
31
+ // the existing providers' `options.apiKey` style). Merges, never clobbers other providers.
32
+ // `configPath` is injectable so it can be unit-tested against a temp file.
33
+ export function wireOpencodeProvider(name, baseUrl, models, configPath = OC_CONFIG) {
34
+ const cfg = existsSync(configPath) ? read(configPath, {}) : {};
35
+ cfg.$schema ||= "https://opencode.ai/config.json";
36
+ cfg.provider ||= {};
37
+ const block = {
38
+ npm: "@ai-sdk/openai-compatible",
39
+ name: name.charAt(0).toUpperCase() + name.slice(1),
40
+ options: { baseURL: baseUrl, apiKey: `{env:${envKeyName(name)}}` },
41
+ };
42
+ if (models && models.length) block.models = Object.fromEntries(models.map(m => [m, {}]));
43
+ cfg.provider[name] = { ...cfg.provider[name], ...block };
44
+ mkdirSync(dirname(configPath), { recursive: true });
45
+ writeFileSync(configPath, JSON.stringify(cfg, null, 2) + "\n");
46
+ return configPath;
47
+ }
48
+
26
49
  function opencodeModelCount(providerOc) {
27
50
  if (!has("opencode")) return null;
28
51
  try { return execSync(`opencode models ${providerOc} 2>/dev/null`, { encoding: "utf8" }).split("\n").filter(Boolean).length; }
@@ -47,7 +70,7 @@ function listSeats() {
47
70
  }
48
71
 
49
72
  function addProvider(name, opts) {
50
- if (!name) { console.error("usage: trantor provider add <name> [--key sk-…] [--plan api] [--label <bus-name>]"); process.exit(1); }
73
+ if (!name) { console.error("usage: trantor provider add <name> [--key sk-…] [--plan api] [--label <bus-name>] [--base-url <url> [--models m1,m2]]"); process.exit(1); }
51
74
  const provider = name.toLowerCase();
52
75
  const label = (opts.label || provider).toLowerCase().replace(/[^a-z0-9-]/g, "-");
53
76
  const plan = (opts.plan || "api").toLowerCase();
@@ -73,13 +96,20 @@ function addProvider(name, opts) {
73
96
  console.log(`${C.grn}✓${C.off} profile: ${provider}=${plan}`);
74
97
  } catch (e) { console.log(`${C.yel}⚠${C.off} could not set profile (run: trantor profile set ${provider}=${plan})`); }
75
98
 
76
- // 3) verify opencode can see the provider's models
99
+ // 3) custom endpoint write the opencode.json provider block (known providers skip this)
100
+ if (opts.baseUrl) {
101
+ const models = (opts.models || "").split(",").map(s => s.trim()).filter(Boolean);
102
+ const where = wireOpencodeProvider(provider, opts.baseUrl, models);
103
+ console.log(`${C.grn}✓${C.off} wired custom provider '${provider}' → ${where.replace(H, "~")} (baseURL ${opts.baseUrl}${models.length ? `, ${models.length} models` : ""})`);
104
+ }
105
+
106
+ // 4) verify opencode can see the provider's models
77
107
  const n = opencodeModelCount(provider);
78
108
  if (n == null) console.log(`${C.yel}⚠${C.off} opencode not on PATH — install it to run this seat (it's the universal adapter)`);
79
- else if (n === 0) console.log(`${C.yel}⚠${C.off} opencode lists 0 models for '${provider}'. If it's a known provider, the key above is enough; for a custom endpoint, add it to ~/.config/opencode/opencode.json (provider config). Then re-check: trantor models ${provider}`);
109
+ else if (n === 0) console.log(`${C.yel}⚠${C.off} opencode lists 0 models for '${provider}'.${opts.baseUrl ? " Check the baseURL/key, or pass --models m1,m2 to declare them." : " If it's a known provider, the key above is enough; for a CUSTOM endpoint re-run with --base-url <url> [--models m1,m2]."} Re-check: trantor models ${provider}`);
80
110
  else console.log(`${C.grn}✓${C.off} opencode sees ${n} models for '${provider}'`);
81
111
 
82
- // 4) score it for difficulty-aware routing + show the seat
112
+ // 5) score it for difficulty-aware routing + show the seat
83
113
  console.log(`\n${C.gold}Seat ready.${C.off} Launch it:`);
84
114
  console.log(` trantor up ${label === provider ? label : `${label}:${provider}`} ${C.dim}# live-selects the best model for the work${C.off}`);
85
115
  console.log(` trantor up ${label}:${provider}/<model> ${C.dim}# pin a specific model${C.off}`);
@@ -106,6 +136,8 @@ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
106
136
  if (rest[i] === "--key") opts.key = rest[++i];
107
137
  else if (rest[i] === "--plan") opts.plan = rest[++i];
108
138
  else if (rest[i] === "--label") opts.label = rest[++i];
139
+ else if (rest[i] === "--base-url" || rest[i] === "--baseurl") opts.baseUrl = rest[++i];
140
+ else if (rest[i] === "--models") opts.models = rest[++i];
109
141
  else pos.push(rest[i]);
110
142
  }
111
143
  if (!sub || sub === "list") listSeats();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trantor",
3
- "version": "0.17.47",
3
+ "version": "0.17.48",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "trantor": "bin/cli.mjs"
@@ -49,11 +49,11 @@ EXACT launch spec per provider (do not improvise these):
49
49
  | Codex | `codex` (or `codex:gpt-5.5` to pin) | OpenAI CLI |
50
50
  | Kimi | `kimi` | Moonshot coding-plan |
51
51
  | DeepSeek | `deepseek:deepseek` | runs via opencode; `deepseek` alone = CLI default |
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. |
52
+ | **GLM (Z.ai)** | **`glm:zai-coding-plan`** | **runs via opencode, NOT a bare `glm`/`zai` terminal command.** `glm:zai-coding-plan/glm-5.2` pins a model; `glm:zai-coding-plan` live-selects. (Legacy `opencode:zai-coding-plan` still works.) |
53
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). |
54
54
 
55
55
  `agent:provider` live-selects the best model now; `agent:provider/model` pins one. Example:
56
- `trantor up codex kimi deepseek:deepseek opencode:zai-coding-plan --task code --difficulty hard`.
56
+ `trantor up codex kimi deepseek:deepseek glm:zai-coding-plan --task code --difficulty hard`.
57
57
  **Whatever the advisor's `launch` field says, run that verbatim** — the roster above is just the
58
58
  built-in menu, but the advisor picks the right seats/specs for THIS work (a user who's only brought
59
59
  an OpenRouter key gets `openrouter` for everything; one who brought five providers gets a
@@ -66,7 +66,7 @@ advisor routes to a brought provider you don't recognize, that's expected — ru
66
66
 
67
67
  ⚠️ **Gemini CLI is RETIRED (Google killed the free seat 2026-06-18).** The advisor no longer
68
68
  offers it and you must NOT fire up `gemini` — `gemini --yolo` exits 1 and crash-loops on the
69
- bus. Its replacement seat is **GLM via `opencode:zai-coding-plan`**. (Gemini still serves as a
69
+ bus. Its replacement seat is **GLM via `glm:zai-coding-plan`**. (Gemini still serves as a
70
70
  Scrooge cheap-model via `GEMINI_API_KEY` — that's a separate, working path, not a crew seat.)
71
71
  Only a holder of a paid Gemini enterprise key should ever `trantor up gemini`.
72
72