glm-coding-router 0.3.0 → 0.4.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/README.md CHANGED
@@ -200,6 +200,39 @@ glm-router delegate backend "Task A" # terminal 1
200
200
  glm-router delegate tests "Task B" # terminal 2
201
201
  ```
202
202
 
203
+ ## benchmark
204
+
205
+ Measure the Claude Code + GLM stack on built-in coding tasks (spec §54 v0.4).
206
+ Each task runs in a throwaway temp directory: the router writes the task files,
207
+ spawns the standard GLM worker (same env injection, plus `--output-format json`
208
+ to capture the result document), then runs the task's validation command:
209
+
210
+ ```powershell
211
+ glm-router benchmark --yes # both built-in tasks, 1 run each
212
+ glm-router benchmark --yes --task fn-reverse --repeat 3
213
+ glm-router benchmark --yes --max-turns 15
214
+ ```
215
+
216
+ Report (per task × run): **duration**, **GLM calls** (assistant turns),
217
+ **retries** (`-` — not exposed by Claude Code yet), **tokens in/out**,
218
+ **tests** (PASS/FAIL of `node test.js`), **success**, **intervention**
219
+ (`needed` when the run did not self-complete). The full JSON report is always
220
+ saved to `%USERPROFILE%\.glm-coding-router\benchmarks\benchmark-<timestamp>.json`
221
+ and `--json` also prints it.
222
+
223
+ Built-in tasks: `fn-reverse` (implement `reverseWords` until the test passes),
224
+ `fix-bug` (repair an even-length `median` bug).
225
+
226
+ Notes:
227
+ - Benchmarking makes **real GLM API calls** — interactive runs ask for
228
+ confirmation; non-interactive runs require `--yes`.
229
+ - Failed tasks are measurements, not errors: the command exits 0 once the
230
+ suite ran. Missing key/claude or a broken spawn still fail with the usual
231
+ `ERROR [10]/[20]/[40]`.
232
+ - `--stack codex` is recognized but not supported yet (headless Codex
233
+ orchestration isn't drivable today); the harness is stack-shaped so it can
234
+ be added later.
235
+
203
236
  ## CLI reference
204
237
 
205
238
  ```text
@@ -211,6 +244,7 @@ glm-router key check key configured? from which source?
211
244
  glm-router config show
212
245
  glm-router config set models.main glm-5.3
213
246
  glm-router delegate <name> run a GLM worker in an isolated git worktree
247
+ glm-router benchmark measure the Claude+GLM stack on built-in tasks
214
248
  glm-router project init CLAUDE.md / AGENTS.md managed blocks (--dry-run supported)
215
249
  glm-router project remove
216
250
  glm-router skill install optional Codex delegation skill
package/dist/cli.js CHANGED
@@ -14,6 +14,7 @@ import { projectRemoveCommand } from "./commands/project-remove.js";
14
14
  import { skillInstallCommand, skillRemoveCommand } from "./commands/skill.js";
15
15
  import { uninstallCommand } from "./commands/uninstall.js";
16
16
  import { delegateCommand } from "./commands/delegate.js";
17
+ import { benchmarkCommand } from "./commands/benchmark.js";
17
18
  const program = new Command();
18
19
  program
19
20
  .name("glm-router")
@@ -94,6 +95,14 @@ program
94
95
  .option("--profile <name>", "profile overlay (defaults to a profile named <name> if defined)")
95
96
  .option("--remove", "remove the worktree after a successful run (branch is always kept)")
96
97
  .action((name, prompt, commandOptions) => execute(() => delegateCommand(name, prompt, { ...globalOptions(), ...commandOptions })));
98
+ program
99
+ .command("benchmark")
100
+ .description("measure the Claude+GLM stack on built-in coding tasks (makes real GLM calls)")
101
+ .option("--task <id>", "run only this task (repeatable)", (value, previous) => previous.concat([value]), [])
102
+ .option("--stack <name>", "orchestration stack (default claude; codex not yet supported)")
103
+ .option("--max-turns <n>", "worker --max-turns override", (value) => Number(value))
104
+ .option("--repeat <n>", "run each task N times", (value) => Number(value))
105
+ .action((commandOptions) => execute(() => benchmarkCommand({ ...globalOptions(), ...commandOptions })));
97
106
  program
