glm-coding-router 2.0.0 → 2.1.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/LICENSE +21 -21
- package/README.md +542 -534
- package/dist/cli.js +8 -3
- package/dist/commands/doctor-auth.js +107 -0
- package/dist/commands/doctor-command.js +171 -41
- package/dist/commands/landing.js +47 -0
- package/dist/commands/status.js +28 -15
- package/dist/commands/usage.js +33 -18
- package/dist/core/key-inspector.js +45 -0
- package/dist/core/user-env.js +17 -7
- package/dist/core/zai-quota.js +110 -8
- package/dist/templates/agents-block.js +53 -53
- package/dist/templates/claude-block.js +56 -56
- package/dist/tui/command-ui.js +158 -0
- package/dist/tui/render.js +70 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,534 +1,542 @@
|
|
|
1
|
-
# GLM Coding Router
|
|
2
|
-
|
|
3
|
-
GLM Coding Plan workers for Claude Code and Codex — on Windows, Linux, and (experimentally) macOS.
|
|
4
|
-
|
|
5
|
-
Claude Code and Codex stay your orchestrators — they keep responsibility for requirements,
|
|
6
|
-
architecture, review, and integration. `glm-coding-router` delegates well-scoped
|
|
7
|
-
implementation work (exploration, CRUD, boilerplate, tests, mechanical refactoring) to
|
|
8
|
-
GLM workers via Z.ai's Anthropic-compatible endpoint.
|
|
9
|
-
|
|
10
|
-
One global npm install replaces the manual `.cmd` shim setup:
|
|
11
|
-
|
|
12
|
-
```text
|
|
13
|
-
Claude / Codex → shell → glm-worker → claude.exe harness → Z.ai endpoint → GLM Coding Plan
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
## Architecture
|
|
17
|
-
|
|
18
|
-
```text
|
|
19
|
-
Developer
|
|
20
|
-
│
|
|
21
|
-
┌───────────────┴───────────────┐
|
|
22
|
-
▼ ▼
|
|
23
|
-
Claude Code Codex
|
|
24
|
-
│ │
|
|
25
|
-
└───────────────┬───────────────┘
|
|
26
|
-
shell command
|
|
27
|
-
│
|
|
28
|
-
┌───────────────┼───────────────┐
|
|
29
|
-
▼ ▼ ▼
|
|
30
|
-
glm-chat glm-fast glm-worker glm-review
|
|
31
|
-
│ │ │ │
|
|
32
|
-
└─────────┴──────────┴──────────┘
|
|
33
|
-
claude.exe
|
|
34
|
-
(injected environment only)
|
|
35
|
-
│
|
|
36
|
-
▼
|
|
37
|
-
https://api.z.ai/api/anthropic
|
|
38
|
-
│
|
|
39
|
-
▼
|
|
40
|
-
GLM Coding Plan
|
|
41
|
-
GLM-5.3 / GLM-5.3-Flash
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
For headless worker/review runs, the router also consumes Claude Code's stream-json output,
|
|
45
|
-
records a provider-neutral event history, and renders progress on stderr. Interactive
|
|
46
|
-
`glm-chat` / `glm-fast` sessions keep the direct pass-through path shown above.
|
|
47
|
-
|
|
48
|
-
## Requirements
|
|
49
|
-
|
|
50
|
-
- Windows 10/11 or Linux (both verified); macOS is experimental — the suite has not been
|
|
51
|
-
run on a Mac
|
|
52
|
-
- Node.js >= 20
|
|
53
|
-
- Claude Code (`claude.exe`) — the GLM commands run on the Claude Code harness
|
|
54
|
-
- Codex (optional — Claude-only setups are fully supported)
|
|
55
|
-
- A Z.ai Coding Plan API key
|
|
56
|
-
|
|
57
|
-
No Anthropic pay-as-you-go, no OpenAI API, no LiteLLM, no proxy.
|
|
58
|
-
|
|
59
|
-
## Installation
|
|
60
|
-
|
|
61
|
-
```powershell
|
|
62
|
-
npm install -g glm-coding-router
|
|
63
|
-
glm-router init
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
`npx glm-coding-router init` also works for a one-off check, but the global install is
|
|
67
|
-
what puts `glm-worker` on your PATH long-term.
|
|
68
|
-
|
|
69
|
-
## Quick start
|
|
70
|
-
|
|
71
|
-
After `glm-router init`:
|
|
72
|
-
|
|
73
|
-
```powershell
|
|
74
|
-
glm-chat
|
|
75
|
-
glm-fast
|
|
76
|
-
glm-worker "Implement validation and add tests"
|
|
77
|
-
glm-review "Analyze the auth module"
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
## glm-chat
|
|
81
|
-
|
|
82
|
-
Interactive GLM-backed Claude Code session. Resolves the Z.ai key, locates `claude.exe`,
|
|
83
|
-
injects the Z.ai environment **into the child process only**, and spawns it with
|
|
84
|
-
pass-through arguments:
|
|
85
|
-
|
|
86
|
-
```powershell
|
|
87
|
-
glm-chat
|
|
88
|
-
glm-chat --version
|
|
89
|
-
glm-chat --any-claude-flag
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
Your normal `claude` command and its authentication are untouched.
|
|
93
|
-
|
|
94
|
-
## glm-fast
|
|
95
|
-
|
|
96
|
-
Interactive GLM-backed session pinned to the **fast model** (`models.fast`,
|
|
97
|
-
`glm-5.3-flash` by default) — every model slot in the child environment maps to
|
|
98
|
-
it, so whichever tier Claude Code picks, it gets the fast model. Same pass-through
|
|
99
|
-
arguments as `glm-chat`:
|
|
100
|
-
|
|
101
|
-
```powershell
|
|
102
|
-
glm-fast
|
|
103
|
-
glm-fast --profile air
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
## glm-worker
|
|
107
|
-
|
|
108
|
-
Headless implementation worker:
|
|
109
|
-
|
|
110
|
-
```powershell
|
|
111
|
-
glm-worker "Implement validation and add tests"
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
Or via stdin (a structured task packet):
|
|
115
|
-
|
|
116
|
-
```powershell
|
|
117
|
-
@"
|
|
118
|
-
TASK:
|
|
119
|
-
Implement refresh token validation.
|
|
120
|
-
|
|
121
|
-
SCOPE:
|
|
122
|
-
internal/auth/
|
|
123
|
-
|
|
124
|
-
VALIDATION:
|
|
125
|
-
go test ./internal/auth/...
|
|
126
|
-
"@ | glm-worker
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
Input priority: **arguments → stdin → error**. Arguments win, and stdin is not even
|
|
130
|
-
read when they carry a prompt — waiting for EOF on a pipe that never closes (an agent
|
|
131
|
-
harness, CI, `nohup`) would hang the run before it started. The worker runs with
|
|
132
|
-
`--max-turns 20 --permission-mode acceptEdits --tools Read,Glob,Grep,Edit,Write,Bash`.
|
|
133
|
-
It never uses `--dangerously-skip-permissions`.
|
|
134
|
-
|
|
135
|
-
Routing flags (v2): `--model main|fast` pins the config slot for this run (it does not
|
|
136
|
-
bypass an enforced refusal), `--force` overrides one, `--refresh-quota` re-reads the
|
|
137
|
-
Z.ai quota instead of the 60 s cache — see [Quota-aware routing](#quota-aware-routing-v2).
|
|
138
|
-
Like `--profile`, they belong to the wrapper and are consumed before the prompt is read.
|
|
139
|
-
|
|
140
|
-
## glm-review
|
|
141
|
-
|
|
142
|
-
Read-only worker for repository exploration, call-graph discovery, duplicate detection,
|
|
143
|
-
dependency inspection, and preliminary review:
|
|
144
|
-
|
|
145
|
-
```powershell
|
|
146
|
-
glm-review "Inspect this repository"
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
Runs with `--tools Read,Glob,Grep --strict-mcp-config` — it cannot edit files or run
|
|
150
|
-
commands.
|
|
151
|
-
|
|
152
|
-
The second flag is part of the guarantee, not a detail: `--tools` restricts only Claude
|
|
153
|
-
Code's **built-in** tools, so without it a review session would also inherit whatever MCP
|
|
154
|
-
servers you have registered — including this project's own, whose `glm_worker` tool writes
|
|
155
|
-
files. `glm-worker` and `glm-router benchmark` are isolated the same way. Interactive
|
|
156
|
-
sessions (`glm-chat`, `glm-fast`) are not: your servers are yours.
|
|
157
|
-
|
|
158
|
-
## Profiles
|
|
159
|
-
|
|
160
|
-
All four task binaries (`glm-chat`, `glm-fast`, `glm-worker`, `glm-review`)
|
|
161
|
-
accept `--profile <name>` to overlay saved model/maxTurns settings. Profiles
|
|
162
|
-
live in `config.json`:
|
|
163
|
-
|
|
164
|
-
```json
|
|
165
|
-
{
|
|
166
|
-
"profiles": {
|
|
167
|
-
"test": { "workerMaxTurns": 10, "fast": "glm-5.3-flash" },
|
|
168
|
-
"frontend": { "main": "glm-5.3", "reviewMaxTurns": 30 }
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
```powershell
|
|
174
|
-
glm-worker --profile test "Add failing test then fix it"
|
|
175
|
-
glm-review --profile frontend "Review the component tree"
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
Fields (all optional): `main`, `fast`, `workerMaxTurns`, `reviewMaxTurns`.
|
|
179
|
-
Unknown profile names fail with `ERROR [11]` listing the available ones.
|
|
180
|
-
Note: `--profile` belongs to these wrappers — it shadows Claude Code's own
|
|
181
|
-
`--profile` flag inside them.
|
|
182
|
-
|
|
183
|
-
## delegate
|
|
184
|
-
|
|
185
|
-
Run a GLM worker in an **isolated git worktree** so parallel tasks never trample
|
|
186
|
-
each other's working tree (`glm-router delegate backend|frontend|tests`):
|
|
187
|
-
|
|
188
|
-
```powershell
|
|
189
|
-
glm-router delegate backend "Implement refresh token validation in internal/auth"
|
|
190
|
-
Get-Content task.md | glm-router delegate auth-refresh
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
Each run creates a worktree at `<repo>.glm-worktrees\<name>` (outside the repo,
|
|
194
|
-
so your checkout's status stays clean) on a new branch `glm/delegate/<name>`
|
|
195
|
-
cut from `HEAD`, and runs the standard `glm-worker` inside it. The worktree and
|
|
196
|
-
branch are **kept** after the run — the tool never commits, merges, or deletes
|
|
197
|
-
your work; the footer prints the path and the merge command:
|
|
198
|
-
|
|
199
|
-
```text
|
|
200
|
-
[glm-router] worktree kept at D:\code\my-repo.glm-worktrees\backend
|
|
201
|
-
[glm-router] next: inspect it, then merge glm/delegate/backend (or discard with git worktree remove)
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
- Prompt priority is arguments → stdin, same as `glm-worker`.
|
|
205
|
-
- Profiles: `--profile test` explicitly, or — when omitted — a profile literally
|
|
206
|
-
named after the delegate (`delegate test` → the `test` profile) if one exists.
|
|
207
|
-
- `--remove` deletes the worktree **after a successful run only**; plain
|
|
208
|
-
`git worktree remove` is used, so git refuses (and the worktree is kept) when
|
|
209
|
-
the worker left uncommitted changes. The branch is always kept.
|
|
210
|
-
- Pre-flight checks fail fast (`ERROR [31]`) when the branch or directory
|
|
211
|
-
already exists, or the repo has no commits yet; outside a git repo →
|
|
212
|
-
`ERROR [30]`. Uncommitted changes in your main checkout are **not** visible
|
|
213
|
-
to the worker — it starts from the last commit.
|
|
214
|
-
- `--dry-run` prints the plan; `--json` prints pre-flight and result objects.
|
|
215
|
-
- Run several delegates concurrently — distinct names cannot collide:
|
|
216
|
-
|
|
217
|
-
```powershell
|
|
218
|
-
glm-router delegate backend "Task A" # terminal 1
|
|
219
|
-
glm-router delegate tests "Task B" # terminal 2
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
## benchmark
|
|
223
|
-
|
|
224
|
-
Measure the Claude Code + GLM stack on built-in coding tasks (spec §54 v0.4).
|
|
225
|
-
Each task runs in a throwaway temp directory: the router writes the task files,
|
|
226
|
-
spawns the standard GLM worker (same env injection, plus `--output-format json`
|
|
227
|
-
to capture the result document), then runs the task's validation command:
|
|
228
|
-
|
|
229
|
-
```powershell
|
|
230
|
-
glm-router benchmark --yes # both built-in tasks, 1 run each
|
|
231
|
-
glm-router benchmark --yes --task fn-reverse --repeat 3
|
|
232
|
-
glm-router benchmark --yes --max-turns 15
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
Report (per task × run): **duration**, **GLM calls** (assistant turns),
|
|
236
|
-
**retries** (`-` — not exposed by Claude Code yet), **tokens in/out**,
|
|
237
|
-
**tests** (PASS/FAIL of `node test.js`), **success**, **intervention**
|
|
238
|
-
(`needed` when the run did not self-complete). The full JSON report is always
|
|
239
|
-
saved to `%USERPROFILE%\.glm-coding-router\benchmarks\benchmark-<timestamp>.json`
|
|
240
|
-
and `--json` also prints it.
|
|
241
|
-
|
|
242
|
-
Built-in tasks: `fn-reverse` (implement `reverseWords` until the test passes),
|
|
243
|
-
`fix-bug` (repair an even-length `median` bug).
|
|
244
|
-
|
|
245
|
-
Notes:
|
|
246
|
-
- Benchmarking makes **real GLM API calls** — interactive runs ask for
|
|
247
|
-
confirmation; non-interactive runs require `--yes`.
|
|
248
|
-
- Failed tasks are measurements, not errors: the command exits 0 once the
|
|
249
|
-
suite ran. Missing key/claude or a broken spawn still fail with the usual
|
|
250
|
-
`ERROR [10]/[20]/[40]`.
|
|
251
|
-
- `--stack codex` is recognized but not supported yet (headless Codex
|
|
252
|
-
orchestration isn't drivable today); the harness is stack-shaped so it can
|
|
253
|
-
be added later.
|
|
254
|
-
|
|
255
|
-
## usage
|
|
256
|
-
|
|
257
|
-
Provider usage snapshots (spec §54 v0.5) — what is reliably retrievable:
|
|
258
|
-
|
|
259
|
-
```powershell
|
|
260
|
-
glm-router usage
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
- **Z.ai Coding Plan quota** (network): queries the Z.ai monitor endpoint
|
|
264
|
-
(`/api/monitor/usage/quota/limit`) with your key and shows each credit
|
|
265
|
-
window — consumed/total, percentage, reset time — plus the plan level.
|
|
266
|
-
Unreachable endpoint or a rejected request renders `✗ <reason>` and exits 1.
|
|
267
|
-
- **Local totals** (offline): aggregates saved benchmark reports — run count
|
|
268
|
-
and summed input/output tokens (`glm-router benchmark` writes them).
|
|
269
|
-
- **Claude quota / Codex usage**: always shown as "not available" — neither
|
|
270
|
-
exposes a headless usage API today (and claude.ai quota is irrelevant while
|
|
271
|
-
traffic is routed to GLM).
|
|
272
|
-
|
|
273
|
-
`--json` emits the same data machine-readably. No key configured → `ERROR [10]`.
|
|
274
|
-
|
|
275
|
-
## Run observability (v2)
|
|
276
|
-
|
|
277
|
-
Every `glm-worker` / `glm-review` run — and every MCP `glm_worker` / `glm_review` call —
|
|
278
|
-
is instrumented: the child runs with `--output-format stream-json`, events are recorded
|
|
279
|
-
under `<configDir>/runs/`, and progress renders live on **stderr**. Stdout stays exactly
|
|
280
|
-
the final assistant text, so pipes, orchestrators, and `benchmark` keep working unchanged.
|
|
281
|
-
|
|
282
|
-
- `runs/history/YYYY-MM-DD/<runId>/` holds `events.jsonl` (one JSON event per line) and
|
|
283
|
-
`summary.json`; `runs/active/` registers live runs with a heartbeat.
|
|
284
|
-
- Progress modes: `rich` (box + turn tree, TTY only), `nested` (one `[GLM] …` line per
|
|
285
|
-
significant event — the default when stderr is piped), `off`. `--no-progress`,
|
|
286
|
-
`--quiet`, or `CI=true` force `off`; `GLM_ROUTER_PROGRESS=off|rich|nested` and
|
|
287
|
-
`GLM_ROUTER_NESTED=1` override config; `ui.mode` is the standing default.
|
|
288
|
-
- `GLM_ROUTER_OBSERVE=off` restores the exact v1 path (also automatic when the caller
|
|
289
|
-
passes its own `--output-format`, as `benchmark` does).
|
|
290
|
-
|
|
291
|
-
```powershell
|
|
292
|
-
glm-router runs # id, state, model, started, duration, turns, files
|
|
293
|
-
glm-router runs --active --limit 5
|
|
294
|
-
glm-router runs show <runId> # metadata, summary, per-turn tool tree
|
|
295
|
-
glm-router runs logs <runId> # events.jsonl, one line per event (--json = raw)
|
|
296
|
-
glm-router runs clean --dry-run --orphans # preview retention prune + orphan reap
|
|
297
|
-
glm-router watch # attach to the newest active run, follow live
|
|
298
|
-
glm-router dashboard # quota + active runs + recent runs
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
- `runs show` accepts a unique id suffix; the whole family supports `--json`.
|
|
302
|
-
- `runs clean --older-than 30d` prunes by age, `--orphans` reaps active runs whose
|
|
303
|
-
process is gone; history is also pruned at run start (`history.retentionDays: 30`,
|
|
304
|
-
`history.maxRuns: 1000` by default).
|
|
305
|
-
- `watch [run-id] [--from-start]` renders through the same renderer as a live run; no active
|
|
306
|
-
run → a message, exit 0.
|
|
307
|
-
- `dashboard` repaints every `--interval` seconds (default 2) on a TTY; piped, it
|
|
308
|
-
prints one snapshot and exits. Ctrl+C quits the live view.
|
|
309
|
-
|
|
310
|
-
**Checkpoints and handoff bundles.** A run that dies with work on disk — child failure,
|
|
311
|
-
crash, kill — always leaves a bundle in `<runDir>/handoff/`: `checkpoint.json` (phase,
|
|
312
|
-
completed turns, pending work, files changed, validations owed), `diff.patch` (the real
|
|
313
|
-
`git diff`; the router never runs `git add`, so untracked files are listed separately),
|
|
314
|
-
`handoff.md`, and `handoff.json`. The bundle path is printed to stderr. Outside a git
|
|
315
|
-
repo the bundle is still written, minus the patch.
|
|
316
|
-
|
|
317
|
-
## Quota-aware routing (v2)
|
|
318
|
-
|
|
319
|
-
Before spawning, the router reads the Z.ai quota (cached 60 s), classifies the task, and
|
|
320
|
-
estimates its cost (p90 from `cost-samples.jsonl` history, else a built-in baseline).
|
|
321
|
-
The binding window (5-hour vs weekly, whichever is lower) picks a zone: HEALTHY runs the
|
|
322
|
-
main model; CONSERVE, HANDOFF_READY, and CRITICAL prefer the fast one. If main does not
|
|
323
|
-
fit the usable budget but fast does, the run is downgraded — never the reverse. Endpoint
|
|
324
|
-
unreachable, no key, or an empty payload → `confidence: "unknown"` → run normally and
|
|
325
|
-
warn once on stderr: a monitoring outage never blocks work.
|
|
326
|
-
|
|
327
|
-
Defaults in 2.0.0: `quotaAware: true`, but `refuseOnCritical: false` and
|
|
328
|
-
`handoffOnLowQuota: false` — the shipped router observes, downgrades, and warns; it
|
|
329
|
-
never refuses a run and never kills a live child. Every `summary.json` records
|
|
330
|
-
`routingAdvice` (`zone`, `wouldRefuse`, `estimatedCost`, `actualCredits`), the evidence
|
|
331
|
-
for revisiting those switches later.
|
|
332
|
-
|
|
333
|
-
```json
|
|
334
|
-
{
|
|
335
|
-
"routing": {
|
|
336
|
-
"quotaAware": true, "refuseOnCritical": false, "handoffOnLowQuota": false,
|
|
337
|
-
"reserveRatio": 0.10, "safetyFactor": 1.3,
|
|
338
|
-
"preferFlashBelow": 0.30, "handoffReadyBelow": 0.15, "criticalBelow": 0.08,
|
|
339
|
-
"pollIntervalSec": 60, "quotaCacheTtlSec": 60
|
|
340
|
-
},
|
|
341
|
-
"history": { "retentionDays": 30, "maxRuns": 1000 },
|
|
342
|
-
"ui": { "mode": "auto", "color": true }
|
|
343
|
-
}
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
Ratio fields must satisfy `0 < x < 1` and stay ordered
|
|
347
|
-
(`criticalBelow < handoffReadyBelow < preferFlashBelow`), else `ERROR [11]`.
|
|
348
|
-
|
|
349
|
-
**Exit 41 / 42 — unfinished, not crashed.** Both mean "work preserved", and both print a
|
|
350
|
-
`HandoffResult` JSON on stdout:
|
|
351
|
-
|
|
352
|
-
- **41 `QUOTA_INSUFFICIENT`** — preflight refused to spawn anything (reachable only
|
|
353
|
-
with `refuseOnCritical: true`). Nothing ran, and no run-history or repository files
|
|
354
|
-
were written; `--model fast` may fit the budget, `--force` overrides the refusal.
|
|
355
|
-
- **42 `HANDOFF_REQUIRED`** — a live run was stopped at a safe tool boundary and handed
|
|
356
|
-
back (reachable only with `handoffOnLowQuota: true`); the JSON carries `handoff_path`.
|
|
357
|
-
|
|
358
|
-
An orchestrator reads 41/42 as "continue in the same worktree", never as "the worker
|
|
359
|
-
broke". A child that fails on its own still exits 40 — the handoff bundle is written
|
|
360
|
-
anyway.
|
|
361
|
-
|
|
362
|
-
## Agent skills (Claude Code + Codex)
|
|
363
|
-
|
|
364
|
-
`glm-router skill install` writes the `glm-delegation` SKILL.md into **both**
|
|
365
|
-
agent homes — `~/.claude/skills/` and `~/.codex/skills/` — so either
|
|
366
|
-
orchestrator natively knows how to delegate to GLM workers. Missing homes are
|
|
367
|
-
skipped with a note (optional enhancement, never fatal); `skill remove`
|
|
368
|
-
cleans both. `status` shows one skill row per agent.
|
|
369
|
-
|
|
370
|
-
## MCP server (optional)
|
|
371
|
-
|
|
372
|
-
`glm-mcp` (installed with the package) exposes the router as MCP tools over
|
|
373
|
-
stdio — any MCP client can delegate without shell syntax:
|
|
374
|
-
|
|
375
|
-
| Tool | What it does |
|
|
376
|
-
|---|---|
|
|
377
|
-
| `glm_worker(prompt, profile?)` | implementation worker, returns output |
|
|
378
|
-
| `glm_review(prompt, profile?)` | read-only review/exploration |
|
|
379
|
-
| `glm_delegate(name, prompt)` | worker in an isolated git worktree |
|
|
380
|
-
| `glm_usage()` | Z.ai quota windows + local benchmark totals |
|
|
381
|
-
|
|
382
|
-
Register it with Claude Code (we never edit `~/.claude.json` ourselves — it
|
|
383
|
-
goes through Claude's own CLI):
|
|
384
|
-
|
|
385
|
-
```powershell
|
|
386
|
-
glm-router mcp # prints the snippet + the exact command
|
|
387
|
-
glm-router mcp install # claude mcp add -s user glm-coding-router -- node .../glm-mcp.js
|
|
388
|
-
glm-router mcp remove # claude mcp remove -s user glm-coding-router
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
Tool-level failures return `isError` results (missing key, no claude, outside
|
|
392
|
-
a git repo, unreachable endpoint); the server never prints anything to stdout
|
|
393
|
-
except JSON-RPC frames. MCP-driven runs are recorded like any other (registry on,
|
|
394
|
-
progress renderer off), so they appear in `glm-router runs` and `dashboard` while
|
|
395
|
-
the protocol channel stays clean.
|
|
396
|
-
|
|
397
|
-
## CLI reference
|
|
398
|
-
|
|
399
|
-
```text
|
|
400
|
-
glm-router init guided setup
|
|
401
|
-
glm-router doctor [--network] full runtime diagnosis
|
|
402
|
-
|
|
403
|
-
glm-router
|
|
404
|
-
glm-router key
|
|
405
|
-
glm-router
|
|
406
|
-
glm-router config
|
|
407
|
-
glm-router
|
|
408
|
-
glm-router
|
|
409
|
-
glm-router
|
|
410
|
-
glm-router
|
|
411
|
-
glm-router
|
|
412
|
-
glm-router
|
|
413
|
-
glm-router
|
|
414
|
-
glm-router
|
|
415
|
-
glm-router project
|
|
416
|
-
glm-router
|
|
417
|
-
glm-router skill
|
|
418
|
-
glm-router
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
-
|
|
438
|
-
-
|
|
439
|
-
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
`glm-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
`
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
`
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
|
481
|
-
|
|
|
482
|
-
| `ERROR [
|
|
483
|
-
|
|
|
484
|
-
|
|
|
485
|
-
|
|
|
486
|
-
| `
|
|
487
|
-
| `
|
|
488
|
-
|
|
|
489
|
-
|
|
|
490
|
-
|
|
|
491
|
-
| `
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
```
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
1
|
+
# GLM Coding Router
|
|
2
|
+
|
|
3
|
+
GLM Coding Plan workers for Claude Code and Codex — on Windows, Linux, and (experimentally) macOS.
|
|
4
|
+
|
|
5
|
+
Claude Code and Codex stay your orchestrators — they keep responsibility for requirements,
|
|
6
|
+
architecture, review, and integration. `glm-coding-router` delegates well-scoped
|
|
7
|
+
implementation work (exploration, CRUD, boilerplate, tests, mechanical refactoring) to
|
|
8
|
+
GLM workers via Z.ai's Anthropic-compatible endpoint.
|
|
9
|
+
|
|
10
|
+
One global npm install replaces the manual `.cmd` shim setup:
|
|
11
|
+
|
|
12
|
+
```text
|
|
13
|
+
Claude / Codex → shell → glm-worker → claude.exe harness → Z.ai endpoint → GLM Coding Plan
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Architecture
|
|
17
|
+
|
|
18
|
+
```text
|
|
19
|
+
Developer
|
|
20
|
+
│
|
|
21
|
+
┌───────────────┴───────────────┐
|
|
22
|
+
▼ ▼
|
|
23
|
+
Claude Code Codex
|
|
24
|
+
│ │
|
|
25
|
+
└───────────────┬───────────────┘
|
|
26
|
+
shell command
|
|
27
|
+
│
|
|
28
|
+
┌───────────────┼───────────────┐
|
|
29
|
+
▼ ▼ ▼
|
|
30
|
+
glm-chat glm-fast glm-worker glm-review
|
|
31
|
+
│ │ │ │
|
|
32
|
+
└─────────┴──────────┴──────────┘
|
|
33
|
+
claude.exe
|
|
34
|
+
(injected environment only)
|
|
35
|
+
│
|
|
36
|
+
▼
|
|
37
|
+
https://api.z.ai/api/anthropic
|
|
38
|
+
│
|
|
39
|
+
▼
|
|
40
|
+
GLM Coding Plan
|
|
41
|
+
GLM-5.3 / GLM-5.3-Flash
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
For headless worker/review runs, the router also consumes Claude Code's stream-json output,
|
|
45
|
+
records a provider-neutral event history, and renders progress on stderr. Interactive
|
|
46
|
+
`glm-chat` / `glm-fast` sessions keep the direct pass-through path shown above.
|
|
47
|
+
|
|
48
|
+
## Requirements
|
|
49
|
+
|
|
50
|
+
- Windows 10/11 or Linux (both verified); macOS is experimental — the suite has not been
|
|
51
|
+
run on a Mac
|
|
52
|
+
- Node.js >= 20
|
|
53
|
+
- Claude Code (`claude.exe`) — the GLM commands run on the Claude Code harness
|
|
54
|
+
- Codex (optional — Claude-only setups are fully supported)
|
|
55
|
+
- A Z.ai Coding Plan API key
|
|
56
|
+
|
|
57
|
+
No Anthropic pay-as-you-go, no OpenAI API, no LiteLLM, no proxy.
|
|
58
|
+
|
|
59
|
+
## Installation
|
|
60
|
+
|
|
61
|
+
```powershell
|
|
62
|
+
npm install -g glm-coding-router
|
|
63
|
+
glm-router init
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`npx glm-coding-router init` also works for a one-off check, but the global install is
|
|
67
|
+
what puts `glm-worker` on your PATH long-term.
|
|
68
|
+
|
|
69
|
+
## Quick start
|
|
70
|
+
|
|
71
|
+
After `glm-router init`:
|
|
72
|
+
|
|
73
|
+
```powershell
|
|
74
|
+
glm-chat
|
|
75
|
+
glm-fast
|
|
76
|
+
glm-worker "Implement validation and add tests"
|
|
77
|
+
glm-review "Analyze the auth module"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## glm-chat
|
|
81
|
+
|
|
82
|
+
Interactive GLM-backed Claude Code session. Resolves the Z.ai key, locates `claude.exe`,
|
|
83
|
+
injects the Z.ai environment **into the child process only**, and spawns it with
|
|
84
|
+
pass-through arguments:
|
|
85
|
+
|
|
86
|
+
```powershell
|
|
87
|
+
glm-chat
|
|
88
|
+
glm-chat --version
|
|
89
|
+
glm-chat --any-claude-flag
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Your normal `claude` command and its authentication are untouched.
|
|
93
|
+
|
|
94
|
+
## glm-fast
|
|
95
|
+
|
|
96
|
+
Interactive GLM-backed session pinned to the **fast model** (`models.fast`,
|
|
97
|
+
`glm-5.3-flash` by default) — every model slot in the child environment maps to
|
|
98
|
+
it, so whichever tier Claude Code picks, it gets the fast model. Same pass-through
|
|
99
|
+
arguments as `glm-chat`:
|
|
100
|
+
|
|
101
|
+
```powershell
|
|
102
|
+
glm-fast
|
|
103
|
+
glm-fast --profile air
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## glm-worker
|
|
107
|
+
|
|
108
|
+
Headless implementation worker:
|
|
109
|
+
|
|
110
|
+
```powershell
|
|
111
|
+
glm-worker "Implement validation and add tests"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Or via stdin (a structured task packet):
|
|
115
|
+
|
|
116
|
+
```powershell
|
|
117
|
+
@"
|
|
118
|
+
TASK:
|
|
119
|
+
Implement refresh token validation.
|
|
120
|
+
|
|
121
|
+
SCOPE:
|
|
122
|
+
internal/auth/
|
|
123
|
+
|
|
124
|
+
VALIDATION:
|
|
125
|
+
go test ./internal/auth/...
|
|
126
|
+
"@ | glm-worker
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Input priority: **arguments → stdin → error**. Arguments win, and stdin is not even
|
|
130
|
+
read when they carry a prompt — waiting for EOF on a pipe that never closes (an agent
|
|
131
|
+
harness, CI, `nohup`) would hang the run before it started. The worker runs with
|
|
132
|
+
`--max-turns 20 --permission-mode acceptEdits --tools Read,Glob,Grep,Edit,Write,Bash`.
|
|
133
|
+
It never uses `--dangerously-skip-permissions`.
|
|
134
|
+
|
|
135
|
+
Routing flags (v2): `--model main|fast` pins the config slot for this run (it does not
|
|
136
|
+
bypass an enforced refusal), `--force` overrides one, `--refresh-quota` re-reads the
|
|
137
|
+
Z.ai quota instead of the 60 s cache — see [Quota-aware routing](#quota-aware-routing-v2).
|
|
138
|
+
Like `--profile`, they belong to the wrapper and are consumed before the prompt is read.
|
|
139
|
+
|
|
140
|
+
## glm-review
|
|
141
|
+
|
|
142
|
+
Read-only worker for repository exploration, call-graph discovery, duplicate detection,
|
|
143
|
+
dependency inspection, and preliminary review:
|
|
144
|
+
|
|
145
|
+
```powershell
|
|
146
|
+
glm-review "Inspect this repository"
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Runs with `--tools Read,Glob,Grep --strict-mcp-config` — it cannot edit files or run
|
|
150
|
+
commands.
|
|
151
|
+
|
|
152
|
+
The second flag is part of the guarantee, not a detail: `--tools` restricts only Claude
|
|
153
|
+
Code's **built-in** tools, so without it a review session would also inherit whatever MCP
|
|
154
|
+
servers you have registered — including this project's own, whose `glm_worker` tool writes
|
|
155
|
+
files. `glm-worker` and `glm-router benchmark` are isolated the same way. Interactive
|
|
156
|
+
sessions (`glm-chat`, `glm-fast`) are not: your servers are yours.
|
|
157
|
+
|
|
158
|
+
## Profiles
|
|
159
|
+
|
|
160
|
+
All four task binaries (`glm-chat`, `glm-fast`, `glm-worker`, `glm-review`)
|
|
161
|
+
accept `--profile <name>` to overlay saved model/maxTurns settings. Profiles
|
|
162
|
+
live in `config.json`:
|
|
163
|
+
|
|
164
|
+
```json
|
|
165
|
+
{
|
|
166
|
+
"profiles": {
|
|
167
|
+
"test": { "workerMaxTurns": 10, "fast": "glm-5.3-flash" },
|
|
168
|
+
"frontend": { "main": "glm-5.3", "reviewMaxTurns": 30 }
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
```powershell
|
|
174
|
+
glm-worker --profile test "Add failing test then fix it"
|
|
175
|
+
glm-review --profile frontend "Review the component tree"
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Fields (all optional): `main`, `fast`, `workerMaxTurns`, `reviewMaxTurns`.
|
|
179
|
+
Unknown profile names fail with `ERROR [11]` listing the available ones.
|
|
180
|
+
Note: `--profile` belongs to these wrappers — it shadows Claude Code's own
|
|
181
|
+
`--profile` flag inside them.
|
|
182
|
+
|
|
183
|
+
## delegate
|
|
184
|
+
|
|
185
|
+
Run a GLM worker in an **isolated git worktree** so parallel tasks never trample
|
|
186
|
+
each other's working tree (`glm-router delegate backend|frontend|tests`):
|
|
187
|
+
|
|
188
|
+
```powershell
|
|
189
|
+
glm-router delegate backend "Implement refresh token validation in internal/auth"
|
|
190
|
+
Get-Content task.md | glm-router delegate auth-refresh
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Each run creates a worktree at `<repo>.glm-worktrees\<name>` (outside the repo,
|
|
194
|
+
so your checkout's status stays clean) on a new branch `glm/delegate/<name>`
|
|
195
|
+
cut from `HEAD`, and runs the standard `glm-worker` inside it. The worktree and
|
|
196
|
+
branch are **kept** after the run — the tool never commits, merges, or deletes
|
|
197
|
+
your work; the footer prints the path and the merge command:
|
|
198
|
+
|
|
199
|
+
```text
|
|
200
|
+
[glm-router] worktree kept at D:\code\my-repo.glm-worktrees\backend
|
|
201
|
+
[glm-router] next: inspect it, then merge glm/delegate/backend (or discard with git worktree remove)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
- Prompt priority is arguments → stdin, same as `glm-worker`.
|
|
205
|
+
- Profiles: `--profile test` explicitly, or — when omitted — a profile literally
|
|
206
|
+
named after the delegate (`delegate test` → the `test` profile) if one exists.
|
|
207
|
+
- `--remove` deletes the worktree **after a successful run only**; plain
|
|
208
|
+
`git worktree remove` is used, so git refuses (and the worktree is kept) when
|
|
209
|
+
the worker left uncommitted changes. The branch is always kept.
|
|
210
|
+
- Pre-flight checks fail fast (`ERROR [31]`) when the branch or directory
|
|
211
|
+
already exists, or the repo has no commits yet; outside a git repo →
|
|
212
|
+
`ERROR [30]`. Uncommitted changes in your main checkout are **not** visible
|
|
213
|
+
to the worker — it starts from the last commit.
|
|
214
|
+
- `--dry-run` prints the plan; `--json` prints pre-flight and result objects.
|
|
215
|
+
- Run several delegates concurrently — distinct names cannot collide:
|
|
216
|
+
|
|
217
|
+
```powershell
|
|
218
|
+
glm-router delegate backend "Task A" # terminal 1
|
|
219
|
+
glm-router delegate tests "Task B" # terminal 2
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## benchmark
|
|
223
|
+
|
|
224
|
+
Measure the Claude Code + GLM stack on built-in coding tasks (spec §54 v0.4).
|
|
225
|
+
Each task runs in a throwaway temp directory: the router writes the task files,
|
|
226
|
+
spawns the standard GLM worker (same env injection, plus `--output-format json`
|
|
227
|
+
to capture the result document), then runs the task's validation command:
|
|
228
|
+
|
|
229
|
+
```powershell
|
|
230
|
+
glm-router benchmark --yes # both built-in tasks, 1 run each
|
|
231
|
+
glm-router benchmark --yes --task fn-reverse --repeat 3
|
|
232
|
+
glm-router benchmark --yes --max-turns 15
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Report (per task × run): **duration**, **GLM calls** (assistant turns),
|
|
236
|
+
**retries** (`-` — not exposed by Claude Code yet), **tokens in/out**,
|
|
237
|
+
**tests** (PASS/FAIL of `node test.js`), **success**, **intervention**
|
|
238
|
+
(`needed` when the run did not self-complete). The full JSON report is always
|
|
239
|
+
saved to `%USERPROFILE%\.glm-coding-router\benchmarks\benchmark-<timestamp>.json`
|
|
240
|
+
and `--json` also prints it.
|
|
241
|
+
|
|
242
|
+
Built-in tasks: `fn-reverse` (implement `reverseWords` until the test passes),
|
|
243
|
+
`fix-bug` (repair an even-length `median` bug).
|
|
244
|
+
|
|
245
|
+
Notes:
|
|
246
|
+
- Benchmarking makes **real GLM API calls** — interactive runs ask for
|
|
247
|
+
confirmation; non-interactive runs require `--yes`.
|
|
248
|
+
- Failed tasks are measurements, not errors: the command exits 0 once the
|
|
249
|
+
suite ran. Missing key/claude or a broken spawn still fail with the usual
|
|
250
|
+
`ERROR [10]/[20]/[40]`.
|
|
251
|
+
- `--stack codex` is recognized but not supported yet (headless Codex
|
|
252
|
+
orchestration isn't drivable today); the harness is stack-shaped so it can
|
|
253
|
+
be added later.
|
|
254
|
+
|
|
255
|
+
## usage
|
|
256
|
+
|
|
257
|
+
Provider usage snapshots (spec §54 v0.5) — what is reliably retrievable:
|
|
258
|
+
|
|
259
|
+
```powershell
|
|
260
|
+
glm-router usage
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
- **Z.ai Coding Plan quota** (network): queries the Z.ai monitor endpoint
|
|
264
|
+
(`/api/monitor/usage/quota/limit`) with your key and shows each credit
|
|
265
|
+
window — consumed/total, percentage, reset time — plus the plan level.
|
|
266
|
+
Unreachable endpoint or a rejected request renders `✗ <reason>` and exits 1.
|
|
267
|
+
- **Local totals** (offline): aggregates saved benchmark reports — run count
|
|
268
|
+
and summed input/output tokens (`glm-router benchmark` writes them).
|
|
269
|
+
- **Claude quota / Codex usage**: always shown as "not available" — neither
|
|
270
|
+
exposes a headless usage API today (and claude.ai quota is irrelevant while
|
|
271
|
+
traffic is routed to GLM).
|
|
272
|
+
|
|
273
|
+
`--json` emits the same data machine-readably. No key configured → `ERROR [10]`.
|
|
274
|
+
|
|
275
|
+
## Run observability (v2)
|
|
276
|
+
|
|
277
|
+
Every `glm-worker` / `glm-review` run — and every MCP `glm_worker` / `glm_review` call —
|
|
278
|
+
is instrumented: the child runs with `--output-format stream-json`, events are recorded
|
|
279
|
+
under `<configDir>/runs/`, and progress renders live on **stderr**. Stdout stays exactly
|
|
280
|
+
the final assistant text, so pipes, orchestrators, and `benchmark` keep working unchanged.
|
|
281
|
+
|
|
282
|
+
- `runs/history/YYYY-MM-DD/<runId>/` holds `events.jsonl` (one JSON event per line) and
|
|
283
|
+
`summary.json`; `runs/active/` registers live runs with a heartbeat.
|
|
284
|
+
- Progress modes: `rich` (box + turn tree, TTY only), `nested` (one `[GLM] …` line per
|
|
285
|
+
significant event — the default when stderr is piped), `off`. `--no-progress`,
|
|
286
|
+
`--quiet`, or `CI=true` force `off`; `GLM_ROUTER_PROGRESS=off|rich|nested` and
|
|
287
|
+
`GLM_ROUTER_NESTED=1` override config; `ui.mode` is the standing default.
|
|
288
|
+
- `GLM_ROUTER_OBSERVE=off` restores the exact v1 path (also automatic when the caller
|
|
289
|
+
passes its own `--output-format`, as `benchmark` does).
|
|
290
|
+
|
|
291
|
+
```powershell
|
|
292
|
+
glm-router runs # id, state, model, started, duration, turns, files
|
|
293
|
+
glm-router runs --active --limit 5
|
|
294
|
+
glm-router runs show <runId> # metadata, summary, per-turn tool tree
|
|
295
|
+
glm-router runs logs <runId> # events.jsonl, one line per event (--json = raw)
|
|
296
|
+
glm-router runs clean --dry-run --orphans # preview retention prune + orphan reap
|
|
297
|
+
glm-router watch # attach to the newest active run, follow live
|
|
298
|
+
glm-router dashboard # quota + active runs + recent runs
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
- `runs show` accepts a unique id suffix; the whole family supports `--json`.
|
|
302
|
+
- `runs clean --older-than 30d` prunes by age, `--orphans` reaps active runs whose
|
|
303
|
+
process is gone; history is also pruned at run start (`history.retentionDays: 30`,
|
|
304
|
+
`history.maxRuns: 1000` by default).
|
|
305
|
+
- `watch [run-id] [--from-start]` renders through the same renderer as a live run; no active
|
|
306
|
+
run → a message, exit 0.
|
|
307
|
+
- `dashboard` repaints every `--interval` seconds (default 2) on a TTY; piped, it
|
|
308
|
+
prints one snapshot and exits. Ctrl+C quits the live view.
|
|
309
|
+
|
|
310
|
+
**Checkpoints and handoff bundles.** A run that dies with work on disk — child failure,
|
|
311
|
+
crash, kill — always leaves a bundle in `<runDir>/handoff/`: `checkpoint.json` (phase,
|
|
312
|
+
completed turns, pending work, files changed, validations owed), `diff.patch` (the real
|
|
313
|
+
`git diff`; the router never runs `git add`, so untracked files are listed separately),
|
|
314
|
+
`handoff.md`, and `handoff.json`. The bundle path is printed to stderr. Outside a git
|
|
315
|
+
repo the bundle is still written, minus the patch.
|
|
316
|
+
|
|
317
|
+
## Quota-aware routing (v2)
|
|
318
|
+
|
|
319
|
+
Before spawning, the router reads the Z.ai quota (cached 60 s), classifies the task, and
|
|
320
|
+
estimates its cost (p90 from `cost-samples.jsonl` history, else a built-in baseline).
|
|
321
|
+
The binding window (5-hour vs weekly, whichever is lower) picks a zone: HEALTHY runs the
|
|
322
|
+
main model; CONSERVE, HANDOFF_READY, and CRITICAL prefer the fast one. If main does not
|
|
323
|
+
fit the usable budget but fast does, the run is downgraded — never the reverse. Endpoint
|
|
324
|
+
unreachable, no key, or an empty payload → `confidence: "unknown"` → run normally and
|
|
325
|
+
warn once on stderr: a monitoring outage never blocks work.
|
|
326
|
+
|
|
327
|
+
Defaults in 2.0.0: `quotaAware: true`, but `refuseOnCritical: false` and
|
|
328
|
+
`handoffOnLowQuota: false` — the shipped router observes, downgrades, and warns; it
|
|
329
|
+
never refuses a run and never kills a live child. Every `summary.json` records
|
|
330
|
+
`routingAdvice` (`zone`, `wouldRefuse`, `estimatedCost`, `actualCredits`), the evidence
|
|
331
|
+
for revisiting those switches later.
|
|
332
|
+
|
|
333
|
+
```json
|
|
334
|
+
{
|
|
335
|
+
"routing": {
|
|
336
|
+
"quotaAware": true, "refuseOnCritical": false, "handoffOnLowQuota": false,
|
|
337
|
+
"reserveRatio": 0.10, "safetyFactor": 1.3,
|
|
338
|
+
"preferFlashBelow": 0.30, "handoffReadyBelow": 0.15, "criticalBelow": 0.08,
|
|
339
|
+
"pollIntervalSec": 60, "quotaCacheTtlSec": 60
|
|
340
|
+
},
|
|
341
|
+
"history": { "retentionDays": 30, "maxRuns": 1000 },
|
|
342
|
+
"ui": { "mode": "auto", "color": true }
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Ratio fields must satisfy `0 < x < 1` and stay ordered
|
|
347
|
+
(`criticalBelow < handoffReadyBelow < preferFlashBelow`), else `ERROR [11]`.
|
|
348
|
+
|
|
349
|
+
**Exit 41 / 42 — unfinished, not crashed.** Both mean "work preserved", and both print a
|
|
350
|
+
`HandoffResult` JSON on stdout:
|
|
351
|
+
|
|
352
|
+
- **41 `QUOTA_INSUFFICIENT`** — preflight refused to spawn anything (reachable only
|
|
353
|
+
with `refuseOnCritical: true`). Nothing ran, and no run-history or repository files
|
|
354
|
+
were written; `--model fast` may fit the budget, `--force` overrides the refusal.
|
|
355
|
+
- **42 `HANDOFF_REQUIRED`** — a live run was stopped at a safe tool boundary and handed
|
|
356
|
+
back (reachable only with `handoffOnLowQuota: true`); the JSON carries `handoff_path`.
|
|
357
|
+
|
|
358
|
+
An orchestrator reads 41/42 as "continue in the same worktree", never as "the worker
|
|
359
|
+
broke". A child that fails on its own still exits 40 — the handoff bundle is written
|
|
360
|
+
anyway.
|
|
361
|
+
|
|
362
|
+
## Agent skills (Claude Code + Codex)
|
|
363
|
+
|
|
364
|
+
`glm-router skill install` writes the `glm-delegation` SKILL.md into **both**
|
|
365
|
+
agent homes — `~/.claude/skills/` and `~/.codex/skills/` — so either
|
|
366
|
+
orchestrator natively knows how to delegate to GLM workers. Missing homes are
|
|
367
|
+
skipped with a note (optional enhancement, never fatal); `skill remove`
|
|
368
|
+
cleans both. `status` shows one skill row per agent.
|
|
369
|
+
|
|
370
|
+
## MCP server (optional)
|
|
371
|
+
|
|
372
|
+
`glm-mcp` (installed with the package) exposes the router as MCP tools over
|
|
373
|
+
stdio — any MCP client can delegate without shell syntax:
|
|
374
|
+
|
|
375
|
+
| Tool | What it does |
|
|
376
|
+
|---|---|
|
|
377
|
+
| `glm_worker(prompt, profile?)` | implementation worker, returns output |
|
|
378
|
+
| `glm_review(prompt, profile?)` | read-only review/exploration |
|
|
379
|
+
| `glm_delegate(name, prompt)` | worker in an isolated git worktree |
|
|
380
|
+
| `glm_usage()` | Z.ai quota windows + local benchmark totals |
|
|
381
|
+
|
|
382
|
+
Register it with Claude Code (we never edit `~/.claude.json` ourselves — it
|
|
383
|
+
goes through Claude's own CLI):
|
|
384
|
+
|
|
385
|
+
```powershell
|
|
386
|
+
glm-router mcp # prints the snippet + the exact command
|
|
387
|
+
glm-router mcp install # claude mcp add -s user glm-coding-router -- node .../glm-mcp.js
|
|
388
|
+
glm-router mcp remove # claude mcp remove -s user glm-coding-router
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
Tool-level failures return `isError` results (missing key, no claude, outside
|
|
392
|
+
a git repo, unreachable endpoint); the server never prints anything to stdout
|
|
393
|
+
except JSON-RPC frames. MCP-driven runs are recorded like any other (registry on,
|
|
394
|
+
progress renderer off), so they appear in `glm-router runs` and `dashboard` while
|
|
395
|
+
the protocol channel stays clean.
|
|
396
|
+
|
|
397
|
+
## CLI reference
|
|
398
|
+
|
|
399
|
+
```text
|
|
400
|
+
glm-router init guided setup
|
|
401
|
+
glm-router doctor [--network|--offline] full runtime diagnosis, authenticates the
|
|
402
|
+
effective key online by default (--offline skips that)
|
|
403
|
+
glm-router status quick offline overview (key presence only, not verified)
|
|
404
|
+
glm-router key set store ZAI_API_KEY in this platform's per-user store
|
|
405
|
+
glm-router key check key configured? from which source?
|
|
406
|
+
glm-router config show
|
|
407
|
+
glm-router config set models.main glm-5.3
|
|
408
|
+
glm-router delegate <name> run a GLM worker in an isolated git worktree
|
|
409
|
+
glm-router benchmark measure the Claude+GLM stack on built-in tasks
|
|
410
|
+
glm-router usage Z.ai quota snapshot + local benchmark totals
|
|
411
|
+
glm-router runs inspect recorded runs (show / logs / clean subcommands)
|
|
412
|
+
glm-router watch [run-id] attach to an active run and follow its progress
|
|
413
|
+
glm-router dashboard quota + active runs + recent runs
|
|
414
|
+
glm-router mcp optional MCP server registration (glm-mcp)
|
|
415
|
+
glm-router project init CLAUDE.md / AGENTS.md managed blocks (--dry-run supported)
|
|
416
|
+
glm-router project remove
|
|
417
|
+
glm-router skill install optional Codex delegation skill
|
|
418
|
+
glm-router skill remove
|
|
419
|
+
glm-router uninstall guided removal (keeps ZAI_API_KEY by default)
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
Global flags: `--json --quiet --verbose --dry-run --force --yes`
|
|
423
|
+
|
|
424
|
+
## Claude integration
|
|
425
|
+
|
|
426
|
+
`glm-router project init` adds a **managed block** to `CLAUDE.md` at the project root
|
|
427
|
+
(`git rev-parse --show-toplevel`, falling back to cwd):
|
|
428
|
+
|
|
429
|
+
```text
|
|
430
|
+
<!-- glm-coding-router:start -->
|
|
431
|
+
... delegation policy ...
|
|
432
|
+
<!-- glm-coding-router:end -->
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
- Everything outside the markers is preserved; existing blocks are replaced in place;
|
|
436
|
+
runs are idempotent and never duplicate.
|
|
437
|
+
- Files are updated atomically (tmp file → fsync → rename).
|
|
438
|
+
- On a malformed marker pair the file is left untouched with an actionable error.
|
|
439
|
+
- CRLF/LF and UTF-8 are preserved.
|
|
440
|
+
- `glm-router project remove` deletes only the managed block. A file the router
|
|
441
|
+
created entirely is deleted only when it would otherwise be empty.
|
|
442
|
+
|
|
443
|
+
## Codex integration
|
|
444
|
+
|
|
445
|
+
The same command updates `AGENTS.md` (Codex's repository instruction file) with an
|
|
446
|
+
equivalent managed block. Additionally, `glm-router skill install` installs the optional
|
|
447
|
+
`glm-delegation` skill to `~/.codex/skills/glm-delegation/SKILL.md`. If Codex is not
|
|
448
|
+
detected, the skill step warns and skips — AGENTS.md integration and the core tool are
|
|
449
|
+
unaffected.
|
|
450
|
+
|
|
451
|
+
## Orca behavior (stale environments)
|
|
452
|
+
|
|
453
|
+
Terminals embedded in Orca snapshot the Windows environment at startup. A key added
|
|
454
|
+
after Orca starts is invisible to those terminals. Every GLM command therefore resolves
|
|
455
|
+
the key in this order:
|
|
456
|
+
|
|
457
|
+
1. `process.env.ZAI_API_KEY`
|
|
458
|
+
2. This platform's per-user store — Windows User Environment (PowerShell), macOS login
|
|
459
|
+
keychain (`security`), or libsecret (`secret-tool`, when installed)
|
|
460
|
+
3. fail with an actionable error
|
|
461
|
+
|
|
462
|
+
The key is never cached to disk.
|
|
463
|
+
|
|
464
|
+
## Security model
|
|
465
|
+
|
|
466
|
+
- The key lives only in that per-user store; it is never written to
|
|
467
|
+
`config.json`, the repo, logs, or stack traces. Debug output redacts
|
|
468
|
+
`ZAI_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, and Authorization headers.
|
|
469
|
+
- Z.ai routing environment variables (`ANTHROPIC_AUTH_TOKEN`,
|
|
470
|
+
`ANTHROPIC_BASE_URL`, model overrides) are injected **only** into the spawned
|
|
471
|
+
`claude.exe` child process. `ANTHROPIC_API_KEY` is blanked in the child so your
|
|
472
|
+
normal Claude auth is never in play. `ANTHROPIC_BASE_URL` is never persisted globally.
|
|
473
|
+
- Claude Code and Codex global authentication are never modified.
|
|
474
|
+
- Child processes are spawned with argument arrays (`shell: false`) — prompts with
|
|
475
|
+
quotes, pipes, ampersands, or newlines are passed verbatim, never through a shell.
|
|
476
|
+
- No telemetry, no automatic git commits.
|
|
477
|
+
|
|
478
|
+
## Troubleshooting
|
|
479
|
+
|
|
480
|
+
| Symptom | Fix |
|
|
481
|
+
| --- | --- |
|
|
482
|
+
| `ERROR [ZAI_KEY_MISSING]` | `glm-router key set`, then open a **new** terminal |
|
|
483
|
+
| `ERROR [CLAUDE_NOT_FOUND]` | Install Claude Code, or `glm-router config set claudePath C:\path\to\claude.exe` |
|
|
484
|
+
| Key works in a new terminal but not inside Orca | Expected — workers re-read the per-user store automatically; run `glm-router doctor` to confirm |
|
|
485
|
+
| `doctor` says `ATTENTION` with "different from the saved key" | The process environment has a key that differs from what `key set` saved; process always wins. Close/restart the terminal's hosting app to drop the stale value, or update the intentional override |
|
|
486
|
+
| `doctor` says `ISSUES` with an HTTP 401/403 | The monitor endpoint rejected the *effective* key itself — not a comparison mismatch. Run `glm-router key set` with a valid key |
|
|
487
|
+
| `doctor` says `UNVERIFIED` | The key could not be confirmed either way (timeout, 429, 5xx, or a malformed response) — this is not proof the key is invalid; retry, or check network/proxy |
|
|
488
|
+
| `glm-router key set` prints an `export` line instead of saving | This platform has no secret store (e.g. Linux without `secret-tool`). Add the line to your shell profile; `glm-router key check` verifies it |
|
|
489
|
+
| The worker creates files but never runs the tests | Its Bash allowlist is empty. `glm-router config show` → `worker.allowedBash`; the default list covers common test commands |
|
|
490
|
+
| `glm-*` not on PATH after install | Reopen the terminal; check `npm config get prefix` is on PATH |
|
|
491
|
+
| `ERROR [MANAGED_BLOCK_CORRUPT]` | Fix the marker pair in the named file manually, then re-run |
|
|
492
|
+
| Exit 41 `QUOTA_INSUFFICIENT` | Preflight refused the run (only with `routing.refuseOnCritical: true`). Wait for the window to reset, use `--model fast`, or `--force` |
|
|
493
|
+
| Exit 42 `HANDOFF_REQUIRED` | Not a crash — the run handed off with a bundle. Read `handoff_path` in the stdout JSON and continue in the same worktree |
|
|
494
|
+
| A run lists as FAILED with no summary | It died mid-run; `runs show <id>` rebuilds the summary from `events.jsonl`, `runs clean --orphans` reaps stale active entries |
|
|
495
|
+
| `runs/` history grows large | `glm-router runs clean --older-than 30d`, or tune `history.retentionDays` / `history.maxRuns` |
|
|
496
|
+
|
|
497
|
+
Run `glm-router doctor` for a full diagnosis — it authenticates the effective key against
|
|
498
|
+
the Z.ai monitor endpoint by default (no coding quota consumed). Add `--offline` for local
|
|
499
|
+
checks only, or `--network` to also probe the configured Anthropic endpoint's reachability
|
|
500
|
+
(reachability only — not the same thing as a verified key). `glm-router` with no arguments
|
|
501
|
+
prints a quick command reference.
|
|
502
|
+
|
|
503
|
+
## Uninstall
|
|
504
|
+
|
|
505
|
+
```powershell
|
|
506
|
+
glm-router uninstall
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
The wizard removes the config, the Codex skill, and optionally the current project
|
|
510
|
+
integration. `ZAI_API_KEY` is **kept** by default — removing credentials requires
|
|
511
|
+
explicit consent. Finish with `npm uninstall -g glm-coding-router`.
|
|
512
|
+
|
|
513
|
+
## Development
|
|
514
|
+
|
|
515
|
+
```powershell
|
|
516
|
+
npm install
|
|
517
|
+
npm run build # tsc → dist/
|
|
518
|
+
npm test # vitest run
|
|
519
|
+
npm run lint # eslint src tests
|
|
520
|
+
npm run dev # tsx src/cli.ts <args>
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
Integration tests spawn `tests/fixtures/fake-agent.mjs` (via `node.exe`) to verify
|
|
524
|
+
argument passing, environment injection, and exit-code propagation without spending
|
|
525
|
+
API quota. See the `docs/GLM Coding Router — Technical Specification v0.1.md` for the full
|
|
526
|
+
v0.1 contract (exit codes, managed-block test matrix, acceptance criteria).
|
|
527
|
+
|
|
528
|
+
## Publishing
|
|
529
|
+
|
|
530
|
+
```powershell
|
|
531
|
+
npm run build
|
|
532
|
+
npm test
|
|
533
|
+
npm publish
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
`prepublishOnly` runs build + tests. The package ships only `dist/`; the six binaries
|
|
537
|
+
(`glm-router`, `glm-chat`, `glm-fast`, `glm-worker`, `glm-review`, `glm-mcp`) are declared
|
|
538
|
+
in `bin`.
|
|
539
|
+
|
|
540
|
+
## License
|
|
541
|
+
|
|
542
|
+
MIT
|