copilot-cursor-proxy 1.0.0 → 1.0.2
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 +134 -151
- package/auth-config.ts +61 -0
- package/dashboard.html +814 -270
- package/package.json +2 -2
- package/proxy-router.ts +213 -4
- package/responses-converters.ts +3 -3
- package/stream-proxy.ts +32 -1
- package/usage-db.ts +210 -0
package/README.md
CHANGED
|
@@ -1,226 +1,209 @@
|
|
|
1
1
|
# 🚀 Copilot Proxy for Cursor
|
|
2
2
|
|
|
3
|
-
> Forked from [jacksonkasi1/copilot-for-cursor](https://github.com/jacksonkasi1/copilot-for-cursor) with
|
|
3
|
+
> Forked from [jacksonkasi1/copilot-for-cursor](https://github.com/jacksonkasi1/copilot-for-cursor) with full Anthropic → OpenAI conversion + Responses API bridge.
|
|
4
4
|
|
|
5
5
|
**Unlock the full power of GitHub Copilot in Cursor IDE.**
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
1. **Bypassing Cursor's Model Routing:** Using a custom prefix (`cus-`) to force Cursor to use your own API endpoint instead of its internal backend.
|
|
9
|
-
2. **Enabling Agentic Capabilities:** Transforming Cursor's Anthropic-style tool calls into OpenAI-compatible formats that Copilot understands. This enables **File Editing, Terminal Execution, Codebase Search, and MCP Tools**.
|
|
10
|
-
3. **Fixing Schema Errors:** Automatically sanitizing requests to prevent `400 Bad Request` errors caused by format mismatches (e.g., `tool_choice`, `cache_control`, unsupported content types).
|
|
11
|
-
|
|
12
|
-
### Changes in this fork
|
|
13
|
-
|
|
14
|
-
- **Full Anthropic → OpenAI message conversion:** Assistant `tool_use` blocks are converted to OpenAI `tool_calls`; user `tool_result` blocks become `tool` role messages. This fixes `unexpected tool_use_id found in tool_result blocks` errors.
|
|
15
|
-
- **Unsupported content type stripping:** Blocks with types like `thinking`, `tool_use` (in user messages), etc. are filtered out before forwarding, preventing `type has to be either 'image_url' or 'text'` errors.
|
|
16
|
-
- **Windows setup instructions** added below.
|
|
7
|
+
Use **all** Copilot models (GPT-5.4, Claude Opus 4.6, Gemini 3.1, etc.) in Cursor — including Plan mode, Agent mode, and tool calls.
|
|
17
8
|
|
|
18
9
|
---
|
|
19
10
|
|
|
20
|
-
##
|
|
21
|
-
|
|
22
|
-
```
|
|
23
|
-
Cursor → (HTTPS tunnel) → proxy-router (:4142) → copilot-api (:4141) → GitHub Copilot
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
* **Port 4141 (`copilot-api`):** The core service that authenticates with GitHub and provides the OpenAI-compatible API.
|
|
27
|
-
* *Powered by [caozhiyuan/copilot-api](https://github.com/caozhiyuan/copilot-api) (installed via `npx`).*
|
|
28
|
-
* **Port 4142 (`proxy-router`):** The intelligence layer. It intercepts requests, converts Anthropic-format messages to OpenAI format, handles the `cus-` prefix, and serves the dashboard.
|
|
29
|
-
* **HTTPS tunnel (Cloudflare/ngrok):** Cursor requires HTTPS — a tunnel exposes the local proxy to the internet.
|
|
30
|
-
|
|
31
|
-
### Proxy Router Modules
|
|
32
|
-
|
|
33
|
-
The proxy router is split into focused modules:
|
|
34
|
-
|
|
35
|
-
| File | Responsibility |
|
|
36
|
-
|---|---|
|
|
37
|
-
| `proxy-router.ts` | Entrypoint — Bun.serve, routing, CORS, dashboard, model list |
|
|
38
|
-
| `anthropic-transforms.ts` | Anthropic → OpenAI normalization (fields, tools, messages) |
|
|
39
|
-
| `responses-bridge.ts` | Chat Completions → Responses API bridge for GPT-5.x / o-series |
|
|
40
|
-
| `responses-converters.ts` | Responses API → Chat Completions format (sync & streaming) |
|
|
41
|
-
| `stream-proxy.ts` | Streaming passthrough with chunk logging and error detection |
|
|
42
|
-
| `debug-logger.ts` | Request/response debug logging helpers |
|
|
43
|
-
|
|
44
|
-
---
|
|
11
|
+
## ⚡ Quick Start
|
|
45
12
|
|
|
46
|
-
|
|
13
|
+
### One Command (npm)
|
|
47
14
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
* A tunnel tool — **Cloudflare Tunnel** (free, no signup) or **ngrok**
|
|
52
|
-
* GitHub account with a **Copilot subscription** (individual, business, or enterprise)
|
|
15
|
+
```bash
|
|
16
|
+
npx copilot-cursor-proxy
|
|
17
|
+
```
|
|
53
18
|
|
|
54
|
-
|
|
19
|
+
> Requires [Bun](https://bun.sh/) installed. First run will prompt GitHub authentication.
|
|
55
20
|
|
|
56
|
-
|
|
21
|
+
This starts both `copilot-api` (port 4141) and the proxy (port 4142) in a single terminal.
|
|
57
22
|
|
|
58
|
-
|
|
59
|
-
```sh
|
|
60
|
-
npx @jeffreycao/copilot-api@latest start
|
|
61
|
-
```
|
|
62
|
-
> On first run, it will prompt you to authenticate via GitHub device flow.
|
|
23
|
+
### Or from source
|
|
63
24
|
|
|
64
|
-
|
|
65
|
-
|
|
25
|
+
```bash
|
|
26
|
+
git clone https://github.com/CharlesYWL/copilot-for-cursor.git
|
|
66
27
|
cd copilot-for-cursor
|
|
67
|
-
bun run
|
|
28
|
+
bun run start.ts
|
|
68
29
|
```
|
|
69
30
|
|
|
70
|
-
|
|
31
|
+
### Then start an HTTPS tunnel
|
|
71
32
|
|
|
72
|
-
|
|
73
|
-
```sh
|
|
74
|
-
# Install (one-time)
|
|
75
|
-
winget install cloudflare.cloudflared
|
|
33
|
+
Cursor requires HTTPS. In a second terminal:
|
|
76
34
|
|
|
77
|
-
|
|
35
|
+
```bash
|
|
36
|
+
# Cloudflare (free, no signup)
|
|
78
37
|
cloudflared tunnel --url http://localhost:4142
|
|
79
|
-
```
|
|
80
38
|
|
|
81
|
-
Or
|
|
82
|
-
```sh
|
|
39
|
+
# Or ngrok
|
|
83
40
|
ngrok http 4142
|
|
84
41
|
```
|
|
85
42
|
|
|
86
|
-
Copy the HTTPS URL
|
|
43
|
+
Copy the HTTPS URL (e.g., `https://xxxxx.trycloudflare.com`).
|
|
87
44
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
Run the setup scripts for persistent background services:
|
|
91
|
-
```bash
|
|
92
|
-
# 1. Setup Core API (Port 4141)
|
|
93
|
-
chmod +x setup-copilot-service.sh
|
|
94
|
-
./setup-copilot-service.sh
|
|
45
|
+
---
|
|
95
46
|
|
|
96
|
-
|
|
97
|
-
chmod +x setup-proxy-service.sh
|
|
98
|
-
./setup-proxy-service.sh
|
|
47
|
+
## 🏗 Architecture
|
|
99
48
|
|
|
100
|
-
|
|
101
|
-
|
|
49
|
+
```
|
|
50
|
+
Cursor → (HTTPS tunnel) → proxy-router (:4142) → copilot-api (:4141) → GitHub Copilot
|
|
102
51
|
```
|
|
103
52
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
53
|
+
* **Port 4141 (`copilot-api`):** Authenticates with GitHub and provides the OpenAI-compatible API.
|
|
54
|
+
* *Powered by [copilot-api](https://www.npmjs.com/package/copilot-api) (installed via `npx`).*
|
|
55
|
+
* **Port 4142 (`proxy-router`):** Converts Anthropic-format messages to OpenAI format, bridges Responses API for GPT-5.x models, handles the `cus-` prefix, and serves the dashboard.
|
|
56
|
+
* **HTTPS tunnel:** Cursor requires HTTPS — a tunnel exposes the local proxy.
|
|
107
57
|
|
|
108
|
-
|
|
58
|
+
### Proxy Router Modules
|
|
59
|
+
|
|
60
|
+
| File | Responsibility |
|
|
61
|
+
|---|---|
|
|
62
|
+
| `proxy-router.ts` | Entrypoint — Bun.serve, routing, CORS, dashboard, model list |
|
|
63
|
+
| `anthropic-transforms.ts` | Anthropic → OpenAI normalization (fields, tools, messages) |
|
|
64
|
+
| `responses-bridge.ts` | Chat Completions → Responses API bridge for GPT-5.x / goldeneye |
|
|
65
|
+
| `responses-converters.ts` | Responses API → Chat Completions format (sync & streaming SSE) |
|
|
66
|
+
| `stream-proxy.ts` | Streaming passthrough with chunk logging and error detection |
|
|
67
|
+
| `debug-logger.ts` | Request/response debug logging helpers |
|
|
68
|
+
| `start.ts` | One-command launcher for copilot-api + proxy-router |
|
|
109
69
|
|
|
110
70
|
---
|
|
111
71
|
|
|
112
72
|
## ⚙️ Cursor Configuration
|
|
113
73
|
|
|
114
74
|
1. Go to **Settings** (Gear Icon) → **Models**.
|
|
115
|
-
2.
|
|
116
|
-
3. Add a new **OpenAI Compatible** model:
|
|
75
|
+
2. Add a new **OpenAI Compatible** model:
|
|
117
76
|
* **Base URL:** `https://your-tunnel-url.trycloudflare.com/v1`
|
|
118
|
-
* **API Key:** `dummy` (any value works
|
|
119
|
-
* **Model Name:** Use a **prefixed name** — e.g., `cus-gpt-
|
|
77
|
+
* **API Key:** `dummy` (any value works)
|
|
78
|
+
* **Model Name:** Use a **prefixed name** — e.g., `cus-gpt-5.4`, `cus-claude-opus-4.6`
|
|
79
|
+
|
|
80
|
+
> **⚠️ Important:** You **must** use the `cus-` prefix. Without it, Cursor routes the request to its own backend.
|
|
81
|
+
|
|
82
|
+
> **💡 Tip:** Visit the [Dashboard](http://localhost:4142) to see all available models and copy their IDs.
|
|
83
|
+
|
|
84
|
+
### Tested Models (15/21 passing)
|
|
85
|
+
|
|
86
|
+
| Cursor Model Name | Actual Model | Status |
|
|
87
|
+
|---|---|---|
|
|
88
|
+
| `cus-gpt-4o` | GPT-4o | ✅ |
|
|
89
|
+
| `cus-gpt-4.1` | GPT-4.1 | ✅ |
|
|
90
|
+
| `cus-gpt-5-mini` | GPT-5 Mini | ✅ |
|
|
91
|
+
| `cus-gpt-5.1` | GPT-5.1 | ✅ |
|
|
92
|
+
| `cus-gpt-5.2` | GPT-5.2 | ⚠️ See note |
|
|
93
|
+
| `cus-gpt-5.2-codex` | GPT-5.2 Codex | ⚠️ See note |
|
|
94
|
+
| `cus-gpt-5.3-codex` | GPT-5.3 Codex | ⚠️ See note |
|
|
95
|
+
| `cus-gpt-5.4` | GPT-5.4 | ⚠️ See note |
|
|
96
|
+
| `cus-gpt-5.4-mini` | GPT-5.4 Mini | ⚠️ See note |
|
|
97
|
+
| `cus-goldeneye` | Goldeneye | ⚠️ See note |
|
|
98
|
+
| `cus-claude-haiku-4.5` | Claude Haiku 4.5 | ✅ |
|
|
99
|
+
| `cus-claude-sonnet-4` | Claude Sonnet 4 | ✅ |
|
|
100
|
+
| `cus-claude-sonnet-4.5` | Claude Sonnet 4.5 | ✅ |
|
|
101
|
+
| `cus-claude-sonnet-4.6` | Claude Sonnet 4.6 | ✅ |
|
|
102
|
+
| `cus-claude-opus-4.5` | Claude Opus 4.5 | ✅ |
|
|
103
|
+
| `cus-claude-opus-4.6` | Claude Opus 4.6 | ✅ |
|
|
104
|
+
| `cus-claude-opus-4.6-1m` | Claude Opus 4.6 (1M) | ✅ |
|
|
105
|
+
| `cus-gemini-2.5-pro` | Gemini 2.5 Pro | ✅ |
|
|
106
|
+
| `cus-gemini-3-flash-preview` | Gemini 3 Flash | ✅ |
|
|
107
|
+
| `cus-gemini-3.1-pro-preview` | Gemini 3.1 Pro | ✅ |
|
|
108
|
+
|
|
109
|
+
> **⚠️ GPT-5.2+, GPT-5.x-codex, and goldeneye** are currently broken. These models require the `/v1/responses` API or `max_completion_tokens` instead of `max_tokens`, but `copilot-api` injects `max_tokens` into all requests. The proxy has a Responses API bridge built in, but `copilot-api` no longer exposes the `/v1/responses` endpoint. This will be resolved when `copilot-api` is updated. **All Claude, Gemini, GPT-4.x, GPT-5-mini, and GPT-5.1 models work fine.**
|
|
120
110
|
|
|
121
|
-
|
|
111
|
+

|
|
122
112
|
|
|
123
|
-
|
|
113
|
+
---
|
|
124
114
|
|
|
125
|
-
|
|
115
|
+
## ✨ Features
|
|
126
116
|
|
|
127
|
-
|
|
128
|
-
|---|---|
|
|
129
|
-
| `cus-gpt-4o` | GPT-4o |
|
|
130
|
-
| `cus-gpt-5.4` | GPT-5.4 |
|
|
131
|
-
| `cus-claude-sonnet-4` | Claude Sonnet 4 |
|
|
132
|
-
| `cus-claude-sonnet-4.5` | Claude Sonnet 4.5 |
|
|
133
|
-
| `cus-claude-opus-4.6` | Claude Opus 4.6 |
|
|
134
|
-
| `cus-gemini-2.5-pro` | Gemini 2.5 Pro |
|
|
117
|
+
### What the proxy handles
|
|
135
118
|
|
|
136
|
-
|
|
119
|
+
| Cursor sends (Anthropic format) | Proxy converts to (OpenAI format) |
|
|
120
|
+
|---|---|
|
|
121
|
+
| `system` as top-level field | System message |
|
|
122
|
+
| `tool_use` blocks in assistant messages | `tool_calls` array |
|
|
123
|
+
| `tool_result` blocks in user messages | `tool` role messages |
|
|
124
|
+
| `input_schema` on tools | `parameters` (cleaned) |
|
|
125
|
+
| `tool_choice` objects (`auto`/`any`/`tool`) | OpenAI format (`auto`/`required`/function) |
|
|
126
|
+
| `stop_sequences` | `stop` |
|
|
127
|
+
| `thinking` / `cache_control` blocks | Stripped |
|
|
128
|
+
| `metadata` / `anthropic_version` | Stripped |
|
|
129
|
+
| Images in Claude requests | `[Image Omitted]` placeholder |
|
|
130
|
+
| GPT-5.x `max_tokens` | Converted to `max_completion_tokens` |
|
|
131
|
+
| GPT-5.x Responses API | **Bridge built in** (needs `copilot-api` support) |
|
|
132
|
+
|
|
133
|
+
### Supported Workflows
|
|
134
|
+
|
|
135
|
+
* **💬 Chat & Reasoning:** Full conversation context with all models
|
|
136
|
+
* **📋 Plan Mode:** Works with tool calls and multi-turn conversations
|
|
137
|
+
* **🤖 Agent Mode:** File editing, terminal, search, MCP tools
|
|
138
|
+
* **📂 File System:** `Read`, `Write`, `StrReplace`, `Delete`
|
|
139
|
+
* **💻 Terminal:** `Shell` (run commands)
|
|
140
|
+
* **🔍 Search:** `Grep`, `Glob`, `SemanticSearch`
|
|
141
|
+
* **🔌 MCP Tools:** External tools (Neon, Playwright, etc.)
|
|
137
142
|
|
|
138
143
|
---
|
|
139
144
|
|
|
140
|
-
## 🔒 Security
|
|
145
|
+
## 🔒 Security
|
|
141
146
|
|
|
142
|
-
|
|
147
|
+
### Dashboard Password
|
|
143
148
|
|
|
144
|
-
|
|
145
|
-
- Linux/macOS: `~/.local/share/copilot-api/config.json`
|
|
146
|
-
- Windows: `%USERPROFILE%\.local\share\copilot-api\config.json`
|
|
149
|
+
The dashboard is password-protected. On first visit, set a password to prevent unauthorized access.
|
|
147
150
|
|
|
148
|
-
|
|
149
|
-
{
|
|
150
|
-
"auth": {
|
|
151
|
-
"apiKeys": ["your-secret-key-here"]
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
```
|
|
151
|
+
### API Key Management
|
|
155
152
|
|
|
156
|
-
|
|
153
|
+
Manage API keys directly from the **Endpoint** tab in the dashboard:
|
|
157
154
|
|
|
158
|
-
|
|
155
|
+
1. Toggle **"Require API Key"** to enable authentication
|
|
156
|
+
2. Click **"+ Create Key"** to generate a new `cpk-xxx` key
|
|
157
|
+
3. Copy the key (shown only once!) and paste it into Cursor's **API Key** field
|
|
158
|
+
4. Enable/disable or delete keys as needed
|
|
159
159
|
|
|
160
|
-
|
|
160
|
+
When enabled, all `/v1/*` requests must include `Authorization: Bearer <your-key>`.
|
|
161
161
|
|
|
162
|
-
|
|
162
|
+

|
|
163
163
|
|
|
164
|
-
|
|
165
|
-
* **📂 File System:** `Read`, `Write`, `StrReplace`, `Delete`.
|
|
166
|
-
* **💻 Terminal:** `Shell` (Run commands).
|
|
167
|
-
* **🔍 Search:** `Grep`, `Glob`, `SemanticSearch`.
|
|
168
|
-
* **🔌 MCP Tools:** Full support for external tools like Neon, Playwright, etc.
|
|
164
|
+
---
|
|
169
165
|
|
|
170
|
-
|
|
166
|
+
## 📊 Dashboard
|
|
171
167
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
| `input_schema` on tools | Converted to `parameters` |
|
|
179
|
-
| Anthropic `tool_choice` objects | OpenAI string format |
|
|
180
|
-
| Images in Claude requests | Stripped with `[Image Omitted]` placeholder |
|
|
168
|
+
Access the dashboard at **[http://localhost:4142](http://localhost:4142)**
|
|
169
|
+
|
|
170
|
+
Three tabs:
|
|
171
|
+
- **Endpoint** — Proxy URL, API key management, model list
|
|
172
|
+
- **Usage** — Request stats, token counts, per-model breakdown, recent requests
|
|
173
|
+
- **Console Log** — Real-time proxy logs with color-coded levels
|
|
181
174
|
|
|
182
175
|
---
|
|
183
176
|
|
|
184
177
|
## ⚠️ Known Limitations
|
|
185
178
|
|
|
186
|
-
### Claude Vision
|
|
187
|
-
* **Gemini / GPT-4o:** Full Vision Support ✅
|
|
188
|
-
* **Claude (via Copilot):** Does **NOT** support images via the API proxy ❌
|
|
189
|
-
|
|
190
|
-
The proxy automatically strips images from Claude requests to prevent crashes.
|
|
191
|
-
|
|
192
|
-
### What's lost vs native Anthropic API
|
|
193
|
-
Since Cursor sends to `/v1/chat/completions` (OpenAI) instead of `/v1/messages` (Anthropic), some features are unavailable:
|
|
194
|
-
|
|
195
179
|
| Feature | Status |
|
|
196
180
|
|---|---|
|
|
197
|
-
| Extended thinking (chain-of-thought) | ❌ Stripped |
|
|
198
|
-
| Prompt caching (`cache_control`) | ❌ Stripped |
|
|
199
|
-
| Context management beta | ❌ Not available |
|
|
200
|
-
| Premium request optimization | ❌ Bypassed |
|
|
201
181
|
| Basic chat & tool calling | ✅ Works |
|
|
202
182
|
| Streaming | ✅ Works |
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
183
|
+
| Plan mode | ✅ Works |
|
|
184
|
+
| Agent mode | ✅ Works |
|
|
185
|
+
| GPT-5.x models | ⚠️ Blocked by copilot-api `max_tokens` bug |
|
|
186
|
+
| Extended thinking (chain-of-thought) | ❌ Stripped |
|
|
187
|
+
| Prompt caching (`cache_control`) | ❌ Stripped |
|
|
188
|
+
| Claude Vision | ❌ Not supported via Copilot |
|
|
189
|
+
| Tunnel URL changes on restart | ⚠️ Use paid plan for fixed subdomain |
|
|
206
190
|
|
|
207
191
|
---
|
|
208
192
|
|
|
209
|
-
|
|
193
|
+
## 📝 Troubleshooting
|
|
210
194
|
|
|
211
195
|
**"Model name is not valid" in Cursor:**
|
|
212
|
-
Make sure you're using the `cus-` prefix (e.g., `cus-gpt-
|
|
196
|
+
Make sure you're using the `cus-` prefix (e.g., `cus-gpt-5.4`, not `gpt-5.4`).
|
|
213
197
|
|
|
214
|
-
**
|
|
215
|
-
Ensure
|
|
198
|
+
**Plan mode response cuts off:**
|
|
199
|
+
Ensure `idleTimeout: 255` is set in `proxy-router.ts` (already configured). Slow models like Opus need longer timeouts.
|
|
216
200
|
|
|
217
|
-
**
|
|
218
|
-
|
|
201
|
+
**GPT-5.x returns "use /v1/responses":**
|
|
202
|
+
The proxy auto-routes these. Make sure you're running the latest version.
|
|
219
203
|
|
|
220
|
-
**
|
|
221
|
-
|
|
222
|
-
* API: `tail -f ~/Library/Logs/copilot-api.log`
|
|
204
|
+
**"connection refused":**
|
|
205
|
+
Ensure services are running: `bun run start.ts` or check `http://localhost:4142`.
|
|
223
206
|
|
|
224
207
|
---
|
|
225
208
|
|
|
226
|
-
> ⚠️ **DISCLAIMER:** This project is **unofficial** and
|
|
209
|
+
> ⚠️ **DISCLAIMER:** This project is **unofficial** and for **educational purposes only**. It interacts with undocumented internal APIs of GitHub Copilot and Cursor. Use at your own risk. The authors are not affiliated with GitHub, Microsoft, or Anysphere (Cursor). Please use your API credits responsibly and in accordance with the provider's Terms of Service.
|
package/auth-config.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
|
|
5
|
+
export interface ApiKey {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
key: string;
|
|
9
|
+
active: boolean;
|
|
10
|
+
createdAt: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface AuthConfig {
|
|
14
|
+
requireApiKey: boolean;
|
|
15
|
+
keys: ApiKey[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const CONFIG_DIR = join(homedir(), '.copilot-proxy');
|
|
19
|
+
const CONFIG_PATH = join(CONFIG_DIR, 'auth.json');
|
|
20
|
+
|
|
21
|
+
const DEFAULT_CONFIG: AuthConfig = { requireApiKey: false, keys: [] };
|
|
22
|
+
|
|
23
|
+
export function loadAuthConfig(): AuthConfig {
|
|
24
|
+
try {
|
|
25
|
+
if (!existsSync(CONFIG_PATH)) return { ...DEFAULT_CONFIG, keys: [] };
|
|
26
|
+
const raw = readFileSync(CONFIG_PATH, 'utf-8');
|
|
27
|
+
const parsed = JSON.parse(raw);
|
|
28
|
+
return {
|
|
29
|
+
requireApiKey: !!parsed.requireApiKey,
|
|
30
|
+
keys: Array.isArray(parsed.keys) ? parsed.keys : [],
|
|
31
|
+
};
|
|
32
|
+
} catch {
|
|
33
|
+
return { ...DEFAULT_CONFIG, keys: [] };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function saveAuthConfig(config: AuthConfig): void {
|
|
38
|
+
if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
|
|
39
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), 'utf-8');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function randomHex(bytes: number): string {
|
|
43
|
+
const arr = new Uint8Array(bytes);
|
|
44
|
+
crypto.getRandomValues(arr);
|
|
45
|
+
return Array.from(arr, b => b.toString(16).padStart(2, '0')).join('');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function generateApiKey(name: string): ApiKey {
|
|
49
|
+
return {
|
|
50
|
+
id: randomHex(4), // 8-char hex
|
|
51
|
+
name,
|
|
52
|
+
key: 'cpk-' + randomHex(16), // cpk- + 32 hex chars
|
|
53
|
+
active: true,
|
|
54
|
+
createdAt: Date.now(),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function validateApiKey(key: string): boolean {
|
|
59
|
+
const config = loadAuthConfig();
|
|
60
|
+
return config.keys.some(k => k.key === key && k.active);
|
|
61
|
+
}
|