glm-coding-router 0.1.0 → 0.1.1

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 CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 hieu9721
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 hieu9721
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,248 +1,248 @@
1
- # GLM Coding Router
2
-
3
- GLM Coding Plan workers for Claude Code and Codex, on Windows.
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-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
- ## Requirements
45
-
46
- - Windows 10/11 (v0.1; Linux/macOS planned for v0.2)
47
- - Node.js >= 20
48
- - Claude Code (`claude.exe`) — the GLM commands run on the Claude Code harness
49
- - Codex (optional — Claude-only setups are fully supported)
50
- - A Z.ai Coding Plan API key
51
-
52
- No Anthropic pay-as-you-go, no OpenAI API, no LiteLLM, no proxy.
53
-
54
- ## Installation
55
-
56
- ```powershell
57
- npm install -g glm-coding-router
58
- glm-router init
59
- ```
60
-
61
- `npx glm-coding-router init` also works for a one-off check, but the global install is
62
- what puts `glm-worker` on your PATH long-term.
63
-
64
- ## Quick start
65
-
66
- After `glm-router init`:
67
-
68
- ```powershell
69
- glm-chat
70
- glm-worker "Implement validation and add tests"
71
- glm-review "Analyze the auth module"
72
- ```
73
-
74
- ## glm-chat
75
-
76
- Interactive GLM-backed Claude Code session. Resolves the Z.ai key, locates `claude.exe`,
77
- injects the Z.ai environment **into the child process only**, and spawns it with
78
- pass-through arguments:
79
-
80
- ```powershell
81
- glm-chat
82
- glm-chat --version
83
- glm-chat --any-claude-flag
84
- ```
85
-
86
- Your normal `claude` command and its authentication are untouched.
87
-
88
- ## glm-worker
89
-
90
- Headless implementation worker:
91
-
92
- ```powershell
93
- glm-worker "Implement validation and add tests"
94
- ```
95
-
96
- Or via stdin (a structured task packet):
97
-
98
- ```powershell
99
- @"
100
- TASK:
101
- Implement refresh token validation.
102
-
103
- SCOPE:
104
- internal/auth/
105
-
106
- VALIDATION:
107
- go test ./internal/auth/...
108
- "@ | glm-worker
109
- ```
110
-
111
- Input priority: **stdin → arguments → error**. The worker runs with
112
- `--max-turns 20 --permission-mode acceptEdits --tools Read,Glob,Grep,Edit,Write,Bash`.
113
- It never uses `--dangerously-skip-permissions`.
114
-
115
- ## glm-review
116
-
117
- Read-only worker for repository exploration, call-graph discovery, duplicate detection,
118
- dependency inspection, and preliminary review:
119
-
120
- ```powershell
121
- glm-review "Inspect this repository"
122
- ```
123
-
124
- Runs with `--tools Read,Glob,Grep` — it cannot edit files or run commands.
125
-
126
- ## CLI reference
127
-
128
- ```text
129
- glm-router init guided setup
130
- glm-router doctor [--network] full runtime diagnosis
131
- glm-router status quick offline overview
132
- glm-router key set store ZAI_API_KEY (Windows User Environment)
133
- glm-router key check key configured? from which source?
134
- glm-router config show
135
- glm-router config set models.main glm-5.3
136
- glm-router project init CLAUDE.md / AGENTS.md managed blocks (--dry-run supported)
137
- glm-router project remove
138
- glm-router skill install optional Codex delegation skill
139
- glm-router skill remove
140
- glm-router uninstall guided removal (keeps ZAI_API_KEY by default)
141
- ```
142
-
143
- Global flags: `--json --quiet --verbose --dry-run --force --yes`
144
-
145
- ## Claude integration
146
-
147
- `glm-router project init` adds a **managed block** to `CLAUDE.md` at the project root
148
- (`git rev-parse --show-toplevel`, falling back to cwd):
149
-
150
- ```text
151
- <!-- glm-coding-router:start -->
152
- ... delegation policy ...
153
- <!-- glm-coding-router:end -->
154
- ```
155
-
156
- - Everything outside the markers is preserved; existing blocks are replaced in place;
157
- runs are idempotent and never duplicate.
158
- - Files are updated atomically (tmp file → fsync → rename).
159
- - On a malformed marker pair the file is left untouched with an actionable error.
160
- - CRLF/LF and UTF-8 are preserved.
161
- - `glm-router project remove` deletes only the managed block. A file the router
162
- created entirely is deleted only when it would otherwise be empty.
163
-
164
- ## Codex integration
165
-
166
- The same command updates `AGENTS.md` (Codex's repository instruction file) with an
167
- equivalent managed block. Additionally, `glm-router skill install` installs the optional
168
- `glm-delegation` skill to `~/.codex/skills/glm-delegation/SKILL.md`. If Codex is not
169
- detected, the skill step warns and skips — AGENTS.md integration and the core tool are
170
- unaffected.
171
-
172
- ## Orca behavior (stale environments)
173
-
174
- Terminals embedded in Orca snapshot the Windows environment at startup. A key added
175
- after Orca starts is invisible to those terminals. Every GLM command therefore resolves
176
- the key in this order:
177
-
178
- 1. `process.env.ZAI_API_KEY`
179
- 2. Windows User Environment (via PowerShell)
180
- 3. fail with an actionable error
181
-
182
- The key is never cached to disk.
183
-
184
- ## Security model
185
-
186
- - The key lives only in the Windows User Environment; it is never written to
187
- `config.json`, the repo, logs, or stack traces. Debug output redacts
188
- `ZAI_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, and Authorization headers.
189
- - Z.ai routing environment variables (`ANTHROPIC_AUTH_TOKEN`,
190
- `ANTHROPIC_BASE_URL`, model overrides) are injected **only** into the spawned
191
- `claude.exe` child process. `ANTHROPIC_API_KEY` is blanked in the child so your
192
- normal Claude auth is never in play. `ANTHROPIC_BASE_URL` is never persisted globally.
193
- - Claude Code and Codex global authentication are never modified.
194
- - Child processes are spawned with argument arrays (`shell: false`) — prompts with
195
- quotes, pipes, ampersands, or newlines are passed verbatim, never through a shell.
196
- - No telemetry, no automatic git commits.
197
-
198
- ## Troubleshooting
199
-
200
- | Symptom | Fix |
201
- | --- | --- |
202
- | `ERROR [ZAI_KEY_MISSING]` | `glm-router key set`, then open a **new** terminal |
203
- | `ERROR [CLAUDE_NOT_FOUND]` | Install Claude Code, or `glm-router config set claudePath C:\path\to\claude.exe` |
204
- | Key works in a new terminal but not inside Orca | Expected — workers re-read the Windows User Environment automatically; run `glm-router doctor` to confirm |
205
- | `glm-*` not on PATH after install | Reopen the terminal; check `npm config get prefix` is on PATH |
206
- | `ERROR [MANAGED_BLOCK_CORRUPT]` | Fix the marker pair in the named file manually, then re-run |
207
-
208
- Run `glm-router doctor` (add `--network` to probe the Z.ai endpoint) for a full diagnosis.
209
-
210
- ## Uninstall
211
-
212
- ```powershell
213
- glm-router uninstall
214
- ```
215
-
216
- The wizard removes the config, the Codex skill, and optionally the current project
217
- integration. `ZAI_API_KEY` is **kept** by default — removing credentials requires
218
- explicit consent. Finish with `npm uninstall -g glm-coding-router`.
219
-
220
- ## Development
221
-
222
- ```powershell
223
- npm install
224
- npm run build # tsc → dist/
225
- npm test # vitest run
226
- npm run lint # eslint src tests
227
- npm run dev # tsx src/cli.ts <args>
228
- ```
229
-
230
- Integration tests spawn `tests/fixtures/fake-agent.mjs` (via `node.exe`) to verify
231
- argument passing, environment injection, and exit-code propagation without spending
232
- API quota. See the `GLM Coding Router — Technical Specification v0.1.md` for the full
233
- v0.1 contract (exit codes, managed-block test matrix, acceptance criteria).
234
-
235
- ## Publishing
236
-
237
- ```powershell
238
- npm run build
239
- npm test
240
- npm publish
241
- ```
242
-
243
- `prepublishOnly` runs build + tests. The package ships only `dist/`; the four binaries
244
- (`glm-router`, `glm-chat`, `glm-worker`, `glm-review`) are declared in `bin`.
245
-
246
- ## License
247
-
248
- MIT
1
+ # GLM Coding Router
2
+
3
+ GLM Coding Plan workers for Claude Code and Codex, on Windows.
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-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
+ ## Requirements
45
+
46
+ - Windows 10/11 (v0.1; Linux/macOS planned for v0.2)
47
+ - Node.js >= 20
48
+ - Claude Code (`claude.exe`) — the GLM commands run on the Claude Code harness
49
+ - Codex (optional — Claude-only setups are fully supported)
50
+ - A Z.ai Coding Plan API key
51
+
52
+ No Anthropic pay-as-you-go, no OpenAI API, no LiteLLM, no proxy.
53
+
54
+ ## Installation
55
+
56
+ ```powershell
57
+ npm install -g glm-coding-router
58
+ glm-router init
59
+ ```
60
+
61
+ `npx glm-coding-router init` also works for a one-off check, but the global install is
62
+ what puts `glm-worker` on your PATH long-term.
63
+
64
+ ## Quick start
65
+
66
+ After `glm-router init`:
67
+
68
+ ```powershell
69
+ glm-chat
70
+ glm-worker "Implement validation and add tests"
71
+ glm-review "Analyze the auth module"
72
+ ```
73
+
74
+ ## glm-chat
75
+
76
+ Interactive GLM-backed Claude Code session. Resolves the Z.ai key, locates `claude.exe`,
77
+ injects the Z.ai environment **into the child process only**, and spawns it with
78
+ pass-through arguments:
79
+
80
+ ```powershell
81
+ glm-chat
82
+ glm-chat --version
83
+ glm-chat --any-claude-flag
84
+ ```
85
+
86
+ Your normal `claude` command and its authentication are untouched.
87
+
88
+ ## glm-worker
89
+
90
+ Headless implementation worker:
91
+
92
+ ```powershell
93
+ glm-worker "Implement validation and add tests"
94
+ ```
95
+
96
+ Or via stdin (a structured task packet):
97
+
98
+ ```powershell
99
+ @"
100
+ TASK:
101
+ Implement refresh token validation.
102
+
103
+ SCOPE:
104
+ internal/auth/
105
+
106
+ VALIDATION:
107
+ go test ./internal/auth/...
108
+ "@ | glm-worker
109
+ ```
110
+
111
+ Input priority: **stdin → arguments → error**. The worker runs with
112
+ `--max-turns 20 --permission-mode acceptEdits --tools Read,Glob,Grep,Edit,Write,Bash`.
113
+ It never uses `--dangerously-skip-permissions`.
114
+
115
+ ## glm-review
116
+
117
+ Read-only worker for repository exploration, call-graph discovery, duplicate detection,
118
+ dependency inspection, and preliminary review:
119
+
120
+ ```powershell
121
+ glm-review "Inspect this repository"
122
+ ```
123
+
124
+ Runs with `--tools Read,Glob,Grep` — it cannot edit files or run commands.
125
+
126
+ ## CLI reference
127
+
128
+ ```text
129
+ glm-router init guided setup
130
+ glm-router doctor [--network] full runtime diagnosis
131
+ glm-router status quick offline overview
132
+ glm-router key set store ZAI_API_KEY (Windows User Environment)
133
+ glm-router key check key configured? from which source?
134
+ glm-router config show
135
+ glm-router config set models.main glm-5.3
136
+ glm-router project init CLAUDE.md / AGENTS.md managed blocks (--dry-run supported)
137
+ glm-router project remove
138
+ glm-router skill install optional Codex delegation skill
139
+ glm-router skill remove
140
+ glm-router uninstall guided removal (keeps ZAI_API_KEY by default)
141
+ ```
142
+
143
+ Global flags: `--json --quiet --verbose --dry-run --force --yes`
144
+
145
+ ## Claude integration
146
+
147
+ `glm-router project init` adds a **managed block** to `CLAUDE.md` at the project root
148
+ (`git rev-parse --show-toplevel`, falling back to cwd):
149
+
150
+ ```text
151
+ <!-- glm-coding-router:start -->
152
+ ... delegation policy ...
153
+ <!-- glm-coding-router:end -->
154
+ ```
155
+
156
+ - Everything outside the markers is preserved; existing blocks are replaced in place;
157
+ runs are idempotent and never duplicate.
158
+ - Files are updated atomically (tmp file → fsync → rename).
159
+ - On a malformed marker pair the file is left untouched with an actionable error.
160
+ - CRLF/LF and UTF-8 are preserved.
161
+ - `glm-router project remove` deletes only the managed block. A file the router
162
+ created entirely is deleted only when it would otherwise be empty.
163
+
164
+ ## Codex integration
165
+
166
+ The same command updates `AGENTS.md` (Codex's repository instruction file) with an
167
+ equivalent managed block. Additionally, `glm-router skill install` installs the optional
168
+ `glm-delegation` skill to `~/.codex/skills/glm-delegation/SKILL.md`. If Codex is not
169
+ detected, the skill step warns and skips — AGENTS.md integration and the core tool are
170
+ unaffected.
171
+
172
+ ## Orca behavior (stale environments)
173
+
174
+ Terminals embedded in Orca snapshot the Windows environment at startup. A key added
175
+ after Orca starts is invisible to those terminals. Every GLM command therefore resolves
176
+ the key in this order:
177
+
178
+ 1. `process.env.ZAI_API_KEY`
179
+ 2. Windows User Environment (via PowerShell)
180
+ 3. fail with an actionable error
181
+
182
+ The key is never cached to disk.
183
+
184
+ ## Security model
185
+
186
+ - The key lives only in the Windows User Environment; it is never written to
187
+ `config.json`, the repo, logs, or stack traces. Debug output redacts
188
+ `ZAI_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, and Authorization headers.
189
+ - Z.ai routing environment variables (`ANTHROPIC_AUTH_TOKEN`,
190
+ `ANTHROPIC_BASE_URL`, model overrides) are injected **only** into the spawned
191
+ `claude.exe` child process. `ANTHROPIC_API_KEY` is blanked in the child so your
192
+ normal Claude auth is never in play. `ANTHROPIC_BASE_URL` is never persisted globally.
193
+ - Claude Code and Codex global authentication are never modified.
194
+ - Child processes are spawned with argument arrays (`shell: false`) — prompts with
195
+ quotes, pipes, ampersands, or newlines are passed verbatim, never through a shell.
196
+ - No telemetry, no automatic git commits.
197
+
198
+ ## Troubleshooting
199
+
200
+ | Symptom | Fix |
201
+ | --- | --- |
202
+ | `ERROR [ZAI_KEY_MISSING]` | `glm-router key set`, then open a **new** terminal |
203
+ | `ERROR [CLAUDE_NOT_FOUND]` | Install Claude Code, or `glm-router config set claudePath C:\path\to\claude.exe` |
204
+ | Key works in a new terminal but not inside Orca | Expected — workers re-read the Windows User Environment automatically; run `glm-router doctor` to confirm |
205
+ | `glm-*` not on PATH after install | Reopen the terminal; check `npm config get prefix` is on PATH |
206
+ | `ERROR [MANAGED_BLOCK_CORRUPT]` | Fix the marker pair in the named file manually, then re-run |
207
+
208
+ Run `glm-router doctor` (add `--network` to probe the Z.ai endpoint) for a full diagnosis.
209
+
210
+ ## Uninstall
211
+
212
+ ```powershell
213
+ glm-router uninstall
214
+ ```
215
+
216
+ The wizard removes the config, the Codex skill, and optionally the current project
217
+ integration. `ZAI_API_KEY` is **kept** by default — removing credentials requires
218
+ explicit consent. Finish with `npm uninstall -g glm-coding-router`.
219
+
220
+ ## Development
221
+
222
+ ```powershell
223
+ npm install
224
+ npm run build # tsc → dist/
225
+ npm test # vitest run
226
+ npm run lint # eslint src tests
227
+ npm run dev # tsx src/cli.ts <args>
228
+ ```
229
+
230
+ Integration tests spawn `tests/fixtures/fake-agent.mjs` (via `node.exe`) to verify
231
+ argument passing, environment injection, and exit-code propagation without spending
232
+ API quota. See the `GLM Coding Router — Technical Specification v0.1.md` for the full
233
+ v0.1 contract (exit codes, managed-block test matrix, acceptance criteria).
234
+
235
+ ## Publishing
236
+
237
+ ```powershell
238
+ npm run build
239
+ npm test
240
+ npm publish
241
+ ```
242
+
243
+ `prepublishOnly` runs build + tests. The package ships only `dist/`; the four binaries
244
+ (`glm-router`, `glm-chat`, `glm-worker`, `glm-review`) are declared in `bin`.
245
+
246
+ ## License
247
+
248
+ MIT
@@ -2,10 +2,10 @@ import { loadConfig, saveConfig, setConfigValue } from "../core/config.js";
2
2
  import { configPath } from "../core/paths.js";
3
3
  import { emitJson } from "./context.js";
4
4
  /** glm-router config show (spec §28). */
5
- export function configShowCommand(options) {
6
- const config = loadConfig();
5
+ export function configShowCommand(options, deps = {}) {
6
+ const config = loadConfig(deps.home);
7
7
  if (options.json) {
8
- emitJson({ path: configPath(), config });
8
+ emitJson({ path: configPath(deps.home), config });
9
9
  return 0;
10
10
  }
11
11
  const lines = [
@@ -24,16 +24,16 @@ export function configShowCommand(options) {
24
24
  ` Codex: ${config.integrations.codex ? "enabled" : "disabled"}`,
25
25
  ` Codex skill: ${config.integrations.codexSkill ? "enabled" : "disabled"}`,
26
26
  "",
27
- `Config file: ${configPath()}`,
27
+ `Config file: ${configPath(deps.home)}`,
28
28
  ];
29
29
  process.stdout.write(lines.join("\n") + "\n");
30
30
  return 0;
31
31
  }
32
32
  /** glm-router config set <dotted.key> <value> (spec §28). */
33
- export function configSetCommand(key, value, _options) {
34
- const config = loadConfig();
33
+ export function configSetCommand(key, value, _options, deps = {}) {
34
+ const config = loadConfig(deps.home);
35
35
  const updated = setConfigValue(config, key, value);
36
- saveConfig(updated);
36
+ saveConfig(updated, deps.home);
37
37
  process.stdout.write(`✓ ${key} = ${value}\n`);
38
38
  return 0;
39
39
  }
@@ -32,9 +32,9 @@ function renderText(results, networkResult) {
32
32
  return lines.join("\n");
33
33
  }
34
34
  /** Lightweight endpoint reachability probe (spec §42). Never consumes coding quota. */
35
- export async function probeEndpoint(baseUrl) {
35
+ export async function probeEndpoint(baseUrl, fetchImpl = fetch) {
36
36
  try {
37
- const response = await fetch(baseUrl, {
37
+ const response = await fetchImpl(baseUrl, {
38
38
  method: "GET",
39
39
  signal: AbortSignal.timeout(10_000),
40
40
  });
@@ -45,18 +45,22 @@ export async function probeEndpoint(baseUrl) {
45
45
  return "not reachable";
46
46
  }
47
47
  }
48
- export async function doctorCommand(options) {
49
- const report = runDoctorChecks();
48
+ export async function doctorCommand(options, deps = {}) {
49
+ const report = runDoctorChecks({ home: deps.home, env: deps.env, readUserEnv: deps.readUserEnv });
50
50
  if (options.json) {
51
+ const networkResult = options.network
52
+ ? await probeEndpoint(report.config.provider.anthropicBaseUrl, deps.fetchImpl)
53
+ : undefined;
51
54
  emitJson({
52
55
  status: doctorHasFailures(report.results) ? "ISSUES" : "HEALTHY",
53
56
  checks: report.results,
54
57
  keySource: report.keySource,
58
+ network: networkResult,
55
59
  });
56
60
  return doctorHasFailures(report.results) ? 1 : 0;
57
61
  }
58
62
  const networkResult = options.network
59
- ? await probeEndpoint(report.config.provider.anthropicBaseUrl)
63
+ ? await probeEndpoint(report.config.provider.anthropicBaseUrl, deps.fetchImpl)
60
64
  : undefined;
61
65
  process.stdout.write(renderText(report.results, networkResult) + "\n");
62
66
  logger.debug(`anthropic base url: ${report.config.provider.anthropicBaseUrl}`);
@@ -27,6 +27,7 @@ function gitFound() {
27
27
  export function runDoctorChecks(options = {}) {
28
28
  const results = [];
29
29
  const home = options.home ?? os.homedir();
30
+ const env = options.env ?? process.env;
30
31
  // --- System ---
31
32
  results.push(check("System", isWindows() ? windowsVersionName() : `Platform ${process.platform}`, isWindows() ? "ok" : "fail", undefined, isWindows() ? undefined : "v0.1 targets Windows only."));
32
33
  const nodeMajor = nodeVersionMajor();
@@ -50,16 +51,16 @@ export function runDoctorChecks(options = {}) {
50
51
  }
51
52
  // --- Agents ---
52
53
  try {
53
- const claudePath = locateClaude(config);
54
+ const claudePath = locateClaude(config, env);
54
55
  results.push(check("Agents", "Claude Code", "ok", claudePath));
55
56
  }
56
57
  catch (error) {
57
58
  results.push(check("Agents", "Claude Code", "fail", error instanceof Error ? error.message : String(error)));
58
59
  }
59
- const codexPath = locateCodex(config);
60
+ const codexPath = locateCodex(config, env);
60
61
  results.push(check("Agents", "Codex", codexPath ? "ok" : "warn", codexPath, codexPath ? undefined : "Optional — Claude-only setups are supported."));
61
62
  // --- Z.ai key ---
62
- const resolved = resolveZaiApiKey();
63
+ const resolved = resolveZaiApiKey({ env, readUserEnv: options.readUserEnv });
63
64
  results.push(check("Z.ai", "ZAI_API_KEY", resolved ? "ok" : "fail", resolved ? `configured (${resolved.source})` : "not found", resolved ? undefined : "Run: glm-router key set"));
64
65
  results.push(check("Z.ai", "Anthropic endpoint", "ok", config.provider.anthropicBaseUrl));
65
66
  // --- Commands (PATH shims; a dev checkout warns instead of failing) ---
@@ -79,7 +80,7 @@ export function runDoctorChecks(options = {}) {
79
80
  ? "not installed (optional)"
80
81
  : "Codex home not detected — skill skipped (optional)"));
81
82
  // --- Environment: the Orca stale-env case (spec §9, §10) ---
82
- const hasProcessKey = Boolean(process.env.ZAI_API_KEY && process.env.ZAI_API_KEY.trim());
83
+ const hasProcessKey = Boolean(env.ZAI_API_KEY && env.ZAI_API_KEY.trim());
83
84
  if (hasProcessKey) {
84
85
  results.push(check("Environment", "Process environment", "ok", "ZAI_API_KEY visible in current process"));
85
86
  }