98
107
  .command("uninstall")
99
108
  .description("guided removal (keeps ZAI_API_KEY by default)")
@@ -0,0 +1,252 @@
1
+ import { execFile } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import prompts from "prompts";
6
+ import { version } from "../core/version.js";
7
+ import { WORKER_TOOLS } from "../bin/glm-worker.js";
8
+ import { loadConfig } from "../core/config.js";
9
+ import { locateClaude } from "../core/claude.js";
10
+ import { createGlmEnv } from "../core/env.js";
11
+ import { Errors } from "../core/errors.js";
12
+ import { configDir } from "../core/paths.js";
13
+ import { spawnAgentCapture } from "../core/process.js";
14
+ import { resolveZaiApiKey } from "../core/zai-key.js";
15
+ import { BENCHMARK_TASKS, benchmarkTaskById } from "../templates/benchmark-tasks.js";
16
+ import { emitJson } from "./context.js";
17
+ const KNOWN_STACKS = ["claude", "codex"];
18
+ /**
19
+ * glm-router benchmark (spec §54 v0.4, specs/benchmark.md): run built-in
20
+ * coding tasks through the Claude+GLM worker path and report §54 metrics.
21
+ * Failed tasks are measurements, not CLI errors — exits 0 once the suite ran.
22
+ */
23
+ export async function benchmarkCommand(options, deps = {}) {
24
+ const stack = options.stack ?? "claude";
25
+ if (stack === "codex") {
26
+ throw Errors.invalidArgs("The codex stack is not supported yet: headless Codex orchestration cannot be driven reliably today.", ["v0.4 ships the harness + claude stack; codex slots in later without redesign."]);
27
+ }
28
+ if (!KNOWN_STACKS.includes(stack)) {
29
+ throw Errors.invalidArgs(`Unknown stack "${stack}".`, [`Known stacks: ${KNOWN_STACKS.join(", ")}`]);
30
+ }
31
+ const ids = options.task && options.task.length > 0 ? options.task : BENCHMARK_TASKS.map((t) => t.id);
32
+ const unknown = ids.filter((id) => !benchmarkTaskById(id));
33
+ if (unknown.length > 0) {
34
+ throw Errors.invalidArgs(`Unknown task id(s): ${unknown.join(", ")}.`, [`Available tasks: ${BENCHMARK_TASKS.map((t) => t.id).join(", ")}`]);
35
+ }
36
+ const tasks = ids.map((id) => benchmarkTaskById(id));
37
+ const repeat = options.repeat ?? 1;
38
+ const maxTurns = options.maxTurns;
39
+ if (!Number.isInteger(repeat) || repeat < 1) {
40
+ throw Errors.invalidArgs(`--repeat expects a positive integer, got "${repeat}".`);
41
+ }
42
+ if (maxTurns !== undefined && (!Number.isInteger(maxTurns) || maxTurns < 1)) {
43
+ throw Errors.invalidArgs(`--max-turns expects a positive integer, got "${maxTurns}".`);
44
+ }
45
+ if (options.dryRun) {
46
+ if (options.json) {
47
+ emitJson({ stack, tasks: ids, repeat, maxTurns: maxTurns ?? "(config default)", dryRun: true });
48
+ }
49
+ else {
50
+ const lines = [
51
+ `would run stack ${stack}`,
52
+ `would run tasks ${ids.join(", ")}`,
53
+ `would repeat ${repeat}x`,
54
+ `would use maxTurns ${maxTurns ?? "(config default)"}`,
55
+ ];
56
+ process.stdout.write(lines.join("\n") + "\n");
57
+ }
58
+ return 0;
59
+ }
60
+ const interactive = deps.interactive ?? process.stdin.isTTY === true;
61
+ if (!options.yes) {
62
+ if (!interactive) {
63
+ throw Errors.invalidArgs(`benchmark makes real GLM API calls; confirm with --yes when running non-interactively.`, [` glm-router benchmark --yes`]);
64
+ }
65
+ const confirm = deps.confirm ?? defaultConfirm;
66
+ const ok = await confirm();
67
+ if (!ok) {
68
+ process.stdout.write("Cancelled.\n");
69
+ return 1;
70
+ }
71
+ }
72
+ const home = deps.home ?? os.homedir();
73
+ const env = deps.env ?? process.env;
74
+ const config = loadConfig(home);
75
+ const resolved = resolveZaiApiKey({ env, readUserEnv: deps.readUserEnv });
76
+ if (!resolved) {
77
+ throw Errors.zaiKeyMissing();
78
+ }
79
+ const claudePath = locateClaude(config, env);
80
+ const turns = maxTurns ?? config.worker.maxTurns;
81
+ const now = deps.now ?? Date.now;
82
+ const mkdtemp = deps.mkdtemp ?? (() => fs.mkdtempSync(path.join(os.tmpdir(), "glm-benchmark-")));
83
+ const spawn = deps.spawn ?? spawnAgentCapture;
84
+ const startedAt = new Date(now()).toISOString();
85
+ const metrics = [];
86
+ for (const task of tasks) {
87
+ for (let run = 1; run <= repeat; run++) {
88
+ metrics.push(await runTask(task, run, { turns, claudePath, config, key: resolved.key, env, now, mkdtemp, spawn }));
89
+ }
90
+ }
91
+ const report = {
92
+ version,
93
+ stack,
94
+ startedAt,
95
+ finishedAt: new Date(now()).toISOString(),
96
+ maxTurns: turns,
97
+ repeat,
98
+ tasks: metrics,
99
+ };
100
+ const savedPath = saveReport(report, home);
101
+ if (!options.quiet && !options.json) {
102
+ process.stdout.write(`report saved to ${savedPath}\n`);
103
+ }
104
+ if (options.json) {
105
+ emitJson({ ...report, savedPath });
106
+ }
107
+ else {
108
+ renderTable(metrics, options);
109
+ }
110
+ return 0;
111
+ }
112
+ async function runTask(task, run, ctx) {
113
+ const dir = ctx.mkdtemp();
114
+ try {
115
+ for (const [relative, content] of Object.entries(task.files)) {
116
+ const file = path.join(dir, relative);
117
+ fs.mkdirSync(path.dirname(file), { recursive: true });
118
+ fs.writeFileSync(file, content, "utf8");
119
+ }
120
+ const args = [
121
+ "-p",
122
+ task.prompt,
123
+ "--max-turns",
124
+ String(ctx.turns),
125
+ "--permission-mode",
126
+ "acceptEdits",
127
+ "--tools",
128
+ WORKER_TOOLS,
129
+ "--output-format",
130
+ "json",
131
+ ];
132
+ const childEnv = createGlmEnv(ctx.config, ctx.key, ctx.env);
133
+ const started = ctx.now();
134
+ let captured;
135
+ try {
136
+ captured = await ctx.spawn(ctx.claudePath, { args, cwd: dir, env: childEnv, interactive: false });
137
+ }
138
+ catch (error) {
139
+ throw Errors.childAgentFailed(error instanceof Error ? error.message : String(error));
140
+ }
141
+ const durationMs = ctx.now() - started;
142
+ const parsed = parseClaudeResult(captured.stdout);
143
+ const validation = await runValidation(task.validate, dir);
144
+ const selfCompleted = captured.code === 0 && parsed?.isError !== true && !isErrorSubtype(parsed?.subtype);
145
+ const success = selfCompleted && validation.pass;
146
+ return {
147
+ task: task.id,
148
+ run,
149
+ durationMs,
150
+ childDurationMs: parsed?.durationMs ?? null,
151
+ workerExit: captured.code,
152
+ glmCalls: parsed?.numTurns ?? null,
153
+ inputTokens: parsed?.usage?.input_tokens ?? null,
154
+ outputTokens: parsed?.usage?.output_tokens ?? null,
155
+ subtype: parsed?.subtype ?? null,
156
+ testsPass: validation.pass,
157
+ testsOutput: validation.output,
158
+ success,
159
+ interventionNeeded: !selfCompleted,
160
+ resultParsed: parsed !== null,
161
+ };
162
+ }
163
+ finally {
164
+ fs.rmSync(dir, { recursive: true, force: true });
165
+ }
166
+ }
167
+ /** Parse the child's `--output-format json` result document (last JSON line wins). */
168
+ export function parseClaudeResult(stdout) {
169
+ const lines = stdout.split(/\r?\n/).filter((line) => line.trim().length > 0);
170
+ for (let i = lines.length - 1; i >= 0; i--) {
171
+ try {
172
+ const parsed = JSON.parse(lines[i]);
173
+ if (typeof parsed === "object" && parsed !== null) {
174
+ return {
175
+ subtype: typeof parsed.subtype === "string" ? parsed.subtype : undefined,
176
+ isError: typeof parsed.is_error === "boolean" ? parsed.is_error : undefined,
177
+ numTurns: typeof parsed.num_turns === "number" ? parsed.num_turns : undefined,
178
+ durationMs: typeof parsed.duration_ms === "number" ? parsed.duration_ms : undefined,
179
+ usage: typeof parsed.usage === "object" && parsed.usage !== null
180
+ ? {
181
+ input_tokens: numberOrUndefined(parsed.usage.input_tokens),
182
+ output_tokens: numberOrUndefined(parsed.usage.output_tokens),
183
+ }
184
+ : undefined,
185
+ };
186
+ }
187
+ }
188
+ catch {
189
+ // Not JSON — keep walking backwards.
190
+ }
191
+ }
192
+ return null;
193
+ }
194
+ function numberOrUndefined(value) {
195
+ return typeof value === "number" ? value : undefined;
196
+ }
197
+ function isErrorSubtype(subtype) {
198
+ return subtype !== undefined && subtype.startsWith("error");
199
+ }
200
+ async function runValidation(argv, cwd) {
201
+ const [bin, ...rest] = argv;
202
+ return new Promise((resolve) => {
203
+ execFile(bin, rest, { cwd, windowsHide: true, encoding: "utf8", timeout: 30_000 }, (error, stdout, stderr) => {
204
+ const output = `${stdout}${stderr}`.trim();
205
+ resolve({ pass: !error, output: output.length > 0 ? output.slice(-400) : "(no output)" });
206
+ });
207
+ });
208
+ }
209
+ function saveReport(report, home) {
210
+ const dir = path.join(configDir(home), "benchmarks");
211
+ fs.mkdirSync(dir, { recursive: true });
212
+ const stamp = new Date().toISOString().replace(/[:.]/g, "-");
213
+ const file = path.join(dir, `benchmark-${stamp}.json`);
214
+ const tmp = `${file}.tmp-${process.pid}`;
215
+ fs.writeFileSync(tmp, JSON.stringify(report, null, 2) + "\n", "utf8");
216
+ fs.renameSync(tmp, file);
217
+ return file;
218
+ }
219
+ function renderTable(metrics, options) {
220
+ if (options.quiet)
221
+ return;
222
+ const header = ["task", "run", "duration", "GLM calls", "retries", "tokens i/o", "tests", "success", "intervention"];
223
+ const rows = metrics.map((m) => [
224
+ m.task,
225
+ String(m.run),
226
+ formatMs(m.durationMs),
227
+ m.glmCalls === null ? "-" : String(m.glmCalls),
228
+ "-", // retry count: not exposed by Claude Code (specs/benchmark.md)
229
+ m.inputTokens === null || m.outputTokens === null ? "-" : `${m.inputTokens}/${m.outputTokens}`,
230
+ m.testsPass ? "PASS" : "FAIL",
231
+ m.success ? "yes" : "no",
232
+ m.interventionNeeded ? "needed" : "none",
233
+ ]);
234
+ const widths = header.map((_, column) => Math.max(header[column].length, ...rows.map((row) => row[column].length)));
235
+ const line = (cells) => cells.map((cell, column) => cell.padEnd(widths[column])).join(" ");
236
+ process.stdout.write([line(header), ...rows.map(line)].join("\n") + "\n");
237
+ for (const m of metrics.filter((m) => !m.testsPass)) {
238
+ process.stdout.write(`\n${m.task} (run ${m.run}) validation output:\n${m.testsOutput}\n`);
239
+ }
240
+ }
241
+ function formatMs(ms) {
242
+ return ms >= 10_000 ? `${(ms / 1000).toFixed(1)}s` : `${Math.round(ms / 100) / 10}s`;
243
+ }
244
+ async function defaultConfirm() {
245
+ const response = await prompts({
246
+ type: "confirm",
247
+ name: "ok",
248
+ message: "Benchmark will make real GLM API calls. Continue?",
249
+ initial: false,
250
+ });
251
+ return response.ok === true;
252
+ }
@@ -96,6 +96,12 @@ export const Errors = {
96
96
  ],
