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.
Files changed (3) hide show
  1. package/README.md +23 -11
  2. package/dist/index.js +585 -32
  3. 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
- - [Bun](https://bun.sh) - JavaScript runtime
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
- **IMPORTANT: Claudish requires Bun runtime for optimal performance.**
33
+ **✨ NEW in v1.3.0: Universal compatibility! Works with both Node.js and Bun.**
32
34
 
33
- **Option 1: Install from npm (recommended)**
35
+ **Option 1: Use without installing (recommended)**
34
36
 
35
37
  ```bash
36
- # Install globally (requires Bun in PATH)
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
- # Or use bunx (recommended - always uses Bun)
40
- bunx claudish --version
51
+ # With Bun (faster)
52
+ bun install -g claudish
41
53
  ```
42
54
 
43
- **Option 2: Install from source**
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
- **Why Bun?** Claudish is built with Bun and runs 10x faster than Node.js for proxy operations. The shebang `#!/usr/bin/env bun` ensures it always runs with Bun, but Bun must be installed on your system.
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 bun
2
- // @bun
1
+ #!/usr/bin/env node
3
2
 
4
3
  // src/claude-runner.ts
5
- import { writeFileSync, unlinkSync } from "fs";
6
- import { tmpdir } from "os";
7
- import { join } from "path";
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}\u2022${RESET} ${YELLOW}%s${RESET} ${DIM}\u2022${RESET} ${GREEN}\\$%.3f${RESET} ${DIM}\u2022${RESET} ${MAGENTA}%s%%${RESET}\\n" "$DIR" "$CLAUDISH_ACTIVE_MODEL_NAME" "$COST" "$CTX"`,
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 = Bun.spawn(["claude", ...claudeArgs], {
146
+ const proc = spawn("claude", claudeArgs, {
147
147
  env,
148
- stdout: "inherit",
149
- stderr: "inherit",
150
- stdin: "inherit"
148
+ stdio: "inherit"
151
149
  });
152
150
  setupSignalHandlers(proc, tempSettingsPath, config.quiet);
153
- const exitCode = await proc.exited;
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 = Bun.spawn(["which", "claude"], {
178
- stdout: "pipe",
179
- stderr: "pipe"
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.2.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
- \u2022 Interactive mode (default): Shows model selector, starts persistent session
375
- \u2022 Single-shot mode: Runs one task in headless mode and exits (requires --model)
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
- \u2022 Permission prompts are SKIPPED by default (--dangerously-skip-permissions)
379
- \u2022 Use --no-auto-approve to enable permission prompts
380
- \u2022 Model selector appears ONLY in interactive mode when --model not specified
381
- \u2022 Use --dangerous to disable sandbox (use with extreme caution!)
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 \u2192 Anthropic API Request ===`);
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 \u2192 Claude Code Response (Streaming) ===`);
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 \u2192 Claude Code Response (JSON) ===`);
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 = Bun.serve({
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
- server.stop();
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.2.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 bun && chmod +x dist/index.js",
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,