open-claude-code-proxy 1.0.0 → 1.1.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 CHANGED
@@ -80,6 +80,43 @@ cd open-claude-code-proxy
80
80
  source ~/.zshrc
81
81
  ```
82
82
 
83
+ ### Port Configuration
84
+
85
+ The proxy supports flexible port configuration:
86
+
87
+ ```bash
88
+ # Use command line argument
89
+ claude-local-proxy --port 8080
90
+ claude-local-proxy -p 8080
91
+
92
+ # Or use environment variable
93
+ PORT=8080 claude-local-proxy
94
+
95
+ # Or let it use saved config (~/.claude-proxy/config.json)
96
+ claude-local-proxy
97
+ ```
98
+
99
+ **Port Priority**: CLI argument > Config file > Environment variable > Default (12346)
100
+
101
+ On first run, you'll be prompted to customize the port. Your choice is saved for future use.
102
+
103
+ ### OpenCode Auto-Configuration
104
+
105
+ The proxy can automatically configure [OpenCode](https://opencode.ai) for you:
106
+
107
+ - Detects OpenCode config at `~/.config/opencode/opencode.json`
108
+ - Updates `provider.anthropic.options.baseURL` to match your proxy port
109
+ - Creates automatic backups before modifying (keeps last 3)
110
+ - Prompts before making changes (use `--skip-opencode` to skip)
111
+
112
+ ```bash
113
+ # Auto-configure OpenCode
114
+ claude-local-proxy -p 8080
115
+
116
+ # Skip OpenCode configuration
117
+ claude-local-proxy -p 8080 --skip-opencode
118
+ ```
119
+
83
120
  ### Configure Your Client
84
121
 
85
122
  Point your app to the local proxy:
@@ -93,6 +130,18 @@ Point your app to the local proxy:
93
130
 
94
131
  > **Note**: The API key can be any string - authentication is handled by your Claude Code session.
95
132
 
133
+ ### CLI Options
134
+
135
+ ```
136
+ Usage: claude-local-proxy [options]
137
+
138
+ Options:
139
+ -p, --port <port> Server port (1024-65535)
140
+ --skip-opencode Skip OpenCode auto-configuration
141
+ -h, --help Show help
142
+ -v, --version Show version
143
+ ```
144
+
96
145
  ### Commands
97
146
 
98
147
  | Command | Description |
@@ -174,6 +223,43 @@ cd open-claude-code-proxy
174
223
  source ~/.zshrc
175
224
  ```
176
225
 
