@simonyea/holysheep-cli 2.1.28 → 2.1.30

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": "2.1.28",
3
+ "version": "2.1.30",
4
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.",
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",
@@ -205,6 +205,26 @@ function buildAuthHeaders(config, lease) {
205
205
  }
206
206
  }
207
207
 
208
+ // [HolySheep fork v2.1.29 / hs22] Rewrite User-Agent away from claude-cli/*.
209
+ // CRS enforces a "Claude Code CLI must use hs claude" policy keyed on UA —
210
+ // when it sees claude-cli/* or claude-code/*, it 403s even with valid bridge
211
+ // headers, under the assumption the user bypassed the wrapper. Every forward
212
+ // path (node-proxy + direct-https fallback) now normalises UA to
213
+ // holysheep-cli so the wrapper identity is preserved end-to-end.
214
+ function sanitizeClaudeClientHeaders(headers, config) {
215
+ const out = { ...(headers || {}) }
216
+ for (const k of Object.keys(out)) {
217
+ if (/^x-forwarded-|^x-real-ip$|^forwarded$|^via$/i.test(k)) {
218
+ delete out[k]
219
+ continue
220
+ }
221
+ if (/^user-agent$/i.test(k) && /claude-cli|claude-code/i.test(String(out[k] || ''))) {
222
+ out[k] = `holysheep-cli/${(config && config.cliVersion) || 'process-proxy'} (hs-claude-proxy)`
223
+ }
224
+ }
225
+ return out
226
+ }
227
+
208
228
  function deriveNodeProxyUrl(lease) {
209
229
  if (process.env.HS_CLAUDE_NODE_PROXY_OVERRIDE) {
210
230
  return String(process.env.HS_CLAUDE_NODE_PROXY_OVERRIDE).replace(/\/+$/, '')
@@ -244,12 +264,7 @@ function forwardDirectHttps({ config, lease, clientReq, clientRes, trace }) {
244
264
  // (or a proxy earlier in the chain could tag the request) and leak the
245
265
  // loopback origin / real client IP to api.holysheep.ai. The upstream
246
266
  // doesn't need them — it authenticates off bridge headers + API key.
247
- const sanitized = { ...clientReq.headers }
248
- for (const k of Object.keys(sanitized)) {
249
- if (/^x-forwarded-|^x-real-ip$|^forwarded$|^via$/i.test(k)) {
250
- delete sanitized[k]
251
- }
252
- }
267
+ const sanitized = sanitizeClaudeClientHeaders(clientReq.headers, config)
253
268
  const headers = {
254
269
  ...sanitized,
255
270
  ...buildAuthHeaders(config, lease),
@@ -375,8 +390,14 @@ function forwardViaNodeProxy({ nodeProxyUrl, targetUrl, clientReq, clientRes, ex
375
390
  stallTimer = setTimeout(() => failWithAnthropicError('upstream stream stalled'), STALL_TIMEOUT_MS)
376
391
  }
377
392
 
393
+ // [HolySheep fork v2.1.29 / hs22] Same UA-rewrite logic as forwardDirectHttps.
394
+ // Even when we go through node4:3129, the forward-proxy delivers the UA
395
+ // verbatim to upstream CRS; keeping claude-cli/* gets us 403.
396
+ // `extraHeaders` already contains bridge auth via buildAuthHeaders.
397
+ // `config` may be undefined here because forwardViaNodeProxy doesn't take
398
+ // one; use a minimal stub — cliVersion is optional.
378
399
  const finalHeaders = {
379
- ...clientReq.headers,
400
+ ...sanitizeClaudeClientHeaders(clientReq.headers, { cliVersion: undefined }),
380
401
  ...extraHeaders,
381
402
  host: targetUrl.host,
382
403
  connection: 'close',
@@ -305,12 +305,25 @@ module.exports = {
305
305
  return isConfiguredInToml(readConfig())
306
306
  },
307
307
  configure(apiKey, _baseUrlAnthropic, baseUrlOpenAI, primaryModel /*, _selectedModels */) {
308
- const merged = mergeConfig(apiKey, baseUrlOpenAI, primaryModel)
308
+ // [HolySheep fork v2.1.29 / hs22] Hermes talks to its provider via the
309
+ // OpenAI-compatible Chat Completions API (`/v1/chat/completions`), which
310
+ // the HolySheep CRS relay does NOT support for `claude-*` model IDs at
311
+ // the moment (returns 503 "Service temporarily unavailable"). Only the
312
+ // anthropic native `/v1/messages` path accepts Claude. GPT-family models
313
+ // work on both paths.
314
+ //
315
+ // To give users a usable hermes out of the box we pick a safe default
316
+ // here: if the caller passes a `claude-*` model, silently downgrade to
317
+ // gpt-5.4 (HolySheep's default GPT model) for hermes only. The TOML
318
+ // keeps the real choice so `hermes --provider holysheep --model claude-*`
319
+ // still works if the user overrides it later (and CRS fixes the upstream).
320
+ const hermesPrimaryModel = /^claude-/i.test(primaryModel || '') ? 'gpt-5.4' : (primaryModel || 'gpt-5.4')
321
+ const merged = mergeConfig(apiKey, baseUrlOpenAI, hermesPrimaryModel)
309
322
  writeConfig(merged)
310
323
  // [HolySheep fork v2.1.28 / hs21] Also patch config.yaml so the actual
311
324
  // agent runtime uses the right base_url. See CONFIG_YAML comment.
312
325
  try {
313
- patchConfigYaml(apiKey, baseUrlOpenAI, primaryModel)
326
+ patchConfigYaml(apiKey, baseUrlOpenAI, hermesPrimaryModel)
314
327
  } catch (e) {
315
328
  // Non-fatal: hermes can still run on config.toml alone if the user
316
329
  // manually edits config.yaml. Surface via stderr for diagnosability.
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- [HolySheep fork v2.1.25 / hs19] Hermes ACP PTY wrapper.
3
+ [HolySheep fork v2.1.29 / hs22] Hermes ACP PTY wrapper.
4
4
 
5
5
  Why: bun 1.3.9's subprocess.spawn with stdio:['pipe','pipe','pipe']
6
6
  passes sockets to the child. Python's asyncio.connect_write_pipe()
@@ -13,9 +13,22 @@ captures Python tracebacks separately. Master side pumps bytes between
13
13
  our (bun-spawned) stdin/stdout and the PTY, so bun reads through kernel
14
14
  PTY buffer which hermes's asyncio transport drives correctly.
15
15
 
16
- Disable ECHO on slave so stdin bytes are not echoed back as stdout bytes
17
- (the old BSD `script -q /dev/null` wrapper had this bug).
18
- Disable ONLCR so '\\n' is not rewritten to '\\r\\n'.
16
+ CRITICAL: disable ICANON (canonical/line-buffered mode) on the slave.
17
+ Default macOS/Linux PTY slaves have ICANON on, which means:
18
+ 1. Input is buffered line-by-line and each line is capped at MAX_CANON
19
+ (1024B on macOS, ~4096B on Linux). Longer lines get truncated.
20
+ 2. Special chars (^C, ^D, ^Z, erase/kill) are interpreted instead of
21
+ passed through — breaks JSON containing random bytes.
22
+
23
+ ACP session/prompt JSON can exceed 4KB easily (system prompt + skill
24
+ list + assistant rules). Before hs22 we only disabled ECHO + ONLCR, so
25
+ id=1 (initialize, ~150B) and id=2/3 (newSession, setSessionMode, <400B)
26
+ went through, but id=4 (prompt, ~4-8KB) was truncated by MAX_CANON —
27
+ hermes got invalid JSON, returned an SDK error with empty data, our
28
+ AcpAgentManager 60s fallback masked it. User saw "hermes thinks forever".
29
+
30
+ Also disable ICRNL and IXON so we don't have \\r<->\\n rewriting or
31
+ flow-control pauses on large bursts.
19
32
 
20
33
  Usage: python3 pty-hermes-wrapper.py
21
34
  HERMES_BIN is auto-resolved from $PATH; override via $HOLYSHEEP_HERMES_BIN.
@@ -39,9 +52,30 @@ def main() -> None:
39
52
  hermes_bin = _resolve_hermes_bin()
40
53
  master_fd, slave_fd = pty.openpty()
41
54
  attrs = termios.tcgetattr(slave_fd)
42
- attrs[3] &= ~termios.ECHO
43
- attrs[1] &= ~termios.ONLCR
55
+ # attrs = [iflag, oflag, cflag, lflag, ispeed, ospeed, cc]
56
+ # lflag (attrs[3]): disable canonical-mode line buffering, ECHO*, signal chars
57
+ attrs[3] &= ~(termios.ICANON | termios.ECHO | termios.ECHOE | termios.ECHOK
58
+ | termios.ECHONL | termios.ISIG)
59
+ # iflag (attrs[0]): disable \r<->\n translation, XON/XOFF flow control,
60
+ # and parity checking (we carry raw bytes only).
61
+ attrs[0] &= ~(termios.ICRNL | termios.INLCR | termios.IGNCR | termios.IXON
62
+ | termios.IXOFF | termios.ISTRIP | termios.IGNBRK | termios.BRKINT
63
+ | termios.INPCK | termios.PARMRK)
64
+ # oflag (attrs[1]): disable post-processing (ONLCR \n -> \r\n, tabs, etc.)
65
+ attrs[1] &= ~termios.OPOST
66
+ # Non-blocking reads: return as soon as 1 byte is available.
67
+ attrs[6][termios.VMIN] = 1
68
+ attrs[6][termios.VTIME] = 0
44
69
  termios.tcsetattr(slave_fd, termios.TCSANOW, attrs)
70
+ # Best-effort: print the applied flags once so future regressions are
71
+ # easy to diagnose from hs web logs.
72
+ try:
73
+ sys.stderr.write(
74
+ f'[pty-hermes-wrapper] slave configured raw: ICANON off, ECHO off, OPOST off\n'
75
+ )
76
+ sys.stderr.flush()
77
+ except OSError:
78
+ pass
45
79
 
46
80
  pid = os.fork()
47
81
  if pid == 0:
@@ -103,9 +137,28 @@ def main() -> None:
103
137
  except ProcessLookupError:
104
138
  pass
105
139
  break
106
- try:
107
- os.write(master_fd, data)
108
- except OSError:
140
+ # [HolySheep fork v2.1.29 / hs22] Diagnostic trace when enabled —
141
+ # emits one line per stdin chunk so we can see exactly what bun
142
+ # pushed to us and verify hermes received the full payload. Only
143
+ # active when HOLYSHEEP_PTY_TRACE=1 is set to avoid log flood.
144
+ if os.environ.get('HOLYSHEEP_PTY_TRACE') == '1':
145
+ try:
146
+ sys.stderr.write(f'[pty-hermes-wrapper] stdin chunk len={len(data)}\n')
147
+ sys.stderr.flush()
148
+ except OSError:
149
+ pass
150
+ # Write in full — os.write may short-write on large buffers; loop.
151
+ total = 0
152
+ while total < len(data):
153
+ try:
154
+ n = os.write(master_fd, data[total:])
155
+ except OSError:
156
+ total = -1
157
+ break
158
+ if n <= 0:
159
+ break
160
+ total += n
161
+ if total < 0:
109
162
  break
110
163
  finally:
111
164
  try:
@@ -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-hs20.tar.gz'
63
+ 'https://mail.holysheep.ai/app/cli/aionui-runtime-v1.9.18-holysheep-hs22.tar.gz'
64
64
  const DEFAULT_RUNTIME_SHA256 =
65
- '99ddb768cacd223a13c77d716413032e913c8be2850e7dbccaf7ae52d0797e37'
66
- const DEFAULT_RUNTIME_VERSION = '1.9.18-holysheep-hs20'
65
+ '03e386ea83660b4074e29026dfc53be6ed01fcd51fa35a9379ed64a396e99f33'
66
+ const DEFAULT_RUNTIME_VERSION = '1.9.18-holysheep-hs22'
67
67
 
68
68
  function isValidRuntimeDir(dir) {
69
69
  if (!dir) return false