claude-code-relay 0.0.1 → 0.0.4

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.
Files changed (43) hide show
  1. package/dist/cjs/cli-wrapper.js +186 -0
  2. package/dist/cjs/cli.js +400 -0
  3. package/dist/cjs/index.js +351 -0
  4. package/dist/cjs/server.js +347 -0
  5. package/dist/cjs/types.js +18 -0
  6. package/dist/cjs/utils.js +53 -0
  7. package/dist/esm/cli-wrapper.d.ts.map +1 -0
  8. package/dist/esm/cli-wrapper.js +161 -0
  9. package/dist/esm/cli.d.ts.map +1 -0
  10. package/dist/esm/cli.js +399 -0
  11. package/dist/esm/index.d.ts.map +1 -0
  12. package/dist/esm/index.js +322 -0
  13. package/dist/esm/server.d.ts.map +1 -0
  14. package/dist/esm/server.js +321 -0
  15. package/dist/esm/types.d.ts.map +1 -0
  16. package/dist/esm/types.js +0 -0
  17. package/dist/esm/utils.d.ts.map +1 -0
  18. package/dist/esm/utils.js +27 -0
  19. package/package.json +14 -8
  20. package/dist/cli-wrapper.d.ts.map +0 -1
  21. package/dist/cli-wrapper.js +0 -149
  22. package/dist/cli-wrapper.js.map +0 -1
  23. package/dist/cli.d.ts.map +0 -1
  24. package/dist/cli.js +0 -87
  25. package/dist/cli.js.map +0 -1
  26. package/dist/index.d.ts.map +0 -1
  27. package/dist/index.js +0 -6
  28. package/dist/index.js.map +0 -1
  29. package/dist/server.d.ts.map +0 -1
  30. package/dist/server.js +0 -168
  31. package/dist/server.js.map +0 -1
  32. package/dist/types.d.ts.map +0 -1
  33. package/dist/types.js +0 -5
  34. package/dist/types.js.map +0 -1
  35. package/dist/utils.d.ts.map +0 -1
  36. package/dist/utils.js +0 -34
  37. package/dist/utils.js.map +0 -1
  38. /package/dist/{cli-wrapper.d.ts → esm/cli-wrapper.d.ts} +0 -0
  39. /package/dist/{cli.d.ts → esm/cli.d.ts} +0 -0
  40. /package/dist/{index.d.ts → esm/index.d.ts} +0 -0
  41. /package/dist/{server.d.ts → esm/server.d.ts} +0 -0
  42. /package/dist/{types.d.ts → esm/types.d.ts} +0 -0
  43. /package/dist/{utils.d.ts → esm/utils.d.ts} +0 -0
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/cli-wrapper.ts
21
+ var cli_wrapper_exports = {};
22
+ __export(cli_wrapper_exports, {
23
+ ClaudeCLI: () => ClaudeCLI
24
+ });
25
+ module.exports = __toCommonJS(cli_wrapper_exports);
26
+ var import_node_child_process2 = require("node:child_process");
27
+
28
+ // src/utils.ts
29
+ var import_node_child_process = require("node:child_process");
30
+ var import_node_fs = require("node:fs");
31
+ var import_node_os = require("node:os");
32
+ function which(command) {
33
+ if (command.startsWith("/") || command.startsWith("~")) {
34
+ return (0, import_node_fs.existsSync)(command) ? command : null;
35
+ }
36
+ try {
37
+ const cmd = (0, import_node_os.platform)() === "win32" ? "where" : "which";
38
+ const result = (0, import_node_child_process.execSync)(`${cmd} ${command}`, {
39
+ encoding: "utf-8",
40
+ stdio: ["pipe", "pipe", "pipe"]
41
+ });
42
+ return result.trim().split("\n")[0] ?? null;
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+
48
+ // src/cli-wrapper.ts
49
+ var MODEL_MAP = {
50
+ sonnet: "sonnet",
51
+ opus: "opus",
52
+ haiku: "haiku",
53
+ "claude-3-sonnet": "sonnet",
54
+ "claude-3-opus": "opus",
55
+ "claude-3-haiku": "haiku",
56
+ "claude-sonnet-4": "sonnet",
57
+ "claude-opus-4": "opus"
58
+ };
59
+ var ClaudeCLI = class {
60
+ config;
61
+ constructor(config) {
62
+ this.config = {
63
+ cliPath: config?.cliPath ?? process.env.CLAUDE_CLI_PATH ?? "claude",
64
+ timeout: config?.timeout ?? parseInt(process.env.CLAUDE_CODE_RELAY_TIMEOUT ?? "300", 10),
65
+ verbose: config?.verbose ?? process.env.CLAUDE_CODE_RELAY_VERBOSE === "1"
66
+ };
67
+ this.validateCLI();
68
+ }
69
+ validateCLI() {
70
+ const cliPath = which(this.config.cliPath);
71
+ if (!cliPath) {
72
+ throw new Error(
73
+ `Claude CLI not found at '${this.config.cliPath}'. Please install it or set CLAUDE_CLI_PATH.`
74
+ );
75
+ }
76
+ if (this.config.verbose) {
77
+ console.log(`Using Claude CLI at: ${cliPath}`);
78
+ }
79
+ }
80
+ normalizeModel(model) {
81
+ return MODEL_MAP[model.toLowerCase()] ?? "sonnet";
82
+ }
83
+ buildPrompt(messages, systemPrompt) {
84
+ const parts = [];
85
+ for (const msg of messages) {
86
+ if (msg.role === "system") {
87
+ systemPrompt = msg.content;
88
+ break;
89
+ }
90
+ }
91
+ if (systemPrompt) {
92
+ parts.push(`System: ${systemPrompt}
93
+ `);
94
+ }
95
+ for (const msg of messages) {
96
+ if (msg.role === "system") continue;
97
+ if (msg.role === "user") {
98
+ parts.push(`Human: ${msg.content}
99
+ `);
100
+ } else if (msg.role === "assistant") {
101
+ parts.push(`Assistant: ${msg.content}
102
+ `);
103
+ }
104
+ }
105
+ parts.push("Assistant:");
106
+ return parts.join("\n");
107
+ }
108
+ async complete(messages, model = "sonnet", systemPrompt) {
109
+ const prompt = this.buildPrompt(messages, systemPrompt);
110
+ const normalizedModel = this.normalizeModel(model);
111
+ return new Promise((resolve, reject) => {
112
+ const args = ["-p", "--model", normalizedModel, "--output-format", "text"];
113
+ if (this.config.verbose) {
114
+ console.log(`Running: ${this.config.cliPath} ${args.join(" ")}`);
115
+ }
116
+ const proc = (0, import_node_child_process2.spawn)(this.config.cliPath, args, {
117
+ stdio: ["pipe", "pipe", "pipe"]
118
+ });
119
+ let stdout = "";
120
+ let stderr = "";
121
+ proc.stdout.on("data", (data) => {
122
+ stdout += data.toString();
123
+ });
124
+ proc.stderr.on("data", (data) => {
125
+ stderr += data.toString();
126
+ });
127
+ proc.on("close", (code) => {
128
+ if (code !== 0) {
129
+ reject(new Error(`Claude CLI failed: ${stderr}`));
130
+ } else {
131
+ resolve(stdout.trim());
132
+ }
133
+ });
134
+ proc.on("error", (err) => {
135
+ reject(err);
136
+ });
137
+ proc.stdin.write(prompt);
138
+ proc.stdin.end();
139
+ setTimeout(() => {
140
+ proc.kill();
141
+ reject(new Error(`Claude CLI timeout after ${this.config.timeout}s`));
142
+ }, this.config.timeout * 1e3);
143
+ });
144
+ }
145
+ async *stream(messages, model = "sonnet", systemPrompt) {
146
+ const prompt = this.buildPrompt(messages, systemPrompt);
147
+ const normalizedModel = this.normalizeModel(model);
148
+ const args = ["-p", "--model", normalizedModel, "--output-format", "stream-json"];
149
+ if (this.config.verbose) {
150
+ console.log(`Running: ${this.config.cliPath} ${args.join(" ")}`);
151
+ }
152
+ const proc = (0, import_node_child_process2.spawn)(this.config.cliPath, args, {
153
+ stdio: ["pipe", "pipe", "pipe"]
154
+ });
155
+ proc.stdin.write(prompt);
156
+ proc.stdin.end();
157
+ let buffer = "";
158
+ for await (const chunk of proc.stdout) {
159
+ buffer += chunk.toString();
160
+ while (buffer.includes("\n")) {
161
+ const [line, rest] = buffer.split("\n", 2);
162
+ buffer = rest ?? "";
163
+ const trimmed = line.trim();
164
+ if (!trimmed) continue;
165
+ try {
166
+ const data = JSON.parse(trimmed);
167
+ if (data.content) {
168
+ yield data.content;
169
+ } else if (data.text) {
170
+ yield data.text;
171
+ } else if (data.delta?.text) {
172
+ yield data.delta.text;
173
+ }
174
+ } catch {
175
+ if (!trimmed.startsWith("{")) {
176
+ yield trimmed;
177
+ }
178
+ }
179
+ }
180
+ }
181
+ }
182
+ };
183
+ // Annotate the CommonJS export names for ESM import in node:
184
+ 0 && (module.exports = {
185
+ ClaudeCLI
186
+ });
@@ -0,0 +1,400 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // src/cli.ts
5
+ var import_node_util = require("node:util");
6
+
7
+ // src/server.ts
8
+ var import_node_http = require("node:http");
9
+
10
+ // src/cli-wrapper.ts
11
+ var import_node_child_process2 = require("node:child_process");
12
+
13
+ // src/utils.ts
14
+ var import_node_child_process = require("node:child_process");
15
+ var import_node_fs = require("node:fs");
16
+ var import_node_os = require("node:os");
17
+ function which(command2) {
18
+ if (command2.startsWith("/") || command2.startsWith("~")) {
19
+ return (0, import_node_fs.existsSync)(command2) ? command2 : null;
20
+ }
21
+ try {
22
+ const cmd = (0, import_node_os.platform)() === "win32" ? "where" : "which";
23
+ const result = (0, import_node_child_process.execSync)(`${cmd} ${command2}`, {
24
+ encoding: "utf-8",
25
+ stdio: ["pipe", "pipe", "pipe"]
26
+ });
27
+ return result.trim().split("\n")[0] ?? null;
28
+ } catch {
29
+ return null;
30
+ }
31
+ }
32
+ function generateId(prefix = "") {
33
+ const random = Math.random().toString(36).substring(2, 14);
34
+ return prefix ? `${prefix}-${random}` : random;
35
+ }
36
+
37
+ // src/cli-wrapper.ts
38
+ var MODEL_MAP = {
39
+ sonnet: "sonnet",
40
+ opus: "opus",
41
+ haiku: "haiku",
42
+ "claude-3-sonnet": "sonnet",
43
+ "claude-3-opus": "opus",
44
+ "claude-3-haiku": "haiku",
45
+ "claude-sonnet-4": "sonnet",
46
+ "claude-opus-4": "opus"
47
+ };
48
+ var ClaudeCLI = class {
49
+ config;
50
+ constructor(config) {
51
+ this.config = {
52
+ cliPath: config?.cliPath ?? process.env.CLAUDE_CLI_PATH ?? "claude",
53
+ timeout: config?.timeout ?? parseInt(process.env.CLAUDE_CODE_RELAY_TIMEOUT ?? "300", 10),
54
+ verbose: config?.verbose ?? process.env.CLAUDE_CODE_RELAY_VERBOSE === "1"
55
+ };
56
+ this.validateCLI();
57
+ }
58
+ validateCLI() {
59
+ const cliPath = which(this.config.cliPath);
60
+ if (!cliPath) {
61
+ throw new Error(
62
+ `Claude CLI not found at '${this.config.cliPath}'. Please install it or set CLAUDE_CLI_PATH.`
63
+ );
64
+ }
65
+ if (this.config.verbose) {
66
+ console.log(`Using Claude CLI at: ${cliPath}`);
67
+ }
68
+ }
69
+ normalizeModel(model) {
70
+ return MODEL_MAP[model.toLowerCase()] ?? "sonnet";
71
+ }
72
+ buildPrompt(messages, systemPrompt) {
73
+ const parts = [];
74
+ for (const msg of messages) {
75
+ if (msg.role === "system") {
76
+ systemPrompt = msg.content;
77
+ break;
78
+ }
79
+ }
80
+ if (systemPrompt) {
81
+ parts.push(`System: ${systemPrompt}
82
+ `);
83
+ }
84
+ for (const msg of messages) {
85
+ if (msg.role === "system") continue;
86
+ if (msg.role === "user") {
87
+ parts.push(`Human: ${msg.content}
88
+ `);
89
+ } else if (msg.role === "assistant") {
90
+ parts.push(`Assistant: ${msg.content}
91
+ `);
92
+ }
93
+ }
94
+ parts.push("Assistant:");
95
+ return parts.join("\n");
96
+ }
97
+ async complete(messages, model = "sonnet", systemPrompt) {
98
+ const prompt = this.buildPrompt(messages, systemPrompt);
99
+ const normalizedModel = this.normalizeModel(model);
100
+ return new Promise((resolve, reject) => {
101
+ const args = ["-p", "--model", normalizedModel, "--output-format", "text"];
102
+ if (this.config.verbose) {
103
+ console.log(`Running: ${this.config.cliPath} ${args.join(" ")}`);
104
+ }
105
+ const proc = (0, import_node_child_process2.spawn)(this.config.cliPath, args, {
106
+ stdio: ["pipe", "pipe", "pipe"]
107
+ });
108
+ let stdout = "";
109
+ let stderr = "";
110
+ proc.stdout.on("data", (data) => {
111
+ stdout += data.toString();
112
+ });
113
+ proc.stderr.on("data", (data) => {
114
+ stderr += data.toString();
115
+ });
116
+ proc.on("close", (code) => {
117
+ if (code !== 0) {
118
+ reject(new Error(`Claude CLI failed: ${stderr}`));
119
+ } else {
120
+ resolve(stdout.trim());
121
+ }
122
+ });
123
+ proc.on("error", (err) => {
124
+ reject(err);
125
+ });
126
+ proc.stdin.write(prompt);
127
+ proc.stdin.end();
128
+ setTimeout(() => {
129
+ proc.kill();
130
+ reject(new Error(`Claude CLI timeout after ${this.config.timeout}s`));
131
+ }, this.config.timeout * 1e3);
132
+ });
133
+ }
134
+ async *stream(messages, model = "sonnet", systemPrompt) {
135
+ const prompt = this.buildPrompt(messages, systemPrompt);
136
+ const normalizedModel = this.normalizeModel(model);
137
+ const args = ["-p", "--model", normalizedModel, "--output-format", "stream-json"];
138
+ if (this.config.verbose) {
139
+ console.log(`Running: ${this.config.cliPath} ${args.join(" ")}`);
140
+ }
141
+ const proc = (0, import_node_child_process2.spawn)(this.config.cliPath, args, {
142
+ stdio: ["pipe", "pipe", "pipe"]
143
+ });
144
+ proc.stdin.write(prompt);
145
+ proc.stdin.end();
146
+ let buffer = "";
147
+ for await (const chunk of proc.stdout) {
148
+ buffer += chunk.toString();
149
+ while (buffer.includes("\n")) {
150
+ const [line, rest] = buffer.split("\n", 2);
151
+ buffer = rest ?? "";
152
+ const trimmed = line.trim();
153
+ if (!trimmed) continue;
154
+ try {
155
+ const data = JSON.parse(trimmed);
156
+ if (data.content) {
157
+ yield data.content;
158
+ } else if (data.text) {
159
+ yield data.text;
160
+ } else if (data.delta?.text) {
161
+ yield data.delta.text;
162
+ }
163
+ } catch {
164
+ if (!trimmed.startsWith("{")) {
165
+ yield trimmed;
166
+ }
167
+ }
168
+ }
169
+ }
170
+ }
171
+ };
172
+
173
+ // src/server.ts
174
+ var _cli = null;
175
+ function sendJson(res, data, status = 200) {
176
+ const body = JSON.stringify(data);
177
+ res.writeHead(status, {
178
+ "Content-Type": "application/json",
179
+ "Content-Length": Buffer.byteLength(body),
180
+ "Access-Control-Allow-Origin": "*"
181
+ });
182
+ res.end(body);
183
+ }
184
+ function sendError(res, message, status = 500) {
185
+ sendJson(res, { error: { message, type: "server_error" } }, status);
186
+ }
187
+ function sendSSEChunk(res, data) {
188
+ res.write(`data: ${data}
189
+
190
+ `);
191
+ }
192
+ async function readBody(req) {
193
+ return new Promise((resolve, reject) => {
194
+ let body = "";
195
+ req.on("data", (chunk) => body += chunk);
196
+ req.on("end", () => resolve(body));
197
+ req.on("error", reject);
198
+ });
199
+ }
200
+ async function handleRequest(req, res) {
201
+ const url = req.url ?? "/";
202
+ const method = req.method ?? "GET";
203
+ if (method === "OPTIONS") {
204
+ res.writeHead(204, {
205
+ "Access-Control-Allow-Origin": "*",
206
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
207
+ "Access-Control-Allow-Headers": "Content-Type, Authorization"
208
+ });
209
+ res.end();
210
+ return;
211
+ }
212
+ if (method === "GET" && url === "/health") {
213
+ sendJson(res, { status: _cli ? "ok" : "degraded", cli_available: _cli !== null });
214
+ return;
215
+ }
216
+ if (method === "GET" && url === "/v1/models") {
217
+ const now = Math.floor(Date.now() / 1e3);
218
+ const models = {
219
+ object: "list",
220
+ data: [
221
+ { id: "sonnet", object: "model", created: now, owned_by: "anthropic" },
222
+ { id: "opus", object: "model", created: now, owned_by: "anthropic" },
223
+ { id: "haiku", object: "model", created: now, owned_by: "anthropic" }
224
+ ]
225
+ };
226
+ sendJson(res, models);
227
+ return;
228
+ }
229
+ if (method === "POST" && url === "/v1/chat/completions") {
230
+ if (!_cli) {
231
+ sendError(res, "Claude CLI not available", 503);
232
+ return;
233
+ }
234
+ let request;
235
+ try {
236
+ const body = await readBody(req);
237
+ request = JSON.parse(body);
238
+ } catch {
239
+ sendError(res, "Invalid JSON", 400);
240
+ return;
241
+ }
242
+ const chatId = generateId("chatcmpl");
243
+ const created = Math.floor(Date.now() / 1e3);
244
+ if (request.stream) {
245
+ res.writeHead(200, {
246
+ "Content-Type": "text/event-stream",
247
+ "Cache-Control": "no-cache",
248
+ Connection: "keep-alive",
249
+ "Access-Control-Allow-Origin": "*",
250
+ "X-Accel-Buffering": "no"
251
+ });
252
+ const initial = {
253
+ id: chatId,
254
+ object: "chat.completion.chunk",
255
+ created,
256
+ model: request.model,
257
+ choices: [{ index: 0, delta: { role: "assistant", content: "" }, finish_reason: null }]
258
+ };
259
+ sendSSEChunk(res, JSON.stringify(initial));
260
+ try {
261
+ for await (const text of _cli.stream(request.messages, request.model)) {
262
+ const chunk = {
263
+ id: chatId,
264
+ object: "chat.completion.chunk",
265
+ created,
266
+ model: request.model,
267
+ choices: [{ index: 0, delta: { content: text }, finish_reason: null }]
268
+ };
269
+ sendSSEChunk(res, JSON.stringify(chunk));
270
+ }
271
+ } catch (err) {
272
+ sendSSEChunk(res, JSON.stringify({ error: { message: String(err), type: "server_error" } }));
273
+ }
274
+ const final = {
275
+ id: chatId,
276
+ object: "chat.completion.chunk",
277
+ created,
278
+ model: request.model,
279
+ choices: [{ index: 0, delta: {}, finish_reason: "stop" }]
280
+ };
281
+ sendSSEChunk(res, JSON.stringify(final));
282
+ sendSSEChunk(res, "[DONE]");
283
+ res.end();
284
+ return;
285
+ }
286
+ try {
287
+ const content = await _cli.complete(request.messages, request.model);
288
+ const response = {
289
+ id: chatId,
290
+ object: "chat.completion",
291
+ created,
292
+ model: request.model,
293
+ choices: [{ index: 0, message: { role: "assistant", content }, finish_reason: "stop" }],
294
+ usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
295
+ };
296
+ sendJson(res, response);
297
+ } catch (err) {
298
+ sendError(res, String(err));
299
+ }
300
+ return;
301
+ }
302
+ sendError(res, "Not found", 404);
303
+ }
304
+ function createApp(config) {
305
+ try {
306
+ _cli = new ClaudeCLI(config?.cli);
307
+ } catch (err) {
308
+ console.error(`Failed to initialize Claude CLI: ${err}`);
309
+ _cli = null;
310
+ }
311
+ return (0, import_node_http.createServer)((req, res) => {
312
+ handleRequest(req, res).catch((err) => {
313
+ console.error("Request error:", err);
314
+ sendError(res, "Internal server error");
315
+ });
316
+ });
317
+ }
318
+ function runServer(host = "127.0.0.1", port = 52014, config) {
319
+ const server = createApp(config);
320
+ server.listen(port, host, () => {
321
+ console.log(`Server running at http://${host}:${port}`);
322
+ });
323
+ }
324
+
325
+ // src/cli.ts
326
+ var { values, positionals } = (0, import_node_util.parseArgs)({
327
+ allowPositionals: true,
328
+ options: {
329
+ port: { type: "string", short: "p", default: process.env.CLAUDE_CODE_RELAY_PORT ?? "52014" },
330
+ host: { type: "string", short: "h", default: process.env.CLAUDE_CODE_RELAY_HOST ?? "127.0.0.1" },
331
+ "claude-path": { type: "string", default: process.env.CLAUDE_CLI_PATH ?? "claude" },
332
+ timeout: { type: "string", default: process.env.CLAUDE_CODE_RELAY_TIMEOUT ?? "300" },
333
+ verbose: { type: "boolean", short: "v", default: false },
334
+ version: { type: "boolean", short: "V", default: false },
335
+ help: { type: "boolean", default: false }
336
+ }
337
+ });
338
+ var command = positionals[0];
339
+ if (values.version) {
340
+ console.log("claude-code-relay 0.0.1");
341
+ process.exit(0);
342
+ }
343
+ if (values.help || !command && !values.version) {
344
+ console.log(`
345
+ claude-code-relay - OpenAI-compatible API server for Claude CLI
346
+
347
+ Usage:
348
+ claude-code-relay <command> [options]
349
+
350
+ Commands:
351
+ serve Start the API server
352
+ check Check if Claude CLI is available
353
+
354
+ Options:
355
+ -p, --port <port> Port to listen on (default: 52014)
356
+ -h, --host <host> Host to bind to (default: 127.0.0.1)
357
+ --claude-path <path> Path to Claude CLI binary (default: claude)
358
+ --timeout <seconds> Request timeout in seconds (default: 300)
359
+ -v, --verbose Enable verbose logging
360
+ -V, --version Show version
361
+ --help Show this help
362
+ `);
363
+ process.exit(0);
364
+ }
365
+ if (command === "serve") {
366
+ const port = parseInt(values.port, 10);
367
+ const host = values.host;
368
+ const claudePath = values["claude-path"];
369
+ const timeout = parseInt(values.timeout, 10);
370
+ const verbose = values.verbose;
371
+ process.env.CLAUDE_CLI_PATH = claudePath;
372
+ process.env.CLAUDE_CODE_RELAY_TIMEOUT = String(timeout);
373
+ process.env.CLAUDE_CODE_RELAY_VERBOSE = verbose ? "1" : "";
374
+ console.log("Starting Claude Code Relay server...");
375
+ console.log(` Host: ${host}`);
376
+ console.log(` Port: ${port}`);
377
+ console.log(` Claude CLI: ${claudePath}`);
378
+ console.log(` Timeout: ${timeout}s`);
379
+ console.log();
380
+ console.log(`API endpoint: http://${host}:${port}/v1/chat/completions`);
381
+ console.log();
382
+ runServer(host, port, {
383
+ cli: { cliPath: claudePath, timeout, verbose }
384
+ });
385
+ } else if (command === "check") {
386
+ console.log("Checking Claude CLI...");
387
+ const cliPath = values["claude-path"] ?? process.env.CLAUDE_CLI_PATH ?? "claude";
388
+ const resolved = which(cliPath);
389
+ if (resolved) {
390
+ console.log(` CLI path: ${resolved}`);
391
+ console.log(" Status: OK");
392
+ } else {
393
+ console.error(` Error: Claude CLI not found at '${cliPath}'`);
394
+ process.exit(1);
395
+ }
396
+ } else {
397
+ console.error(`Unknown command: ${command}`);
398
+ console.error("Run 'claude-code-relay --help' for usage");
399
+ process.exit(1);
400
+ }