ccproxypal 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +105 -0
- package/bin/ccproxypal.js +172 -0
- package/bun.lock +709 -0
- package/package.json +24 -0
- package/src/adapter.js +228 -0
- package/src/configure.js +75 -0
- package/src/server.js +215 -0
- package/src/token.js +85 -0
- package/src/tunnel.js +67 -0
package/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# ccproxypal
|
|
2
|
+
|
|
3
|
+
Local proxy server that routes AI API requests through your Claude Code OAuth subscription.
|
|
4
|
+
|
|
5
|
+
Zero dependencies. Node.js 18+ only.
|
|
6
|
+
|
|
7
|
+
## Commands
|
|
8
|
+
|
|
9
|
+
### `token` — Print OAuth tokens
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx ccproxypal token
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Output:
|
|
16
|
+
```json
|
|
17
|
+
{ "accessToken": "sk-ant-oau01-...", "refreshToken": "..." }
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
### `serve` — Start the proxy server
|
|
23
|
+
|
|
24
|
+
**Host mode** (uses local Claude credentials):
|
|
25
|
+
```bash
|
|
26
|
+
npx ccproxypal serve
|
|
27
|
+
npx ccproxypal serve --tunnel # + Cloudflare public URL
|
|
28
|
+
npx ccproxypal serve --port 9000
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Client mode** (tokens provided externally — no local credentials needed):
|
|
32
|
+
```bash
|
|
33
|
+
npx ccproxypal serve \
|
|
34
|
+
--access-token sk-ant-oau01-... \
|
|
35
|
+
--refresh-token ...
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
With tunnel:
|
|
39
|
+
```bash
|
|
40
|
+
npx ccproxypal serve \
|
|
41
|
+
--access-token sk-ant-... \
|
|
42
|
+
--refresh-token ... \
|
|
43
|
+
--tunnel
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
### `configure` — Configure tools to use the proxy
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# List available tools
|
|
52
|
+
npx ccproxypal configure
|
|
53
|
+
|
|
54
|
+
# Configure (writes ANTHROPIC_BASE_URL to tool's config file)
|
|
55
|
+
npx ccproxypal configure claude-code
|
|
56
|
+
npx ccproxypal configure opencode
|
|
57
|
+
|
|
58
|
+
# Custom URL (e.g. tunnel)
|
|
59
|
+
npx ccproxypal configure claude-code --url https://xxxx.trycloudflare.com
|
|
60
|
+
|
|
61
|
+
# Custom port
|
|
62
|
+
npx ccproxypal configure claude-code --port 9000
|
|
63
|
+
|
|
64
|
+
# Remove config
|
|
65
|
+
npx ccproxypal configure remove claude-code
|
|
66
|
+
npx ccproxypal configure remove opencode
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Supported tools:**
|
|
70
|
+
|
|
71
|
+
| Tool | Config file |
|
|
72
|
+
|------|-------------|
|
|
73
|
+
| `claude-code` | `~/.claude/settings.json` |
|
|
74
|
+
| `opencode` | `~/.config/opencode/config.json` |
|
|
75
|
+
|
|
76
|
+
For **Cursor**: copy the proxy URL and paste into *Settings → Models → API Base URL*.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Proxy Endpoints
|
|
81
|
+
|
|
82
|
+
| Method | Path | Description |
|
|
83
|
+
|--------|------|-------------|
|
|
84
|
+
| `POST` | `/v1/messages` | Anthropic Messages API |
|
|
85
|
+
| `POST` | `/v1/chat/completions` | OpenAI Chat Completions (auto-converted) |
|
|
86
|
+
| `GET` | `/v1/models` | Live model list from Anthropic |
|
|
87
|
+
| `GET` | `/health` | Health + token status |
|
|
88
|
+
|
|
89
|
+
## Prerequisites
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Host mode: authenticate with Claude CLI
|
|
93
|
+
claude auth login
|
|
94
|
+
|
|
95
|
+
# Tunnel support
|
|
96
|
+
brew install cloudflared
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Publish
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
cd npm-pkg
|
|
103
|
+
npm login
|
|
104
|
+
npm publish
|
|
105
|
+
```
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { getToken, setManualToken } from '../src/token.js';
|
|
4
|
+
import { createServer } from '../src/server.js';
|
|
5
|
+
import { startTunnel, isCloudflaredAvailable } from '../src/tunnel.js';
|
|
6
|
+
import { configureTool, removeToolConfig, listTools } from '../src/configure.js';
|
|
7
|
+
|
|
8
|
+
// ─── Argument parsing ─────────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
function parseArgs(argv) {
|
|
11
|
+
const opts = {
|
|
12
|
+
port: 8082,
|
|
13
|
+
tunnel: false,
|
|
14
|
+
accessToken: null,
|
|
15
|
+
refreshToken: null,
|
|
16
|
+
};
|
|
17
|
+
for (let i = 0; i < argv.length; i++) {
|
|
18
|
+
const a = argv[i];
|
|
19
|
+
if ((a === '--port' || a === '-p') && argv[i + 1]) opts.port = parseInt(argv[++i], 10);
|
|
20
|
+
else if (a === '--tunnel' || a === '-t') opts.tunnel = true;
|
|
21
|
+
else if ((a === '--access-token' || a === '-a') && argv[i + 1]) opts.accessToken = argv[++i];
|
|
22
|
+
else if ((a === '--refresh-token' || a === '-r') && argv[i + 1]) opts.refreshToken = argv[++i];
|
|
23
|
+
}
|
|
24
|
+
return opts;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ─── Commands ─────────────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
async function cmdToken() {
|
|
30
|
+
const token = await getToken();
|
|
31
|
+
process.stdout.write(JSON.stringify(token, null, 2) + '\n');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function cmdServe(argv) {
|
|
35
|
+
const { port, tunnel, accessToken, refreshToken } = parseArgs(argv);
|
|
36
|
+
|
|
37
|
+
// Client mode: inject tokens manually
|
|
38
|
+
if (accessToken || refreshToken) {
|
|
39
|
+
if (!accessToken || !refreshToken) {
|
|
40
|
+
process.stderr.write('Error: --access-token and --refresh-token must both be provided.\n');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
setManualToken(accessToken, refreshToken);
|
|
44
|
+
process.stdout.write('Using provided tokens (client mode).\n');
|
|
45
|
+
} else {
|
|
46
|
+
// Host mode: validate local credentials before starting
|
|
47
|
+
try {
|
|
48
|
+
await getToken();
|
|
49
|
+
} catch (err) {
|
|
50
|
+
process.stderr.write(`Error: ${err.message}\n`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (tunnel && !isCloudflaredAvailable()) {
|
|
56
|
+
process.stderr.write('Error: cloudflared not found.\nInstall with: brew install cloudflared\n');
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const server = createServer(port);
|
|
61
|
+
|
|
62
|
+
if (tunnel) {
|
|
63
|
+
startTunnel(port, {
|
|
64
|
+
onError: (err) => process.stderr.write(`Tunnel: ${err.message}\n`),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function shutdown() {
|
|
69
|
+
process.stdout.write('\nShutting down...\n');
|
|
70
|
+
server.close(() => process.exit(0));
|
|
71
|
+
setTimeout(() => process.exit(0), 3000);
|
|
72
|
+
}
|
|
73
|
+
process.on('SIGINT', shutdown);
|
|
74
|
+
process.on('SIGTERM', shutdown);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function cmdConfigure(argv) {
|
|
78
|
+
const [sub, toolId] = argv;
|
|
79
|
+
|
|
80
|
+
if (!sub) {
|
|
81
|
+
// List tools
|
|
82
|
+
process.stdout.write('Available tools:\n');
|
|
83
|
+
for (const t of listTools()) {
|
|
84
|
+
process.stdout.write(` ${t.id.padEnd(14)} ${t.name} → ${t.path}\n`);
|
|
85
|
+
}
|
|
86
|
+
process.stdout.write('\nUsage:\n');
|
|
87
|
+
process.stdout.write(' ccproxypal configure <tool> [--url <proxy_url>]\n');
|
|
88
|
+
process.stdout.write(' ccproxypal configure remove <tool>\n');
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (sub === 'remove') {
|
|
93
|
+
if (!toolId) {
|
|
94
|
+
process.stderr.write('Usage: ccproxypal configure remove <tool>\n');
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
await removeToolConfig(toolId);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// sub is the tool id; look for --url flag
|
|
102
|
+
let proxyUrl = 'http://localhost:8082';
|
|
103
|
+
const urlIdx = argv.indexOf('--url');
|
|
104
|
+
if (urlIdx !== -1 && argv[urlIdx + 1]) proxyUrl = argv[urlIdx + 1];
|
|
105
|
+
|
|
106
|
+
const portIdx = argv.indexOf('--port');
|
|
107
|
+
if (portIdx !== -1 && argv[portIdx + 1]) {
|
|
108
|
+
proxyUrl = `http://localhost:${argv[portIdx + 1]}`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
await configureTool(sub, proxyUrl);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function cmdHelp() {
|
|
115
|
+
process.stdout.write(
|
|
116
|
+
[
|
|
117
|
+
'Usage: ccproxypal <command> [options]',
|
|
118
|
+
'',
|
|
119
|
+
'Commands:',
|
|
120
|
+
' token Print Claude OAuth tokens as JSON',
|
|
121
|
+
'',
|
|
122
|
+
' serve Start proxy (uses local Claude credentials)',
|
|
123
|
+
' serve --tunnel Start proxy + Cloudflare tunnel',
|
|
124
|
+
' serve --port <port> Custom port (default: 8082)',
|
|
125
|
+
' serve --access-token <token> Client mode: use provided tokens',
|
|
126
|
+
' --refresh-token <token>',
|
|
127
|
+
'',
|
|
128
|
+
' configure List configurable tools',
|
|
129
|
+
' configure <tool> [--url <url>] Write proxy URL to tool config',
|
|
130
|
+
' configure <tool> --port <port> Write http://localhost:<port> to tool config',
|
|
131
|
+
' configure remove <tool> Remove proxy config from tool',
|
|
132
|
+
'',
|
|
133
|
+
'Tools:',
|
|
134
|
+
' claude-code ~/.claude/settings.json',
|
|
135
|
+
' opencode ~/.config/opencode/config.json',
|
|
136
|
+
'',
|
|
137
|
+
'Examples:',
|
|
138
|
+
' npx ccproxypal token',
|
|
139
|
+
' npx ccproxypal serve --tunnel',
|
|
140
|
+
' npx ccproxypal serve --access-token sk-ant-... --refresh-token ...',
|
|
141
|
+
' npx ccproxypal configure claude-code',
|
|
142
|
+
' npx ccproxypal configure claude-code --url https://xxxx.trycloudflare.com',
|
|
143
|
+
' npx ccproxypal configure remove claude-code',
|
|
144
|
+
'',
|
|
145
|
+
].join('\n')
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ─── Dispatch ─────────────────────────────────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
const [, , cmd, ...rest] = process.argv;
|
|
152
|
+
|
|
153
|
+
const dispatch = {
|
|
154
|
+
token: () => cmdToken().catch(bail),
|
|
155
|
+
serve: () => cmdServe(rest).catch(bail),
|
|
156
|
+
configure: () => cmdConfigure(rest).catch(bail),
|
|
157
|
+
help: () => cmdHelp(),
|
|
158
|
+
'--help': () => cmdHelp(),
|
|
159
|
+
'-h': () => cmdHelp(),
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
function bail(err) {
|
|
163
|
+
process.stderr.write(`Error: ${err.message}\n`);
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (cmd && dispatch[cmd]) {
|
|
168
|
+
dispatch[cmd]();
|
|
169
|
+
} else {
|
|
170
|
+
cmdHelp();
|
|
171
|
+
process.exit(cmd ? 1 : 0);
|
|
172
|
+
}
|