@simonyea/holysheep-cli 2.1.38 → 2.1.40

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,7 +1,7 @@
1
1
  {
2
2
  "name": "@simonyea/holysheep-cli",
3
- "version": "2.1.38",
4
- "description": "Claude Code/Cursor/Cline API relay for China \u2014 \u00a51=$1, WeChat/Alipay payment, no credit card, no VPN. One command setup for all AI coding tools.",
3
+ "version": "2.1.40",
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
  "scripts": {
6
6
  "test": "node tests/droid.test.js && node tests/workspace-store.test.js && node tests/runtime-stale-upgrade.test.js && node tests/hermes.test.js && node tests/preflight.test.js && node tests/opencode-auth-purge.test.js && node tests/shell-winpath.test.js && node tests/openclaw-atomic-write.test.js",
7
7
  "prepublishOnly": "node scripts/check-tarball-size.js"
@@ -1,7 +1,30 @@
1
1
  /**
2
2
  * 跨平台检测命令是否存在
3
3
  * Windows 用 where,Unix 用 which,兜底用 --version
4
+ *
5
+ * [HolySheep fork v2.1.40 / hs27] Windows-specific fallbacks.
6
+ *
7
+ * Background: some installers (notably Claude Code's Windows installer
8
+ * irm https://claude.ai/install.ps1 | iex) drop their binary into
9
+ * `%USERPROFILE%\.local\bin\` and print a warning that this directory
10
+ * is NOT on PATH. The *current* Node process started by hs web has a
11
+ * frozen process.env.PATH snapshot, so even after
12
+ * ensureWindowsUserPathHasLocalBin() persists the User PATH in the
13
+ * registry, `where claude` still reports "not found" until the next
14
+ * terminal is opened. The UI then shows 未安装 despite a successful
15
+ * install.
16
+ *
17
+ * Fix strategy (Windows only):
18
+ * 1. Enumerate known install locations as a fallback AFTER `where` fails:
19
+ * %USERPROFILE%\.local\bin\<cmd>.exe (Claude official installer)
20
+ * %APPDATA%\npm\<cmd>.cmd (npm global bin)
21
+ * %LOCALAPPDATA%\Programs\<cmd>\<cmd>.exe
22
+ * %LOCALAPPDATA%\<cmd>\<cmd>.exe
23
+ * 2. Also re-read the USER-level PATH from the registry via PowerShell
24
+ * so we catch updates that landed after this process started.
4
25
  */
26
+ const path = require('path')
27
+ const fs = require('fs')
5
28
  const { exec, execSync } = require('child_process')
6
29
 
7
30
  function canRun(command, options = {}) {
@@ -13,6 +36,82 @@ function canRun(command, options = {}) {
13
36
  }
14
37
  }
15
38
 
39
+ function canRunAsync(command, options = {}) {
40
+ return new Promise((resolve) => {
41
+ exec(command, { timeout: 3000, windowsHide: true, ...options }, (error) => {
42
+ resolve(!error)
43
+ })
44
+ })
45
+ }
46
+
47
+ // [HolySheep fork v2.1.40 / hs27]
48
+ // Known absolute install locations per-CLI on Windows. Checked AFTER
49
+ // PATH lookup fails but BEFORE we report "not found". Keeping this list
50
+ // small on purpose — only locations that official installers actually use.
51
+ function getWindowsKnownPaths(cmd) {
52
+ if (process.platform !== 'win32') return []
53
+ const home = process.env.USERPROFILE || process.env.HOME || ''
54
+ const appData = process.env.APPDATA || ''
55
+ const localAppData = process.env.LOCALAPPDATA || ''
56
+ const paths = []
57
+ // Claude official installer → %USERPROFILE%\.local\bin\claude.exe
58
+ if (home) paths.push(path.join(home, '.local', 'bin', `${cmd}.exe`))
59
+ // npm global → %APPDATA%\npm\<cmd>.{cmd,exe,ps1}
60
+ if (appData) {
61
+ paths.push(path.join(appData, 'npm', `${cmd}.cmd`))
62
+ paths.push(path.join(appData, 'npm', `${cmd}.exe`))
63
+ }
64
+ // Programs directories → %LOCALAPPDATA%\Programs\<cmd>\<cmd>.exe
65
+ if (localAppData) {
66
+ paths.push(path.join(localAppData, 'Programs', cmd, `${cmd}.exe`))
67
+ paths.push(path.join(localAppData, cmd, `${cmd}.exe`))
68
+ paths.push(path.join(localAppData, 'factory', `${cmd}.exe`)) // droid
69
+ }
70
+ return paths
71
+ }
72
+
73
+ function checkKnownPathsSync(cmd) {
74
+ for (const p of getWindowsKnownPaths(cmd)) {
75
+ try {
76
+ if (fs.existsSync(p)) return p
77
+ } catch {}
78
+ }
79
+ return null
80
+ }
81
+
82
+ // Refresh this process's PATH from the USER-level registry PATH. Cheap — only
83
+ // runs once per commandExists miss. Noop on non-Windows. Guarded by a module
84
+ // flag so we don't run it more than once per short window.
85
+ let _userPathRefreshedAt = 0
86
+ function refreshWindowsUserPath() {
87
+ if (process.platform !== 'win32') return
88
+ const now = Date.now()
89
+ if (now - _userPathRefreshedAt < 5000) return
90
+ _userPathRefreshedAt = now
91
+ try {
92
+ const out = execSync(
93
+ 'powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "[Environment]::GetEnvironmentVariable(\'Path\', \'User\')"',
94
+ { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 5000 }
95
+ ).trim()
96
+ if (!out) return
97
+ const userParts = out.split(';').map((s) => s.trim()).filter(Boolean)
98
+ const curParts = (process.env.PATH || '').split(';').map((s) => s.trim()).filter(Boolean)
99
+ const curSet = new Set(curParts.map((s) => s.toLowerCase()))
100
+ const added = []
101
+ for (const p of userParts) {
102
+ if (!curSet.has(p.toLowerCase())) {
103
+ curParts.push(p)
104
+ added.push(p)
105
+ }
106
+ }
107
+ if (added.length) {
108
+ process.env.PATH = curParts.join(';')
109
+ }
110
+ } catch {
111
+ // noop
112
+ }
113
+ }
114
+
16
115
  function commandExists(cmd) {
17
116
  if (process.platform === 'win32') {
18
117
  const variants = [cmd, `${cmd}.cmd`, `${cmd}.exe`, `${cmd}.bat`]
@@ -25,6 +124,15 @@ function commandExists(cmd) {
25
124
  if (canRun(`cmd /d /s /c "${variant} --version"`, { timeout: 3000 })) return true
26
125
  }
27
126
 
127
+ // [HolySheep fork v2.1.40 / hs27] Last-resort: re-read the USER-level
128
+ // registry PATH (catches recently installed binaries) and check a
129
+ // handful of known install locations before giving up.
130
+ refreshWindowsUserPath()
131
+ for (const variant of variants) {
132
+ if (canRun(`where ${variant}`)) return true
133
+ }
134
+ if (checkKnownPathsSync(cmd)) return true
135
+
28
136
  return false
29
137
  }
30
138
 
@@ -34,14 +142,6 @@ function commandExists(cmd) {
34
142
  return canRun(`${cmd} --version`, { timeout: 3000 })
35
143
  }
36
144
 
37
- function canRunAsync(command, options = {}) {
38
- return new Promise((resolve) => {
39
- exec(command, { timeout: 3000, windowsHide: true, ...options }, (error) => {
40
- resolve(!error)
41
- })
42
- })
43
- }
44
-
45
145
  async function commandExistsAsync(cmd) {
46
146
  if (process.platform === 'win32') {
47
147
  const variants = [cmd, `${cmd}.cmd`, `${cmd}.exe`, `${cmd}.bat`]
@@ -53,6 +153,13 @@ async function commandExistsAsync(cmd) {
53
153
  if (await canRunAsync(`cmd /d /s /c "${variant} --version"`)) return true
54
154
  }
55
155
 
156
+ // [HolySheep fork v2.1.40 / hs27] Last-resort (see commandExists).
157
+ refreshWindowsUserPath()
158
+ for (const variant of variants) {
159
+ if (await canRunAsync(`where ${variant}`)) return true
160
+ }
161
+ if (checkKnownPathsSync(cmd)) return true
162
+
56
163
  return false
57
164
  }
58
165
 
@@ -60,4 +167,10 @@ async function commandExistsAsync(cmd) {
60
167
  return canRunAsync(`${cmd} --version`)
61
168
  }
62
169
 
63
- module.exports = { commandExists, commandExistsAsync }
170
+ module.exports = {
171
+ commandExists,
172
+ commandExistsAsync,
173
+ // test-only exports
174
+ _getWindowsKnownPaths: getWindowsKnownPaths,
175
+ _refreshWindowsUserPath: refreshWindowsUserPath,
176
+ }
@@ -60,10 +60,10 @@ const VENDOR_DIR = path.join(__dirname, 'vendor', 'aionui')
60
60
  // new CLI release, the next `hs web` invocation on user machines will detect
61
61
  // the version drift and upgrade the cache in place.
62
62
  const DEFAULT_RUNTIME_URL =
63
- 'https://mail.holysheep.ai/app/cli/aionui-runtime-v1.9.18-holysheep-hs26.tar.gz'
63
+ 'https://mail.holysheep.ai/app/cli/aionui-runtime-v1.9.18-holysheep-hs27.tar.gz'
64
64
  const DEFAULT_RUNTIME_SHA256 =
65
- 'e0502f21376d21b3797c51a3d932970c5a86e29076cadf3eeb2939941abb47ed'
66
- const DEFAULT_RUNTIME_VERSION = '1.9.18-holysheep-hs26'
65
+ 'de9cb08d071d1254f8e6ad69f249d73d8b1b8fd5c4a07ca6d6009a902e0cccb1'
66
+ const DEFAULT_RUNTIME_VERSION = '1.9.18-holysheep-hs27'
67
67
 
68
68
  function isValidRuntimeDir(dir) {
69
69
  if (!dir) return false
@@ -776,6 +776,12 @@ async function handleToolConfigure(req, res) {
776
776
  await new Promise((resolve) => {
777
777
  child.on('exit', (code) => {
778
778
  success = code === 0 && lastResult?.status === 'ok'
779
+ // [HolySheep fork v2.1.40 / hs27] Bust tool-check cache on success so
780
+ // CLI Manager's follow-up `/api/holysheep/tools` immediately sees the
781
+ // refreshed configured/installed state without waiting 10s for TTL.
782
+ if (success) {
783
+ toolStateCache.delete(toolId)
784
+ }
779
785
  sseEmit(res, {
780
786
  type: 'done',
781
787
  success,