@simonyea/holysheep-cli 1.7.66 → 1.7.68

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.66",
3
+ "version": "1.7.68",
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",
@@ -187,7 +187,7 @@ const I18N = {
187
187
  installSuccess: '安装完成', installFailed: '安装失败',
188
188
  needLogin: '请先登录', cleanDone: '已清理',
189
189
  hotReload: '已生效,无需重启', needRestart: '重启终端后生效',
190
- launch: '启动命令', upgradeOne: '升级',
190
+ launch: '启动命令', upgradeOne: '升级', open: '打开',
191
191
  updateAvailable: '有新版本可用', updateNow: '立即升级',
192
192
  },
193
193
  en: {
@@ -210,7 +210,7 @@ const I18N = {
210
210
  installSuccess: 'Installed', installFailed: 'Install failed',
211
211
  needLogin: 'Please login first', cleanDone: 'Cleaned',
212
212
  hotReload: 'Active, no restart needed', needRestart: 'Restart terminal to apply',
213
- launch: 'Launch', upgradeOne: 'Upgrade',
213
+ launch: 'Launch', upgradeOne: 'Upgrade', open: 'Open',
214
214
  updateAvailable: 'Update available', updateNow: 'Update now',
215
215
  },
216
216
  }
@@ -365,7 +365,8 @@ function renderToolCard(tool) {
365
365
  dotClass = 'dot-ok'
366
366
  statusBadges = `<span class="badge badge-ok">${t('installed')}</span> <span class="badge badge-ok">${t('configured')}</span>`
367
367
  const upgradeBtn = tool.canUpgrade ? `<button class="btn btn-outline btn-sm" onclick="doUpgradeTool('${tool.id}','${esc(tool.name)}')" ${busy ? 'disabled' : ''}>${t('upgradeOne')}</button>` : ''
368
- actions = `<button class="btn btn-outline btn-sm" onclick="doConfigureTool('${tool.id}','${esc(tool.name)}')" ${busy ? 'disabled' : ''}>${t('reconfigure')}</button>
368
+ actions = `<button class="btn btn-primary btn-sm" onclick="doLaunchTool('${tool.id}')">${t('open')}</button>
369
+ <button class="btn btn-outline btn-sm" onclick="doConfigureTool('${tool.id}','${esc(tool.name)}')" ${busy ? 'disabled' : ''}>${t('reconfigure')}</button>
369
370
  ${upgradeBtn}
370
371
  <button class="btn btn-danger btn-sm" onclick="doResetTool('${tool.id}','${esc(tool.name)}')" ${busy ? 'disabled' : ''}>${t('reset')}</button>`
371
372
  hintLine = tool.version ? `<span class="mono" style="font-size:0.78rem;color:var(--text2)">${esc(tool.version)}</span>` : ''
@@ -423,6 +424,12 @@ async function doConfigureTool(id, name) {
423
424
  loadTools()
424
425
  }
425
426
 
427
+ async function doLaunchTool(id) {
428
+ try {
429
+ await api('tool/launch', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ toolId: id }) })
430
+ } catch {}
431
+ }
432
+
426
433
  async function doResetTool(id, name) {
427
434
  if (!confirm(t('confirmReset'))) return
428
435
  try {
@@ -26,15 +26,20 @@ function maskKey(key) {
26
26
  return key.slice(0, 6) + '...' + key.slice(-4)
27
27
  }
28
28
 
29
- async function fetchWithRetry(url, opts = {}, retries = 3) {
29
+ async function fetchWithRetry(url, opts = {}, retries = 3, timeoutMs = 20000) {
30
30
  const fetch = require('node-fetch')
31
31
  for (let i = 0; i < retries; i++) {
32
+ const controller = new AbortController()
33
+ const timer = setTimeout(() => controller.abort(), timeoutMs)
32
34
  try {
33
- return await fetch(url, { timeout: 10000, ...opts })
35
+ const res = await fetch(url, { ...opts, signal: controller.signal })
36
+ clearTimeout(timer)
37
+ return res
34
38
  } catch (e) {
35
- // DNS 暂时失败 (EAI_AGAIN) 或网络抖动,重试
36
- if (i < retries - 1 && (e.code === 'EAI_AGAIN' || e.code === 'ETIMEDOUT' || e.code === 'ECONNRESET' || e.type === 'system')) {
37
- await new Promise(r => setTimeout(r, 1500 * (i + 1)))
39
+ clearTimeout(timer)
40
+ const retryable = e.code === 'EAI_AGAIN' || e.code === 'ETIMEDOUT' || e.code === 'ECONNRESET' || e.code === 'ECONNREFUSED' || e.type === 'system' || e.name === 'AbortError'
41
+ if (i < retries - 1 && retryable) {
42
+ await new Promise(r => setTimeout(r, 2000 * (i + 1)))
38
43
  continue
39
44
  }
40
45
  throw e
@@ -615,6 +620,43 @@ async function handleToolReset(req, res) {
615
620
  }
616
621
  }
617
622
 
623
+ // ── Launch tool in new terminal ───────────────────────────────────────────────
624
+
625
+ async function handleToolLaunch(req, res) {
626
+ const body = await parseBody(req)
627
+ const { toolId } = body
628
+ const tool = TOOLS.find(t => t.id === toolId)
629
+ if (!tool) return json(res, { error: '未知工具' }, 400)
630
+
631
+ // OpenClaw: 打开浏览器 dashboard 而非终端
632
+ if (toolId === 'openclaw') {
633
+ const port = tool.getGatewayPort?.() || 18789
634
+ const url = `http://127.0.0.1:${port}/`
635
+ if (process.platform === 'darwin') spawn('open', [url], { detached: true, stdio: 'ignore' }).unref()
636
+ else if (process.platform === 'win32') spawn('cmd.exe', ['/c', 'start', '', url], { detached: true, stdio: 'ignore', shell: true }).unref()
637
+ else spawn('xdg-open', [url], { detached: true, stdio: 'ignore' }).unref()
638
+ return json(res, { ok: true, type: 'browser', url })
639
+ }
640
+
641
+ const cmd = tool.launchCmd
642
+ if (!cmd) return json(res, { error: '无启动命令' }, 400)
643
+
644
+ if (process.platform === 'darwin') {
645
+ spawn('osascript', ['-e', `tell app "Terminal" to do script "${cmd.replace(/"/g, '\\"')}"`], { detached: true, stdio: 'ignore' }).unref()
646
+ } else if (process.platform === 'win32') {
647
+ spawn('cmd.exe', ['/c', 'start', 'cmd.exe', '/k', cmd], { detached: true, stdio: 'ignore', shell: true }).unref()
648
+ } else {
649
+ const terms = ['x-terminal-emulator', 'gnome-terminal', 'xterm']
650
+ for (const term of terms) {
651
+ if (commandExists(term)) {
652
+ spawn(term, ['-e', cmd], { detached: true, stdio: 'ignore' }).unref()
653
+ break
654
+ }
655
+ }
656
+ }
657
+ json(res, { ok: true, type: 'terminal', cmd })
658
+ }
659
+
618
660
  // ── Environment variables ────────────────────────────────────────────────────
619
661
 
620
662
  const HS_ENV_KEYS = ['ANTHROPIC_API_KEY', 'ANTHROPIC_AUTH_TOKEN', 'ANTHROPIC_BASE_URL', 'OPENAI_API_KEY', 'OPENAI_BASE_URL']
@@ -696,6 +738,7 @@ async function handleRequest(req, res) {
696
738
  if (route === '/api/tool/configure' && req.method === 'POST') return await handleToolConfigure(req, res)
697
739
  if (route === '/api/tool/reset' && req.method === 'POST') return await handleToolReset(req, res)
698
740
  if (route === '/api/tool/upgrade' && req.method === 'POST') return await handleToolUpgrade(req, res)
741
+ if (route === '/api/tool/launch' && req.method === 'POST') return await handleToolLaunch(req, res)
699
742
  if (route === '/api/env' && req.method === 'GET') return handleEnv(req, res)
700
743
  if (route === '/api/env/clean' && req.method === 'POST') return handleEnvClean(req, res)
701
744
  if (route === '/api/restart' && req.method === 'POST') {