@simonyea/holysheep-cli 2.1.21 → 2.1.23
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.
|
|
3
|
+
"version": "2.1.23",
|
|
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",
|
|
@@ -324,6 +324,26 @@ function forwardViaNodeProxy({ nodeProxyUrl, targetUrl, clientReq, clientRes, ex
|
|
|
324
324
|
host: targetUrl.host,
|
|
325
325
|
connection: 'close',
|
|
326
326
|
}
|
|
327
|
+
// Build the forward-proxy path. CRS's per-node proxy at :3129 accepts an
|
|
328
|
+
// absolute URL as the HTTP/1.1 request target and rewrites the scheme
|
|
329
|
+
// internally. We ALWAYS send http:// in the path (even if targetUrl is
|
|
330
|
+
// https://) because:
|
|
331
|
+
// 1. node:3129 listens on plain HTTP and ignores the scheme of the
|
|
332
|
+
// request-target — it reads `targetUrl = new URL(req.url)`,
|
|
333
|
+
// validates hostname, and proxies upstream.
|
|
334
|
+
// 2. bun's `http.request` treats `path: 'https://...'` as a signal to
|
|
335
|
+
// issue a CONNECT tunnel (instead of a plain absolute-URI forward
|
|
336
|
+
// request), which silently drops the x-hs-* headers we injected
|
|
337
|
+
// because they would only ride on the outer CONNECT, not the inner
|
|
338
|
+
// TLS tunnel — yielding 403 'Missing bridge session headers'.
|
|
339
|
+
// (Node's `http.request` does NOT have this behaviour; the bug is
|
|
340
|
+
// bun-specific. Reproduced under bun 1.3.9 at AionUi's runtime.)
|
|
341
|
+
// Rewriting to http:// preserves correctness on both runtimes.
|
|
342
|
+
const forwardPath = (() => {
|
|
343
|
+
const clone = new URL(targetUrl.toString())
|
|
344
|
+
clone.protocol = 'http:'
|
|
345
|
+
return clone.toString()
|
|
346
|
+
})()
|
|
327
347
|
if (ENABLE_TIMING_LOG) {
|
|
328
348
|
const hsHeaders = Object.fromEntries(
|
|
329
349
|
Object.entries(finalHeaders).filter(([k]) => /^x-hs-/i.test(k))
|
|
@@ -331,6 +351,7 @@ function forwardViaNodeProxy({ nodeProxyUrl, targetUrl, clientReq, clientRes, ex
|
|
|
331
351
|
console.error(
|
|
332
352
|
`[hs-claude-proxy] forward.headers ${JSON.stringify({
|
|
333
353
|
target: sanitizeUrl(targetUrl),
|
|
354
|
+
forwardPath,
|
|
334
355
|
node: sanitizeUrl(nodeProxyUrl),
|
|
335
356
|
hsHeaders,
|
|
336
357
|
clientHeadersKeys: Object.keys(clientReq.headers).slice(0, 20),
|
|
@@ -341,7 +362,7 @@ function forwardViaNodeProxy({ nodeProxyUrl, targetUrl, clientReq, clientRes, ex
|
|
|
341
362
|
host: upstream.hostname,
|
|
342
363
|
port: Number(upstream.port || 80),
|
|
343
364
|
method: clientReq.method,
|
|
344
|
-
path:
|
|
365
|
+
path: forwardPath,
|
|
345
366
|
agent: false,
|
|
346
367
|
headers: finalHeaders,
|
|
347
368
|
}, (forwardRes) => {
|
|
@@ -505,6 +526,17 @@ function pipeWithCleanup(a, b) {
|
|
|
505
526
|
function createProcessProxyServer({ sessionId, configPath = CONFIG_PATH, allowAnthropicConnect = false }) {
|
|
506
527
|
const server = http.createServer(async (clientReq, clientRes) => {
|
|
507
528
|
const isDirect = !clientReq.url.startsWith('http')
|
|
529
|
+
if (ENABLE_TIMING_LOG) {
|
|
530
|
+
console.error(
|
|
531
|
+
`[hs-claude-proxy] incoming ${JSON.stringify({
|
|
532
|
+
method: clientReq.method,
|
|
533
|
+
url: String(clientReq.url).slice(0, 120),
|
|
534
|
+
ua: String(clientReq.headers['user-agent'] || '').slice(0, 60),
|
|
535
|
+
host: clientReq.headers.host || '',
|
|
536
|
+
isDirect,
|
|
537
|
+
})}`
|
|
538
|
+
)
|
|
539
|
+
}
|
|
508
540
|
|
|
509
541
|
const doForward = async (lease, attempt) => {
|
|
510
542
|
const config = readConfig(configPath)
|
|
@@ -602,6 +634,15 @@ function createProcessProxyServer({ sessionId, configPath = CONFIG_PATH, allowAn
|
|
|
602
634
|
|
|
603
635
|
server.on('connect', async (req, clientSocket, head) => {
|
|
604
636
|
const target = String(req.url || '').trim()
|
|
637
|
+
if (ENABLE_TIMING_LOG) {
|
|
638
|
+
console.error(
|
|
639
|
+
`[hs-claude-proxy] incoming.CONNECT ${JSON.stringify({
|
|
640
|
+
target: target.slice(0, 120),
|
|
641
|
+
ua: String(req.headers['user-agent'] || '').slice(0, 60),
|
|
642
|
+
headers: Object.keys(req.headers).slice(0, 20),
|
|
643
|
+
})}`
|
|
644
|
+
)
|
|
645
|
+
}
|
|
605
646
|
const [host, rawPort] = target.split(':')
|
|
606
647
|
const port = Number(rawPort || 443)
|
|
607
648
|
if (!host || !Number.isInteger(port) || ![80, 443].includes(port)) {
|
|
@@ -698,14 +739,32 @@ async function closeSession(configPath, sessionId) {
|
|
|
698
739
|
if (!sessionId) return
|
|
699
740
|
const config = readConfig(configPath)
|
|
700
741
|
const controlPlaneUrl = getControlPlaneUrl(config)
|
|
701
|
-
if (!controlPlaneUrl)
|
|
742
|
+
if (!controlPlaneUrl) {
|
|
743
|
+
logProxyTiming('lease.close', { sessionId, skipped: 'no-control-plane' })
|
|
744
|
+
return
|
|
745
|
+
}
|
|
746
|
+
const startedAt = Date.now()
|
|
747
|
+
let status = 'unknown'
|
|
702
748
|
try {
|
|
703
|
-
await fetch(`${controlPlaneUrl}/session/close`, {
|
|
749
|
+
const response = await fetch(`${controlPlaneUrl}/session/close`, {
|
|
704
750
|
method: 'POST',
|
|
705
751
|
headers: { 'content-type': 'application/json' },
|
|
706
752
|
body: JSON.stringify({ sessionId }),
|
|
707
753
|
})
|
|
708
|
-
|
|
754
|
+
status = response.ok ? 'ok' : `http_${response.status}`
|
|
755
|
+
} catch (err) {
|
|
756
|
+
status = `err_${err.name || 'unknown'}`
|
|
757
|
+
}
|
|
758
|
+
// Always log lease.close — unlike other events this isn't slow-path-gated
|
|
759
|
+
// because lease leaks silently accumulate on CRS, making it critical to
|
|
760
|
+
// observe every release attempt.
|
|
761
|
+
console.error(
|
|
762
|
+
`[hs-claude-proxy] lease.close ${JSON.stringify({
|
|
763
|
+
sessionId,
|
|
764
|
+
status,
|
|
765
|
+
durationMs: Date.now() - startedAt,
|
|
766
|
+
})}`
|
|
767
|
+
)
|
|
709
768
|
}
|
|
710
769
|
|
|
711
770
|
module.exports = {
|
|
@@ -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-
|
|
63
|
+
'https://mail.holysheep.ai/app/cli/aionui-runtime-v1.9.18-holysheep-hs18.tar.gz'
|
|
64
64
|
const DEFAULT_RUNTIME_SHA256 =
|
|
65
|
-
'
|
|
66
|
-
const DEFAULT_RUNTIME_VERSION = '1.9.18-holysheep-
|
|
65
|
+
'402739938fb30ddf09e31ed9fd64eb1453726cc062cc5d2ab8f3fbf19f7e6e66'
|
|
66
|
+
const DEFAULT_RUNTIME_VERSION = '1.9.18-holysheep-hs18'
|
|
67
67
|
|
|
68
68
|
function isValidRuntimeDir(dir) {
|
|
69
69
|
if (!dir) return false
|
|
@@ -609,21 +609,33 @@ async function startWrapper({ port, runtimeDir, runtimeVersion, runtimeSource, b
|
|
|
609
609
|
// CLI package root containing package.json + src/.
|
|
610
610
|
const cliRoot = path.resolve(__dirname, '..', '..')
|
|
611
611
|
const wrapperVersion = require('../../package.json').version
|
|
612
|
+
// [HolySheep fork v2.1.23] Do NOT put HOLYSHEEP_API_KEY in env — any other
|
|
613
|
+
// process owned by the same user can read it via `ps ewww`. Instead, the
|
|
614
|
+
// fork side reads ~/.holysheep/config.json (already 0644 today; will be
|
|
615
|
+
// tightened to 0600 in the same release). Pass only the path hint.
|
|
616
|
+
const credPath = path.join(os.homedir(), '.holysheep', 'config.json')
|
|
617
|
+
try {
|
|
618
|
+
// Belt-and-suspenders: ensure the creds file is not world-readable.
|
|
619
|
+
if (fs.existsSync(credPath)) {
|
|
620
|
+
fs.chmodSync(credPath, 0o600)
|
|
621
|
+
}
|
|
622
|
+
} catch { /* best effort */ }
|
|
623
|
+
const sanitizedParentEnv = { ...process.env }
|
|
624
|
+
// If the parent process already had HOLYSHEEP_API_KEY in its env (e.g. the
|
|
625
|
+
// user exported it in their shell), DON'T propagate it — the fork reads
|
|
626
|
+
// the file instead. This closes the ps-leak vector for npm-global users.
|
|
627
|
+
delete sanitizedParentEnv.HOLYSHEEP_API_KEY
|
|
612
628
|
const aionui = spawn(bunPath, ['dist-server/server.mjs'], {
|
|
613
629
|
cwd: runtimeDir,
|
|
614
630
|
env: {
|
|
615
|
-
...
|
|
631
|
+
...sanitizedParentEnv,
|
|
616
632
|
PORT: String(internalPort),
|
|
617
633
|
HOST: '127.0.0.1',
|
|
618
634
|
ALLOW_REMOTE: '',
|
|
619
635
|
NODE_ENV: 'production',
|
|
620
636
|
HOLYSHEEP_CLI_ROOT: cliRoot,
|
|
621
637
|
HOLYSHEEP_CLI_VERSION: wrapperVersion,
|
|
622
|
-
|
|
623
|
-
// Dashboard without going through /api/holysheep/status round-trip.
|
|
624
|
-
...(process.env.HOLYSHEEP_API_KEY ? {} : (() => {
|
|
625
|
-
try { const k = require('../utils/config').getApiKey(); return k ? { HOLYSHEEP_API_KEY: k } : {} } catch { return {} }
|
|
626
|
-
})()),
|
|
638
|
+
HOLYSHEEP_CONFIG_PATH: credPath,
|
|
627
639
|
},
|
|
628
640
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
629
641
|
})
|