97
97
  exitCode: ExitCode.InvalidArgs,
98
98
  }),
99
+ invalidArgs: (detail, hint) => new GlmRouterError({
100
+ name: "INVALID_ARGS",
101
+ message: detail,
102
+ hint: hint && hint.length > 0 ? hint : undefined,
103
+ exitCode: ExitCode.InvalidArgs,
104
+ }),
99
105
  gitNotFound: () => new GlmRouterError({
100
106
  name: "GIT_NOT_FOUND",
101
107
  message: "git was not found on PATH.",
@@ -10,51 +10,84 @@ import { Errors } from "./errors.js";
10
10
  export function spawnAgent(binPath, options) {
11
11
  const { args, cwd, env, interactive = true } = options;
12
12
  return new Promise((resolve, reject) => {
13
- let child;
14
- try {
15
- child = spawn(binPath, args, {
16
- cwd,
17
- env,
18
- stdio: interactive ? "inherit" : ["ignore", "inherit", "inherit"],
19
- shell: false,
20
- windowsHide: false,
21
- });
22
- }
23
- catch (error) {
24
- reject(Errors.childAgentFailed(errorMessage(error)));
25
- return;
26
- }
27
- const signals = ["SIGINT", "SIGTERM"];
28
- const handlers = new Map();
29
- for (const signal of signals) {
30
- const handler = () => {
31
- if (child.killed)
32
- return;
33
- try {
34
- child.kill(signal);
35
- }
36
- catch {
37
- // Child already gone; the exit event settles the promise.
38
- }
39
- };
40
- handlers.set(signal, handler);
41
- process.on(signal, handler);
42
- }
43
- const cleanup = () => {
44
- for (const [signal, handler] of handlers) {
45
- process.removeListener(signal, handler);
46
- }
47
- };
13
+ const child = spawn(binPath, args, {
14
+ cwd,
15
+ env,
16
+ stdio: interactive ? "inherit" : ["ignore", "inherit", "inherit"],
17
+ shell: false,
18
+ windowsHide: false,
19
+ });
20
+ const handlers = forwardSignals(child);
48
21
  child.on("error", (error) => {
49
- cleanup();
22
+ removeSignals(handlers);
50
23
  reject(Errors.childAgentFailed(errorMessage(error)));
51
24
  });
52
25
  child.on("exit", (code) => {
53
- cleanup();
26
+ removeSignals(handlers);
54
27
  resolve(code ?? 1);
55
28
  });
56
29
  });
57
30
  }
31
+ /**
32
+ * Like spawnAgent but pipes stdout/stderr instead of inheriting them and
33
+ * resolves the captured output (specs/benchmark.md) — same no-shell rule and
34
+ * signal forwarding. Used by `benchmark` to parse the child's result JSON.
35
+ */
36
+ export function spawnAgentCapture(binPath, options) {
37
+ const { args, cwd, env } = options;
38
+ return new Promise((resolve, reject) => {
39
+ const child = spawn(binPath, args, {
40
+ cwd,
41
+ env,
42
+ stdio: ["ignore", "pipe", "pipe"],
43
+ shell: false,
44
+ windowsHide: false,
45
+ });
46
+ let stdout = "";
47
+ let stderr = "";
48
+ child.stdout?.setEncoding("utf8");
49
+ child.stderr?.setEncoding("utf8");
50
+ child.stdout?.on("data", (chunk) => {
51
+ stdout += chunk;
52
+ });
53
+ child.stderr?.on("data", (chunk) => {
54
+ stderr += chunk;
55
+ });
56
+ const handlers = forwardSignals(child);
57
+ child.on("error", (error) => {
58
+ removeSignals(handlers);
59
+ reject(Errors.childAgentFailed(errorMessage(error)));
60
+ });
61
+ child.on("exit", (code) => {
62
+ removeSignals(handlers);
63
+ resolve({ code: code ?? 1, stdout, stderr });
64
+ });
65
+ });
66
+ }
67
+ /** Install SIGINT/SIGTERM forwarding; returns the handlers for cleanup. */
68
+ function forwardSignals(child) {
69
+ const handlers = new Map();
70
+ for (const signal of ["SIGINT", "SIGTERM"]) {
71
+ const handler = () => {
72
+ if (child.killed)
73
+ return;
74
+ try {
75
+ child.kill(signal);
76
+ }
77
+ catch {
78
+ // Child already gone; the exit event settles the promise.
79
+ }
80
+ };
81
+ handlers.set(signal, handler);
82
+ process.on(signal, handler);
83
+ }
84
+ return handlers;
85
+ }
86
+ function removeSignals(handlers) {
87
+ for (const [signal, handler] of handlers) {
88
+ process.removeListener(signal, handler);
89
+ }
90
+ }
58
91
  function errorMessage(error) {
59
92
  return error instanceof Error ? error.message : String(error);
60
93
  }
@@ -0,0 +1,70 @@
1
+ export const BENCHMARK_TASKS = [
2
+ {
3
+ id: "fn-reverse",
4
+ description: "implement reverseWords from a stub until node test.js passes",
5
+ files: {
6
+ "src/util.js": [
7
+ "// Implement reverseWords(str): reverse the ORDER of the words in str.",
8
+ '// Example: reverseWords("hello world") === "world hello".',
9
+ "// Collapse extra whitespace between words and trim the ends.",
10
+ "function reverseWords(str) {",
11
+ " // TODO: implement",
12
+ "}",
13
+ "",
14
+ "module.exports = { reverseWords };",
15
+ "",
16
+ ].join("\n"),
17
+ "test.js": [
18
+ 'const assert = require("node:assert");',
19
+ 'const { reverseWords } = require("./src/util.js");',
20
+ 'assert.strictEqual(reverseWords("hello world"), "world hello");',
21
+ 'assert.strictEqual(reverseWords("a"), "a");',
22
+ 'assert.strictEqual(reverseWords(" spaced out "), "out spaced");',
23
+ 'assert.strictEqual(reverseWords(""), "");',
24
+ 'console.log("PASS");',
25
+ "",
26
+ ].join("\n"),
27
+ },
28
+ prompt: [
29
+ "Implement reverseWords in src/util.js so that `node test.js` passes.",
30
+ "Words are separated by whitespace; collapse repeats and trim the ends.",
31
+ "Do not modify test.js. Verify by running `node test.js` with Bash.",
32
+ ].join(" "),
33
+ validate: ["node", "test.js"],
34
+ },
35
+ {
36
+ id: "fix-bug",
37
+ description: "repair an even-length median bug until node test.js passes",
38
+ files: {
39
+ "stats.js": [
40
+ "// median(values) returns the median of a non-empty list of numbers.",
41
+ "function median(values) {",
42
+ " const sorted = [...values].sort((a, b) => a - b);",
43
+ " const mid = Math.floor(sorted.length / 2);",
44
+ " return sorted[mid]; // BUG: wrong for even-length lists",
45
+ "}",
46
+ "",
47
+ "module.exports = { median };",
48
+ "",
49
+ ].join("\n"),
50
+ "test.js": [
51
+ 'const assert = require("node:assert");',
52
+ 'const { median } = require("./stats.js");',
53
+ "assert.strictEqual(median([3, 1, 2]), 2);",
54
+ "assert.strictEqual(median([4, 1, 3, 2]), 2.5);",
55
+ "assert.strictEqual(median([5]), 5);",
56
+ 'console.log("PASS");',
57
+ "",
58
+ ].join("\n"),
59
+ },
60
+ prompt: [
61
+ "stats.js has a bug: median() returns the wrong result for even-length lists.",
62
+ "Fix it so that `node test.js` passes (even lists return the average of the two middle values).",
63
+ "Do not modify test.js. Verify by running `node test.js` with Bash.",
64
+ ].join(" "),
65
+ validate: ["node", "test.js"],
66
+ },
67
+ ];
68
+ export function benchmarkTaskById(id) {
69
+ return BENCHMARK_TASKS.find((task) => task.id === id);
70
+ }
package/package.json CHANGED
@@ -1,47 +1,47 @@
1
- {
2
- "name": "glm-coding-router",
3
- "version": "0.3.0",
4
- "description": "GLM Coding Plan workers for Claude Code and Codex",
5
- "type": "module",
6
- "license": "MIT",
7
- "author": "hieu9721",
8
- "repository": {
9
- "type": "git",
10
- "url": "git+https://github.com/hieu9721/GLM-coding-router.git"
11
- },
12
- "bin": {
13
- "glm-router": "./dist/cli.js",
14
- "glm-chat": "./dist/bin/glm-chat.js",
15
- "glm-worker": "./dist/bin/glm-worker.js",
16
- "glm-review": "./dist/bin/glm-review.js",
17
- "glm-fast": "./dist/bin/glm-fast.js"
18
- },
19
- "files": [
20
- "dist"
21
- ],
22
- "scripts": {
23
- "dev": "tsx src/cli.ts",
24
- "build": "tsc",
25
- "test": "vitest run",
26
- "test:watch": "vitest",
27
- "lint": "eslint src tests",
28
- "prepublishOnly": "npm run build && npm test"
29
- },
30
- "engines": {
31
- "node": ">=20"
32
- },
33
- "dependencies": {
34
- "commander": "^15.0.0",
35
- "prompts": "^2.4.2",
36
- "zod": "^4.6.5"
37
- },
38
- "devDependencies": {
39
- "@types/node": "^22.20.3",
40
- "@types/prompts": "^2.4.9",
41
- "eslint": "^9.39.5",
42
- "tsx": "^4.23.13",
43
- "typescript": "^5.9.3",
44
- "typescript-eslint": "^8.70.0",
45
- "vitest": "^5.0.1"
46
- }
47
- }
1
+ {
2
+ "name": "glm-coding-router",
3
+ "version": "0.4.0",
4
+ "description": "GLM Coding Plan workers for Claude Code and Codex",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "hieu9721",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/hieu9721/GLM-coding-router.git"
11
+ },
12
+ "bin": {
13
+ "glm-router": "./dist/cli.js",
14
+ "glm-chat": "./dist/bin/glm-chat.js",
15
+ "glm-worker": "./dist/bin/glm-worker.js",
16
+ "glm-review": "./dist/bin/glm-review.js",
17
+ "glm-fast": "./dist/bin/glm-fast.js"
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "scripts": {
23
+ "dev": "tsx src/cli.ts",
24
+ "build": "tsc",
25
+ "test": "vitest run",
26
+ "test:watch": "vitest",
27
+ "lint": "eslint src tests",
28
+ "prepublishOnly": "npm run build && npm test"
29
+ },
30
+ "engines": {
31
+ "node": ">=20"
32
+ },
33
+ "dependencies": {
34
+ "commander": "^15.0.0",
35
+ "prompts": "^2.4.2",
36
+ "zod": "^4.6.5"
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "^22.20.3",
40
+ "@types/prompts": "^2.4.9",
41
+ "eslint": "^9.39.5",
42
+ "tsx": "^4.23.13",
43
+ "typescript": "^5.9.3",
44
+ "typescript-eslint": "^8.70.0",
45
+ "vitest": "^5.0.1"
46
+ }
47
+ }