@jcanizalez7/clauxy 0.1.9 → 0.2.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 +39 -25
- package/dist/cli.js +344 -145
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,14 +29,19 @@ No API keys needed. Just authenticate once with OAuth and you're ready to go.
|
|
|
29
29
|
npm install -g @jcanizalez7/clauxy
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
Also requires [Claude Code](https://docs.anthropic.com/en/docs/claude-code):
|
|
33
|
+
```bash
|
|
34
|
+
npm install -g @anthropic-ai/claude-code
|
|
35
|
+
```
|
|
36
|
+
|
|
32
37
|
---
|
|
33
38
|
|
|
34
39
|
## Quick Start
|
|
35
40
|
|
|
36
|
-
|
|
41
|
+
Just run:
|
|
37
42
|
|
|
38
43
|
```bash
|
|
39
|
-
clauxy
|
|
44
|
+
clauxy
|
|
40
45
|
```
|
|
41
46
|
|
|
42
47
|
First time? Select your provider and authenticate:
|
|
@@ -51,35 +56,45 @@ First time? Select your provider and authenticate:
|
|
|
51
56
|
└
|
|
52
57
|
```
|
|
53
58
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
In a new terminal:
|
|
57
|
-
|
|
58
|
-
```bash
|
|
59
|
-
export ANTHROPIC_BASE_URL="http://localhost:3000"
|
|
60
|
-
export ANTHROPIC_API_KEY="clauxy"
|
|
61
|
-
claude
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
That's it! Claude Code now uses your selected provider.
|
|
65
|
-
|
|
66
|
-
> **Tip:** Add the exports to your `~/.zshrc` or `~/.bashrc` to make them permanent.
|
|
59
|
+
That's it! Clauxy starts the proxy and launches Claude Code automatically with the correct environment variables.
|
|
67
60
|
|
|
68
61
|
---
|
|
69
62
|
|
|
70
63
|
## Commands
|
|
71
64
|
|
|
72
65
|
```bash
|
|
73
|
-
|
|
74
|
-
clauxy
|
|
75
|
-
clauxy
|
|
76
|
-
clauxy
|
|
77
|
-
clauxy
|
|
66
|
+
# Default: Start proxy + launch Claude Code
|
|
67
|
+
clauxy # Just works!
|
|
68
|
+
clauxy "fix the bug" # Pass arguments to Claude
|
|
69
|
+
clauxy -p copilot # Use specific provider
|
|
70
|
+
clauxy -c # Re-authenticate before starting
|
|
71
|
+
clauxy --port 3000 # Use specific port (reuses existing proxy if running)
|
|
72
|
+
|
|
73
|
+
# Proxy-only mode (for advanced users)
|
|
74
|
+
clauxy run # Start proxy without launching Claude
|
|
75
|
+
clauxy run --port 3001 # Proxy on specific port
|
|
76
|
+
clauxy run -p chatgpt # Proxy with specific provider
|
|
77
|
+
|
|
78
|
+
# Configuration
|
|
78
79
|
clauxy model # Configure model mappings
|
|
79
80
|
clauxy auth list # Show configured providers
|
|
80
81
|
```
|
|
81
82
|
|
|
82
|
-
|
|
83
|
+
### Port Reuse
|
|
84
|
+
|
|
85
|
+
When you specify `--port`, Clauxy checks if a proxy is already running on that port:
|
|
86
|
+
- If yes, it reuses the existing proxy (no new instance started)
|
|
87
|
+
- If no, it starts a new proxy on that port
|
|
88
|
+
|
|
89
|
+
This lets you run multiple Claude Code sessions sharing a single proxy:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Terminal 1
|
|
93
|
+
clauxy --port 3000 # Starts proxy + Claude
|
|
94
|
+
|
|
95
|
+
# Terminal 2
|
|
96
|
+
clauxy --port 3000 # Reuses proxy, launches another Claude
|
|
97
|
+
```
|
|
83
98
|
|
|
84
99
|
---
|
|
85
100
|
|
|
@@ -96,7 +111,7 @@ Claude Code requests are automatically mapped to your provider's models:
|
|
|
96
111
|
Customize with `clauxy model` or environment variables:
|
|
97
112
|
|
|
98
113
|
```bash
|
|
99
|
-
CLAUXY_MODEL_BIG=gpt-4o CLAUXY_MODEL_MID=gpt-4o clauxy
|
|
114
|
+
CLAUXY_MODEL_BIG=gpt-4o CLAUXY_MODEL_MID=gpt-4o clauxy
|
|
100
115
|
```
|
|
101
116
|
|
|
102
117
|
---
|
|
@@ -123,14 +138,13 @@ Credentials are stored locally in `~/.clauxy/` with restricted permissions.
|
|
|
123
138
|
|
|
124
139
|
**"Token refresh failed"** or **"Not authenticated"**
|
|
125
140
|
```bash
|
|
126
|
-
clauxy
|
|
141
|
+
clauxy -c
|
|
127
142
|
```
|
|
128
143
|
|
|
129
144
|
**Port already in use**
|
|
130
145
|
```bash
|
|
131
|
-
clauxy
|
|
146
|
+
clauxy --port 3001
|
|
132
147
|
```
|
|
133
|
-
Then update `ANTHROPIC_BASE_URL`.
|
|
134
148
|
|
|
135
149
|
**Reset everything**
|
|
136
150
|
```bash
|
package/dist/cli.js
CHANGED
|
@@ -2041,84 +2041,18 @@ var init_converter_gemini = __esm(() => {
|
|
|
2041
2041
|
|
|
2042
2042
|
// proxy.ts
|
|
2043
2043
|
var exports_proxy = {};
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
return true;
|
|
2049
|
-
} catch {
|
|
2050
|
-
return false;
|
|
2051
|
-
}
|
|
2052
|
-
}
|
|
2053
|
-
async function findAvailablePort() {
|
|
2054
|
-
if (process.env.PORT) {
|
|
2055
|
-
const port = parseInt(process.env.PORT, 10);
|
|
2056
|
-
if (await isPortAvailable(port)) {
|
|
2057
|
-
return port;
|
|
2058
|
-
}
|
|
2059
|
-
log.error(`Port ${port} is in use. Try a different port with --port <port>`);
|
|
2060
|
-
process.exit(1);
|
|
2061
|
-
}
|
|
2062
|
-
for (const port of PORT_RANGE) {
|
|
2063
|
-
if (await isPortAvailable(port)) {
|
|
2064
|
-
return port;
|
|
2065
|
-
}
|
|
2066
|
-
}
|
|
2067
|
-
log.error(`All ports (${PORT_RANGE.join(", ")}) are in use.`);
|
|
2068
|
-
log.error(`Try: clauxy run --port <port>`);
|
|
2069
|
-
process.exit(1);
|
|
2070
|
-
}
|
|
2071
|
-
async function loadProvider() {
|
|
2072
|
-
const providerId = process.env.CLAUXY_PROVIDER || await Config.getActiveProvider();
|
|
2073
|
-
if (!providerId) {
|
|
2074
|
-
log.error("No provider configured. Run 'clauxy run' to set up.");
|
|
2075
|
-
process.exit(1);
|
|
2076
|
-
}
|
|
2077
|
-
return await getProvider(providerId);
|
|
2078
|
-
}
|
|
2079
|
-
function convertAnthropicToOpenAI(anthropicReq) {
|
|
2080
|
-
if (currentLogLevel === "debug") {
|
|
2081
|
-
for (let i = 0;i < anthropicReq.messages.length; i++) {
|
|
2082
|
-
const msg = anthropicReq.messages[i];
|
|
2083
|
-
if (Array.isArray(msg.content)) {
|
|
2084
|
-
const toolUses = msg.content.filter((b) => b.type === "tool_use").map((b) => b.id);
|
|
2085
|
-
const toolResults = msg.content.filter((b) => b.type === "tool_result").map((b) => b.tool_use_id);
|
|
2086
|
-
if (toolUses.length > 0)
|
|
2087
|
-
log.debug(`msg[${i}] ${msg.role}: tool_use ids: ${toolUses.join(", ")}`);
|
|
2088
|
-
if (toolResults.length > 0)
|
|
2089
|
-
log.debug(`msg[${i}] ${msg.role}: tool_result ids: ${toolResults.join(", ")}`);
|
|
2090
|
-
}
|
|
2091
|
-
}
|
|
2092
|
-
}
|
|
2093
|
-
const messages = convertMessages(anthropicReq.messages, anthropicReq.system);
|
|
2094
|
-
const tools = convertTools(anthropicReq.tools);
|
|
2095
|
-
const mappedModel = provider.mapModel(anthropicReq.model);
|
|
2096
|
-
log.info(`Model: ${anthropicReq.model} -> ${mappedModel}`);
|
|
2044
|
+
__export(exports_proxy, {
|
|
2045
|
+
startProxy: () => startProxy
|
|
2046
|
+
});
|
|
2047
|
+
function createLogger(level) {
|
|
2097
2048
|
return {
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
max_tokens: anthropicReq.max_tokens,
|
|
2101
|
-
temperature: anthropicReq.temperature,
|
|
2102
|
-
stream: anthropicReq.stream ?? false,
|
|
2103
|
-
...tools && { tools }
|
|
2104
|
-
};
|
|
2105
|
-
}
|
|
2106
|
-
var LOG_LEVELS, currentLogLevel, log, DEFAULT_PORT, PORT_RANGE, provider, convertOpenAIToAnthropic, token, models, PORT, server, c, wireApi, wireApiLabel;
|
|
2107
|
-
var init_proxy = __esm(async () => {
|
|
2108
|
-
init_providers();
|
|
2109
|
-
init_config();
|
|
2110
|
-
init_converter();
|
|
2111
|
-
init_converter_gemini();
|
|
2112
|
-
LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
2113
|
-
currentLogLevel = process.env.CLAUXY_LOG_LEVEL || "info";
|
|
2114
|
-
log = {
|
|
2115
|
-
_shouldLog(level) {
|
|
2116
|
-
return LOG_LEVELS[level] >= LOG_LEVELS[currentLogLevel];
|
|
2049
|
+
_shouldLog(checkLevel) {
|
|
2050
|
+
return LOG_LEVELS[checkLevel] >= LOG_LEVELS[level];
|
|
2117
2051
|
},
|
|
2118
|
-
_format(
|
|
2052
|
+
_format(levelStr, msg) {
|
|
2119
2053
|
const now = new Date;
|
|
2120
2054
|
const ts = now.toISOString().replace("T", " ").substring(0, 19);
|
|
2121
|
-
return `${ts} ${
|
|
2055
|
+
return `${ts} ${levelStr.padEnd(5)} ${msg}`;
|
|
2122
2056
|
},
|
|
2123
2057
|
info(msg) {
|
|
2124
2058
|
if (this._shouldLog("info")) {
|
|
@@ -2146,12 +2080,73 @@ var init_proxy = __esm(async () => {
|
|
|
2146
2080
|
}
|
|
2147
2081
|
}
|
|
2148
2082
|
};
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2083
|
+
}
|
|
2084
|
+
async function isPortAvailable(port) {
|
|
2085
|
+
try {
|
|
2086
|
+
const server = Bun.serve({ port, fetch: () => new Response("") });
|
|
2087
|
+
server.stop();
|
|
2088
|
+
return true;
|
|
2089
|
+
} catch {
|
|
2090
|
+
return false;
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
async function findAvailablePort(requestedPort) {
|
|
2094
|
+
if (requestedPort) {
|
|
2095
|
+
if (await isPortAvailable(requestedPort)) {
|
|
2096
|
+
return requestedPort;
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
for (const port2 of PORT_RANGE) {
|
|
2100
|
+
if (await isPortAvailable(port2)) {
|
|
2101
|
+
return port2;
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
const server = Bun.serve({ port: 0, fetch: () => new Response("") });
|
|
2105
|
+
const port = server.port;
|
|
2106
|
+
server.stop();
|
|
2107
|
+
return port;
|
|
2108
|
+
}
|
|
2109
|
+
async function loadProvider(providerId) {
|
|
2110
|
+
const id = providerId || process.env.CLAUXY_PROVIDER || await Config.getActiveProvider();
|
|
2111
|
+
if (!id) {
|
|
2112
|
+
throw new Error("No provider configured. Run 'clauxy' to set up.");
|
|
2113
|
+
}
|
|
2114
|
+
return await getProvider(id);
|
|
2115
|
+
}
|
|
2116
|
+
async function startProxy(options = {}) {
|
|
2117
|
+
const { port: requestedPort, logLevel = "info", providerId, quiet = false } = options;
|
|
2118
|
+
const log = createLogger(logLevel);
|
|
2119
|
+
const provider = await loadProvider(providerId);
|
|
2120
|
+
function convertAnthropicToOpenAI(anthropicReq) {
|
|
2121
|
+
if (logLevel === "debug") {
|
|
2122
|
+
for (let i = 0;i < anthropicReq.messages.length; i++) {
|
|
2123
|
+
const msg = anthropicReq.messages[i];
|
|
2124
|
+
if (Array.isArray(msg.content)) {
|
|
2125
|
+
const toolUses = msg.content.filter((b) => b.type === "tool_use").map((b) => b.id);
|
|
2126
|
+
const toolResults = msg.content.filter((b) => b.type === "tool_result").map((b) => b.tool_use_id);
|
|
2127
|
+
if (toolUses.length > 0)
|
|
2128
|
+
log.debug(`msg[${i}] ${msg.role}: tool_use ids: ${toolUses.join(", ")}`);
|
|
2129
|
+
if (toolResults.length > 0)
|
|
2130
|
+
log.debug(`msg[${i}] ${msg.role}: tool_result ids: ${toolResults.join(", ")}`);
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
const messages = convertMessages(anthropicReq.messages, anthropicReq.system);
|
|
2135
|
+
const tools = convertTools(anthropicReq.tools);
|
|
2136
|
+
const mappedModel = provider.mapModel(anthropicReq.model);
|
|
2137
|
+
log.info(`Model: ${anthropicReq.model} -> ${mappedModel}`);
|
|
2138
|
+
return {
|
|
2139
|
+
model: mappedModel,
|
|
2140
|
+
messages,
|
|
2141
|
+
max_tokens: anthropicReq.max_tokens,
|
|
2142
|
+
temperature: anthropicReq.temperature,
|
|
2143
|
+
stream: anthropicReq.stream ?? false,
|
|
2144
|
+
...tools && { tools }
|
|
2145
|
+
};
|
|
2146
|
+
}
|
|
2147
|
+
const convertOpenAIToAnthropic = convertResponse;
|
|
2153
2148
|
log.info(`Authenticating with ${provider.info.name}...`);
|
|
2154
|
-
token = await provider.getToken();
|
|
2149
|
+
const token = await provider.getToken();
|
|
2155
2150
|
log.info("\x1B[32m+\x1B[0m Authentication successful");
|
|
2156
2151
|
if (provider.loadCodeAssist) {
|
|
2157
2152
|
log.info("Initializing Cloud Code session...");
|
|
@@ -2162,15 +2157,17 @@ var init_proxy = __esm(async () => {
|
|
|
2162
2157
|
log.warn("\x1B[33m!\x1B[0m Could not get Cloud Code project ID - requests may fail");
|
|
2163
2158
|
}
|
|
2164
2159
|
}
|
|
2165
|
-
models = { big: "default", mid: "default", small: "default" };
|
|
2160
|
+
let models = { big: "default", mid: "default", small: "default" };
|
|
2166
2161
|
if (provider.loadModels) {
|
|
2167
2162
|
models = await provider.loadModels();
|
|
2168
2163
|
}
|
|
2169
|
-
PORT = await findAvailablePort();
|
|
2170
|
-
if (PORT !==
|
|
2164
|
+
const PORT = await findAvailablePort(requestedPort);
|
|
2165
|
+
if (requestedPort && PORT !== requestedPort) {
|
|
2166
|
+
log.warn(`Port ${requestedPort} in use, using port ${PORT} instead`);
|
|
2167
|
+
} else if (!requestedPort && PORT !== DEFAULT_PORT) {
|
|
2171
2168
|
log.warn(`Port 3000 in use, using port ${PORT} instead`);
|
|
2172
2169
|
}
|
|
2173
|
-
server = Bun.serve({
|
|
2170
|
+
const server = Bun.serve({
|
|
2174
2171
|
port: PORT,
|
|
2175
2172
|
async fetch(req) {
|
|
2176
2173
|
const url = new URL(req.url);
|
|
@@ -2365,6 +2362,47 @@ var init_proxy = __esm(async () => {
|
|
|
2365
2362
|
}
|
|
2366
2363
|
}
|
|
2367
2364
|
});
|
|
2365
|
+
if (!quiet) {
|
|
2366
|
+
const wireApi = provider.info.wireApi || "chat";
|
|
2367
|
+
const wireApiLabel = wireApi === "gemini" ? "Gemini API" : wireApi === "responses" ? "Responses API" : "Chat Completions";
|
|
2368
|
+
log.raw(c.dim("=".repeat(60)));
|
|
2369
|
+
log.raw(`${c.green(">")} ${c.bold("Clauxy")} proxy ready on ${c.cyan(`http://localhost:${PORT}`)}`);
|
|
2370
|
+
log.raw(`${c.green(">")} Provider: ${c.cyan(provider.info.name)}`);
|
|
2371
|
+
log.raw(`${c.green(">")} Wire API: ${c.cyan(wireApiLabel)}`);
|
|
2372
|
+
log.raw(c.dim("=".repeat(60)));
|
|
2373
|
+
log.raw(`
|
|
2374
|
+
${c.yellow("Model Mapping")} (Claude -> Provider):`);
|
|
2375
|
+
log.raw(` opus/big -> ${c.cyan(models.big)}`);
|
|
2376
|
+
log.raw(` sonnet/mid -> ${c.cyan(models.mid)}`);
|
|
2377
|
+
log.raw(` haiku/small -> ${c.cyan(models.small)}`);
|
|
2378
|
+
log.raw(`
|
|
2379
|
+
${c.yellow("To change models:")}`);
|
|
2380
|
+
log.raw(c.dim(" CLAUXY_MODEL_BIG=<model> CLAUXY_MODEL_MID=<model> CLAUXY_MODEL_SMALL=<model>"));
|
|
2381
|
+
log.raw(c.dim(" Or run: clauxy model"));
|
|
2382
|
+
log.raw(c.dim("=".repeat(60)));
|
|
2383
|
+
log.raw(`
|
|
2384
|
+
${c.yellow("Run in a NEW terminal:")}
|
|
2385
|
+
`);
|
|
2386
|
+
log.raw(` ${c.green("export")} ANTHROPIC_BASE_URL=${c.cyan(`"http://localhost:${PORT}"`)}`);
|
|
2387
|
+
log.raw(` ${c.green("export")} ANTHROPIC_API_KEY=${c.cyan('"clauxy"')}`);
|
|
2388
|
+
log.raw(` ${c.bold("claude")}
|
|
2389
|
+
`);
|
|
2390
|
+
log.raw(c.dim("=".repeat(60)));
|
|
2391
|
+
}
|
|
2392
|
+
return {
|
|
2393
|
+
server,
|
|
2394
|
+
port: PORT,
|
|
2395
|
+
stop: () => server.stop()
|
|
2396
|
+
};
|
|
2397
|
+
}
|
|
2398
|
+
var LOG_LEVELS, DEFAULT_PORT = 3000, PORT_RANGE, c;
|
|
2399
|
+
var init_proxy = __esm(async () => {
|
|
2400
|
+
init_providers();
|
|
2401
|
+
init_config();
|
|
2402
|
+
init_converter();
|
|
2403
|
+
init_converter_gemini();
|
|
2404
|
+
LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
2405
|
+
PORT_RANGE = [3000, 3001, 3002, 3003, 3004, 3005, 3006, 3007, 3008, 3009];
|
|
2368
2406
|
c = {
|
|
2369
2407
|
green: (s) => `\x1B[32m${s}\x1B[0m`,
|
|
2370
2408
|
cyan: (s) => `\x1B[36m${s}\x1B[0m`,
|
|
@@ -2372,31 +2410,7 @@ var init_proxy = __esm(async () => {
|
|
|
2372
2410
|
dim: (s) => `\x1B[2m${s}\x1B[0m`,
|
|
2373
2411
|
bold: (s) => `\x1B[1m${s}\x1B[0m`
|
|
2374
2412
|
};
|
|
2375
|
-
|
|
2376
|
-
wireApiLabel = wireApi === "gemini" ? "Gemini API" : wireApi === "responses" ? "Responses API" : "Chat Completions";
|
|
2377
|
-
log.raw(c.dim("=".repeat(60)));
|
|
2378
|
-
log.raw(`${c.green(">")} ${c.bold("Clauxy")} proxy ready on ${c.cyan(`http://localhost:${PORT}`)}`);
|
|
2379
|
-
log.raw(`${c.green(">")} Provider: ${c.cyan(provider.info.name)}`);
|
|
2380
|
-
log.raw(`${c.green(">")} Wire API: ${c.cyan(wireApiLabel)}`);
|
|
2381
|
-
log.raw(c.dim("=".repeat(60)));
|
|
2382
|
-
log.raw(`
|
|
2383
|
-
${c.yellow("Model Mapping")} (Claude -> Provider):`);
|
|
2384
|
-
log.raw(` opus/big -> ${c.cyan(models.big)}`);
|
|
2385
|
-
log.raw(` sonnet/mid -> ${c.cyan(models.mid)}`);
|
|
2386
|
-
log.raw(` haiku/small -> ${c.cyan(models.small)}`);
|
|
2387
|
-
log.raw(`
|
|
2388
|
-
${c.yellow("To change models:")}`);
|
|
2389
|
-
log.raw(c.dim(" CLAUXY_MODEL_BIG=<model> CLAUXY_MODEL_MID=<model> CLAUXY_MODEL_SMALL=<model>"));
|
|
2390
|
-
log.raw(c.dim(" Or run: clauxy model"));
|
|
2391
|
-
log.raw(c.dim("=".repeat(60)));
|
|
2392
|
-
log.raw(`
|
|
2393
|
-
${c.yellow("Run in a NEW terminal:")}
|
|
2394
|
-
`);
|
|
2395
|
-
log.raw(` ${c.green("export")} ANTHROPIC_BASE_URL=${c.cyan(`"http://localhost:${PORT}"`)}`);
|
|
2396
|
-
log.raw(` ${c.green("export")} ANTHROPIC_API_KEY=${c.cyan('"clauxy"')}`);
|
|
2397
|
-
log.raw(` ${c.bold("claude")}
|
|
2398
|
-
`);
|
|
2399
|
-
log.raw(c.dim("=".repeat(60)));
|
|
2413
|
+
if (false) {}
|
|
2400
2414
|
});
|
|
2401
2415
|
|
|
2402
2416
|
// cli/index.ts
|
|
@@ -2441,23 +2455,21 @@ async function selectAndAuthenticate() {
|
|
|
2441
2455
|
return authenticateProvider(providerId);
|
|
2442
2456
|
}
|
|
2443
2457
|
async function authenticateProvider(providerId) {
|
|
2444
|
-
const
|
|
2445
|
-
prompts2.intro(`Clauxy - Connecting to ${
|
|
2446
|
-
const result = await
|
|
2458
|
+
const provider = await getProvider(providerId);
|
|
2459
|
+
prompts2.intro(`Clauxy - Connecting to ${provider.info.name}`);
|
|
2460
|
+
const result = await provider.authenticate();
|
|
2447
2461
|
if (result.type === "failed") {
|
|
2448
2462
|
prompts2.cancel(`Authentication failed: ${result.error}`);
|
|
2449
2463
|
process.exit(1);
|
|
2450
2464
|
}
|
|
2451
2465
|
await Config.setActiveProvider(providerId);
|
|
2452
|
-
prompts2.outro(`Connected to ${
|
|
2466
|
+
prompts2.outro(`Connected to ${provider.info.name}`);
|
|
2453
2467
|
return providerId;
|
|
2454
2468
|
}
|
|
2455
2469
|
async function runCommand(options) {
|
|
2456
|
-
const { port, logLevel, connect, provider
|
|
2457
|
-
process.env.PORT = port.toString();
|
|
2458
|
-
process.env.CLAUXY_LOG_LEVEL = logLevel;
|
|
2470
|
+
const { port, logLevel, connect, provider } = options;
|
|
2459
2471
|
const availableProviders = getProviderList().map((item) => item.id);
|
|
2460
|
-
const providerOverride =
|
|
2472
|
+
const providerOverride = provider?.toLowerCase();
|
|
2461
2473
|
if (providerOverride && !availableProviders.includes(providerOverride)) {
|
|
2462
2474
|
console.error(`Invalid provider: "${providerOverride}". Valid values: ${availableProviders.join(" | ")}.`);
|
|
2463
2475
|
process.exit(1);
|
|
@@ -2482,8 +2494,158 @@ async function runCommand(options) {
|
|
|
2482
2494
|
}
|
|
2483
2495
|
}
|
|
2484
2496
|
await Config.setActiveProvider(activeProvider);
|
|
2485
|
-
|
|
2486
|
-
await
|
|
2497
|
+
const { startProxy: startProxy2 } = await init_proxy().then(() => exports_proxy);
|
|
2498
|
+
await startProxy2({
|
|
2499
|
+
port,
|
|
2500
|
+
logLevel,
|
|
2501
|
+
providerId: activeProvider
|
|
2502
|
+
});
|
|
2503
|
+
await new Promise(() => {});
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2506
|
+
// cli/commands/wrapper.ts
|
|
2507
|
+
init_auth();
|
|
2508
|
+
init_config();
|
|
2509
|
+
init_providers();
|
|
2510
|
+
import * as prompts3 from "@clack/prompts";
|
|
2511
|
+
var c2 = {
|
|
2512
|
+
green: (s) => `\x1B[32m${s}\x1B[0m`,
|
|
2513
|
+
cyan: (s) => `\x1B[36m${s}\x1B[0m`,
|
|
2514
|
+
yellow: (s) => `\x1B[33m${s}\x1B[0m`,
|
|
2515
|
+
dim: (s) => `\x1B[2m${s}\x1B[0m`,
|
|
2516
|
+
bold: (s) => `\x1B[1m${s}\x1B[0m`,
|
|
2517
|
+
red: (s) => `\x1B[31m${s}\x1B[0m`
|
|
2518
|
+
};
|
|
2519
|
+
async function findClaudeCli() {
|
|
2520
|
+
try {
|
|
2521
|
+
const proc = Bun.spawn(["which", "claude"], {
|
|
2522
|
+
stdout: "pipe",
|
|
2523
|
+
stderr: "pipe"
|
|
2524
|
+
});
|
|
2525
|
+
const exitCode = await proc.exited;
|
|
2526
|
+
if (exitCode === 0) {
|
|
2527
|
+
const output = await new Response(proc.stdout).text();
|
|
2528
|
+
return output.trim();
|
|
2529
|
+
}
|
|
2530
|
+
return null;
|
|
2531
|
+
} catch {
|
|
2532
|
+
return null;
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
async function selectAndAuthenticate2() {
|
|
2536
|
+
prompts3.intro("Clauxy - Connect to AI Provider");
|
|
2537
|
+
const providers = getProviderList();
|
|
2538
|
+
const providerChoice = await prompts3.select({
|
|
2539
|
+
message: "Select your AI provider",
|
|
2540
|
+
options: providers.map((p) => ({
|
|
2541
|
+
label: p.name,
|
|
2542
|
+
value: p.id,
|
|
2543
|
+
hint: p.description
|
|
2544
|
+
}))
|
|
2545
|
+
});
|
|
2546
|
+
const providerId = handleCancel(providerChoice, "Setup cancelled");
|
|
2547
|
+
return authenticateProvider2(providerId);
|
|
2548
|
+
}
|
|
2549
|
+
async function authenticateProvider2(providerId) {
|
|
2550
|
+
const provider = await getProvider(providerId);
|
|
2551
|
+
prompts3.intro(`Clauxy - Connecting to ${provider.info.name}`);
|
|
2552
|
+
const result = await provider.authenticate();
|
|
2553
|
+
if (result.type === "failed") {
|
|
2554
|
+
prompts3.cancel(`Authentication failed: ${result.error}`);
|
|
2555
|
+
process.exit(1);
|
|
2556
|
+
}
|
|
2557
|
+
await Config.setActiveProvider(providerId);
|
|
2558
|
+
prompts3.outro(`Connected to ${provider.info.name}`);
|
|
2559
|
+
return providerId;
|
|
2560
|
+
}
|
|
2561
|
+
async function isClauxyRunning(port) {
|
|
2562
|
+
try {
|
|
2563
|
+
const response = await fetch(`http://localhost:${port}/health`, {
|
|
2564
|
+
signal: AbortSignal.timeout(1000)
|
|
2565
|
+
});
|
|
2566
|
+
return response.ok && await response.text() === "OK";
|
|
2567
|
+
} catch {
|
|
2568
|
+
return false;
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
async function wrapperCommand(options) {
|
|
2572
|
+
const { logLevel = "warn", connect = false, provider, port: requestedPort, claudeArgs = [] } = options;
|
|
2573
|
+
const claudePath = await findClaudeCli();
|
|
2574
|
+
if (!claudePath) {
|
|
2575
|
+
console.error(c2.red("Error: Claude CLI not found."));
|
|
2576
|
+
console.error(`
|
|
2577
|
+
Install it with: ${c2.cyan("npm i -g @anthropic-ai/claude-code")}`);
|
|
2578
|
+
console.error(`Or visit: ${c2.cyan("https://docs.anthropic.com/en/docs/claude-code")}`);
|
|
2579
|
+
process.exit(1);
|
|
2580
|
+
}
|
|
2581
|
+
const availableProviders = getProviderList().map((item) => item.id);
|
|
2582
|
+
const providerOverride = provider?.toLowerCase();
|
|
2583
|
+
if (providerOverride && !availableProviders.includes(providerOverride)) {
|
|
2584
|
+
console.error(`Invalid provider: "${providerOverride}". Valid values: ${availableProviders.join(" | ")}.`);
|
|
2585
|
+
process.exit(1);
|
|
2586
|
+
}
|
|
2587
|
+
let activeProvider;
|
|
2588
|
+
if (providerOverride) {
|
|
2589
|
+
const credentials = await Auth.get(providerOverride);
|
|
2590
|
+
if (credentials && !connect) {
|
|
2591
|
+
activeProvider = providerOverride;
|
|
2592
|
+
} else {
|
|
2593
|
+
activeProvider = await authenticateProvider2(providerOverride);
|
|
2594
|
+
}
|
|
2595
|
+
} else {
|
|
2596
|
+
activeProvider = await Config.getActiveProvider();
|
|
2597
|
+
if (!activeProvider || connect) {
|
|
2598
|
+
activeProvider = await selectAndAuthenticate2();
|
|
2599
|
+
} else {
|
|
2600
|
+
const credentials = await Auth.get(activeProvider);
|
|
2601
|
+
if (!credentials) {
|
|
2602
|
+
activeProvider = await selectAndAuthenticate2();
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
await Config.setActiveProvider(activeProvider);
|
|
2607
|
+
let proxyPort;
|
|
2608
|
+
let proxyServer = null;
|
|
2609
|
+
if (requestedPort && await isClauxyRunning(requestedPort)) {
|
|
2610
|
+
proxyPort = requestedPort;
|
|
2611
|
+
console.log(`${c2.green(">")} ${c2.bold("Clauxy")} reusing proxy on ${c2.cyan(`localhost:${proxyPort}`)}`);
|
|
2612
|
+
} else {
|
|
2613
|
+
const { startProxy: startProxy2 } = await init_proxy().then(() => exports_proxy);
|
|
2614
|
+
const server = await startProxy2({
|
|
2615
|
+
port: requestedPort,
|
|
2616
|
+
logLevel,
|
|
2617
|
+
providerId: activeProvider,
|
|
2618
|
+
quiet: true
|
|
2619
|
+
});
|
|
2620
|
+
proxyServer = server;
|
|
2621
|
+
proxyPort = server.port;
|
|
2622
|
+
const providerInfo = await getProvider(activeProvider);
|
|
2623
|
+
console.log(`${c2.green(">")} ${c2.bold("Clauxy")} proxy on ${c2.cyan(`localhost:${proxyPort}`)} ${c2.dim(`(${providerInfo.info.name})`)}`);
|
|
2624
|
+
}
|
|
2625
|
+
const claude = Bun.spawn(["claude", ...claudeArgs], {
|
|
2626
|
+
env: {
|
|
2627
|
+
...process.env,
|
|
2628
|
+
ANTHROPIC_BASE_URL: `http://localhost:${proxyPort}`,
|
|
2629
|
+
ANTHROPIC_API_KEY: "clauxy"
|
|
2630
|
+
},
|
|
2631
|
+
stdin: "inherit",
|
|
2632
|
+
stdout: "inherit",
|
|
2633
|
+
stderr: "inherit"
|
|
2634
|
+
});
|
|
2635
|
+
let isCleaningUp = false;
|
|
2636
|
+
const cleanup = () => {
|
|
2637
|
+
if (isCleaningUp)
|
|
2638
|
+
return;
|
|
2639
|
+
isCleaningUp = true;
|
|
2640
|
+
proxyServer?.stop();
|
|
2641
|
+
claude.kill();
|
|
2642
|
+
};
|
|
2643
|
+
process.on("SIGINT", cleanup);
|
|
2644
|
+
process.on("SIGTERM", cleanup);
|
|
2645
|
+
process.on("SIGHUP", cleanup);
|
|
2646
|
+
const exitCode = await claude.exited;
|
|
2647
|
+
proxyServer?.stop();
|
|
2648
|
+
process.exit(exitCode);
|
|
2487
2649
|
}
|
|
2488
2650
|
|
|
2489
2651
|
// cli/commands/auth.ts
|
|
@@ -2503,14 +2665,14 @@ Configured Providers:
|
|
|
2503
2665
|
`);
|
|
2504
2666
|
return;
|
|
2505
2667
|
}
|
|
2506
|
-
for (const
|
|
2507
|
-
const auth = allAuth[
|
|
2668
|
+
for (const provider of providers) {
|
|
2669
|
+
const auth = allAuth[provider.id];
|
|
2508
2670
|
if (auth) {
|
|
2509
|
-
const isActive =
|
|
2671
|
+
const isActive = provider.id === activeProvider;
|
|
2510
2672
|
const marker = isActive ? colors.green("*") : " ";
|
|
2511
2673
|
const authType = auth.type === "oauth" ? "OAuth" : "API Key";
|
|
2512
2674
|
const activeLabel = isActive ? ` ${colors.green("[active]")}` : "";
|
|
2513
|
-
console.log(` ${marker} ${
|
|
2675
|
+
console.log(` ${marker} ${provider.name} (${authType})${activeLabel}`);
|
|
2514
2676
|
}
|
|
2515
2677
|
}
|
|
2516
2678
|
console.log("");
|
|
@@ -2520,7 +2682,7 @@ Configured Providers:
|
|
|
2520
2682
|
init_config();
|
|
2521
2683
|
init_providers();
|
|
2522
2684
|
init_models();
|
|
2523
|
-
import * as
|
|
2685
|
+
import * as prompts4 from "@clack/prompts";
|
|
2524
2686
|
|
|
2525
2687
|
// cli/constants.ts
|
|
2526
2688
|
var CUSTOM_MODEL_VALUE = "__custom__";
|
|
@@ -2544,21 +2706,21 @@ async function selectModel(tier, providerId, currentValue) {
|
|
|
2544
2706
|
})),
|
|
2545
2707
|
{ label: "Enter custom model", value: CUSTOM_MODEL_VALUE, hint: "Type model name" }
|
|
2546
2708
|
];
|
|
2547
|
-
const choice = await
|
|
2709
|
+
const choice = await prompts4.select({
|
|
2548
2710
|
message: `Select ${tier} model`,
|
|
2549
2711
|
options,
|
|
2550
2712
|
initialValue: currentValue
|
|
2551
2713
|
});
|
|
2552
|
-
if (
|
|
2714
|
+
if (prompts4.isCancel(choice)) {
|
|
2553
2715
|
return null;
|
|
2554
2716
|
}
|
|
2555
2717
|
if (choice === CUSTOM_MODEL_VALUE) {
|
|
2556
|
-
const custom = await
|
|
2718
|
+
const custom = await prompts4.text({
|
|
2557
2719
|
message: `Enter custom ${tier} model name`,
|
|
2558
2720
|
placeholder: currentValue,
|
|
2559
2721
|
defaultValue: currentValue
|
|
2560
2722
|
});
|
|
2561
|
-
if (
|
|
2723
|
+
if (prompts4.isCancel(custom)) {
|
|
2562
2724
|
return null;
|
|
2563
2725
|
}
|
|
2564
2726
|
return custom;
|
|
@@ -2567,9 +2729,9 @@ async function selectModel(tier, providerId, currentValue) {
|
|
|
2567
2729
|
}
|
|
2568
2730
|
async function configureModels() {
|
|
2569
2731
|
const activeProvider = await Config.getActiveProvider();
|
|
2570
|
-
|
|
2732
|
+
prompts4.intro("Clauxy - Model Configuration");
|
|
2571
2733
|
const providers = getProviderList();
|
|
2572
|
-
const providerChoice = await
|
|
2734
|
+
const providerChoice = await prompts4.select({
|
|
2573
2735
|
message: "Select provider to configure models for",
|
|
2574
2736
|
options: providers.map((p) => ({
|
|
2575
2737
|
label: p.name,
|
|
@@ -2587,7 +2749,7 @@ Current models for ${PROVIDERS[providerId].name}:`);
|
|
|
2587
2749
|
console.log(` Mid: ${currentModels.mid}`);
|
|
2588
2750
|
console.log(` Small: ${currentModels.small}
|
|
2589
2751
|
`);
|
|
2590
|
-
const action = await
|
|
2752
|
+
const action = await prompts4.select({
|
|
2591
2753
|
message: "What would you like to do?",
|
|
2592
2754
|
options: [
|
|
2593
2755
|
{ label: "Configure all models", value: "all" },
|
|
@@ -2605,36 +2767,36 @@ Models reset to defaults:`);
|
|
|
2605
2767
|
console.log(` Big: ${defaults.big}`);
|
|
2606
2768
|
console.log(` Mid: ${defaults.mid}`);
|
|
2607
2769
|
console.log(` Small: ${defaults.small}`);
|
|
2608
|
-
|
|
2770
|
+
prompts4.outro("Done");
|
|
2609
2771
|
return;
|
|
2610
2772
|
}
|
|
2611
|
-
const
|
|
2773
|
+
const models = {
|
|
2612
2774
|
big: currentModels.big,
|
|
2613
2775
|
mid: currentModels.mid,
|
|
2614
2776
|
small: currentModels.small
|
|
2615
2777
|
};
|
|
2616
2778
|
for (const tier of MODEL_TIERS) {
|
|
2617
2779
|
if (action === "all" || action === tier.key) {
|
|
2618
|
-
const result = await selectModel(tier.label, providerId,
|
|
2780
|
+
const result = await selectModel(tier.label, providerId, models[tier.key]);
|
|
2619
2781
|
if (result === null) {
|
|
2620
|
-
|
|
2782
|
+
prompts4.cancel("Cancelled");
|
|
2621
2783
|
process.exit(0);
|
|
2622
2784
|
}
|
|
2623
|
-
|
|
2785
|
+
models[tier.key] = result;
|
|
2624
2786
|
}
|
|
2625
2787
|
}
|
|
2626
|
-
await saveModels(
|
|
2788
|
+
await saveModels(models);
|
|
2627
2789
|
console.log(`
|
|
2628
2790
|
Models configured:`);
|
|
2629
|
-
console.log(` opus/big -> ${
|
|
2630
|
-
console.log(` sonnet/mid -> ${
|
|
2631
|
-
console.log(` haiku/small -> ${
|
|
2632
|
-
|
|
2791
|
+
console.log(` opus/big -> ${models.big}`);
|
|
2792
|
+
console.log(` sonnet/mid -> ${models.mid}`);
|
|
2793
|
+
console.log(` haiku/small -> ${models.small}`);
|
|
2794
|
+
prompts4.outro("Model configuration saved");
|
|
2633
2795
|
}
|
|
2634
2796
|
// package.json
|
|
2635
2797
|
var package_default = {
|
|
2636
2798
|
name: "@jcanizalez7/clauxy",
|
|
2637
|
-
version: "0.1
|
|
2799
|
+
version: "0.2.1",
|
|
2638
2800
|
description: "Multi-provider AI proxy for Claude Code (GitHub Copilot, ChatGPT Plus, Google Gemini)",
|
|
2639
2801
|
type: "module",
|
|
2640
2802
|
bin: {
|
|
@@ -2679,7 +2841,7 @@ var package_default = {
|
|
|
2679
2841
|
var run = defineCommand({
|
|
2680
2842
|
meta: {
|
|
2681
2843
|
name: "run",
|
|
2682
|
-
description: "Start the proxy server"
|
|
2844
|
+
description: "Start the proxy server (without launching Claude)"
|
|
2683
2845
|
},
|
|
2684
2846
|
args: {
|
|
2685
2847
|
port: {
|
|
@@ -2715,12 +2877,12 @@ var run = defineCommand({
|
|
|
2715
2877
|
if (!LOG_LEVELS2.includes(logLevel)) {
|
|
2716
2878
|
console.error(`Invalid log level: "${args["log-level"]}". Valid values: ${LOG_LEVELS2.join(" | ")}. Using "${DEFAULT_LOG_LEVEL}".`);
|
|
2717
2879
|
}
|
|
2718
|
-
const
|
|
2880
|
+
const provider = args.provider ? String(args.provider).toLowerCase() : undefined;
|
|
2719
2881
|
await runCommand({
|
|
2720
2882
|
port,
|
|
2721
2883
|
logLevel: LOG_LEVELS2.includes(logLevel) ? logLevel : DEFAULT_LOG_LEVEL,
|
|
2722
2884
|
connect: args.connect,
|
|
2723
|
-
provider
|
|
2885
|
+
provider
|
|
2724
2886
|
});
|
|
2725
2887
|
}
|
|
2726
2888
|
});
|
|
@@ -2756,13 +2918,50 @@ var main = defineCommand({
|
|
|
2756
2918
|
meta: {
|
|
2757
2919
|
name: "clauxy",
|
|
2758
2920
|
version: package_default.version,
|
|
2759
|
-
description: "Multi-Provider AI Proxy for Claude Code"
|
|
2921
|
+
description: "Multi-Provider AI Proxy for Claude Code. Run without arguments to start proxy and launch Claude automatically."
|
|
2922
|
+
},
|
|
2923
|
+
args: {
|
|
2924
|
+
"log-level": {
|
|
2925
|
+
type: "string",
|
|
2926
|
+
alias: "l",
|
|
2927
|
+
description: "Log level: debug, info, warn, error",
|
|
2928
|
+
default: "warn"
|
|
2929
|
+
},
|
|
2930
|
+
connect: {
|
|
2931
|
+
type: "boolean",
|
|
2932
|
+
alias: "c",
|
|
2933
|
+
description: "Re-authenticate before starting",
|
|
2934
|
+
default: false
|
|
2935
|
+
},
|
|
2936
|
+
provider: {
|
|
2937
|
+
type: "string",
|
|
2938
|
+
alias: "p",
|
|
2939
|
+
description: "Provider to use (copilot, chatgpt, google)"
|
|
2940
|
+
},
|
|
2941
|
+
port: {
|
|
2942
|
+
type: "string",
|
|
2943
|
+
description: "Port to use (reuses existing proxy if running)"
|
|
2944
|
+
}
|
|
2760
2945
|
},
|
|
2761
2946
|
subCommands: {
|
|
2762
2947
|
run,
|
|
2763
2948
|
auth,
|
|
2764
2949
|
model,
|
|
2765
2950
|
models: model
|
|
2951
|
+
},
|
|
2952
|
+
async run({ args, rawArgs }) {
|
|
2953
|
+
const port = args.port ? parseInt(args.port, 10) : undefined;
|
|
2954
|
+
if (args.port && isNaN(port)) {
|
|
2955
|
+
console.error(`Invalid port number: "${args.port}". Use --port <number> (e.g., --port 3000).`);
|
|
2956
|
+
process.exit(1);
|
|
2957
|
+
}
|
|
2958
|
+
await wrapperCommand({
|
|
2959
|
+
logLevel: args["log-level"],
|
|
2960
|
+
connect: args.connect,
|
|
2961
|
+
provider: args.provider,
|
|
2962
|
+
port,
|
|
2963
|
+
claudeArgs: rawArgs
|
|
2964
|
+
});
|
|
2766
2965
|
}
|
|
2767
2966
|
});
|
|
2768
2967
|
function cli() {
|