claudish 1.2.1 → 1.3.1
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 +43 -27
- package/dist/index.js +653 -52
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -6,6 +6,9 @@
|
|
|
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
|
|
11
|
+
- ✅ **Interactive setup** - Prompts for API key and model if not provided (zero config!)
|
|
9
12
|
- ✅ **Monitor mode** - Proxy to real Anthropic API and log all traffic (for debugging)
|
|
10
13
|
- ✅ **Protocol compliance** - 1:1 compatibility with Claude Code communication protocol
|
|
11
14
|
- ✅ **Snapshot testing** - Comprehensive test suite with 13/13 passing tests
|
|
@@ -22,63 +25,74 @@
|
|
|
22
25
|
|
|
23
26
|
### Prerequisites
|
|
24
27
|
|
|
25
|
-
-
|
|
28
|
+
- **Node.js 18+** or **Bun 1.0+** - JavaScript runtime (either works!)
|
|
26
29
|
- [Claude Code](https://claude.com/claude-code) - Claude CLI must be installed
|
|
27
30
|
- [OpenRouter API Key](https://openrouter.ai/keys) - Free tier available
|
|
28
31
|
|
|
29
32
|
### Install Claudish
|
|
30
33
|
|
|
31
|
-
|
|
34
|
+
**✨ NEW in v1.3.0: Universal compatibility! Works with both Node.js and Bun.**
|
|
32
35
|
|
|
33
|
-
**Option 1:
|
|
36
|
+
**Option 1: Use without installing (recommended)**
|
|
34
37
|
|
|
35
38
|
```bash
|
|
36
|
-
#
|
|
39
|
+
# With Node.js (works everywhere)
|
|
40
|
+
npx claudish@latest --model x-ai/grok-code-fast-1 "your prompt"
|
|
41
|
+
|
|
42
|
+
# With Bun (faster execution)
|
|
43
|
+
bunx claudish@latest --model openai/gpt-5-codex "your prompt"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Option 2: Install globally**
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# With npm (Node.js)
|
|
37
50
|
npm install -g claudish
|
|
38
51
|
|
|
39
|
-
#
|
|
40
|
-
|
|
52
|
+
# With Bun (faster)
|
|
53
|
+
bun install -g claudish
|
|
41
54
|
```
|
|
42
55
|
|
|
43
|
-
**Option
|
|
56
|
+
**Option 3: Install from source**
|
|
44
57
|
|
|
45
58
|
```bash
|
|
46
59
|
cd mcp/claudish
|
|
47
|
-
bun install
|
|
48
|
-
bun run build
|
|
49
|
-
bun link
|
|
60
|
+
bun install # or: npm install
|
|
61
|
+
bun run build # or: npm run build
|
|
62
|
+
bun link # or: npm link
|
|
50
63
|
```
|
|
51
64
|
|
|
52
|
-
**
|
|
65
|
+
**Performance Note:** While Claudish works with both runtimes, Bun offers faster startup times. Both provide identical functionality.
|
|
53
66
|
|
|
54
67
|
## Quick Start
|
|
55
68
|
|
|
56
|
-
### 1
|
|
69
|
+
### Option 1: Interactive Mode (Easiest)
|
|
57
70
|
|
|
58
71
|
```bash
|
|
59
|
-
#
|
|
60
|
-
|
|
72
|
+
# Just run it - will prompt for API key and model
|
|
73
|
+
claudish
|
|
61
74
|
|
|
62
|
-
#
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
# Recommended: Set placeholder to avoid Claude Code's API key prompt
|
|
66
|
-
export ANTHROPIC_API_KEY=sk-ant-api03-placeholder
|
|
75
|
+
# Enter your OpenRouter API key when prompted
|
|
76
|
+
# Select a model from the list
|
|
77
|
+
# Start coding!
|
|
67
78
|
```
|
|
68
79
|
|
|
69
|
-
### 2
|
|
80
|
+
### Option 2: With Environment Variables
|
|
70
81
|
|
|
71
82
|
```bash
|
|
72
|
-
#
|
|
83
|
+
# Set up environment
|
|
84
|
+
export OPENROUTER_API_KEY=sk-or-v1-...
|
|
85
|
+
export ANTHROPIC_API_KEY=sk-ant-api03-placeholder
|
|
86
|
+
|
|
87
|
+
# Run with specific task
|
|
73
88
|
claudish "implement user authentication"
|
|
74
89
|
|
|
75
|
-
#
|
|
90
|
+
# Or with specific model
|
|
76
91
|
claudish --model openai/gpt-5-codex "add tests"
|
|
77
|
-
|
|
78
|
-
# Fully autonomous mode (auto-approve + dangerous)
|
|
79
|
-
claudish --dangerous "refactor codebase"
|
|
80
92
|
```
|
|
81
93
|
|
|
94
|
+
**Note:** In interactive mode, if `OPENROUTER_API_KEY` is not set, you'll be prompted to enter it. This makes first-time usage super simple!
|
|
95
|
+
|
|
82
96
|
## Usage
|
|
83
97
|
|
|
84
98
|
### Basic Syntax
|
|
@@ -107,13 +121,15 @@ claudish [OPTIONS] <claude-args...>
|
|
|
107
121
|
|
|
108
122
|
| Variable | Description | Required |
|
|
109
123
|
|----------|-------------|----------|
|
|
110
|
-
| `OPENROUTER_API_KEY` | Your OpenRouter API key |
|
|
124
|
+
| `OPENROUTER_API_KEY` | Your OpenRouter API key | ⚡ **Optional in interactive mode** (will prompt if not set)<br>✅ **Required in non-interactive mode** |
|
|
111
125
|
| `ANTHROPIC_API_KEY` | Placeholder to prevent Claude Code dialog (not used for auth) | ✅ **Required** |
|
|
112
126
|
| `CLAUDISH_MODEL` | Default model to use | ❌ No |
|
|
113
127
|
| `CLAUDISH_PORT` | Default proxy port | ❌ No |
|
|
114
128
|
| `CLAUDISH_ACTIVE_MODEL_NAME` | Automatically set by claudish to show active model in status line (read-only) | ❌ No |
|
|
115
129
|
|
|
116
|
-
**Important:**
|
|
130
|
+
**Important Notes:**
|
|
131
|
+
- **NEW in v1.3.0:** In interactive mode, if `OPENROUTER_API_KEY` is not set, you'll be prompted to enter it
|
|
132
|
+
- You MUST set `ANTHROPIC_API_KEY=sk-ant-api03-placeholder` (or any value). Without it, Claude Code will show a dialog
|
|
117
133
|
|
|
118
134
|
## Available Models
|
|
119
135
|
|
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;
|
|
@@ -294,6 +299,9 @@ function parseArgs(args) {
|
|
|
294
299
|
}
|
|
295
300
|
i++;
|
|
296
301
|
}
|
|
302
|
+
if ((!config.claudeArgs || config.claudeArgs.length === 0) && !config.stdin) {
|
|
303
|
+
config.interactive = true;
|
|
304
|
+
}
|
|
297
305
|
if (config.monitor) {
|
|
298
306
|
if (process.env.ANTHROPIC_API_KEY && process.env.ANTHROPIC_API_KEY.includes("placeholder")) {
|
|
299
307
|
delete process.env.ANTHROPIC_API_KEY;
|
|
@@ -309,27 +317,19 @@ function parseArgs(args) {
|
|
|
309
317
|
} else {
|
|
310
318
|
const apiKey = process.env[ENV.OPENROUTER_API_KEY];
|
|
311
319
|
if (!apiKey) {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
console.error(" export ANTHROPIC_API_KEY='sk-ant-api03-placeholder'");
|
|
324
|
-
console.error("");
|
|
325
|
-
console.error("Or add it to your shell profile (~/.zshrc or ~/.bashrc) to set permanently.");
|
|
326
|
-
console.error("");
|
|
327
|
-
console.error("Note: This key is NOT used for auth - claudish uses OPENROUTER_API_KEY");
|
|
328
|
-
process.exit(1);
|
|
320
|
+
if (!config.interactive) {
|
|
321
|
+
console.error("Error: OPENROUTER_API_KEY environment variable is required");
|
|
322
|
+
console.error("Get your API key from: https://openrouter.ai/keys");
|
|
323
|
+
console.error("");
|
|
324
|
+
console.error("Set it now:");
|
|
325
|
+
console.error(" export OPENROUTER_API_KEY='sk-or-v1-...'");
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
config.openrouterApiKey = undefined;
|
|
329
|
+
} else {
|
|
330
|
+
config.openrouterApiKey = apiKey;
|
|
329
331
|
}
|
|
330
|
-
|
|
331
|
-
if (!config.claudeArgs || config.claudeArgs.length === 0) {
|
|
332
|
-
config.interactive = true;
|
|
332
|
+
config.anthropicApiKey = process.env.ANTHROPIC_API_KEY;
|
|
333
333
|
}
|
|
334
334
|
if (config.quiet === undefined) {
|
|
335
335
|
config.quiet = !config.interactive;
|
|
@@ -340,7 +340,7 @@ Error: ANTHROPIC_API_KEY is not set`);
|
|
|
340
340
|
return config;
|
|
341
341
|
}
|
|
342
342
|
function printVersion() {
|
|
343
|
-
console.log("claudish version 1.
|
|
343
|
+
console.log("claudish version 1.3.0");
|
|
344
344
|
}
|
|
345
345
|
function printHelp() {
|
|
346
346
|
console.log(`
|
|
@@ -371,14 +371,14 @@ OPTIONS:
|
|
|
371
371
|
-h, --help Show this help message
|
|
372
372
|
|
|
373
373
|
MODES:
|
|
374
|
-
|
|
375
|
-
|
|
374
|
+
• Interactive mode (default): Shows model selector, starts persistent session
|
|
375
|
+
• Single-shot mode: Runs one task in headless mode and exits (requires --model)
|
|
376
376
|
|
|
377
377
|
NOTES:
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
378
|
+
• Permission prompts are SKIPPED by default (--dangerously-skip-permissions)
|
|
379
|
+
• Use --no-auto-approve to enable permission prompts
|
|
380
|
+
• Model selector appears ONLY in interactive mode when --model not specified
|
|
381
|
+
• Use --dangerous to disable sandbox (use with extreme caution!)
|
|
382
382
|
|
|
383
383
|
ENVIRONMENT VARIABLES:
|
|
384
384
|
OPENROUTER_API_KEY Required: Your OpenRouter API key
|
|
@@ -451,6 +451,55 @@ Available OpenRouter Models (in priority order):
|
|
|
451
451
|
|
|
452
452
|
// src/simple-selector.ts
|
|
453
453
|
import { createInterface } from "readline";
|
|
454
|
+
async function promptForApiKey() {
|
|
455
|
+
return new Promise((resolve) => {
|
|
456
|
+
console.log(`
|
|
457
|
+
\x1B[1m\x1B[36mOpenRouter API Key Required\x1B[0m
|
|
458
|
+
`);
|
|
459
|
+
console.log(`\x1B[2mGet your free API key from: https://openrouter.ai/keys\x1B[0m
|
|
460
|
+
`);
|
|
461
|
+
console.log("Enter your OpenRouter API key:");
|
|
462
|
+
console.log(`\x1B[2m(it will not be saved, only used for this session)\x1B[0m
|
|
463
|
+
`);
|
|
464
|
+
const rl = createInterface({
|
|
465
|
+
input: process.stdin,
|
|
466
|
+
output: process.stdout,
|
|
467
|
+
terminal: false
|
|
468
|
+
});
|
|
469
|
+
let apiKey = null;
|
|
470
|
+
rl.on("line", (input) => {
|
|
471
|
+
const trimmed = input.trim();
|
|
472
|
+
if (!trimmed) {
|
|
473
|
+
console.log("\x1B[31mError: API key cannot be empty\x1B[0m");
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
if (!trimmed.startsWith("sk-or-v1-")) {
|
|
477
|
+
console.log("\x1B[33mWarning: OpenRouter API keys usually start with 'sk-or-v1-'\x1B[0m");
|
|
478
|
+
console.log("\x1B[2mContinuing anyway...\x1B[0m");
|
|
479
|
+
}
|
|
480
|
+
apiKey = trimmed;
|
|
481
|
+
rl.close();
|
|
482
|
+
});
|
|
483
|
+
rl.on("close", () => {
|
|
484
|
+
if (apiKey) {
|
|
485
|
+
process.stdin.pause();
|
|
486
|
+
process.stdin.removeAllListeners("data");
|
|
487
|
+
process.stdin.removeAllListeners("end");
|
|
488
|
+
process.stdin.removeAllListeners("error");
|
|
489
|
+
process.stdin.removeAllListeners("readable");
|
|
490
|
+
if (process.stdin.isTTY && process.stdin.setRawMode) {
|
|
491
|
+
process.stdin.setRawMode(false);
|
|
492
|
+
}
|
|
493
|
+
setTimeout(() => {
|
|
494
|
+
resolve(apiKey);
|
|
495
|
+
}, 200);
|
|
496
|
+
} else {
|
|
497
|
+
console.error("\x1B[31mError: API key is required\x1B[0m");
|
|
498
|
+
process.exit(1);
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
});
|
|
502
|
+
}
|
|
454
503
|
async function selectModelInteractively() {
|
|
455
504
|
return new Promise((resolve) => {
|
|
456
505
|
console.log(`
|
|
@@ -659,7 +708,7 @@ function logStructured(label, data) {
|
|
|
659
708
|
}
|
|
660
709
|
|
|
661
710
|
// src/port-manager.ts
|
|
662
|
-
import { createServer } from "net";
|
|
711
|
+
import { createServer } from "node:net";
|
|
663
712
|
async function findAvailablePort(startPort = 3000, endPort = 9000) {
|
|
664
713
|
const randomPort = Math.floor(Math.random() * (endPort - startPort + 1)) + startPort;
|
|
665
714
|
if (await isPortAvailable(randomPort)) {
|
|
@@ -2285,8 +2334,550 @@ var cors = (options) => {
|
|
|
2285
2334
|
};
|
|
2286
2335
|
};
|
|
2287
2336
|
|
|
2337
|
+
// node_modules/@hono/node-server/dist/index.mjs
|
|
2338
|
+
import { createServer as createServerHTTP } from "http";
|
|
2339
|
+
import { Http2ServerRequest as Http2ServerRequest2 } from "http2";
|
|
2340
|
+
import { Http2ServerRequest } from "http2";
|
|
2341
|
+
import { Readable } from "stream";
|
|
2342
|
+
import crypto from "crypto";
|
|
2343
|
+
var RequestError = class extends Error {
|
|
2344
|
+
constructor(message, options) {
|
|
2345
|
+
super(message, options);
|
|
2346
|
+
this.name = "RequestError";
|
|
2347
|
+
}
|
|
2348
|
+
};
|
|
2349
|
+
var toRequestError = (e) => {
|
|
2350
|
+
if (e instanceof RequestError) {
|
|
2351
|
+
return e;
|
|
2352
|
+
}
|
|
2353
|
+
return new RequestError(e.message, { cause: e });
|
|
2354
|
+
};
|
|
2355
|
+
var GlobalRequest = global.Request;
|
|
2356
|
+
var Request2 = class extends GlobalRequest {
|
|
2357
|
+
constructor(input, options) {
|
|
2358
|
+
if (typeof input === "object" && getRequestCache in input) {
|
|
2359
|
+
input = input[getRequestCache]();
|
|
2360
|
+
}
|
|
2361
|
+
if (typeof options?.body?.getReader !== "undefined") {
|
|
2362
|
+
options.duplex ??= "half";
|
|
2363
|
+
}
|
|
2364
|
+
super(input, options);
|
|
2365
|
+
}
|
|
2366
|
+
};
|
|
2367
|
+
var newHeadersFromIncoming = (incoming) => {
|
|
2368
|
+
const headerRecord = [];
|
|
2369
|
+
const rawHeaders = incoming.rawHeaders;
|
|
2370
|
+
for (let i = 0;i < rawHeaders.length; i += 2) {
|
|
2371
|
+
const { [i]: key, [i + 1]: value } = rawHeaders;
|
|
2372
|
+
if (key.charCodeAt(0) !== 58) {
|
|
2373
|
+
headerRecord.push([key, value]);
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
return new Headers(headerRecord);
|
|
2377
|
+
};
|
|
2378
|
+
var wrapBodyStream = Symbol("wrapBodyStream");
|
|
2379
|
+
var newRequestFromIncoming = (method, url, headers, incoming, abortController) => {
|
|
2380
|
+
const init = {
|
|
2381
|
+
method,
|
|
2382
|
+
headers,
|
|
2383
|
+
signal: abortController.signal
|
|
2384
|
+
};
|
|
2385
|
+
if (method === "TRACE") {
|
|
2386
|
+
init.method = "GET";
|
|
2387
|
+
const req = new Request2(url, init);
|
|
2388
|
+
Object.defineProperty(req, "method", {
|
|
2389
|
+
get() {
|
|
2390
|
+
return "TRACE";
|
|
2391
|
+
}
|
|
2392
|
+
});
|
|
2393
|
+
return req;
|
|
2394
|
+
}
|
|
2395
|
+
if (!(method === "GET" || method === "HEAD")) {
|
|
2396
|
+
if ("rawBody" in incoming && incoming.rawBody instanceof Buffer) {
|
|
2397
|
+
init.body = new ReadableStream({
|
|
2398
|
+
start(controller) {
|
|
2399
|
+
controller.enqueue(incoming.rawBody);
|
|
2400
|
+
controller.close();
|
|
2401
|
+
}
|
|
2402
|
+
});
|
|
2403
|
+
} else if (incoming[wrapBodyStream]) {
|
|
2404
|
+
let reader;
|
|
2405
|
+
init.body = new ReadableStream({
|
|
2406
|
+
async pull(controller) {
|
|
2407
|
+
try {
|
|
2408
|
+
reader ||= Readable.toWeb(incoming).getReader();
|
|
2409
|
+
const { done, value } = await reader.read();
|
|
2410
|
+
if (done) {
|
|
2411
|
+
controller.close();
|
|
2412
|
+
} else {
|
|
2413
|
+
controller.enqueue(value);
|
|
2414
|
+
}
|
|
2415
|
+
} catch (error) {
|
|
2416
|
+
controller.error(error);
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
});
|
|
2420
|
+
} else {
|
|
2421
|
+
init.body = Readable.toWeb(incoming);
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
return new Request2(url, init);
|
|
2425
|
+
};
|
|
2426
|
+
var getRequestCache = Symbol("getRequestCache");
|
|
2427
|
+
var requestCache = Symbol("requestCache");
|
|
2428
|
+
var incomingKey = Symbol("incomingKey");
|
|
2429
|
+
var urlKey = Symbol("urlKey");
|
|
2430
|
+
var headersKey = Symbol("headersKey");
|
|
2431
|
+
var abortControllerKey = Symbol("abortControllerKey");
|
|
2432
|
+
var getAbortController = Symbol("getAbortController");
|
|
2433
|
+
var requestPrototype = {
|
|
2434
|
+
get method() {
|
|
2435
|
+
return this[incomingKey].method || "GET";
|
|
2436
|
+
},
|
|
2437
|
+
get url() {
|
|
2438
|
+
return this[urlKey];
|
|
2439
|
+
},
|
|
2440
|
+
get headers() {
|
|
2441
|
+
return this[headersKey] ||= newHeadersFromIncoming(this[incomingKey]);
|
|
2442
|
+
},
|
|
2443
|
+
[getAbortController]() {
|
|
2444
|
+
this[getRequestCache]();
|
|
2445
|
+
return this[abortControllerKey];
|
|
2446
|
+
},
|
|
2447
|
+
[getRequestCache]() {
|
|
2448
|
+
this[abortControllerKey] ||= new AbortController;
|
|
2449
|
+
return this[requestCache] ||= newRequestFromIncoming(this.method, this[urlKey], this.headers, this[incomingKey], this[abortControllerKey]);
|
|
2450
|
+
}
|
|
2451
|
+
};
|
|
2452
|
+
[
|
|
2453
|
+
"body",
|
|
2454
|
+
"bodyUsed",
|
|
2455
|
+
"cache",
|
|
2456
|
+
"credentials",
|
|
2457
|
+
"destination",
|
|
2458
|
+
"integrity",
|
|
2459
|
+
"mode",
|
|
2460
|
+
"redirect",
|
|
2461
|
+
"referrer",
|
|
2462
|
+
"referrerPolicy",
|
|
2463
|
+
"signal",
|
|
2464
|
+
"keepalive"
|
|
2465
|
+
].forEach((k) => {
|
|
2466
|
+
Object.defineProperty(requestPrototype, k, {
|
|
2467
|
+
get() {
|
|
2468
|
+
return this[getRequestCache]()[k];
|
|
2469
|
+
}
|
|
2470
|
+
});
|
|
2471
|
+
});
|
|
2472
|
+
["arrayBuffer", "blob", "clone", "formData", "json", "text"].forEach((k) => {
|
|
2473
|
+
Object.defineProperty(requestPrototype, k, {
|
|
2474
|
+
value: function() {
|
|
2475
|
+
return this[getRequestCache]()[k]();
|
|
2476
|
+
}
|
|
2477
|
+
});
|
|
2478
|
+
});
|
|
2479
|
+
Object.setPrototypeOf(requestPrototype, Request2.prototype);
|
|
2480
|
+
var newRequest = (incoming, defaultHostname) => {
|
|
2481
|
+
const req = Object.create(requestPrototype);
|
|
2482
|
+
req[incomingKey] = incoming;
|
|
2483
|
+
const incomingUrl = incoming.url || "";
|
|
2484
|
+
if (incomingUrl[0] !== "/" && (incomingUrl.startsWith("http://") || incomingUrl.startsWith("https://"))) {
|
|
2485
|
+
if (incoming instanceof Http2ServerRequest) {
|
|
2486
|
+
throw new RequestError("Absolute URL for :path is not allowed in HTTP/2");
|
|
2487
|
+
}
|
|
2488
|
+
try {
|
|
2489
|
+
const url2 = new URL(incomingUrl);
|
|
2490
|
+
req[urlKey] = url2.href;
|
|
2491
|
+
} catch (e) {
|
|
2492
|
+
throw new RequestError("Invalid absolute URL", { cause: e });
|
|
2493
|
+
}
|
|
2494
|
+
return req;
|
|
2495
|
+
}
|
|
2496
|
+
const host = (incoming instanceof Http2ServerRequest ? incoming.authority : incoming.headers.host) || defaultHostname;
|
|
2497
|
+
if (!host) {
|
|
2498
|
+
throw new RequestError("Missing host header");
|
|
2499
|
+
}
|
|
2500
|
+
let scheme;
|
|
2501
|
+
if (incoming instanceof Http2ServerRequest) {
|
|
2502
|
+
scheme = incoming.scheme;
|
|
2503
|
+
if (!(scheme === "http" || scheme === "https")) {
|
|
2504
|
+
throw new RequestError("Unsupported scheme");
|
|
2505
|
+
}
|
|
2506
|
+
} else {
|
|
2507
|
+
scheme = incoming.socket && incoming.socket.encrypted ? "https" : "http";
|
|
2508
|
+
}
|
|
2509
|
+
const url = new URL(`${scheme}://${host}${incomingUrl}`);
|
|
2510
|
+
if (url.hostname.length !== host.length && url.hostname !== host.replace(/:\d+$/, "")) {
|
|
2511
|
+
throw new RequestError("Invalid host header");
|
|
2512
|
+
}
|
|
2513
|
+
req[urlKey] = url.href;
|
|
2514
|
+
return req;
|
|
2515
|
+
};
|
|
2516
|
+
var responseCache = Symbol("responseCache");
|
|
2517
|
+
var getResponseCache = Symbol("getResponseCache");
|
|
2518
|
+
var cacheKey = Symbol("cache");
|
|
2519
|
+
var GlobalResponse = global.Response;
|
|
2520
|
+
var Response2 = class _Response {
|
|
2521
|
+
#body;
|
|
2522
|
+
#init;
|
|
2523
|
+
[getResponseCache]() {
|
|
2524
|
+
delete this[cacheKey];
|
|
2525
|
+
return this[responseCache] ||= new GlobalResponse(this.#body, this.#init);
|
|
2526
|
+
}
|
|
2527
|
+
constructor(body, init) {
|
|
2528
|
+
let headers;
|
|
2529
|
+
this.#body = body;
|
|
2530
|
+
if (init instanceof _Response) {
|
|
2531
|
+
const cachedGlobalResponse = init[responseCache];
|
|
2532
|
+
if (cachedGlobalResponse) {
|
|
2533
|
+
this.#init = cachedGlobalResponse;
|
|
2534
|
+
this[getResponseCache]();
|
|
2535
|
+
return;
|
|
2536
|
+
} else {
|
|
2537
|
+
this.#init = init.#init;
|
|
2538
|
+
headers = new Headers(init.#init.headers);
|
|
2539
|
+
}
|
|
2540
|
+
} else {
|
|
2541
|
+
this.#init = init;
|
|
2542
|
+
}
|
|
2543
|
+
if (typeof body === "string" || typeof body?.getReader !== "undefined" || body instanceof Blob || body instanceof Uint8Array) {
|
|
2544
|
+
headers ||= init?.headers || { "content-type": "text/plain; charset=UTF-8" };
|
|
2545
|
+
this[cacheKey] = [init?.status || 200, body, headers];
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
get headers() {
|
|
2549
|
+
const cache = this[cacheKey];
|
|
2550
|
+
if (cache) {
|
|
2551
|
+
if (!(cache[2] instanceof Headers)) {
|
|
2552
|
+
cache[2] = new Headers(cache[2]);
|
|
2553
|
+
}
|
|
2554
|
+
return cache[2];
|
|
2555
|
+
}
|
|
2556
|
+
return this[getResponseCache]().headers;
|
|
2557
|
+
}
|
|
2558
|
+
get status() {
|
|
2559
|
+
return this[cacheKey]?.[0] ?? this[getResponseCache]().status;
|
|
2560
|
+
}
|
|
2561
|
+
get ok() {
|
|
2562
|
+
const status = this.status;
|
|
2563
|
+
return status >= 200 && status < 300;
|
|
2564
|
+
}
|
|
2565
|
+
};
|
|
2566
|
+
["body", "bodyUsed", "redirected", "statusText", "trailers", "type", "url"].forEach((k) => {
|
|
2567
|
+
Object.defineProperty(Response2.prototype, k, {
|
|
2568
|
+
get() {
|
|
2569
|
+
return this[getResponseCache]()[k];
|
|
2570
|
+
}
|
|
2571
|
+
});
|
|
2572
|
+
});
|
|
2573
|
+
["arrayBuffer", "blob", "clone", "formData", "json", "text"].forEach((k) => {
|
|
2574
|
+
Object.defineProperty(Response2.prototype, k, {
|
|
2575
|
+
value: function() {
|
|
2576
|
+
return this[getResponseCache]()[k]();
|
|
2577
|
+
}
|
|
2578
|
+
});
|
|
2579
|
+
});
|
|
2580
|
+
Object.setPrototypeOf(Response2, GlobalResponse);
|
|
2581
|
+
Object.setPrototypeOf(Response2.prototype, GlobalResponse.prototype);
|
|
2582
|
+
async function readWithoutBlocking(readPromise) {
|
|
2583
|
+
return Promise.race([readPromise, Promise.resolve().then(() => Promise.resolve(undefined))]);
|
|
2584
|
+
}
|
|
2585
|
+
function writeFromReadableStreamDefaultReader(reader, writable, currentReadPromise) {
|
|
2586
|
+
const cancel = (error) => {
|
|
2587
|
+
reader.cancel(error).catch(() => {});
|
|
2588
|
+
};
|
|
2589
|
+
writable.on("close", cancel);
|
|
2590
|
+
writable.on("error", cancel);
|
|
2591
|
+
(currentReadPromise ?? reader.read()).then(flow, handleStreamError);
|
|
2592
|
+
return reader.closed.finally(() => {
|
|
2593
|
+
writable.off("close", cancel);
|
|
2594
|
+
writable.off("error", cancel);
|
|
2595
|
+
});
|
|
2596
|
+
function handleStreamError(error) {
|
|
2597
|
+
if (error) {
|
|
2598
|
+
writable.destroy(error);
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
function onDrain() {
|
|
2602
|
+
reader.read().then(flow, handleStreamError);
|
|
2603
|
+
}
|
|
2604
|
+
function flow({ done, value }) {
|
|
2605
|
+
try {
|
|
2606
|
+
if (done) {
|
|
2607
|
+
writable.end();
|
|
2608
|
+
} else if (!writable.write(value)) {
|
|
2609
|
+
writable.once("drain", onDrain);
|
|
2610
|
+
} else {
|
|
2611
|
+
return reader.read().then(flow, handleStreamError);
|
|
2612
|
+
}
|
|
2613
|
+
} catch (e) {
|
|
2614
|
+
handleStreamError(e);
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
function writeFromReadableStream(stream, writable) {
|
|
2619
|
+
if (stream.locked) {
|
|
2620
|
+
throw new TypeError("ReadableStream is locked.");
|
|
2621
|
+
} else if (writable.destroyed) {
|
|
2622
|
+
return;
|
|
2623
|
+
}
|
|
2624
|
+
return writeFromReadableStreamDefaultReader(stream.getReader(), writable);
|
|
2625
|
+
}
|
|
2626
|
+
var buildOutgoingHttpHeaders = (headers) => {
|
|
2627
|
+
const res = {};
|
|
2628
|
+
if (!(headers instanceof Headers)) {
|
|
2629
|
+
headers = new Headers(headers ?? undefined);
|
|
2630
|
+
}
|
|
2631
|
+
const cookies = [];
|
|
2632
|
+
for (const [k, v] of headers) {
|
|
2633
|
+
if (k === "set-cookie") {
|
|
2634
|
+
cookies.push(v);
|
|
2635
|
+
} else {
|
|
2636
|
+
res[k] = v;
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
if (cookies.length > 0) {
|
|
2640
|
+
res["set-cookie"] = cookies;
|
|
2641
|
+
}
|
|
2642
|
+
res["content-type"] ??= "text/plain; charset=UTF-8";
|
|
2643
|
+
return res;
|
|
2644
|
+
};
|
|
2645
|
+
var X_ALREADY_SENT = "x-hono-already-sent";
|
|
2646
|
+
var webFetch = global.fetch;
|
|
2647
|
+
if (typeof global.crypto === "undefined") {
|
|
2648
|
+
global.crypto = crypto;
|
|
2649
|
+
}
|
|
2650
|
+
global.fetch = (info, init) => {
|
|
2651
|
+
init = {
|
|
2652
|
+
compress: false,
|
|
2653
|
+
...init
|
|
2654
|
+
};
|
|
2655
|
+
return webFetch(info, init);
|
|
2656
|
+
};
|
|
2657
|
+
var outgoingEnded = Symbol("outgoingEnded");
|
|
2658
|
+
var handleRequestError = () => new Response(null, {
|
|
2659
|
+
status: 400
|
|
2660
|
+
});
|
|
2661
|
+
var handleFetchError = (e) => new Response(null, {
|
|
2662
|
+
status: e instanceof Error && (e.name === "TimeoutError" || e.constructor.name === "TimeoutError") ? 504 : 500
|
|
2663
|
+
});
|
|
2664
|
+
var handleResponseError = (e, outgoing) => {
|
|
2665
|
+
const err = e instanceof Error ? e : new Error("unknown error", { cause: e });
|
|
2666
|
+
if (err.code === "ERR_STREAM_PREMATURE_CLOSE") {
|
|
2667
|
+
console.info("The user aborted a request.");
|
|
2668
|
+
} else {
|
|
2669
|
+
console.error(e);
|
|
2670
|
+
if (!outgoing.headersSent) {
|
|
2671
|
+
outgoing.writeHead(500, { "Content-Type": "text/plain" });
|
|
2672
|
+
}
|
|
2673
|
+
outgoing.end(`Error: ${err.message}`);
|
|
2674
|
+
outgoing.destroy(err);
|
|
2675
|
+
}
|
|
2676
|
+
};
|
|
2677
|
+
var flushHeaders = (outgoing) => {
|
|
2678
|
+
if ("flushHeaders" in outgoing && outgoing.writable) {
|
|
2679
|
+
outgoing.flushHeaders();
|
|
2680
|
+
}
|
|
2681
|
+
};
|
|
2682
|
+
var responseViaCache = async (res, outgoing) => {
|
|
2683
|
+
let [status, body, header] = res[cacheKey];
|
|
2684
|
+
if (header instanceof Headers) {
|
|
2685
|
+
header = buildOutgoingHttpHeaders(header);
|
|
2686
|
+
}
|
|
2687
|
+
if (typeof body === "string") {
|
|
2688
|
+
header["Content-Length"] = Buffer.byteLength(body);
|
|
2689
|
+
} else if (body instanceof Uint8Array) {
|
|
2690
|
+
header["Content-Length"] = body.byteLength;
|
|
2691
|
+
} else if (body instanceof Blob) {
|
|
2692
|
+
header["Content-Length"] = body.size;
|
|
2693
|
+
}
|
|
2694
|
+
outgoing.writeHead(status, header);
|
|
2695
|
+
if (typeof body === "string" || body instanceof Uint8Array) {
|
|
2696
|
+
outgoing.end(body);
|
|
2697
|
+
} else if (body instanceof Blob) {
|
|
2698
|
+
outgoing.end(new Uint8Array(await body.arrayBuffer()));
|
|
2699
|
+
} else {
|
|
2700
|
+
flushHeaders(outgoing);
|
|
2701
|
+
await writeFromReadableStream(body, outgoing)?.catch((e) => handleResponseError(e, outgoing));
|
|
2702
|
+
}
|
|
2703
|
+
outgoing[outgoingEnded]?.();
|
|
2704
|
+
};
|
|
2705
|
+
var isPromise = (res) => typeof res.then === "function";
|
|
2706
|
+
var responseViaResponseObject = async (res, outgoing, options = {}) => {
|
|
2707
|
+
if (isPromise(res)) {
|
|
2708
|
+
if (options.errorHandler) {
|
|
2709
|
+
try {
|
|
2710
|
+
res = await res;
|
|
2711
|
+
} catch (err) {
|
|
2712
|
+
const errRes = await options.errorHandler(err);
|
|
2713
|
+
if (!errRes) {
|
|
2714
|
+
return;
|
|
2715
|
+
}
|
|
2716
|
+
res = errRes;
|
|
2717
|
+
}
|
|
2718
|
+
} else {
|
|
2719
|
+
res = await res.catch(handleFetchError);
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
if (cacheKey in res) {
|
|
2723
|
+
return responseViaCache(res, outgoing);
|
|
2724
|
+
}
|
|
2725
|
+
const resHeaderRecord = buildOutgoingHttpHeaders(res.headers);
|
|
2726
|
+
if (res.body) {
|
|
2727
|
+
const reader = res.body.getReader();
|
|
2728
|
+
const values = [];
|
|
2729
|
+
let done = false;
|
|
2730
|
+
let currentReadPromise = undefined;
|
|
2731
|
+
if (resHeaderRecord["transfer-encoding"] !== "chunked") {
|
|
2732
|
+
let maxReadCount = 2;
|
|
2733
|
+
for (let i = 0;i < maxReadCount; i++) {
|
|
2734
|
+
currentReadPromise ||= reader.read();
|
|
2735
|
+
const chunk = await readWithoutBlocking(currentReadPromise).catch((e) => {
|
|
2736
|
+
console.error(e);
|
|
2737
|
+
done = true;
|
|
2738
|
+
});
|
|
2739
|
+
if (!chunk) {
|
|
2740
|
+
if (i === 1) {
|
|
2741
|
+
await new Promise((resolve) => setTimeout(resolve));
|
|
2742
|
+
maxReadCount = 3;
|
|
2743
|
+
continue;
|
|
2744
|
+
}
|
|
2745
|
+
break;
|
|
2746
|
+
}
|
|
2747
|
+
currentReadPromise = undefined;
|
|
2748
|
+
if (chunk.value) {
|
|
2749
|
+
values.push(chunk.value);
|
|
2750
|
+
}
|
|
2751
|
+
if (chunk.done) {
|
|
2752
|
+
done = true;
|
|
2753
|
+
break;
|
|
2754
|
+
}
|
|
2755
|
+
}
|
|
2756
|
+
if (done && !("content-length" in resHeaderRecord)) {
|
|
2757
|
+
resHeaderRecord["content-length"] = values.reduce((acc, value) => acc + value.length, 0);
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
outgoing.writeHead(res.status, resHeaderRecord);
|
|
2761
|
+
values.forEach((value) => {
|
|
2762
|
+
outgoing.write(value);
|
|
2763
|
+
});
|
|
2764
|
+
if (done) {
|
|
2765
|
+
outgoing.end();
|
|
2766
|
+
} else {
|
|
2767
|
+
if (values.length === 0) {
|
|
2768
|
+
flushHeaders(outgoing);
|
|
2769
|
+
}
|
|
2770
|
+
await writeFromReadableStreamDefaultReader(reader, outgoing, currentReadPromise);
|
|
2771
|
+
}
|
|
2772
|
+
} else if (resHeaderRecord[X_ALREADY_SENT]) {} else {
|
|
2773
|
+
outgoing.writeHead(res.status, resHeaderRecord);
|
|
2774
|
+
outgoing.end();
|
|
2775
|
+
}
|
|
2776
|
+
outgoing[outgoingEnded]?.();
|
|
2777
|
+
};
|
|
2778
|
+
var getRequestListener = (fetchCallback, options = {}) => {
|
|
2779
|
+
const autoCleanupIncoming = options.autoCleanupIncoming ?? true;
|
|
2780
|
+
if (options.overrideGlobalObjects !== false && global.Request !== Request2) {
|
|
2781
|
+
Object.defineProperty(global, "Request", {
|
|
2782
|
+
value: Request2
|
|
2783
|
+
});
|
|
2784
|
+
Object.defineProperty(global, "Response", {
|
|
2785
|
+
value: Response2
|
|
2786
|
+
});
|
|
2787
|
+
}
|
|
2788
|
+
return async (incoming, outgoing) => {
|
|
2789
|
+
let res, req;
|
|
2790
|
+
try {
|
|
2791
|
+
req = newRequest(incoming, options.hostname);
|
|
2792
|
+
let incomingEnded = !autoCleanupIncoming || incoming.method === "GET" || incoming.method === "HEAD";
|
|
2793
|
+
if (!incomingEnded) {
|
|
2794
|
+
incoming[wrapBodyStream] = true;
|
|
2795
|
+
incoming.on("end", () => {
|
|
2796
|
+
incomingEnded = true;
|
|
2797
|
+
});
|
|
2798
|
+
if (incoming instanceof Http2ServerRequest2) {
|
|
2799
|
+
outgoing[outgoingEnded] = () => {
|
|
2800
|
+
if (!incomingEnded) {
|
|
2801
|
+
setTimeout(() => {
|
|
2802
|
+
if (!incomingEnded) {
|
|
2803
|
+
setTimeout(() => {
|
|
2804
|
+
incoming.destroy();
|
|
2805
|
+
outgoing.destroy();
|
|
2806
|
+
});
|
|
2807
|
+
}
|
|
2808
|
+
});
|
|
2809
|
+
}
|
|
2810
|
+
};
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
outgoing.on("close", () => {
|
|
2814
|
+
const abortController = req[abortControllerKey];
|
|
2815
|
+
if (abortController) {
|
|
2816
|
+
if (incoming.errored) {
|
|
2817
|
+
req[abortControllerKey].abort(incoming.errored.toString());
|
|
2818
|
+
} else if (!outgoing.writableFinished) {
|
|
2819
|
+
req[abortControllerKey].abort("Client connection prematurely closed.");
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
if (!incomingEnded) {
|
|
2823
|
+
setTimeout(() => {
|
|
2824
|
+
if (!incomingEnded) {
|
|
2825
|
+
setTimeout(() => {
|
|
2826
|
+
incoming.destroy();
|
|
2827
|
+
});
|
|
2828
|
+
}
|
|
2829
|
+
});
|
|
2830
|
+
}
|
|
2831
|
+
});
|
|
2832
|
+
res = fetchCallback(req, { incoming, outgoing });
|
|
2833
|
+
if (cacheKey in res) {
|
|
2834
|
+
return responseViaCache(res, outgoing);
|
|
2835
|
+
}
|
|
2836
|
+
} catch (e) {
|
|
2837
|
+
if (!res) {
|
|
2838
|
+
if (options.errorHandler) {
|
|
2839
|
+
res = await options.errorHandler(req ? e : toRequestError(e));
|
|
2840
|
+
if (!res) {
|
|
2841
|
+
return;
|
|
2842
|
+
}
|
|
2843
|
+
} else if (!req) {
|
|
2844
|
+
res = handleRequestError();
|
|
2845
|
+
} else {
|
|
2846
|
+
res = handleFetchError(e);
|
|
2847
|
+
}
|
|
2848
|
+
} else {
|
|
2849
|
+
return handleResponseError(e, outgoing);
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
try {
|
|
2853
|
+
return await responseViaResponseObject(res, outgoing, options);
|
|
2854
|
+
} catch (e) {
|
|
2855
|
+
return handleResponseError(e, outgoing);
|
|
2856
|
+
}
|
|
2857
|
+
};
|
|
2858
|
+
};
|
|
2859
|
+
var createAdaptorServer = (options) => {
|
|
2860
|
+
const fetchCallback = options.fetch;
|
|
2861
|
+
const requestListener = getRequestListener(fetchCallback, {
|
|
2862
|
+
hostname: options.hostname,
|
|
2863
|
+
overrideGlobalObjects: options.overrideGlobalObjects,
|
|
2864
|
+
autoCleanupIncoming: options.autoCleanupIncoming
|
|
2865
|
+
});
|
|
2866
|
+
const createServer2 = options.createServer || createServerHTTP;
|
|
2867
|
+
const server = createServer2(options.serverOptions || {}, requestListener);
|
|
2868
|
+
return server;
|
|
2869
|
+
};
|
|
2870
|
+
var serve = (options, listeningListener) => {
|
|
2871
|
+
const server = createAdaptorServer(options);
|
|
2872
|
+
server.listen(options?.port ?? 3000, options.hostname, () => {
|
|
2873
|
+
const serverInfo = server.address();
|
|
2874
|
+
listeningListener && listeningListener(serverInfo);
|
|
2875
|
+
});
|
|
2876
|
+
return server;
|
|
2877
|
+
};
|
|
2878
|
+
|
|
2288
2879
|
// src/proxy-server.ts
|
|
2289
|
-
import { writeFileSync as writeFileSync3 } from "fs";
|
|
2880
|
+
import { writeFileSync as writeFileSync3 } from "node:fs";
|
|
2290
2881
|
|
|
2291
2882
|
// src/transform.ts
|
|
2292
2883
|
function removeUriFormat(schema) {
|
|
@@ -2577,7 +3168,7 @@ async function createProxyServer(port, openrouterApiKey, model, monitorMode = fa
|
|
|
2577
3168
|
const originalHeaders = c.req.header();
|
|
2578
3169
|
const extractedApiKey = originalHeaders["x-api-key"] || originalHeaders["authorization"] || anthropicApiKey;
|
|
2579
3170
|
log(`
|
|
2580
|
-
=== [MONITOR] Claude Code
|
|
3171
|
+
=== [MONITOR] Claude Code → Anthropic API Request ===`);
|
|
2581
3172
|
log(`Headers received: ${JSON.stringify(originalHeaders, null, 2)}`);
|
|
2582
3173
|
if (!extractedApiKey) {
|
|
2583
3174
|
log("[Monitor] WARNING: No API key found in headers!");
|
|
@@ -2625,7 +3216,7 @@ async function createProxyServer(port, openrouterApiKey, model, monitorMode = fa
|
|
|
2625
3216
|
let buffer = "";
|
|
2626
3217
|
let eventLog = "";
|
|
2627
3218
|
log(`
|
|
2628
|
-
=== [MONITOR] Anthropic API
|
|
3219
|
+
=== [MONITOR] Anthropic API → Claude Code Response (Streaming) ===`);
|
|
2629
3220
|
try {
|
|
2630
3221
|
while (true) {
|
|
2631
3222
|
const { done, value } = await reader.read();
|
|
@@ -2667,7 +3258,7 @@ async function createProxyServer(port, openrouterApiKey, model, monitorMode = fa
|
|
|
2667
3258
|
}
|
|
2668
3259
|
const responseData = await anthropicResponse.json();
|
|
2669
3260
|
log(`
|
|
2670
|
-
=== [MONITOR] Anthropic API
|
|
3261
|
+
=== [MONITOR] Anthropic API → Claude Code Response (JSON) ===`);
|
|
2671
3262
|
log(JSON.stringify(responseData, null, 2));
|
|
2672
3263
|
log(`=== End Response ===
|
|
2673
3264
|
`);
|
|
@@ -3484,11 +4075,10 @@ data: ${JSON.stringify(data)}
|
|
|
3484
4075
|
}, 400);
|
|
3485
4076
|
}
|
|
3486
4077
|
});
|
|
3487
|
-
const server =
|
|
4078
|
+
const server = serve({
|
|
4079
|
+
fetch: app.fetch,
|
|
3488
4080
|
port,
|
|
3489
|
-
hostname: "127.0.0.1"
|
|
3490
|
-
idleTimeout: 255,
|
|
3491
|
-
fetch: app.fetch
|
|
4081
|
+
hostname: "127.0.0.1"
|
|
3492
4082
|
});
|
|
3493
4083
|
if (monitorMode) {
|
|
3494
4084
|
log(`[Monitor] Server started on http://127.0.0.1:${port}`);
|
|
@@ -3502,7 +4092,14 @@ data: ${JSON.stringify(data)}
|
|
|
3502
4092
|
port,
|
|
3503
4093
|
url: `http://127.0.0.1:${port}`,
|
|
3504
4094
|
shutdown: async () => {
|
|
3505
|
-
|
|
4095
|
+
await new Promise((resolve, reject) => {
|
|
4096
|
+
server.close((err) => {
|
|
4097
|
+
if (err)
|
|
4098
|
+
reject(err);
|
|
4099
|
+
else
|
|
4100
|
+
resolve();
|
|
4101
|
+
});
|
|
4102
|
+
});
|
|
3506
4103
|
log("[Proxy] Server stopped");
|
|
3507
4104
|
}
|
|
3508
4105
|
};
|
|
@@ -3560,6 +4157,10 @@ async function main() {
|
|
|
3560
4157
|
console.error("Install it from: https://claude.com/claude-code");
|
|
3561
4158
|
process.exit(1);
|
|
3562
4159
|
}
|
|
4160
|
+
if (config.interactive && !config.monitor && !config.openrouterApiKey) {
|
|
4161
|
+
config.openrouterApiKey = await promptForApiKey();
|
|
4162
|
+
console.log("");
|
|
4163
|
+
}
|
|
3563
4164
|
if (config.interactive && !config.monitor && !config.model) {
|
|
3564
4165
|
config.model = await selectModelInteractively();
|
|
3565
4166
|
console.log("");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claudish",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
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,12 +25,12 @@
|
|
|
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",
|
|
32
33
|
"@types/bun": "latest",
|
|
33
|
-
"@types/react": "^19.2.2",
|
|
34
34
|
"typescript": "^5.7.0"
|
|
35
35
|
},
|
|
36
36
|
"files": [
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"scripts/"
|
|
39
39
|
],
|
|
40
40
|
"engines": {
|
|
41
|
+
"node": ">=18.0.0",
|
|
41
42
|
"bun": ">=1.0.0"
|
|
42
43
|
},
|
|
43
44
|
"preferGlobal": true,
|