226
+ ### 端口配置
227
+
228
+ 代理支持灵活的端口配置方式:
229
+
230
+ ```bash
231
+ # 使用命令行参数
232
+ claude-local-proxy --port 8080
233
+ claude-local-proxy -p 8080
234
+
235
+ # 或使用环境变量
236
+ PORT=8080 claude-local-proxy
237
+
238
+ # 或使用已保存的配置 (~/.claude-proxy/config.json)
239
+ claude-local-proxy
240
+ ```
241
+
242
+ **端口优先级**:命令行参数 > 配置文件 > 环境变量 > 默认值 (12346)
243
+
244
+ 首次运行时会提示你自定义端口,你的选择会被保存供后续使用。
245
+
246
+ ### OpenCode 自动配置
247
+
248
+ 代理可以自动为 [OpenCode](https://opencode.ai) 配置连接:
249
+
250
+ - 自动检测 `~/.config/opencode/opencode.json` 配置文件
251
+ - 更新 `provider.anthropic.options.baseURL` 为代理端口
252
+ - 修改前自动备份(保留最近 3 个备份)
253
+ - 修改前会询问确认(使用 `--skip-opencode` 跳过)
254
+
255
+ ```bash
256
+ # 自动配置 OpenCode
257
+ claude-local-proxy -p 8080
258
+
259
+ # 跳过 OpenCode 配置
260
+ claude-local-proxy -p 8080 --skip-opencode
261
+ ```
262
+
177
263
  ### 配置客户端
178
264
 
179
265
  将你的应用指向本地代理:
@@ -187,6 +273,18 @@ cd open-claude-code-proxy
187
273
 
188
274
  > **注意**:API Key 可以是任意字符串,实际认证由 Claude Code 会话处理。
189
275
 
276
+ ### CLI 选项
277
+
278
+ ```
279
+ 用法: claude-local-proxy [选项]
280
+
281
+ 选项:
282
+ -p, --port <port> 服务器端口 (1024-65535)
283
+ --skip-opencode 跳过 OpenCode 自动配置
284
+ -h, --help 显示帮助
285
+ -v, --version 显示版本
286
+ ```
287
+
190
288
  ### 命令说明
191
289
 
192
290
  | 命令 | 说明 |
package/claude-proxy CHANGED
@@ -6,6 +6,21 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
6
6
  SERVER_JS="$SCRIPT_DIR/server.js"
7
7
  PID_FILE="$SCRIPT_DIR/.claude-proxy.pid"
8
8
  LOG_FILE="$SCRIPT_DIR/proxy.log"
9
+ CONFIG_FILE="$HOME/.claude-proxy/config.json"
10
+
11
+ # 从配置文件读取端口
12
+ get_port() {
13
+ if [ -f "$CONFIG_FILE" ]; then
14
+ PORT=$(cat "$CONFIG_FILE" | grep -o '"port":[[:space:]]*[0-9]*' | grep -o '[0-9]*')
15
+ if [ -n "$PORT" ]; then
16
+ echo "$PORT"
17
+ return
18
+ fi
19
+ fi
20
+ echo "${PORT:-12346}"
21
+ }
22
+
23
+ PROXY_PORT=$(get_port)
9
24
 
10
25
  # 颜色定义
11
26
  RED='\033[0;31m'
@@ -115,8 +130,8 @@ start() {
115
130
  return 1
116
131
  fi
117
132
 
118
- # 后台启动
119
- nohup node "$SERVER_JS" > "$LOG_FILE" 2>&1 &
133
+ # 后台启动(使用配置的端口)
134
+ PORT=$PROXY_PORT nohup node "$SERVER_JS" > "$LOG_FILE" 2>&1 &
120
135
  PID=$!
121
136
  echo $PID > "$PID_FILE"
122
137
 
@@ -125,7 +140,7 @@ start() {
125
140
 
126
141
  if is_running; then
127
142
  success "服务已启动 (PID: $PID)"
128
- success "监听地址: http://localhost:12346"
143
+ success "监听地址: http://localhost:$PROXY_PORT"
129
144
  echo ""
130
145
  info "查看日志: $0 logs"
131
146
  info "查看状态: $0 status"
@@ -184,7 +199,7 @@ status() {
184
199
  success "服务运行中"
185
200
  echo ""
186
201
  echo " PID: $PID"
187
- echo " 地址: http://localhost:12346"
202
+ echo " 地址: http://localhost:$PROXY_PORT"
188
203
  echo " 日志: $LOG_FILE"
189
204
  echo ""
190
205
 
@@ -240,7 +255,7 @@ Claude Local Proxy 控制脚本
240
255
  配置文件:
241
256
  客户端配置 (opencode/cursor):
242
257
  {
243
- "baseURL": "http://localhost:12346",
258
+ "baseURL": "http://localhost:$PROXY_PORT",
244
259
  "apiKey": "sk-any-key-works"
245
260
  }
246
261
 
package/cli.js ADDED
@@ -0,0 +1,307 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Claude Local Proxy CLI
5
+ * 交互式命令行入口
6
+ */
7
+
8
+ const readline = require('readline');
9
+ const config = require('./lib/config');
10
+ const portUtils = require('./lib/port-utils');
11
+ const opencodeConfig = require('./lib/opencode-config');
12
+
13
+ const VERSION = '1.0.0';
14
+
15
+ /**
16
+ * 解析命令行参数
17
+ * @returns {{port: number|null, help: boolean, version: boolean, skipOpencode: boolean}}
18
+ */
19
+ function parseArgs() {
20
+ const args = process.argv.slice(2);
21
+ const result = {
22
+ port: null,
23
+ help: false,
24
+ version: false,
25
+ skipOpencode: false
26
+ };
27
+
28
+ for (let i = 0; i < args.length; i++) {
29
+ const arg = args[i];
30
+
31
+ if (arg === '--help' || arg === '-h') {
32
+ result.help = true;
33
+ } else if (arg === '--version' || arg === '-v') {
34
+ result.version = true;
35
+ } else if (arg === '--skip-opencode') {
36
+ result.skipOpencode = true;
37
+ } else if (arg === '--port' || arg === '-p') {
38
+ const portValue = args[++i];
39
+ if (portValue) {
40
+ result.port = parseInt(portValue, 10);
41
+ }
42
+ } else if (arg.startsWith('--port=')) {
43
+ result.port = parseInt(arg.split('=')[1], 10);
44
+ } else if (arg.startsWith('-p=')) {
45
+ result.port = parseInt(arg.split('=')[1], 10);
46
+ }
47
+ }
48
+
49
+ return result;
50
+ }
51
+
52
+ /**
53
+ * 显示帮助信息
54
+ */
55
+ function showHelp() {
56
+ console.log(`
57
+ Claude Local Proxy - 本地代理服务器
58
+
59
+ 用法:
60
+ claude-local-proxy [选项]
61
+
62
+ 选项:
63
+ -p, --port <port> 指定服务器监听端口 (1024-65535)
64
+ --skip-opencode 跳过 OpenCode 配置自动更新
65
+ -h, --help 显示帮助信息
66
+ -v, --version 显示版本号
67
+
68
+ 端口优先级:
69
+ 1. 命令行参数 (--port)
70
+ 2. 配置文件 (~/.claude-proxy/config.json)
71
+ 3. 环境变量 (PORT)
72
+ 4. 默认值 (12346)
73
+
74
+ 配置文件位置:
75
+ ${config.CONFIG_FILE}
76
+
77
+ 示例:
78
+ claude-local-proxy # 使用默认端口或已保存的配置
79
+ claude-local-proxy -p 8080 # 使用端口 8080
80
+ claude-local-proxy --port=9000 # 使用端口 9000
81
+ claude-local-proxy --skip-opencode # 跳过 OpenCode 配置更新
82
+ `);
83
+ }
84
+
85
+ /**
86
+ * 创建 readline 接口
87
+ */
88
+ function createReadlineInterface() {
89
+ return readline.createInterface({
90
+ input: process.stdin,
91
+ output: process.stdout
92
+ });
93
+ }
94
+
95
+ /**
96
+ * 异步询问用户输入
97
+ * @param {readline.Interface} rl readline 接口
98
+ * @param {string} question 问题
99
+ * @returns {Promise<string>}
100
+ */
101
+ function ask(rl, question) {
102
+ return new Promise((resolve) => {
103
+ rl.question(question, (answer) => {
104
+ resolve(answer.trim());
105
+ });
106
+ });
107
+ }
108
+
109
+ /**
110
+ * 交互式获取端口
111
+ * @param {readline.Interface} rl readline 接口
112
+ * @param {number} defaultPort 默认端口
113
+ * @returns {Promise<number>}
114
+ */
115
+ async function promptForPort(rl, defaultPort) {
116
+ let attempts = 0;
117
+ const maxAttempts = 3;
118
+
119
+ while (attempts < maxAttempts) {
120
+ const answer = await ask(rl, `请输入端口号 (默认: ${defaultPort}): `);
121
+
122
+ if (!answer) {
123
+ return defaultPort;
124
+ }
125
+
126
+ const port = parseInt(answer, 10);
127
+ const validation = config.validatePort(port);
128
+
129
+ if (validation.valid) {
130
+ const available = await portUtils.isPortAvailable(port);
131
+ if (available) {
132
+ return port;
133
+ } else {
134
+ console.log(`\n${portUtils.getPortOccupiedMessage(port)}`);
135
+ attempts++;
136
+ }
137
+ } else {
138
+ console.log(`\n错误: ${validation.error}`);
139
+ attempts++;
140
+ }
141
+
142
+ if (attempts < maxAttempts) {
143
+ console.log(`请重新输入 (剩余尝试次数: ${maxAttempts - attempts})\n`);
144
+ }
145
+ }
146
+
147
+ console.log(`\n已达到最大尝试次数,将使用默认端口 ${defaultPort}`);
148
+ return defaultPort;
149
+ }
150
+
151
+ /**
152
+ * 询问是否更新 OpenCode 配置
153
+ * @param {readline.Interface} rl readline 接口
154
+ * @returns {Promise<boolean>}
155
+ */
156
+ async function askUpdateOpenCode(rl) {
157
+ const answer = await ask(rl, '是否自动更新 OpenCode 配置? (Y/n): ');
158
+ return answer.toLowerCase() !== 'n';
159
+ }
160
+
161
+ /**
162
+ * 询问备份失败时是否继续
163
+ * @param {readline.Interface} rl readline 接口
164
+ * @returns {Promise<boolean>}
165
+ */
166
+ async function askContinueWithoutBackup(rl) {
167
+ const answer = await ask(rl, '备份失败,是否仍然继续更新配置? (y/N): ');
168
+ return answer.toLowerCase() === 'y';
169
+ }
170
+
171
+ /**
172
+ * 主函数
173
+ */
174
+ async function main() {
175
+ const args = parseArgs();
176
+
177
+ // 显示版本
178
+ if (args.version) {
179
+ console.log(`claude-local-proxy v${VERSION}`);
180
+ process.exit(0);
181
+ }
182
+
183
+ // 显示帮助
184
+ if (args.help) {
185
+ showHelp();
186
+ process.exit(0);
187
+ }
188
+
189
+ // 验证 CLI 端口参数
190
+ if (args.port !== null) {
191
+ const validation = config.validatePort(args.port);
192
+ if (!validation.valid) {
193
+ console.error(`错误: ${validation.error}`);
194
+ process.exit(1);
195
+ }
196
+ }
197
+
198
+ let finalPort;
199
+ let rl = null;
200
+
201
+ try {
202
+ // 确定端口
203
+ if (args.port !== null) {
204
+ // CLI 参数指定了端口
205
+ finalPort = args.port;
206
+ } else {
207
+ // 获取配置的端口
208
+ finalPort = config.getPort();
209
+
210
+ // 检查端口是否可用
211
+ const isAvailable = await portUtils.isPortAvailable(finalPort);
212
+
213
+ if (!isAvailable) {
214
+ console.log(`\n默认端口 ${finalPort} 已被占用。`);
215
+
216
+ rl = createReadlineInterface();
217
+
218
+ // 交互式获取新端口
219
+ finalPort = await promptForPort(rl, config.DEFAULT_PORT);
220
+ } else if (config.isFirstRun()) {
221
+ // 首次运行,询问是否自定义端口
222
+ console.log('\n欢迎使用 Claude Local Proxy!');
223
+ console.log('这是首次运行,您可以自定义服务器端口。\n');
224
+
225
+ rl = createReadlineInterface();
226
+
227
+ const answer = await ask(rl, `使用默认端口 ${config.DEFAULT_PORT}? (Y/n): `);
228
+
229
+ if (answer.toLowerCase() === 'n') {
230
+ finalPort = await promptForPort(rl, config.DEFAULT_PORT);
231
+ }
232
+ }
233
+ }
234
+
235
+ // 再次检查最终端口是否可用
236
+ const finalAvailable = await portUtils.isPortAvailable(finalPort);
237
+ if (!finalAvailable) {
238
+ console.error(`\n错误: 端口 ${finalPort} 不可用。`);
239
+ console.log(portUtils.getPortOccupiedMessage(finalPort));
240
+ if (rl) rl.close();
241
+ process.exit(1);
242
+ }
243
+
244
+ // 保存端口配置
245
+ config.savePort(finalPort);
246
+ console.log(`\n端口配置已保存: ${finalPort}`);
247
+
248
+ // OpenCode 配置更新
249
+ if (!args.skipOpencode) {
250
+ const opencodeDetect = opencodeConfig.detectOpenCodeConfig();
251
+
252
+ if (opencodeDetect.exists) {
253
+ let shouldUpdate = true;
254
+
255
+ // 如果有交互能力,询问用户
256
+ if (!rl && process.stdin.isTTY) {
257
+ rl = createReadlineInterface();
258
+ }
259
+
260
+ if (rl) {
261
+ shouldUpdate = await askUpdateOpenCode(rl);
262
+ }
263
+
264
+ if (shouldUpdate) {
265
+ const updateResult = opencodeConfig.updateOpenCodeBaseURL(finalPort);
266
+
267
+ if (updateResult.success) {
268
+ console.log(`\n${updateResult.message}`);
269
+ if (updateResult.backupPath) {
270
+ console.log(`备份文件: ${updateResult.backupPath}`);
271
+ }
272
+ console.log('\n请重启 OpenCode 以使配置生效。');
273
+ } else {
274
+ console.log(`\n警告: ${updateResult.message}`);
275
+ console.log(opencodeConfig.getManualConfigGuide(finalPort));
276
+ }
277
+ } else {
278
+ console.log('\n跳过 OpenCode 配置更新。');
279
+ console.log(opencodeConfig.getManualConfigGuide(finalPort));
280
+ }
281
+ } else {
282
+ console.log('\n未检测到 OpenCode 配置文件。');
283
+ console.log(opencodeConfig.getManualConfigGuide(finalPort));
284
+ }
285
+ }
286
+
287
+ if (rl) {
288
+ rl.close();
289
+ }
290
+
291
+ // 设置环境变量并启动服务器
292
+ process.env.PORT = finalPort.toString();
293
+
294
+ console.log('\n正在启动代理服务器...\n');
295
+
296
+ // 加载并运行服务器
297
+ require('./server');
298
+
299
+ } catch (error) {
300
+ console.error('启动失败:', error.message);
301
+ if (rl) rl.close();
302
+ process.exit(1);
303
+ }
304
+ }
305
+
306
+ // 运行
307
+ main();
package/lib/config.js ADDED
@@ -0,0 +1,122 @@
1
+ /**
2
+ * 配置文件管理模块
3
+ * 负责读写 ~/.claude-proxy/config.json
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const os = require('os');
9
+
10
+ const CONFIG_DIR = path.join(os.homedir(), '.claude-proxy');
11
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
12
+ const DEFAULT_PORT = 12346;
13
+
14
+ /**
15
+ * 确保配置目录存在
16
+ */
17
+ function ensureConfigDir() {
18
+ if (!fs.existsSync(CONFIG_DIR)) {
19
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
20
+ }
21
+ }
22
+
23
+ /**
24
+ * 读取配置文件
25
+ * @returns {Object} 配置对象
26
+ */
27
+ function readConfig() {
28
+ try {
29
+ if (fs.existsSync(CONFIG_FILE)) {
30
+ const content = fs.readFileSync(CONFIG_FILE, 'utf8');
31
+ return JSON.parse(content);
32
+ }
33
+ } catch (error) {
34
+ console.warn(`警告: 配置文件读取失败 - ${error.message}`);
35
+ }
36
+ return {};
37
+ }
38
+
39
+ /**
40
+ * 写入配置文件
41
+ * @param {Object} config 配置对象
42
+ */
43
+ function writeConfig(config) {
44
+ ensureConfigDir();
45
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf8');
46
+ }
47
+
48
+ /**
49
+ * 获取端口配置
50
+ * 优先级: CLI 参数 > 配置文件 > 环境变量 > 默认值
51
+ * @param {number|null} cliPort CLI 参数指定的端口
52
+ * @returns {number} 端口号
53
+ */
54
+ function getPort(cliPort = null) {
55
+ // 1. CLI 参数优先级最高
56
+ if (cliPort !== null && cliPort !== undefined) {
57
+ return cliPort;
58
+ }
59
+
60
+ // 2. 配置文件
61
+ const config = readConfig();
62
+ if (config.port) {
63
+ return config.port;
64
+ }
65
+
66
+ // 3. 环境变量
67
+ if (process.env.PORT) {
68
+ const envPort = parseInt(process.env.PORT, 10);
69
+ if (!isNaN(envPort)) {
70
+ return envPort;
71
+ }
72
+ }
73
+
74
+ // 4. 默认值
75
+ return DEFAULT_PORT;
76
+ }
77
+
78
+ /**
79
+ * 保存端口配置
80
+ * @param {number} port 端口号
81
+ */
82
+ function savePort(port) {
83
+ const config = readConfig();
84
+ config.port = port;
85
+ writeConfig(config);
86
+ }
87
+
88
+ /**
89
+ * 检查是否首次运行(配置文件不存在)
90
+ * @returns {boolean}
91
+ */
92
+ function isFirstRun() {
93
+ return !fs.existsSync(CONFIG_FILE);
94
+ }
95
+
96
+ /**
97
+ * 验证端口号是否有效
98
+ * @param {number} port 端口号
99
+ * @returns {{valid: boolean, error?: string}}
100
+ */
101
+ function validatePort(port) {
102
+ const num = parseInt(port, 10);
103
+ if (isNaN(num)) {
104
+ return { valid: false, error: '端口必须是数字' };
105
+ }
106
+ if (num < 1024 || num > 65535) {
107
+ return { valid: false, error: '端口范围必须在 1024-65535 之间' };
108
+ }
109
+ return { valid: true };
110
+ }
111
+
112
+ module.exports = {
113
+ CONFIG_DIR,
114
+ CONFIG_FILE,
115
+ DEFAULT_PORT,
116
+ readConfig,
117
+ writeConfig,
118
+ getPort,
119
+ savePort,
120
+ isFirstRun,
121
+ validatePort
122
+ };
@@ -0,0 +1,228 @@
1
+ /**
2
+ * OpenCode 配置管理模块
3
+ * 负责检测、备份和更新 OpenCode 配置文件
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const os = require('os');
9
+
10
+ /**
11
+ * 获取 OpenCode 配置文件路径(跨平台)
12
+ * @returns {string} 配置文件路径
13
+ */
14
+ function getOpenCodeConfigPath() {
15
+ const homedir = os.homedir();
16
+ if (process.platform === 'win32') {
17
+ return path.join(homedir, '.config', 'opencode', 'opencode.json');
18
+ }
19
+ // macOS / Linux
20
+ return path.join(homedir, '.config', 'opencode', 'opencode.json');
21
+ }
22
+
23
+ /**
24
+ * 检测 OpenCode 配置文件是否存在
25
+ * @returns {{exists: boolean, path: string}}
26
+ */
27
+ function detectOpenCodeConfig() {
28
+ const configPath = getOpenCodeConfigPath();
29
+ return {
30
+ exists: fs.existsSync(configPath),
31
+ path: configPath
32
+ };
33
+ }
34
+
35
+ /**
36
+ * 读取 OpenCode 配置
37
+ * @returns {{success: boolean, config?: Object, error?: string, path: string}}
38
+ */
39
+ function readOpenCodeConfig() {
40
+ const configPath = getOpenCodeConfigPath();
41
+ try {
42
+ if (!fs.existsSync(configPath)) {
43
+ return { success: false, error: '配置文件不存在', path: configPath };
44
+ }
45
+ const content = fs.readFileSync(configPath, 'utf8');
46
+ const config = JSON.parse(content);
47
+ return { success: true, config, path: configPath };
48
+ } catch (error) {
49
+ return { success: false, error: error.message, path: configPath };
50
+ }
51
+ }
52
+
53
+ /**
54
+ * 创建备份文件
55
+ * @param {string} configPath 配置文件路径
56
+ * @returns {{success: boolean, backupPath?: string, error?: string}}
57
+ */
58
+ function createBackup(configPath) {
59
+ try {
60
+ if (!fs.existsSync(configPath)) {
61
+ return { success: false, error: '配置文件不存在' };
62
+ }
63
+
64
+ const dir = path.dirname(configPath);
65
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '').slice(0, 15);
66
+ const backupPath = path.join(dir, `opencode.json.backup.${timestamp}`);
67
+
68
+ // 复制文件
69
+ fs.copyFileSync(configPath, backupPath);
70
+
71
+ // 清理旧备份,只保留最近 3 个
72
+ cleanupOldBackups(dir, 3);
73
+
74
+ return { success: true, backupPath };
75
+ } catch (error) {
76
+ return { success: false, error: error.message };
77
+ }
78
+ }
79
+
80
+ /**
81
+ * 清理旧备份文件
82
+ * @param {string} dir 目录路径
83
+ * @param {number} keepCount 保留数量
84
+ */
85
+ function cleanupOldBackups(dir, keepCount) {
86
+ try {
87
+ const files = fs.readdirSync(dir)
88
+ .filter(f => f.startsWith('opencode.json.backup.'))
89
+ .map(f => ({
90
+ name: f,
91
+ path: path.join(dir, f),
92
+ mtime: fs.statSync(path.join(dir, f)).mtime
93
+ }))
94
+ .sort((a, b) => b.mtime - a.mtime); // 按修改时间降序
95
+
96
+ // 删除多余的备份
97
+ for (let i = keepCount; i < files.length; i++) {
98
+ fs.unlinkSync(files[i].path);
99
+ }
100
+ } catch (error) {
101
+ // 清理失败不影响主流程
102
+ }
103
+ }
104
+
105
+ /**
106
+ * 更新 OpenCode 配置中的 baseURL
107
+ * @param {number} port 端口号
108
+ * @param {boolean} createBackupFirst 是否先创建备份
109
+ * @returns {{success: boolean, message: string, backupPath?: string, configPath?: string}}
110
+ */
111
+ function updateOpenCodeBaseURL(port, createBackupFirst = true) {
112
+ const configPath = getOpenCodeConfigPath();
113
+ const newBaseURL = `http://localhost:${port}`;
114
+
115
+ try {
116
+ // 检查文件是否存在
117
+ if (!fs.existsSync(configPath)) {
118
+ return {
119
+ success: false,
120
+ message: 'OpenCode 配置文件不存在',
121
+ configPath
122
+ };
123
+ }
124
+
125
+ // 读取现有配置
126
+ const content = fs.readFileSync(configPath, 'utf8');
127
+ let config;
128
+ try {
129
+ config = JSON.parse(content);
130
+ } catch {
131
+ return {
132
+ success: false,
133
+ message: 'OpenCode 配置文件 JSON 格式无效',
134
+ configPath
135
+ };
136
+ }
137
+
138
+ // 检查当前 baseURL
139
+ const currentBaseURL = config?.provider?.anthropic?.options?.baseURL;
140
+ if (currentBaseURL === newBaseURL) {
141
+ return {
142
+ success: true,
143
+ message: `OpenCode 配置已是最新 (baseURL: ${newBaseURL})`,
144
+ configPath
145
+ };
146
+ }
147
+
148
+ // 创建备份
149
+ let backupPath = null;
150
+ if (createBackupFirst) {
151
+ const backupResult = createBackup(configPath);
152
+ if (!backupResult.success) {
153
+ return {
154
+ success: false,
155
+ message: `备份失败: ${backupResult.error}`,
156
+ configPath
157
+ };
158
+ }
159
+ backupPath = backupResult.backupPath;
160
+ }
161
+
162
+ // 确保嵌套结构存在
163
+ if (!config.provider) config.provider = {};
164
+ if (!config.provider.anthropic) config.provider.anthropic = {};
165
+ if (!config.provider.anthropic.options) config.provider.anthropic.options = {};
166
+
167
+ // 更新 baseURL
168
+ config.provider.anthropic.options.baseURL = newBaseURL;
169
+
170
+ // 写回配置文件
171
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
172
+
173
+ return {
174
+ success: true,
175
+ message: `OpenCode 配置已更新 (baseURL: ${newBaseURL})`,
176
+ backupPath,
177
+ configPath
178
+ };
179
+ } catch (error) {
180
+ if (error.code === 'EACCES') {
181
+ return {
182
+ success: false,
183
+ message: '权限不足,无法写入 OpenCode 配置文件',
184
+ configPath
185
+ };
186
+ }
187
+ return {
188
+ success: false,
189
+ message: `更新失败: ${error.message}`,
190
+ configPath
191
+ };
192
+ }
193
+ }
194
+
195
+ /**
196
+ * 获取手动配置指南
197
+ * @param {number} port 端口号
198
+ * @returns {string} 配置指南文本
199
+ */
200
+ function getManualConfigGuide(port) {
201
+ const configPath = getOpenCodeConfigPath();
202
+ return `
203
+ 手动配置 OpenCode:
204
+ 1. 编辑配置文件: ${configPath}
205
+ 2. 添加或修改以下配置:
206
+
207
+ {
208
+ "provider": {
209
+ "anthropic": {
210
+ "options": {
211
+ "baseURL": "http://localhost:${port}"
212
+ }
213
+ }
214
+ }
215
+ }
216
+
217
+ 3. 重启 OpenCode 使配置生效
218
+ `;
219
+ }
220
+
221
+ module.exports = {
222
+ getOpenCodeConfigPath,
223
+ detectOpenCodeConfig,
224
+ readOpenCodeConfig,
225
+ createBackup,
226
+ updateOpenCodeBaseURL,
227
+ getManualConfigGuide
228
+ };
@@ -0,0 +1,67 @@
1
+ /**
2
+ * 端口检测工具模块
3
+ * 负责检测端口可用性
4
+ */
5
+
6
+ const net = require('net');
7
+
8
+ /**
9
+ * 检测端口是否可用
10
+ * @param {number} port 端口号
11
+ * @returns {Promise<boolean>} 端口是否可用
12
+ */
13
+ function isPortAvailable(port) {
14
+ return new Promise((resolve) => {
15
+ const server = net.createServer();
16
+
17
+ server.once('error', (err) => {
18
+ if (err.code === 'EADDRINUSE') {
19
+ resolve(false);
20
+ } else {
21
+ resolve(false);
22
+ }
23
+ });
24
+
25
+ server.once('listening', () => {
26
+ server.close();
27
+ resolve(true);
28
+ });
29
+
30
+ // 不指定地址,检测所有接口
31
+ server.listen(port);
32
+ });
33
+ }
34
+
35
+ /**
36
+ * 查找可用端口(从指定端口开始递增查找)
37
+ * @param {number} startPort 起始端口
38
+ * @param {number} maxAttempts 最大尝试次数
39
+ * @returns {Promise<number|null>} 可用端口或 null
40
+ */
41
+ async function findAvailablePort(startPort, maxAttempts = 10) {
42
+ for (let i = 0; i < maxAttempts; i++) {
43
+ const port = startPort + i;
44
+ if (port > 65535) break;
45
+
46
+ const available = await isPortAvailable(port);
47
+ if (available) {
48
+ return port;
49
+ }
50
+ }
51
+ return null;
52
+ }
53
+
54
+ /**
55
+ * 获取占用指定端口的进程信息(仅用于提示)
56
+ * @param {number} port 端口号
57
+ * @returns {string} 提示信息
58
+ */
59
+ function getPortOccupiedMessage(port) {
60
+ return `端口 ${port} 已被占用。请使用 \`lsof -i :${port}\` 或 \`netstat -an | grep ${port}\` 查看占用进程。`;
61
+ }
62
+
63
+ module.exports = {
64
+ isPortAvailable,
65
+ findAvailablePort,
66
+ getPortOccupiedMessage
67
+ };
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "open-claude-code-proxy",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Local proxy that forwards API requests through the official Claude Code CLI",
5
5
  "main": "server.js",
6
6
  "bin": {
7
- "open-claude-code-proxy": "server.js",
8
- "claude-local-proxy": "claude-proxy"
7
+ "open-claude-code-proxy": "cli.js",
8
+ "claude-local-proxy": "cli.js",
9
+ "claude-proxy": "claude-proxy"
9
10
  },
10
11
  "scripts": {
11
12
  "start": "node server.js",
package/server.js CHANGED
File without changes