claudish 1.2.1 → 1.3.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 +23 -11
- package/dist/index.js +585 -32
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
+
- ✅ **Cross-platform** - Works with both Node.js and Bun (v1.3.0+)
|
|
10
|
+
- ✅ **Universal compatibility** - Use with `npx` or `bunx` - no installation required
|
|
9
11
|
- ✅ **Monitor mode** - Proxy to real Anthropic API and log all traffic (for debugging)
|
|
10
12
|
- ✅ **Protocol compliance** - 1:1 compatibility with Claude Code communication protocol
|
|
11
13
|
- ✅ **Snapshot testing** - Comprehensive test suite with 13/13 passing tests
|
|
@@ -22,34 +24,44 @@
|
|
|
22
24
|
|
|
23
25
|
### Prerequisites
|
|
24
26
|
|
|
25
|
-
-
|
|
27
|
+
- **Node.js 18+** or **Bun 1.0+** - JavaScript runtime (either works!)
|
|
26
28
|
- [Claude Code](https://claude.com/claude-code) - Claude CLI must be installed
|
|
27
29
|
- [OpenRouter API Key](https://openrouter.ai/keys) - Free tier available
|
|
28
30
|
|
|
29
31
|
### Install Claudish
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
**✨ NEW in v1.3.0: Universal compatibility! Works with both Node.js and Bun.**
|
|
32
34
|
|
|
33
|
-
**Option 1:
|
|
35
|
+
**Option 1: Use without installing (recommended)**
|
|
34
36
|
|
|
35
37
|
```bash
|
|
36
|
-
#
|
|
38
|
+
# With Node.js (works everywhere)
|
|
39
|
+
npx claudish@latest --model x-ai/grok-code-fast-1 "your prompt"
|
|
40
|
+
|
|
41
|
+
# With Bun (faster execution)
|
|
42
|
+
bunx claudish@latest --model openai/gpt-5-codex "your prompt"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Option 2: Install globally**
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# With npm (Node.js)
|
|
37
49
|
npm install -g claudish
|
|
38
50
|
|
|
39
|
-
#
|
|
40
|
-
|
|
51
|
+
# With Bun (faster)
|
|
52
|
+
bun install -g claudish
|
|
41
53
|
```
|
|
42
54
|
|
|
43
|
-
**Option
|
|
55
|
+
**Option 3: Install from source**
|
|
44
56
|
|
|
45
57
|
```bash
|
|
46
58
|
cd mcp/claudish
|
|
47
|
-
bun install
|
|
48
|
-
bun run build
|
|
49
|
-
bun link
|
|
59
|
+
bun install # or: npm install
|
|
60
|
+
bun run build # or: npm run build
|
|
61
|
+
bun link # or: npm link
|
|
50
62
|
```
|
|
51
63
|
|
|
52
|
-
**
|
|
64
|
+
**Performance Note:** While Claudish works with both runtimes, Bun offers faster startup times. Both provide identical functionality.
|
|
53
65
|
|
|
54
66
|
## Quick Start
|
|
55
67
|
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
2
|
-
// @bun
|
|
1
|
+
#!/usr/bin/env node
|
|
3
2
|
|
|
4
3
|
// src/claude-runner.ts
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { writeFileSync, unlinkSync } from "node:fs";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
8
|
|
|
9
9
|
// src/config.ts
|
|
10
10
|
var DEFAULT_PORT_RANGE = { start: 3000, end: 9000 };
|
|
@@ -84,7 +84,7 @@ function createTempSettingsFile(modelDisplay, port) {
|
|
|
84
84
|
const settings = {
|
|
85
85
|
statusLine: {
|
|
86
86
|
type: "command",
|
|
87
|
-
command: `JSON=$(cat) && DIR=$(basename "$(pwd)") && [ \${#DIR} -gt 15 ] && DIR="\${DIR:0:12}..." || true && COST=$(echo "$JSON" | grep -o '"total_cost_usd":[0-9.]*' | cut -d: -f2) && [ -z "$COST" ] && COST="0" || true && if [ -f "${tokenFilePath}" ]; then TOKENS=$(cat "${tokenFilePath}" 2>/dev/null) && INPUT=$(echo "$TOKENS" | grep -o '"input_tokens":[0-9]*' | grep -o '[0-9]*') && OUTPUT=$(echo "$TOKENS" | grep -o '"output_tokens":[0-9]*' | grep -o '[0-9]*') && TOTAL=$((INPUT + OUTPUT)) && CTX=$(echo "scale=0; (${maxTokens} - $TOTAL) * 100 / ${maxTokens}" | bc 2>/dev/null); else INPUT=0 && OUTPUT=0 && CTX=100; fi && [ -z "$CTX" ] && CTX="100" || true && printf "${CYAN}${BOLD}%s${RESET} ${DIM}
|
|
87
|
+
command: `JSON=$(cat) && DIR=$(basename "$(pwd)") && [ \${#DIR} -gt 15 ] && DIR="\${DIR:0:12}..." || true && COST=$(echo "$JSON" | grep -o '"total_cost_usd":[0-9.]*' | cut -d: -f2) && [ -z "$COST" ] && COST="0" || true && if [ -f "${tokenFilePath}" ]; then TOKENS=$(cat "${tokenFilePath}" 2>/dev/null) && INPUT=$(echo "$TOKENS" | grep -o '"input_tokens":[0-9]*' | grep -o '[0-9]*') && OUTPUT=$(echo "$TOKENS" | grep -o '"output_tokens":[0-9]*' | grep -o '[0-9]*') && TOTAL=$((INPUT + OUTPUT)) && CTX=$(echo "scale=0; (${maxTokens} - $TOTAL) * 100 / ${maxTokens}" | bc 2>/dev/null); else INPUT=0 && OUTPUT=0 && CTX=100; fi && [ -z "$CTX" ] && CTX="100" || true && printf "${CYAN}${BOLD}%s${RESET} ${DIM}•${RESET} ${YELLOW}%s${RESET} ${DIM}•${RESET} ${GREEN}\\$%.3f${RESET} ${DIM}•${RESET} ${MAGENTA}%s%%${RESET}\\n" "$DIR" "$CLAUDISH_ACTIVE_MODEL_NAME" "$COST" "$CTX"`,
|
|
88
88
|
padding: 0
|
|
89
89
|
}
|
|
90
90
|
};
|
|
@@ -143,14 +143,16 @@ async function runClaudeWithProxy(config, proxyUrl) {
|
|
|
143
143
|
log(`[claudish] Arguments: ${claudeArgs.join(" ")}
|
|
144
144
|
`);
|
|
145
145
|
}
|
|
146
|
-
const proc =
|
|
146
|
+
const proc = spawn("claude", claudeArgs, {
|
|
147
147
|
env,
|
|
148
|
-
|
|
149
|
-
stderr: "inherit",
|
|
150
|
-
stdin: "inherit"
|
|
148
|
+
stdio: "inherit"
|
|
151
149
|
});
|
|
152
150
|
setupSignalHandlers(proc, tempSettingsPath, config.quiet);
|
|
153
|
-
const exitCode = await
|
|
151
|
+
const exitCode = await new Promise((resolve) => {
|
|
152
|
+
proc.on("exit", (code) => {
|
|
153
|
+
resolve(code ?? 1);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
154
156
|
try {
|
|
155
157
|
unlinkSync(tempSettingsPath);
|
|
156
158
|
} catch (error) {}
|
|
@@ -174,11 +176,14 @@ function setupSignalHandlers(proc, tempSettingsPath, quiet) {
|
|
|
174
176
|
}
|
|
175
177
|
async function checkClaudeInstalled() {
|
|
176
178
|
try {
|
|
177
|
-
const proc =
|
|
178
|
-
|
|
179
|
-
|
|
179
|
+
const proc = spawn("which", ["claude"], {
|
|
180
|
+
stdio: "ignore"
|
|
181
|
+
});
|
|
182
|
+
const exitCode = await new Promise((resolve) => {
|
|
183
|
+
proc.on("exit", (code) => {
|
|
184
|
+
resolve(code ?? 1);
|
|
185
|
+
});
|
|
180
186
|
});
|
|
181
|
-
const exitCode = await proc.exited;
|
|
182
187
|
return exitCode === 0;
|
|
183
188
|
} catch {
|
|
184
189
|
return false;
|
|
@@ -340,7 +345,7 @@ Error: ANTHROPIC_API_KEY is not set`);
|
|
|
340
345
|
return config;
|
|
341
346
|
}
|
|
342
347
|
function printVersion() {
|
|
343
|
-
console.log("claudish version 1.
|
|
348
|
+
console.log("claudish version 1.3.0");
|
|
344
349
|
}
|
|
345
350
|
function printHelp() {
|
|
346
351
|
console.log(`
|
|
@@ -371,14 +376,14 @@ OPTIONS:
|
|
|
371
376
|
-h, --help Show this help message
|
|
372
377
|
|
|
373
378
|
MODES:
|
|
374
|
-
|
|
375
|
-
|
|
379
|
+
• Interactive mode (default): Shows model selector, starts persistent session
|
|
380
|
+
• Single-shot mode: Runs one task in headless mode and exits (requires --model)
|
|
376
381
|
|
|
377
382
|
NOTES:
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
383
|
+
• Permission prompts are SKIPPED by default (--dangerously-skip-permissions)
|
|
384
|
+
• Use --no-auto-approve to enable permission prompts
|
|
385
|
+
• Model selector appears ONLY in interactive mode when --model not specified
|
|
386
|
+
• Use --dangerous to disable sandbox (use with extreme caution!)
|
|
382
387
|
|
|
383
388
|
ENVIRONMENT VARIABLES:
|
|
384
389
|
OPENROUTER_API_KEY Required: Your OpenRouter API key
|
|
@@ -659,7 +664,7 @@ function logStructured(label, data) {
|
|
|
659
664
|
}
|
|
660
665
|
|
|
661
666
|
// src/port-manager.ts
|
|
662
|
-
import { createServer } from "net";
|
|
667
|
+
import { createServer } from "node:net";
|
|
663
668
|
async function findAvailablePort(startPort = 3000, endPort = 9000) {
|
|
664
669
|
const randomPort = Math.floor(Math.random() * (endPort - startPort + 1)) + startPort;
|
|
665
670
|
if (await isPortAvailable(randomPort)) {
|
|
@@ -2285,8 +2290,550 @@ var cors = (options) => {
|
|
|
2285
2290
|
};
|
|
2286
2291
|
};
|
|
2287
2292
|
|
|
2293
|
+
// node_modules/@hono/node-server/dist/index.mjs
|
|
2294
|
+
import { createServer as createServerHTTP } from "http";
|
|
2295
|
+
import { Http2ServerRequest as Http2ServerRequest2 } from "http2";
|
|
2296
|
+
import { Http2ServerRequest } from "http2";
|
|
2297
|
+
import { Readable } from "stream";
|
|
2298
|
+
import crypto from "crypto";
|
|
2299
|
+
var RequestError = class extends Error {
|
|
2300
|
+
constructor(message, options) {
|
|
2301
|
+
super(message, options);
|
|
2302
|
+
this.name = "RequestError";
|
|
2303
|
+
}
|
|
2304
|
+
};
|
|
2305
|
+
var toRequestError = (e) => {
|
|
2306
|
+
if (e instanceof RequestError) {
|
|
2307
|
+
return e;
|
|
2308
|
+
}
|
|
2309
|
+
return new RequestError(e.message, { cause: e });
|
|
2310
|
+
};
|
|
2311
|
+
var GlobalRequest = global.Request;
|
|
2312
|
+
var Request2 = class extends GlobalRequest {
|
|
2313
|
+
constructor(input, options) {
|
|
2314
|
+
if (typeof input === "object" && getRequestCache in input) {
|
|
2315
|
+
input = input[getRequestCache]();
|
|
2316
|
+
}
|
|
2317
|
+
if (typeof options?.body?.getReader !== "undefined") {
|
|
2318
|
+
options.duplex ??= "half";
|
|
2319
|
+
}
|
|
2320
|
+
super(input, options);
|
|
2321
|
+
}
|
|
2322
|
+
};
|
|
2323
|
+
var newHeadersFromIncoming = (incoming) => {
|
|
2324
|
+
const headerRecord = [];
|
|
2325
|
+
const rawHeaders = incoming.rawHeaders;
|
|
2326
|
+
for (let i = 0;i < rawHeaders.length; i += 2) {
|
|
2327
|
+
const { [i]: key, [i + 1]: value } = rawHeaders;
|
|
2328
|
+
if (key.charCodeAt(0) !== 58) {
|
|
2329
|
+
headerRecord.push([key, value]);
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
return new Headers(headerRecord);
|
|
2333
|
+
};
|
|
2334
|
+
var wrapBodyStream = Symbol("wrapBodyStream");
|
|
2335
|
+
var newRequestFromIncoming = (method, url, headers, incoming, abortController) => {
|
|
2336
|
+
const init = {
|
|
2337
|
+
method,
|
|
2338
|
+
headers,
|
|
2339
|
+
signal: abortController.signal
|
|
2340
|
+
};
|
|
2341
|
+
if (method === "TRACE") {
|
|
2342
|
+
init.method = "GET";
|
|
2343
|
+
const req = new Request2(url, init);
|
|
2344
|
+
Object.defineProperty(req, "method", {
|
|
2345
|
+
get() {
|
|
2346
|
+
return "TRACE";
|
|
2347
|
+
}
|
|
2348
|
+
});
|
|
2349
|
+
return req;
|
|
2350
|
+
}
|
|
2351
|
+
if (!(method === "GET" || method === "HEAD")) {
|
|
2352
|
+
if ("rawBody" in incoming && incoming.rawBody instanceof Buffer) {
|
|
2353
|
+
init.body = new ReadableStream({
|
|
2354
|
+
start(controller) {
|
|
2355
|
+
controller.enqueue(incoming.rawBody);
|
|
2356
|
+
controller.close();
|
|
2357
|
+
}
|
|
2358
|
+
});
|
|
2359
|
+
} else if (incoming[wrapBodyStream]) {
|
|
2360
|
+
let reader;
|
|
2361
|
+
init.body = new ReadableStream({
|
|
2362
|
+
async pull(controller) {
|
|
2363
|
+
try {
|
|
2364
|
+
reader ||= Readable.toWeb(incoming).getReader();
|
|
2365
|
+
const { done, value } = await reader.read();
|
|
2366
|
+
if (done) {
|
|
2367
|
+
controller.close();
|
|
2368
|
+
} else {
|
|
2369
|
+
controller.enqueue(value);
|
|
2370
|
+
}
|
|
2371
|
+
} catch (error) {
|
|
2372
|
+
controller.error(error);
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
});
|
|
2376
|
+
} else {
|
|
2377
|
+
init.body = Readable.toWeb(incoming);
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
return new Request2(url, init);
|
|
2381
|
+
};
|
|
2382
|
+
var getRequestCache = Symbol("getRequestCache");
|
|
2383
|
+
var requestCache = Symbol("requestCache");
|
|
2384
|
+
var incomingKey = Symbol("incomingKey");
|
|
2385
|
+
var urlKey = Symbol("urlKey");
|
|
2386
|
+
var headersKey = Symbol("headersKey");
|
|
2387
|
+
var abortControllerKey = Symbol("abortControllerKey");
|
|
2388
|
+
var getAbortController = Symbol("getAbortController");
|
|
2389
|
+
var requestPrototype = {
|
|
2390
|
+
get method() {
|
|
2391
|
+
return this[incomingKey].method || "GET";
|
|
2392
|
+
},
|
|
2393
|
+
get url() {
|
|
2394
|
+
return this[urlKey];
|
|
2395
|
+
},
|
|
2396
|
+
get headers() {
|
|
2397
|
+
return this[headersKey] ||= newHeadersFromIncoming(this[incomingKey]);
|
|
2398
|
+
},
|
|
2399
|
+
[getAbortController]() {
|
|
2400
|
+
this[getRequestCache]();
|
|
2401
|
+
return this[abortControllerKey];
|
|
2402
|
+
},
|
|
2403
|
+
[getRequestCache]() {
|
|
2404
|
+
this[abortControllerKey] ||= new AbortController;
|
|
2405
|
+
return this[requestCache] ||= newRequestFromIncoming(this.method, this[urlKey], this.headers, this[incomingKey], this[abortControllerKey]);
|
|
2406
|
+
}
|
|
2407
|
+
};
|
|
2408
|
+
[
|
|
2409
|
+
"body",
|
|
2410
|
+
"bodyUsed",
|
|
2411
|
+
"cache",
|
|
2412
|
+
"credentials",
|
|
2413
|
+
"destination",
|
|
2414
|
+
"integrity",
|
|
2415
|
+
"mode",
|
|
2416
|
+
"redirect",
|
|
2417
|
+
"referrer",
|
|
2418
|
+
"referrerPolicy",
|
|
2419
|
+
"signal",
|
|
2420
|
+
"keepalive"
|
|
2421
|
+
].forEach((k) => {
|
|
2422
|
+
Object.defineProperty(requestPrototype, k, {
|
|
2423
|
+
get() {
|
|
2424
|
+
return this[getRequestCache]()[k];
|
|
2425
|
+
}
|
|
2426
|
+
});
|
|
2427
|
+
});
|
|
2428
|
+
["arrayBuffer", "blob", "clone", "formData", "json", "text"].forEach((k) => {
|
|
2429
|
+
Object.defineProperty(requestPrototype, k, {
|
|
2430
|
+
value: function() {
|
|
2431
|
+
return this[getRequestCache]()[k]();
|
|
2432
|
+
}
|
|
2433
|
+
});
|
|
2434
|
+
});
|
|
2435
|
+
Object.setPrototypeOf(requestPrototype, Request2.prototype);
|
|
2436
|
+
var newRequest = (incoming, defaultHostname) => {
|
|
2437
|
+
const req = Object.create(requestPrototype);
|
|
2438
|
+
req[incomingKey] = incoming;
|
|
2439
|
+
const incomingUrl = incoming.url || "";
|
|
2440
|
+
if (incomingUrl[0] !== "/" && (incomingUrl.startsWith("http://") || incomingUrl.startsWith("https://"))) {
|
|
2441
|
+
if (incoming instanceof Http2ServerRequest) {
|
|
2442
|
+
throw new RequestError("Absolute URL for :path is not allowed in HTTP/2");
|
|
2443
|
+
}
|
|
2444
|
+
try {
|
|
2445
|
+
const url2 = new URL(incomingUrl);
|
|
2446
|
+
req[urlKey] = url2.href;
|
|
2447
|
+
} catch (e) {
|
|
2448
|
+
throw new RequestError("Invalid absolute URL", { cause: e });
|
|
2449
|
+
}
|
|
2450
|
+
return req;
|
|
2451
|
+
}
|
|
2452
|
+
const host = (incoming instanceof Http2ServerRequest ? incoming.authority : incoming.headers.host) || defaultHostname;
|
|
2453
|
+
if (!host) {
|
|
2454
|
+
throw new RequestError("Missing host header");
|
|
2455
|
+
}
|
|
2456
|
+
let scheme;
|
|
2457
|
+
if (incoming instanceof Http2ServerRequest) {
|
|
2458
|
+
scheme = incoming.scheme;
|
|
2459
|
+
if (!(scheme === "http" || scheme === "https")) {
|
|
2460
|
+
throw new RequestError("Unsupported scheme");
|
|
2461
|
+
}
|
|
2462
|
+
} else {
|
|
2463
|
+
scheme = incoming.socket && incoming.socket.encrypted ? "https" : "http";
|
|
2464
|
+
}
|
|
2465
|
+
const url = new URL(`${scheme}://${host}${incomingUrl}`);
|
|
2466
|
+
if (url.hostname.length !== host.length && url.hostname !== host.replace(/:\d+$/, "")) {
|
|
2467
|
+
throw new RequestError("Invalid host header");
|
|
2468
|
+
}
|
|
2469
|
+
req[urlKey] = url.href;
|
|
2470
|
+
return req;
|
|
2471
|
+
};
|
|
2472
|
+
var responseCache = Symbol("responseCache");
|
|
2473
|
+
var getResponseCache = Symbol("getResponseCache");
|
|
2474
|
+
var cacheKey = Symbol("cache");
|
|
2475
|
+
var GlobalResponse = global.Response;
|
|
2476
|
+
var Response2 = class _Response {
|
|
2477
|
+
#body;
|
|
2478
|
+
#init;
|
|
2479
|
+
[getResponseCache]() {
|
|
2480
|
+
delete this[cacheKey];
|
|
2481
|
+
return this[responseCache] ||= new GlobalResponse(this.#body, this.#init);
|
|
2482
|
+
}
|
|
2483
|
+
constructor(body, init) {
|
|
2484
|
+
let headers;
|
|
2485
|
+
this.#body = body;
|
|
2486
|
+
if (init instanceof _Response) {
|
|
2487
|
+
const cachedGlobalResponse = init[responseCache];
|
|
2488
|
+
if (cachedGlobalResponse) {
|
|
2489
|
+
this.#init = cachedGlobalResponse;
|
|
2490
|
+
this[getResponseCache]();
|
|
2491
|
+
return;
|
|
2492
|
+
} else {
|
|
2493
|
+
this.#init = init.#init;
|
|
2494
|
+
headers = new Headers(init.#init.headers);
|
|
2495
|
+
}
|
|
2496
|
+
} else {
|
|
2497
|
+
this.#init = init;
|
|
2498
|
+
}
|
|
2499
|
+
if (typeof body === "string" || typeof body?.getReader !== "undefined" || body instanceof Blob || body instanceof Uint8Array) {
|
|
2500
|
+
headers ||= init?.headers || { "content-type": "text/plain; charset=UTF-8" };
|
|
2501
|
+
this[cacheKey] = [init?.status || 200, body, headers];
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
get headers() {
|
|
2505
|
+
const cache = this[cacheKey];
|
|
2506
|
+
if (cache) {
|
|
2507
|
+
if (!(cache[2] instanceof Headers)) {
|
|
2508
|
+
cache[2] = new Headers(cache[2]);
|
|
2509
|
+
}
|
|
2510
|
+
return cache[2];
|
|
2511
|
+
}
|
|
2512
|
+
return this[getResponseCache]().headers;
|
|
2513
|
+
}
|
|
2514
|
+
get status() {
|
|
2515
|
+
return this[cacheKey]?.[0] ?? this[getResponseCache]().status;
|
|
2516
|
+
}
|
|
2517
|
+
get ok() {
|
|
2518
|
+
const status = this.status;
|
|
2519
|
+
return status >= 200 && status < 300;
|
|
2520
|
+
}
|
|
2521
|
+
};
|
|
2522
|
+
["body", "bodyUsed", "redirected", "statusText", "trailers", "type", "url"].forEach((k) => {
|
|
2523
|
+
Object.defineProperty(Response2.prototype, k, {
|
|
2524
|
+
get() {
|
|
2525
|
+
return this[getResponseCache]()[k];
|
|
2526
|
+
}
|
|
2527
|
+
});
|
|
2528
|
+
});
|
|
2529
|
+
["arrayBuffer", "blob", "clone", "formData", "json", "text"].forEach((k) => {
|
|
2530
|
+
Object.defineProperty(Response2.prototype, k, {
|
|
2531
|
+
value: function() {
|
|
2532
|
+
return this[getResponseCache]()[k]();
|
|
2533
|
+
}
|
|
2534
|
+
});
|
|
2535
|
+
});
|
|
2536
|
+
Object.setPrototypeOf(Response2, GlobalResponse);
|
|
2537
|
+
Object.setPrototypeOf(Response2.prototype, GlobalResponse.prototype);
|
|
2538
|
+
async function readWithoutBlocking(readPromise) {
|
|
2539
|
+
return Promise.race([readPromise, Promise.resolve().then(() => Promise.resolve(undefined))]);
|
|
2540
|
+
}
|
|
2541
|
+
function writeFromReadableStreamDefaultReader(reader, writable, currentReadPromise) {
|
|
2542
|
+
const cancel = (error) => {
|
|
2543
|
+
reader.cancel(error).catch(() => {});
|
|
2544
|
+
};
|
|
2545
|
+
writable.on("close", cancel);
|
|
2546
|
+
writable.on("error", cancel);
|
|
2547
|
+
(currentReadPromise ?? reader.read()).then(flow, handleStreamError);
|
|
2548
|
+
return reader.closed.finally(() => {
|
|
2549
|
+
writable.off("close", cancel);
|
|
2550
|
+
writable.off("error", cancel);
|
|
2551
|
+
});
|
|
2552
|
+
function handleStreamError(error) {
|
|
2553
|
+
if (error) {
|
|
2554
|
+
writable.destroy(error);
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
function onDrain() {
|
|
2558
|
+
reader.read().then(flow, handleStreamError);
|
|
2559
|
+
}
|
|
2560
|
+
function flow({ done, value }) {
|
|
2561
|
+
try {
|
|
2562
|
+
if (done) {
|
|
2563
|
+
writable.end();
|
|
2564
|
+
} else if (!writable.write(value)) {
|
|
2565
|
+
writable.once("drain", onDrain);
|
|
2566
|
+
} else {
|
|
2567
|
+
return reader.read().then(flow, handleStreamError);
|
|
2568
|
+
}
|
|
2569
|
+
} catch (e) {
|
|
2570
|
+
handleStreamError(e);
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
function writeFromReadableStream(stream, writable) {
|
|
2575
|
+
if (stream.locked) {
|
|
2576
|
+
throw new TypeError("ReadableStream is locked.");
|
|
2577
|
+
} else if (writable.destroyed) {
|
|
2578
|
+
return;
|
|
2579
|
+
}
|
|
2580
|
+
return writeFromReadableStreamDefaultReader(stream.getReader(), writable);
|
|
2581
|
+
}
|
|
2582
|
+
var buildOutgoingHttpHeaders = (headers) => {
|
|
2583
|
+
const res = {};
|
|
2584
|
+
if (!(headers instanceof Headers)) {
|
|
2585
|
+
headers = new Headers(headers ?? undefined);
|
|
2586
|
+
}
|
|
2587
|
+
const cookies = [];
|
|
2588
|
+
for (const [k, v] of headers) {
|
|
2589
|
+
if (k === "set-cookie") {
|
|
2590
|
+
cookies.push(v);
|
|
2591
|
+
} else {
|
|
2592
|
+
res[k] = v;
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
if (cookies.length > 0) {
|
|
2596
|
+
res["set-cookie"] = cookies;
|
|
2597
|
+
}
|
|
2598
|
+
res["content-type"] ??= "text/plain; charset=UTF-8";
|
|
2599
|
+
return res;
|
|
2600
|
+
};
|
|
2601
|
+
var X_ALREADY_SENT = "x-hono-already-sent";
|
|
2602
|
+
var webFetch = global.fetch;
|
|
2603
|
+
if (typeof global.crypto === "undefined") {
|
|
2604
|
+
global.crypto = crypto;
|
|
2605
|
+
}
|
|
2606
|
+
global.fetch = (info, init) => {
|
|
2607
|
+
init = {
|
|
2608
|
+
compress: false,
|
|
2609
|
+
...init
|
|
2610
|
+
};
|
|
2611
|
+
return webFetch(info, init);
|
|
2612
|
+
};
|
|
2613
|
+
var outgoingEnded = Symbol("outgoingEnded");
|
|
2614
|
+
var handleRequestError = () => new Response(null, {
|
|
2615
|
+
status: 400
|
|
2616
|
+
});
|
|
2617
|
+
var handleFetchError = (e) => new Response(null, {
|
|
2618
|
+
status: e instanceof Error && (e.name === "TimeoutError" || e.constructor.name === "TimeoutError") ? 504 : 500
|
|
2619
|
+
});
|
|
2620
|
+
var handleResponseError = (e, outgoing) => {
|
|
2621
|
+
const err = e instanceof Error ? e : new Error("unknown error", { cause: e });
|
|
2622
|
+
if (err.code === "ERR_STREAM_PREMATURE_CLOSE") {
|
|
2623
|
+
console.info("The user aborted a request.");
|
|
2624
|
+
} else {
|
|
2625
|
+
console.error(e);
|
|
2626
|
+
if (!outgoing.headersSent) {
|
|
2627
|
+
outgoing.writeHead(500, { "Content-Type": "text/plain" });
|
|
2628
|
+
}
|
|
2629
|
+
outgoing.end(`Error: ${err.message}`);
|
|
2630
|
+
outgoing.destroy(err);
|
|
2631
|
+
}
|
|
2632
|
+
};
|
|
2633
|
+
var flushHeaders = (outgoing) => {
|
|
2634
|
+
if ("flushHeaders" in outgoing && outgoing.writable) {
|
|
2635
|
+
outgoing.flushHeaders();
|
|
2636
|
+
}
|
|
2637
|
+
};
|
|
2638
|
+
var responseViaCache = async (res, outgoing) => {
|
|
2639
|
+
let [status, body, header] = res[cacheKey];
|
|
2640
|
+
if (header instanceof Headers) {
|
|
2641
|
+
header = buildOutgoingHttpHeaders(header);
|
|
2642
|
+
}
|
|
2643
|
+
if (typeof body === "string") {
|
|
2644
|
+
header["Content-Length"] = Buffer.byteLength(body);
|
|
2645
|
+
} else if (body instanceof Uint8Array) {
|
|
2646
|
+
header["Content-Length"] = body.byteLength;
|
|
2647
|
+
} else if (body instanceof Blob) {
|
|
2648
|
+
header["Content-Length"] = body.size;
|
|
2649
|
+
}
|
|
2650
|
+
outgoing.writeHead(status, header);
|
|
2651
|
+
if (typeof body === "string" || body instanceof Uint8Array) {
|
|
2652
|
+
outgoing.end(body);
|
|
2653
|
+
} else if (body instanceof Blob) {
|
|
2654
|
+
outgoing.end(new Uint8Array(await body.arrayBuffer()));
|
|
2655
|
+
} else {
|
|
2656
|
+
flushHeaders(outgoing);
|
|
2657
|
+
await writeFromReadableStream(body, outgoing)?.catch((e) => handleResponseError(e, outgoing));
|
|
2658
|
+
}
|
|
2659
|
+
outgoing[outgoingEnded]?.();
|
|
2660
|
+
};
|
|
2661
|
+
var isPromise = (res) => typeof res.then === "function";
|
|
2662
|
+
var responseViaResponseObject = async (res, outgoing, options = {}) => {
|
|
2663
|
+
if (isPromise(res)) {
|
|
2664
|
+
if (options.errorHandler) {
|
|
2665
|
+
try {
|
|
2666
|
+
res = await res;
|
|
2667
|
+
} catch (err) {
|
|
2668
|
+
const errRes = await options.errorHandler(err);
|
|
2669
|
+
if (!errRes) {
|
|
2670
|
+
return;
|
|
2671
|
+
}
|
|
2672
|
+
res = errRes;
|
|
2673
|
+
}
|
|
2674
|
+
} else {
|
|
2675
|
+
res = await res.catch(handleFetchError);
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
if (cacheKey in res) {
|
|
2679
|
+
return responseViaCache(res, outgoing);
|
|
2680
|
+
}
|
|
2681
|
+
const resHeaderRecord = buildOutgoingHttpHeaders(res.headers);
|
|
2682
|
+
if (res.body) {
|
|
2683
|
+
const reader = res.body.getReader();
|
|
2684
|
+
const values = [];
|
|
2685
|
+
let done = false;
|
|
2686
|
+
let currentReadPromise = undefined;
|
|
2687
|
+
if (resHeaderRecord["transfer-encoding"] !== "chunked") {
|
|
2688
|
+
let maxReadCount = 2;
|
|
2689
|
+
for (let i = 0;i < maxReadCount; i++) {
|
|
2690
|
+
currentReadPromise ||= reader.read();
|
|
2691
|
+
const chunk = await readWithoutBlocking(currentReadPromise).catch((e) => {
|
|
2692
|
+
console.error(e);
|
|
2693
|
+
done = true;
|
|
2694
|
+
});
|
|
2695
|
+
if (!chunk) {
|
|
2696
|
+
if (i === 1) {
|
|
2697
|
+
await new Promise((resolve) => setTimeout(resolve));
|
|
2698
|
+
maxReadCount = 3;
|
|
2699
|
+
continue;
|
|
2700
|
+
}
|
|
2701
|
+
break;
|
|
2702
|
+
}
|
|
2703
|
+
currentReadPromise = undefined;
|
|
2704
|
+
if (chunk.value) {
|
|
2705
|
+
values.push(chunk.value);
|
|
2706
|
+
}
|
|
2707
|
+
if (chunk.done) {
|
|
2708
|
+
done = true;
|
|
2709
|
+
break;
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
if (done && !("content-length" in resHeaderRecord)) {
|
|
2713
|
+
resHeaderRecord["content-length"] = values.reduce((acc, value) => acc + value.length, 0);
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
outgoing.writeHead(res.status, resHeaderRecord);
|
|
2717
|
+
values.forEach((value) => {
|
|
2718
|
+
outgoing.write(value);
|
|
2719
|
+
});
|
|
2720
|
+
if (done) {
|
|
2721
|
+
outgoing.end();
|
|
2722
|
+
} else {
|
|
2723
|
+
if (values.length === 0) {
|
|
2724
|
+
flushHeaders(outgoing);
|
|
2725
|
+
}
|
|
2726
|
+
await writeFromReadableStreamDefaultReader(reader, outgoing, currentReadPromise);
|
|
2727
|
+
}
|
|
2728
|
+
} else if (resHeaderRecord[X_ALREADY_SENT]) {} else {
|
|
2729
|
+
outgoing.writeHead(res.status, resHeaderRecord);
|
|
2730
|
+
outgoing.end();
|
|
2731
|
+
}
|
|
2732
|
+
outgoing[outgoingEnded]?.();
|
|
2733
|
+
};
|
|
2734
|
+
var getRequestListener = (fetchCallback, options = {}) => {
|
|
2735
|
+
const autoCleanupIncoming = options.autoCleanupIncoming ?? true;
|
|
2736
|
+
if (options.overrideGlobalObjects !== false && global.Request !== Request2) {
|
|
2737
|
+
Object.defineProperty(global, "Request", {
|
|
2738
|
+
value: Request2
|
|
2739
|
+
});
|
|
2740
|
+
Object.defineProperty(global, "Response", {
|
|
2741
|
+
value: Response2
|
|
2742
|
+
});
|
|
2743
|
+
}
|
|
2744
|
+
return async (incoming, outgoing) => {
|
|
2745
|
+
let res, req;
|
|
2746
|
+
try {
|
|
2747
|
+
req = newRequest(incoming, options.hostname);
|
|
2748
|
+
let incomingEnded = !autoCleanupIncoming || incoming.method === "GET" || incoming.method === "HEAD";
|
|
2749
|
+
if (!incomingEnded) {
|
|
2750
|
+
incoming[wrapBodyStream] = true;
|
|
2751
|
+
incoming.on("end", () => {
|
|
2752
|
+
incomingEnded = true;
|
|
2753
|
+
});
|
|
2754
|
+
if (incoming instanceof Http2ServerRequest2) {
|
|
2755
|
+
outgoing[outgoingEnded] = () => {
|
|
2756
|
+
if (!incomingEnded) {
|
|
2757
|
+
setTimeout(() => {
|
|
2758
|
+
if (!incomingEnded) {
|
|
2759
|
+
setTimeout(() => {
|
|
2760
|
+
incoming.destroy();
|
|
2761
|
+
outgoing.destroy();
|
|
2762
|
+
});
|
|
2763
|
+
}
|
|
2764
|
+
});
|
|
2765
|
+
}
|
|
2766
|
+
};
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
2769
|
+
outgoing.on("close", () => {
|
|
2770
|
+
const abortController = req[abortControllerKey];
|
|
2771
|
+
if (abortController) {
|
|
2772
|
+
if (incoming.errored) {
|
|
2773
|
+
req[abortControllerKey].abort(incoming.errored.toString());
|
|
2774
|
+
} else if (!outgoing.writableFinished) {
|
|
2775
|
+
req[abortControllerKey].abort("Client connection prematurely closed.");
|
|
2776
|
+
}
|
|
2777
|
+
}
|
|
2778
|
+
if (!incomingEnded) {
|
|
2779
|
+
setTimeout(() => {
|
|
2780
|
+
if (!incomingEnded) {
|
|
2781
|
+
setTimeout(() => {
|
|
2782
|
+
incoming.destroy();
|
|
2783
|
+
});
|
|
2784
|
+
}
|
|
2785
|
+
});
|
|
2786
|
+
}
|
|
2787
|
+
});
|
|
2788
|
+
res = fetchCallback(req, { incoming, outgoing });
|
|
2789
|
+
if (cacheKey in res) {
|
|
2790
|
+
return responseViaCache(res, outgoing);
|
|
2791
|
+
}
|
|
2792
|
+
} catch (e) {
|
|
2793
|
+
if (!res) {
|
|
2794
|
+
if (options.errorHandler) {
|
|
2795
|
+
res = await options.errorHandler(req ? e : toRequestError(e));
|
|
2796
|
+
if (!res) {
|
|
2797
|
+
return;
|
|
2798
|
+
}
|
|
2799
|
+
} else if (!req) {
|
|
2800
|
+
res = handleRequestError();
|
|
2801
|
+
} else {
|
|
2802
|
+
res = handleFetchError(e);
|
|
2803
|
+
}
|
|
2804
|
+
} else {
|
|
2805
|
+
return handleResponseError(e, outgoing);
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
try {
|
|
2809
|
+
return await responseViaResponseObject(res, outgoing, options);
|
|
2810
|
+
} catch (e) {
|
|
2811
|
+
return handleResponseError(e, outgoing);
|
|
2812
|
+
}
|
|
2813
|
+
};
|
|
2814
|
+
};
|
|
2815
|
+
var createAdaptorServer = (options) => {
|
|
2816
|
+
const fetchCallback = options.fetch;
|
|
2817
|
+
const requestListener = getRequestListener(fetchCallback, {
|
|
2818
|
+
hostname: options.hostname,
|
|
2819
|
+
overrideGlobalObjects: options.overrideGlobalObjects,
|
|
2820
|
+
autoCleanupIncoming: options.autoCleanupIncoming
|
|
2821
|
+
});
|
|
2822
|
+
const createServer2 = options.createServer || createServerHTTP;
|
|
2823
|
+
const server = createServer2(options.serverOptions || {}, requestListener);
|
|
2824
|
+
return server;
|
|
2825
|
+
};
|
|
2826
|
+
var serve = (options, listeningListener) => {
|
|
2827
|
+
const server = createAdaptorServer(options);
|
|
2828
|
+
server.listen(options?.port ?? 3000, options.hostname, () => {
|
|
2829
|
+
const serverInfo = server.address();
|
|
2830
|
+
listeningListener && listeningListener(serverInfo);
|
|
2831
|
+
});
|
|
2832
|
+
return server;
|
|
2833
|
+
};
|
|
2834
|
+
|
|
2288
2835
|
// src/proxy-server.ts
|
|
2289
|
-
import { writeFileSync as writeFileSync3 } from "fs";
|
|
2836
|
+
import { writeFileSync as writeFileSync3 } from "node:fs";
|
|
2290
2837
|
|
|
2291
2838
|
// src/transform.ts
|
|
2292
2839
|
function removeUriFormat(schema) {
|
|
@@ -2577,7 +3124,7 @@ async function createProxyServer(port, openrouterApiKey, model, monitorMode = fa
|
|
|
2577
3124
|
const originalHeaders = c.req.header();
|
|
2578
3125
|
const extractedApiKey = originalHeaders["x-api-key"] || originalHeaders["authorization"] || anthropicApiKey;
|
|
2579
3126
|
log(`
|
|
2580
|
-
=== [MONITOR] Claude Code
|
|
3127
|
+
=== [MONITOR] Claude Code → Anthropic API Request ===`);
|
|
2581
3128
|
log(`Headers received: ${JSON.stringify(originalHeaders, null, 2)}`);
|
|
2582
3129
|
if (!extractedApiKey) {
|
|
2583
3130
|
log("[Monitor] WARNING: No API key found in headers!");
|
|
@@ -2625,7 +3172,7 @@ async function createProxyServer(port, openrouterApiKey, model, monitorMode = fa
|
|
|
2625
3172
|
let buffer = "";
|
|
2626
3173
|
let eventLog = "";
|
|
2627
3174
|
log(`
|
|
2628
|
-
=== [MONITOR] Anthropic API
|
|
3175
|
+
=== [MONITOR] Anthropic API → Claude Code Response (Streaming) ===`);
|
|
2629
3176
|
try {
|
|
2630
3177
|
while (true) {
|
|
2631
3178
|
const { done, value } = await reader.read();
|
|
@@ -2667,7 +3214,7 @@ async function createProxyServer(port, openrouterApiKey, model, monitorMode = fa
|
|
|
2667
3214
|
}
|
|
2668
3215
|
const responseData = await anthropicResponse.json();
|
|
2669
3216
|
log(`
|
|
2670
|
-
=== [MONITOR] Anthropic API
|
|
3217
|
+
=== [MONITOR] Anthropic API → Claude Code Response (JSON) ===`);
|
|
2671
3218
|
log(JSON.stringify(responseData, null, 2));
|
|
2672
3219
|
log(`=== End Response ===
|
|
2673
3220
|
`);
|
|
@@ -3484,11 +4031,10 @@ data: ${JSON.stringify(data)}
|
|
|
3484
4031
|
}, 400);
|
|
3485
4032
|
}
|
|
3486
4033
|
});
|
|
3487
|
-
const server =
|
|
4034
|
+
const server = serve({
|
|
4035
|
+
fetch: app.fetch,
|
|
3488
4036
|
port,
|
|
3489
|
-
hostname: "127.0.0.1"
|
|
3490
|
-
idleTimeout: 255,
|
|
3491
|
-
fetch: app.fetch
|
|
4037
|
+
hostname: "127.0.0.1"
|
|
3492
4038
|
});
|
|
3493
4039
|
if (monitorMode) {
|
|
3494
4040
|
log(`[Monitor] Server started on http://127.0.0.1:${port}`);
|
|
@@ -3502,7 +4048,14 @@ data: ${JSON.stringify(data)}
|
|
|
3502
4048
|
port,
|
|
3503
4049
|
url: `http://127.0.0.1:${port}`,
|
|
3504
4050
|
shutdown: async () => {
|
|
3505
|
-
|
|
4051
|
+
await new Promise((resolve, reject) => {
|
|
4052
|
+
server.close((err) => {
|
|
4053
|
+
if (err)
|
|
4054
|
+
reject(err);
|
|
4055
|
+
else
|
|
4056
|
+
resolve();
|
|
4057
|
+
});
|
|
4058
|
+
});
|
|
3506
4059
|
log("[Proxy] Server stopped");
|
|
3507
4060
|
}
|
|
3508
4061
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claudish",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "CLI tool to run Claude Code with any OpenRouter model (Grok, GPT-5, MiniMax, etc.) via local Anthropic API-compatible proxy",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"dev:grok": "bun run src/index.ts --interactive --model x-ai/grok-code-fast-1",
|
|
13
13
|
"dev:grok:debug": "bun run src/index.ts --interactive --debug --log-level info --model x-ai/grok-code-fast-1",
|
|
14
14
|
"dev:info": "bun run src/index.ts --interactive --monitor",
|
|
15
|
-
"build": "bun build src/index.ts --outdir dist --target
|
|
15
|
+
"build": "bun build src/index.ts --outdir dist --target node && chmod +x dist/index.js",
|
|
16
16
|
"link": "npm link",
|
|
17
17
|
"unlink": "npm unlink -g claudish",
|
|
18
18
|
"install-global": "bun run build && npm link",
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"postinstall": "node scripts/postinstall.cjs"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"hono": "^4.9.0"
|
|
28
|
+
"hono": "^4.9.0",
|
|
29
|
+
"@hono/node-server": "^1.13.7"
|
|
29
30
|
},
|
|
30
31
|
"devDependencies": {
|
|
31
32
|
"@biomejs/biome": "^1.9.4",
|
|
@@ -38,6 +39,7 @@
|
|
|
38
39
|
"scripts/"
|
|
39
40
|
],
|
|
40
41
|
"engines": {
|
|
42
|
+
"node": ">=18.0.0",
|
|
41
43
|
"bun": ">=1.0.0"
|
|
42
44
|
},
|
|
43
45
|
"preferGlobal": true,
|