claude-code-relay 0.0.1 → 0.0.7

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