@simonyea/holysheep-cli 1.7.105 → 1.7.106
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/package.json +1 -1
- package/src/commands/claude-proxy.js +247 -0
- package/src/index.js +10 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simonyea/holysheep-cli",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.106",
|
|
4
4
|
"description": "Claude Code/Cursor/Cline API relay for China — ¥1=$1, WeChat/Alipay payment, no credit card, no VPN. One command setup for all AI coding tools.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"openai-china",
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hs claude-proxy — 独立后台代理,让 VS Code Claude 扩展也能用 HolySheep
|
|
3
|
+
*
|
|
4
|
+
* 用法:
|
|
5
|
+
* hs claude-proxy 前台启动代理
|
|
6
|
+
* hs claude-proxy --daemon 后台启动
|
|
7
|
+
* hs claude-proxy --stop 停止后台代理
|
|
8
|
+
* hs claude-proxy --status 查看代理状态
|
|
9
|
+
*/
|
|
10
|
+
'use strict'
|
|
11
|
+
|
|
12
|
+
const fs = require('fs')
|
|
13
|
+
const path = require('path')
|
|
14
|
+
const os = require('os')
|
|
15
|
+
const { spawn, execSync } = require('child_process')
|
|
16
|
+
const chalk = require('chalk')
|
|
17
|
+
|
|
18
|
+
const {
|
|
19
|
+
startProcessProxy,
|
|
20
|
+
closeSession,
|
|
21
|
+
getProcessProxyPort,
|
|
22
|
+
getLocalProxyUrl,
|
|
23
|
+
readConfig,
|
|
24
|
+
} = require('../tools/claude-process-proxy')
|
|
25
|
+
|
|
26
|
+
const claudeCodeTool = require('../tools/claude-code')
|
|
27
|
+
const { getApiKey } = require('../utils/config')
|
|
28
|
+
|
|
29
|
+
const PID_FILE = path.join(os.homedir(), '.holysheep', 'claude-proxy.pid')
|
|
30
|
+
const isWin = process.platform === 'win32'
|
|
31
|
+
|
|
32
|
+
function readPid() {
|
|
33
|
+
try {
|
|
34
|
+
const content = fs.readFileSync(PID_FILE, 'utf8').trim()
|
|
35
|
+
return JSON.parse(content)
|
|
36
|
+
} catch {
|
|
37
|
+
return null
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function writePid(pid, port, sessionId) {
|
|
42
|
+
const dir = path.dirname(PID_FILE)
|
|
43
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true })
|
|
44
|
+
fs.writeFileSync(PID_FILE, JSON.stringify({ pid, port, sessionId, startedAt: new Date().toISOString() }), 'utf8')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function clearPid() {
|
|
48
|
+
try { fs.unlinkSync(PID_FILE) } catch {}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isProcessAlive(pid) {
|
|
52
|
+
try {
|
|
53
|
+
process.kill(pid, 0)
|
|
54
|
+
return true
|
|
55
|
+
} catch {
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function isProxyHealthy(port) {
|
|
61
|
+
try {
|
|
62
|
+
execSync(
|
|
63
|
+
isWin
|
|
64
|
+
? `powershell -NonInteractive -Command "try{(Invoke-WebRequest -Uri http://127.0.0.1:${port}/ -TimeoutSec 1 -UseBasicParsing).StatusCode}catch{exit 1}"`
|
|
65
|
+
: `curl -sf http://127.0.0.1:${port}/ -o /dev/null --max-time 1`,
|
|
66
|
+
{ stdio: 'ignore', timeout: 3000, windowsHide: true }
|
|
67
|
+
)
|
|
68
|
+
return true
|
|
69
|
+
} catch {
|
|
70
|
+
return false
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function writeBaseUrlToSettings(port) {
|
|
75
|
+
const settings = claudeCodeTool.readSettings()
|
|
76
|
+
if (!settings.env) settings.env = {}
|
|
77
|
+
settings.env.ANTHROPIC_BASE_URL = getLocalProxyUrl(port)
|
|
78
|
+
claudeCodeTool.writeSettings(settings)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function clearBaseUrlFromSettings() {
|
|
82
|
+
const settings = claudeCodeTool.readSettings()
|
|
83
|
+
if (settings.env?.ANTHROPIC_BASE_URL?.includes('127.0.0.1')) {
|
|
84
|
+
delete settings.env.ANTHROPIC_BASE_URL
|
|
85
|
+
claudeCodeTool.writeSettings(settings)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── 子命令 ──────────────────────────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
async function handleStop() {
|
|
92
|
+
const info = readPid()
|
|
93
|
+
if (!info) {
|
|
94
|
+
console.log(chalk.yellow('没有正在运行的代理'))
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (isProcessAlive(info.pid)) {
|
|
99
|
+
try {
|
|
100
|
+
process.kill(info.pid, 'SIGTERM')
|
|
101
|
+
console.log(chalk.green(`已停止代理 (PID ${info.pid})`))
|
|
102
|
+
} catch (e) {
|
|
103
|
+
console.log(chalk.red(`停止失败: ${e.message}`))
|
|
104
|
+
if (isWin) {
|
|
105
|
+
try { execSync(`taskkill /F /PID ${info.pid}`, { stdio: 'ignore' }) } catch {}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
console.log(chalk.gray('代理进程已不存在'))
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
clearBaseUrlFromSettings()
|
|
113
|
+
clearPid()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function handleStatus() {
|
|
117
|
+
const info = readPid()
|
|
118
|
+
if (!info) {
|
|
119
|
+
console.log(chalk.yellow('代理未启动'))
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const alive = isProcessAlive(info.pid)
|
|
124
|
+
const healthy = alive && isProxyHealthy(info.port)
|
|
125
|
+
|
|
126
|
+
if (healthy) {
|
|
127
|
+
console.log(chalk.green(`代理运行中`))
|
|
128
|
+
console.log(chalk.gray(` PID: ${info.pid}`))
|
|
129
|
+
console.log(chalk.gray(` 端口: ${info.port}`))
|
|
130
|
+
console.log(chalk.gray(` 地址: ${getLocalProxyUrl(info.port)}`))
|
|
131
|
+
console.log(chalk.gray(` 启动: ${info.startedAt}`))
|
|
132
|
+
} else if (alive) {
|
|
133
|
+
console.log(chalk.yellow(`代理进程存在 (PID ${info.pid}) 但未响应`))
|
|
134
|
+
} else {
|
|
135
|
+
console.log(chalk.yellow('代理进程已退出'))
|
|
136
|
+
clearPid()
|
|
137
|
+
clearBaseUrlFromSettings()
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function handleDaemon() {
|
|
142
|
+
// 检查是否已运行
|
|
143
|
+
const info = readPid()
|
|
144
|
+
if (info && isProcessAlive(info.pid) && isProxyHealthy(info.port)) {
|
|
145
|
+
console.log(chalk.green(`代理已在运行 (PID ${info.pid}, 端口 ${info.port})`))
|
|
146
|
+
return
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const scriptPath = path.join(__dirname, '..', 'index.js')
|
|
150
|
+
const spawnCmd = isWin ? 'node' : process.execPath
|
|
151
|
+
const child = spawn(spawnCmd, [scriptPath, 'claude-proxy'], {
|
|
152
|
+
detached: true,
|
|
153
|
+
stdio: 'ignore',
|
|
154
|
+
windowsHide: true,
|
|
155
|
+
})
|
|
156
|
+
child.unref()
|
|
157
|
+
|
|
158
|
+
// 等代理就绪
|
|
159
|
+
const port = getProcessProxyPort()
|
|
160
|
+
for (let i = 0; i < 15; i++) {
|
|
161
|
+
await new Promise(r => setTimeout(r, 500))
|
|
162
|
+
if (isProxyHealthy(port)) {
|
|
163
|
+
console.log(chalk.green(`代理已在后台启动`))
|
|
164
|
+
console.log(chalk.gray(` PID: ${child.pid}`))
|
|
165
|
+
console.log(chalk.gray(` 端口: ${port}`))
|
|
166
|
+
console.log(chalk.gray(` 地址: ${getLocalProxyUrl(port)}`))
|
|
167
|
+
console.log(chalk.cyan('\n VS Code Claude 扩展现在可以使用了'))
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
console.log(chalk.yellow('代理启动中,请稍等...'))
|
|
173
|
+
console.log(chalk.gray(` PID: ${child.pid}`))
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function handleForeground() {
|
|
177
|
+
const config = readConfig()
|
|
178
|
+
const apiKey = config.apiKey || getApiKey()
|
|
179
|
+
if (!apiKey) {
|
|
180
|
+
console.log(chalk.red('缺少 API Key,请先运行 hs setup'))
|
|
181
|
+
process.exit(1)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// 检查是否已运行
|
|
185
|
+
const existing = readPid()
|
|
186
|
+
if (existing && isProcessAlive(existing.pid) && isProxyHealthy(existing.port)) {
|
|
187
|
+
console.log(chalk.yellow(`代理已在运行 (PID ${existing.pid}, 端口 ${existing.port}),先停止: hs claude-proxy --stop`))
|
|
188
|
+
process.exit(1)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const ensureClaudeProxyConfig = require('./claude').ensureClaudeProxyConfig || (() => {})
|
|
192
|
+
try { ensureClaudeProxyConfig(apiKey) } catch {}
|
|
193
|
+
|
|
194
|
+
console.log(chalk.gray('启动 Claude 代理...'))
|
|
195
|
+
|
|
196
|
+
const { server, port, sessionId } = await startProcessProxy({})
|
|
197
|
+
|
|
198
|
+
writePid(process.pid, port, sessionId)
|
|
199
|
+
writeBaseUrlToSettings(port)
|
|
200
|
+
|
|
201
|
+
console.log(chalk.green(`\n✓ Claude 代理已启动`))
|
|
202
|
+
console.log(chalk.gray(` 端口: ${port}`))
|
|
203
|
+
console.log(chalk.gray(` 地址: ${getLocalProxyUrl(port)}`))
|
|
204
|
+
console.log(chalk.gray(` session: ${sessionId}`))
|
|
205
|
+
console.log(chalk.cyan('\n VS Code Claude 扩展现在可以使用了'))
|
|
206
|
+
console.log(chalk.gray(' 按 Ctrl+C 停止\n'))
|
|
207
|
+
|
|
208
|
+
const cleanup = async () => {
|
|
209
|
+
console.log(chalk.gray('\n正在停止...'))
|
|
210
|
+
clearBaseUrlFromSettings()
|
|
211
|
+
clearPid()
|
|
212
|
+
server.close()
|
|
213
|
+
await closeSession(undefined, sessionId)
|
|
214
|
+
process.exit(0)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
process.on('SIGINT', cleanup)
|
|
218
|
+
process.on('SIGTERM', cleanup)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ── 入口 ──────────────────────────────────────────────────────────────────
|
|
222
|
+
|
|
223
|
+
async function claudeProxy(args = []) {
|
|
224
|
+
if (args.includes('--stop')) {
|
|
225
|
+
return handleStop()
|
|
226
|
+
}
|
|
227
|
+
if (args.includes('--status')) {
|
|
228
|
+
return handleStatus()
|
|
229
|
+
}
|
|
230
|
+
if (args.includes('--daemon') || args.includes('-d')) {
|
|
231
|
+
return handleDaemon()
|
|
232
|
+
}
|
|
233
|
+
return handleForeground()
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// 导出 ensureClaudeProxyConfig 检测函数供 daemon 使用
|
|
237
|
+
claudeProxy.ensureClaudeProxyConfig = function (apiKey) {
|
|
238
|
+
const claudeCodeTool = require('../tools/claude-code')
|
|
239
|
+
const proxy = require('../tools/claude-process-proxy')
|
|
240
|
+
const config = proxy.readConfig()
|
|
241
|
+
if (!config.apiKey || !config.bridgeSecret) {
|
|
242
|
+
const bridgeConfig = claudeCodeTool.buildBridgeConfig(apiKey, undefined, config)
|
|
243
|
+
proxy.writeConfig(bridgeConfig)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
module.exports = claudeProxy
|
package/src/index.js
CHANGED
|
@@ -194,6 +194,16 @@ program
|
|
|
194
194
|
process.exit(code)
|
|
195
195
|
})
|
|
196
196
|
|
|
197
|
+
// ── claude-proxy ────────────────────────────────────────────────────────────
|
|
198
|
+
program
|
|
199
|
+
.command('claude-proxy [args...]')
|
|
200
|
+
.allowUnknownOption(true)
|
|
201
|
+
.description('启动 Claude 代理服务(让 VS Code Claude 扩展也能用 HolySheep)')
|
|
202
|
+
.action(async (args = []) => {
|
|
203
|
+
const claudeProxy = require('./commands/claude-proxy')
|
|
204
|
+
await claudeProxy(args)
|
|
205
|
+
})
|
|
206
|
+
|
|
197
207
|
program.parse(process.argv)
|
|
198
208
|
|
|
199
209
|
// 默认:无子命令时显示帮助 + 提示 setup
|