@simonyea/holysheep-cli 1.7.104 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simonyea/holysheep-cli",
3
- "version": "1.7.104",
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
@@ -179,7 +179,7 @@ module.exports = {
179
179
  return result
180
180
  },
181
181
 
182
- hint: '配置后 VS Code Claude 扩展、Cursor、Continue、Cline、Windsurf 等工具均可直接使用',
182
+ hint: '一键配置全局终端环境变量(ANTHROPIC / OPENAI API Key + Base URL)',
183
183
  launchCmd: null,
184
184
  docsUrl: 'https://holysheep.ai',
185
185
  }