@simonyea/holysheep-cli 2.1.25 → 2.1.26

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.25",
3
+ "version": "2.1.26",
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",
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ [HolySheep fork v2.1.25 / hs19] Hermes ACP PTY wrapper.
4
+
5
+ Why: bun 1.3.9's subprocess.spawn with stdio:['pipe','pipe','pipe']
6
+ passes sockets to the child. Python's asyncio.connect_write_pipe()
7
+ silently drops the 2nd+ message written under bun's event loop routing.
8
+ Reproduced: bun delivers id=1 response but not id=2+; node delivers both.
9
+
10
+ Fix: allocate a PTY master/slave pair. Hand the SLAVE to hermes as its
11
+ stdin+stdout, keep stderr on the wrapper's fd 2 so hs web log still
12
+ captures Python tracebacks separately. Master side pumps bytes between
13
+ our (bun-spawned) stdin/stdout and the PTY, so bun reads through kernel
14
+ PTY buffer which hermes's asyncio transport drives correctly.
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'.
19
+
20
+ Usage: python3 pty-hermes-wrapper.py
21
+ HERMES_BIN is auto-resolved from $PATH; override via $HOLYSHEEP_HERMES_BIN.
22
+ """
23
+ import os, sys, pty, termios, select, signal
24
+
25
+
26
+ def _resolve_hermes_bin() -> str:
27
+ override = os.environ.get('HOLYSHEEP_HERMES_BIN')
28
+ if override and os.access(override, os.X_OK):
29
+ return override
30
+ for d in os.environ.get('PATH', '').split(os.pathsep):
31
+ candidate = os.path.join(d, 'hermes')
32
+ if os.access(candidate, os.X_OK):
33
+ return candidate
34
+ sys.stderr.write('[pty-hermes-wrapper] hermes binary not found in $PATH\n')
35
+ sys.exit(127)
36
+
37
+
38
+ def main() -> None:
39
+ hermes_bin = _resolve_hermes_bin()
40
+ master_fd, slave_fd = pty.openpty()
41
+ attrs = termios.tcgetattr(slave_fd)
42
+ attrs[3] &= ~termios.ECHO
43
+ attrs[1] &= ~termios.ONLCR
44
+ termios.tcsetattr(slave_fd, termios.TCSANOW, attrs)
45
+
46
+ pid = os.fork()
47
+ if pid == 0:
48
+ os.setsid()
49
+ os.dup2(slave_fd, 0)
50
+ os.dup2(slave_fd, 1)
51
+ os.close(master_fd)
52
+ os.close(slave_fd)
53
+ os.execvp(hermes_bin, [hermes_bin, 'acp'])
54
+ return
55
+
56
+ os.close(slave_fd)
57
+
58
+ def _forward(signum, _frame):
59
+ try:
60
+ os.kill(pid, signum)
61
+ except ProcessLookupError:
62
+ pass
63
+
64
+ signal.signal(signal.SIGTERM, _forward)
65
+ signal.signal(signal.SIGINT, _forward)
66
+
67
+ try:
68
+ while True:
69
+ try:
70
+ wpid, _ = os.waitpid(pid, os.WNOHANG)
71
+ if wpid != 0:
72
+ break
73
+ except ChildProcessError:
74
+ break
75
+ try:
76
+ r, _, _ = select.select([master_fd, 0], [], [], 0.5)
77
+ except (InterruptedError, OSError):
78
+ continue
79
+ if master_fd in r:
80
+ try:
81
+ data = os.read(master_fd, 65536)
82
+ except OSError:
83
+ break
84
+ if not data:
85
+ break
86
+ try:
87
+ sys.stdout.buffer.write(data)
88
+ sys.stdout.buffer.flush()
89
+ except (BrokenPipeError, OSError):
90
+ try:
91
+ os.kill(pid, signal.SIGTERM)
92
+ except ProcessLookupError:
93
+ pass
94
+ break
95
+ if 0 in r:
96
+ try:
97
+ data = os.read(0, 65536)
98
+ except OSError:
99
+ break
100
+ if not data:
101
+ try:
102
+ os.kill(pid, signal.SIGTERM)
103
+ except ProcessLookupError:
104
+ pass
105
+ break
106
+ try:
107
+ os.write(master_fd, data)
108
+ except OSError:
109
+ break
110
+ finally:
111
+ try:
112
+ os.close(master_fd)
113
+ except OSError:
114
+ pass
115
+ try:
116
+ os.waitpid(pid, 0)
117
+ except OSError:
118
+ pass
119
+
120
+
121
+ if __name__ == '__main__':
122
+ main()