@simonyea/holysheep-cli 1.7.96 → 1.7.98

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.96",
3
+ "version": "1.7.98",
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",
@@ -386,18 +386,20 @@ async function setup(options) {
386
386
  }
387
387
  }
388
388
 
389
- // Step 5: 清理 shell 环境变量(所有工具改用 JSON/config 文件配置,不再写 shell)
390
- const staleKeys = ['ANTHROPIC_API_KEY', 'ANTHROPIC_BASE_URL', 'OPENAI_API_KEY', 'OPENAI_BASE_URL']
391
- try {
392
- const cleaned = removeEnvFromShell(staleKeys)
393
- if (cleaned.length > 0) {
394
- console.log(chalk.gray(`已清理 shell 中的过时变量: ${cleaned.map(f => chalk.cyan(f)).join(', ')}`))
395
- }
396
- } catch {}
397
- if (process.platform === 'win32') {
398
- const removed = removeWindowsUserEnvVars(staleKeys)
399
- if (removed.length > 0) {
400
- console.log(chalk.gray(`已移除过时环境变量: ${removed.map(item => chalk.cyan(item)).join(', ')}`))
389
+ // Step 5: 清理 shell 环境变量(跳过已选全局环境变量工具的情况)
390
+ if (!toolIds.includes('env-config')) {
391
+ const staleKeys = ['ANTHROPIC_API_KEY', 'ANTHROPIC_BASE_URL', 'OPENAI_API_KEY', 'OPENAI_BASE_URL']
392
+ try {
393
+ const cleaned = removeEnvFromShell(staleKeys)
394
+ if (cleaned.length > 0) {
395
+ console.log(chalk.gray(`已清理 shell 中的过时变量: ${cleaned.map(f => chalk.cyan(f)).join(', ')}`))
396
+ }
397
+ } catch {}
398
+ if (process.platform === 'win32') {
399
+ const removed = removeWindowsUserEnvVars(staleKeys)
400
+ if (removed.length > 0) {
401
+ console.log(chalk.gray(`已移除过时环境变量: ${removed.map(item => chalk.cyan(item)).join(', ')}`))
402
+ }
401
403
  }
402
404
  }
403
405
 
@@ -47,6 +47,26 @@ function getControlPlaneUrl(config) {
47
47
 
48
48
  const leaseCache = new Map()
49
49
 
50
+ function isRetryableNodeLeaseError(err) {
51
+ const message = String(err?.message || '')
52
+ return [
53
+ 'No session lease',
54
+ 'ECONNREFUSED',
55
+ 'ECONNRESET',
56
+ 'socket hang up',
57
+ 'ETIMEDOUT',
58
+ 'EHOSTUNREACH',
59
+ 'ENETUNREACH',
60
+ 'Upstream proxy CONNECT failed',
61
+ 'No available Claude accounts support the requested model',
62
+ 'No available Claude accounts',
63
+ 'Bad Gateway',
64
+ 'HTTP 502',
65
+ 'HTTP 503',
66
+ 'HTTP 504'
67
+ ].some((token) => message.includes(token))
68
+ }
69
+
50
70
  async function readJsonResponse(response) {
51
71
  const chunks = []
52
72
  for await (const chunk of response) chunks.push(Buffer.from(chunk))
@@ -211,10 +231,9 @@ function createProcessProxyServer({ sessionId, configPath = CONFIG_PATH }) {
211
231
  await doForward(getCachedLease(sessionId))
212
232
  } catch (err) {
213
233
  if (clientRes.headersSent) return
214
- // 只在没有 lease 缓存时重试(首次连接或 lease 过期)
215
- // 网络超时/连接错误不重试 Claude Code 自己处理重试,避免重复 API 请求
216
- const noLease = !leaseCache.has(sessionId) || (err && err.message && err.message.includes('No session lease'))
217
- if (noLease) {
234
+ const shouldRefreshLease =
235
+ !leaseCache.has(sessionId) || (err && isRetryableNodeLeaseError(err))
236
+ if (shouldRefreshLease) {
218
237
  try {
219
238
  const config = readConfig(configPath)
220
239
  leaseCache.delete(sessionId)
@@ -267,8 +286,9 @@ function createProcessProxyServer({ sessionId, configPath = CONFIG_PATH }) {
267
286
  try {
268
287
  await doConnect(getCachedLease(sessionId))
269
288
  } catch (err) {
270
- const noLease = !leaseCache.has(sessionId) || (err && err.message && err.message.includes('No session lease'))
271
- if (noLease) {
289
+ const shouldRefreshLease =
290
+ !leaseCache.has(sessionId) || (err && isRetryableNodeLeaseError(err))
291
+ if (shouldRefreshLease) {
272
292
  try {
273
293
  const config = readConfig(configPath)
274
294
  leaseCache.delete(sessionId)
@@ -0,0 +1,94 @@
1
+ /**
2
+ * 全局环境变量适配器
3
+ *
4
+ * 一键将 HolySheep API 配置写入系统环境变量,
5
+ * 让所有读取标准 env 的编程工具(VS Code Claude 扩展、Cursor、Continue、Cline、Windsurf 等)
6
+ * 无需单独配置即可使用 HolySheep 中继。
7
+ *
8
+ * 写入的环境变量:
9
+ * ANTHROPIC_API_KEY — Anthropic SDK 标准 key
10
+ * ANTHROPIC_AUTH_TOKEN — Claude Code 优先读取
11
+ * ANTHROPIC_BASE_URL — Anthropic API 基地址(不带 /v1)
12
+ * OPENAI_API_KEY — OpenAI SDK 标准 key
13
+ * OPENAI_BASE_URL — OpenAI API 基地址(带 /v1)
14
+ *
15
+ * Mac/Linux: 写入 .zshrc / .bashrc / config.fish(holysheep managed 块)
16
+ * Windows: 通过 setx 写入用户级环境变量(需重启终端生效)
17
+ */
18
+ 'use strict'
19
+
20
+ const { writeEnvToShell, removeEnvFromShell, getShellRcFiles } = require('../utils/shell')
21
+ const fs = require('fs')
22
+
23
+ const MANAGED_KEYS = [
24
+ 'ANTHROPIC_API_KEY',
25
+ 'ANTHROPIC_AUTH_TOKEN',
26
+ 'ANTHROPIC_BASE_URL',
27
+ 'OPENAI_API_KEY',
28
+ 'OPENAI_BASE_URL',
29
+ ]
30
+
31
+ const MARKER = '# >>> holysheep-cli managed >>>'
32
+
33
+ function isConfiguredInShell() {
34
+ if (process.platform === 'win32') {
35
+ // Windows: 检查环境变量是否存在且包含 holysheep
36
+ return !!(
37
+ process.env.ANTHROPIC_BASE_URL?.includes('holysheep') ||
38
+ process.env.OPENAI_BASE_URL?.includes('holysheep')
39
+ )
40
+ }
41
+
42
+ // Mac/Linux: 检查 shell rc 文件里是否有 managed 块
43
+ try {
44
+ const files = getShellRcFiles()
45
+ for (const file of files) {
46
+ try {
47
+ const content = fs.readFileSync(file, 'utf8')
48
+ if (content.includes(MARKER) && content.includes('holysheep')) {
49
+ return true
50
+ }
51
+ } catch {}
52
+ }
53
+ } catch {}
54
+ return false
55
+ }
56
+
57
+ module.exports = {
58
+ name: '全局环境变量',
59
+ id: 'env-config',
60
+
61
+ checkInstalled() {
62
+ return true // 不依赖任何 CLI,总是可用
63
+ },
64
+
65
+ isConfigured() {
66
+ return isConfiguredInShell()
67
+ },
68
+
69
+ configure(apiKey, baseUrlAnthropic, baseUrlOpenAI) {
70
+ const envVars = {
71
+ ANTHROPIC_API_KEY: apiKey,
72
+ ANTHROPIC_AUTH_TOKEN: apiKey,
73
+ ANTHROPIC_BASE_URL: baseUrlAnthropic,
74
+ OPENAI_API_KEY: apiKey,
75
+ OPENAI_BASE_URL: baseUrlOpenAI,
76
+ }
77
+
78
+ const written = writeEnvToShell(envVars)
79
+
80
+ return {
81
+ file: written[0] || 'shell env',
82
+ hot: false,
83
+ }
84
+ },
85
+
86
+ reset() {
87
+ removeEnvFromShell(MANAGED_KEYS)
88
+ },
89
+
90
+ getConfigPath() { return null },
91
+ hint: '配置后 VS Code Claude 扩展、Cursor、Continue、Cline、Windsurf 等工具均可直接使用',
92
+ launchCmd: null,
93
+ docsUrl: 'https://holysheep.ai',
94
+ }
@@ -7,4 +7,5 @@ module.exports = [
7
7
  require('./droid'),
8
8
  require('./opencode'),
9
9
  require('./openclaw'),
10
+ require('./env-config'),
10
11
  ]
@@ -402,13 +402,15 @@ async function handleSetup(req, res) {
402
402
  }
403
403
  }
404
404
 
405
- // Step 5: Clean env vars
406
- try {
407
- const cleaned = removeEnvFromShell(['ANTHROPIC_API_KEY', 'ANTHROPIC_BASE_URL', 'OPENAI_API_KEY', 'OPENAI_BASE_URL'])
408
- if (cleaned.length) {
409
- sseEmit(res, { type: 'progress', step: 'clean-env', status: 'ok', message: `已清理环境变量: ${cleaned.join(', ')}` })
410
- }
411
- } catch {}
405
+ // Step 5: Clean env vars (skip if env-config tool is selected)
406
+ if (!selectedToolIds.includes('env-config')) {
407
+ try {
408
+ const cleaned = removeEnvFromShell(['ANTHROPIC_API_KEY', 'ANTHROPIC_BASE_URL', 'OPENAI_API_KEY', 'OPENAI_BASE_URL'])
409
+ if (cleaned.length) {
410
+ sseEmit(res, { type: 'progress', step: 'clean-env', status: 'ok', message: `已清理环境变量: ${cleaned.join(', ')}` })
411
+ }
412
+ } catch {}
413
+ }
412
414
 
413
415
  // Done
414
416
  const ok = results.filter(r => r.status === 'ok').length