@simonyea/holysheep-cli 2.1.38 → 2.1.41

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.
Files changed (45) hide show
  1. package/dist/configure-worker.js +4491 -0
  2. package/dist/index.js +9591 -0
  3. package/dist/process-proxy-inject.js +117 -0
  4. package/package.json +19 -6
  5. package/.gitea/workflows/sanity.yml +0 -125
  6. package/scripts/check-tarball-size.js +0 -44
  7. package/src/commands/balance.js +0 -57
  8. package/src/commands/claude-proxy.js +0 -248
  9. package/src/commands/claude.js +0 -135
  10. package/src/commands/doctor.js +0 -282
  11. package/src/commands/login.js +0 -211
  12. package/src/commands/openclaw.js +0 -258
  13. package/src/commands/reset.js +0 -53
  14. package/src/commands/setup.js +0 -493
  15. package/src/commands/upgrade.js +0 -168
  16. package/src/commands/webui.js +0 -622
  17. package/src/index.js +0 -226
  18. package/src/tools/aider.js +0 -78
  19. package/src/tools/antigravity.js +0 -42
  20. package/src/tools/claude-code.js +0 -228
  21. package/src/tools/claude-process-proxy.js +0 -1030
  22. package/src/tools/codex.js +0 -254
  23. package/src/tools/continue.js +0 -146
  24. package/src/tools/cursor.js +0 -71
  25. package/src/tools/droid.js +0 -281
  26. package/src/tools/env-config.js +0 -185
  27. package/src/tools/gemini-cli.js +0 -82
  28. package/src/tools/hermes.js +0 -354
  29. package/src/tools/index.js +0 -13
  30. package/src/tools/openclaw-bridge.js +0 -987
  31. package/src/tools/openclaw.js +0 -925
  32. package/src/tools/opencode.js +0 -227
  33. package/src/tools/process-proxy-inject.js +0 -142
  34. package/src/utils/config.js +0 -54
  35. package/src/utils/shell.js +0 -342
  36. package/src/utils/which.js +0 -63
  37. package/src/webui/aionui-runtime-fetcher.js +0 -429
  38. package/src/webui/aionui-runtime.js +0 -139
  39. package/src/webui/aionui-wrapper.js +0 -734
  40. package/src/webui/configure-worker.js +0 -67
  41. package/src/webui/server.js +0 -1566
  42. package/src/webui/workspace-runtime.js +0 -288
  43. package/src/webui/workspace-store.js +0 -325
  44. /package/{src/webui → dist}/index.html +0 -0
  45. /package/{src/tools → dist}/pty-hermes-wrapper.py +0 -0
@@ -1,227 +0,0 @@
1
- /**
2
- * OpenCode 适配器 (anomalyco/opencode,原 sst/opencode)
3
- *
4
- * ⚠️ 重要:OpenCode 仓库已从 sst/opencode 迁移到 anomalyco/opencode
5
- * 全局配置文件: ~/.config/opencode/opencode.json (不是 config.json!)
6
- * 格式: JSON/JSONC,provider 配置格式如下:
7
- *
8
- * {
9
- * "$schema": "https://opencode.ai/config.json",
10
- * "model": "anthropic/claude-sonnet-4-5",
11
- * "provider": {
12
- * "anthropic": {
13
- * "options": {
14
- * "baseURL": "https://api.holysheep.ai/v1",
15
- * "apiKey": "cr_xxx"
16
- * }
17
- * },
18
- * "openai": {
19
- * "options": {
20
- * "baseURL": "https://api.holysheep.ai/v1",
21
- * "apiKey": "cr_xxx"
22
- * }
23
- * }
24
- * }
25
- * }
26
- *
27
- * 安装方式(推荐): brew install anomalyco/tap/opencode
28
- * 或: npm i -g opencode-ai@latest
29
- * 说明:OpenCode 的 anthropic provider 会自行拼接 /messages,
30
- * 因此这里必须写 /v1 基地址,最终才会落到 /v1/messages。
31
- * 官方文档: https://opencode.ai/docs/config
32
- */
33
- const fs = require('fs')
34
- const path = require('path')
35
- const os = require('os')
36
-
37
- function getConfigFile() {
38
- const candidates = [
39
- // 新版标准路径(官方文档)
40
- path.join(os.homedir(), '.config', 'opencode', 'opencode.json'),
41
- // 旧版路径兼容
42
- path.join(os.homedir(), '.config', 'opencode', 'config.json'),
43
- path.join(os.homedir(), '.opencode', 'opencode.json'),
44
- path.join(os.homedir(), '.opencode', 'config.json'),
45
- // Windows
46
- path.join(os.homedir(), 'AppData', 'Roaming', 'opencode', 'opencode.json'),
47
- ]
48
- for (const f of candidates) {
49
- if (fs.existsSync(f)) return f
50
- }
51
- // 默认路径(新版标准)
52
- return path.join(os.homedir(), '.config', 'opencode', 'opencode.json')
53
- }
54
-
55
- function readConfig() {
56
- const file = getConfigFile()
57
- try {
58
- if (fs.existsSync(file)) {
59
- // 支持 JSONC(带注释的 JSON)— 只删行首注释,避免误删 URL 中的 //
60
- const content = fs.readFileSync(file, 'utf8')
61
- return JSON.parse(content.replace(/^\s*\/\/[^\n]*/gm, '').replace(/\/\*[\s\S]*?\*\//g, ''))
62
- }
63
- } catch {}
64
- return {}
65
- }
66
-
67
- function writeConfig(data) {
68
- const file = getConfigFile()
69
- fs.mkdirSync(path.dirname(file), { recursive: true })
70
- fs.writeFileSync(file, JSON.stringify(data, null, 2), 'utf8')
71
- }
72
-
73
- /**
74
- * [HolySheep fork v2.1.36 / hs25] OpenCode OAuth credential store paths.
75
- * OpenCode >=1.3.0 keeps `opencode auth login` tokens in auth.json. If an
76
- * Anthropic OAuth access token lives here, it OVERRIDES the config-file
77
- * `baseURL`, routing requests straight to api.anthropic.com — bypassing the
78
- * HolySheep relay. Symptom in the wild:
79
- * "Third-party apps now draw from your extra usage, not your plan limits."
80
- * which is Anthropic's plan-limit string, i.e. the request never hit us.
81
- */
82
- function getAuthFileCandidates() {
83
- const home = os.homedir()
84
- if (process.platform === 'win32') {
85
- return [
86
- path.join(process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local'), 'opencode', 'auth.json'),
87
- path.join(home, '.local', 'share', 'opencode', 'auth.json'),
88
- ]
89
- }
90
- const xdg = process.env.XDG_DATA_HOME || path.join(home, '.local', 'share')
91
- return [
92
- path.join(xdg, 'opencode', 'auth.json'),
93
- path.join(home, '.local', 'share', 'opencode', 'auth.json'),
94
- path.join(home, 'Library', 'Application Support', 'opencode', 'auth.json'),
95
- ]
96
- }
97
-
98
- /**
99
- * Strip `anthropic` and `openai` OAuth tokens from OpenCode's auth.json.
100
- * Uses `delete` on individual keys so other provider logins (github,
101
- * deepseek, zhipu, …) survive. Deletes the whole file if empty after purge.
102
- * Returns { touched: string[], keysAfter: string[] } for test assertions.
103
- */
104
- function purgeOauthAuth() {
105
- const touched = []
106
- let keysAfter = []
107
- for (const file of getAuthFileCandidates()) {
108
- try {
109
- if (!fs.existsSync(file)) continue
110
- const raw = fs.readFileSync(file, 'utf8')
111
- let data
112
- try { data = JSON.parse(raw) } catch { continue }
113
- if (!data || typeof data !== 'object') continue
114
-
115
- let changed = false
116
- for (const key of ['anthropic', 'openai']) {
117
- if (Object.prototype.hasOwnProperty.call(data, key)) {
118
- delete data[key]
119
- changed = true
120
- }
121
- }
122
- if (!changed) continue
123
-
124
- touched.push(file)
125
- const remaining = Object.keys(data)
126
- keysAfter = remaining
127
- if (remaining.length === 0) {
128
- try { fs.unlinkSync(file) } catch {}
129
- } else {
130
- fs.writeFileSync(file, JSON.stringify(data, null, 2), 'utf8')
131
- }
132
- } catch {
133
- // best-effort
134
- }
135
- }
136
- return { touched, keysAfter }
137
- }
138
-
139
-
140
- module.exports = {
141
- name: 'OpenCode',
142
- id: 'opencode',
143
- checkInstalled() {
144
- return require('../utils/which').commandExists('opencode')
145
- },
146
- isConfigured() {
147
- const c = readConfig()
148
- return !!(
149
- c.provider?.anthropic?.options?.baseURL?.includes('holysheep') ||
150
- c.provider?.openai?.options?.baseURL?.includes('holysheep')
151
- )
152
- },
153
- configure(apiKey, baseUrlAnthropicNoV1, baseUrlOpenAI, primaryModel) {
154
- // [HolySheep fork v2.1.36 / hs25] Strip any stale Anthropic/OpenAI OAuth
155
- // tokens from OpenCode's auth.json BEFORE writing the config file, so
156
- // the relay baseURL in opencode.json actually takes effect.
157
- try { purgeOauthAuth() } catch {}
158
- const config = readConfig()
159
- if (!config.provider) config.provider = {}
160
-
161
- // 设置 schema(方便编辑器自动补全)
162
- if (!config['$schema']) config['$schema'] = 'https://opencode.ai/config.json'
163
-
164
- // OpenCode 的 anthropic provider 需要 /v1 基地址,否则会打到 /messages 并返回 404。
165
- config.provider.anthropic = {
166
- options: {
167
- baseURL: `${String(baseUrlAnthropicNoV1 || '').replace(/\/+$/, '')}/v1`,
168
- apiKey,
169
- },
170
- }
171
-
172
- // 配置 OpenAI provider(GPT 模型)
173
- config.provider.openai = {
174
- options: {
175
- baseURL: baseUrlOpenAI, // https://api.holysheep.ai/v1
176
- apiKey,
177
- },
178
- }
179
-
180
- // 设置默认模型
181
- config.model = `anthropic/${primaryModel || 'claude-sonnet-4-6'}`
182
-
183
- // [HolySheep fork v2.1.27 / hs20] Disable the giant OpenCode tools that
184
- // blow the Anthropic request body past ~25 KB. When bodies exceed ~40 KB
185
- // against the HolySheep relay, Anthropic returns a plan-limit error:
186
- // "Third-party apps now draw from your extra usage, not your plan limits"
187
- // which OpenCode surfaces as a silent `stop_reason: end_turn` with
188
- // zero tokens (class-C failure). Removing just the biggest schemas
189
- // (todowrite ~8.8 KB, task ~4.4 KB, webfetch/skill smaller) brings
190
- // bodies below the threshold while keeping bash/read/glob/grep/edit/write
191
- // which are the core coding tools users actually need.
192
- //
193
- // Users who want these tools back can set HOLYSHEEP_OPENCODE_KEEP_ALL_TOOLS=1
194
- // in their shell, or manually remove the entries from opencode.json.
195
- if (!process.env.HOLYSHEEP_OPENCODE_KEEP_ALL_TOOLS) {
196
- const tools = { ...(config.tools || {}) }
197
- if (tools.todowrite === undefined) tools.todowrite = false
198
- if (tools.todoread === undefined) tools.todoread = false
199
- if (tools.task === undefined) tools.task = false
200
- if (tools.skill === undefined) tools.skill = false
201
- if (tools.webfetch === undefined) tools.webfetch = false
202
- config.tools = tools
203
- }
204
-
205
- writeConfig(config)
206
- return { file: getConfigFile(), hot: false }
207
- },
208
- reset() {
209
- const config = readConfig()
210
- if (config.provider) {
211
- delete config.provider.anthropic
212
- delete config.provider.openai
213
- }
214
- writeConfig(config)
215
- // [HolySheep fork v2.1.36 / hs25] Also purge OAuth tokens so a subsequent
216
- // reconfigure doesn't silently fall back to direct-to-Anthropic.
217
- try { purgeOauthAuth() } catch {}
218
- },
219
- getConfigPath() { return getConfigFile() },
220
- hint: '切换后重启 OpenCode 生效;配置文件: ~/.config/opencode/opencode.json',
221
- launchCmd: 'opencode',
222
- installCmd: 'brew install anomalyco/tap/opencode # 或: npm i -g opencode-ai@latest',
223
- docsUrl: 'https://opencode.ai',
224
- // [HolySheep fork v2.1.36 / hs25] Exposed for tests — see tests/opencode-auth-purge.test.js
225
- _purgeOauthAuth: purgeOauthAuth,
226
- _getAuthFileCandidates: getAuthFileCandidates,
227
- }
@@ -1,142 +0,0 @@
1
- 'use strict'
2
- /**
3
- * 进程级代理注入 — 通过 NODE_OPTIONS=--require 加载
4
- *
5
- * Node.js v22+ 中 tls.connect / undici 不走 net.createConnection,
6
- * 直接调用 net.Socket.prototype.connect,因此同时 patch 两者。
7
- *
8
- * 原理:将所有非本地 TCP 连接重定向到本地 bridge(CONNECT 代理),
9
- * bridge 再通过 relay-control 分配的 node 节点出口,使 CRS 看到
10
- * 可信的节点 IP。
11
- */
12
- const net = require('net')
13
- const { URL } = require('url')
14
-
15
- const raw = process.env.HS_PROXY_URL
16
- if (!raw) return
17
-
18
- let parsed
19
- try {
20
- parsed = new URL(raw)
21
- } catch {
22
- return
23
- }
24
-
25
- const PROXY_HOST = parsed.hostname
26
- const PROXY_PORT = Number(parsed.port) || 80
27
- const SKIP = new Set(['127.0.0.1', '::1', 'localhost', '0.0.0.0', PROXY_HOST])
28
- // 阻断直连 Anthropic — 强制 Claude Code 走 ANTHROPIC_BASE_URL (HTTP 路径 → proxy → CRS)
29
- // CONNECT 隧道因 TLS SNI 不匹配无法改写目标,只能阻断
30
- const BLOCK = new Set(['api.anthropic.com'])
31
-
32
- function shouldProxy(host, port) {
33
- if (!port || !host || SKIP.has(host)) return false
34
- if (BLOCK.has(host)) return 'block'
35
- return true
36
- }
37
-
38
- // api.anthropic.com → api.holysheep.ai,让 CONNECT 隧道也走 CRS 多账号调度
39
- // 否则 CONNECT 隧道直达 Anthropic,绕过 CRS,单 token 直连容易触发 429
40
- function rewriteHost(host) {
41
- if (host === 'api.anthropic.com') return 'api.holysheep.ai'
42
- return host
43
- }
44
-
45
- function setupTunnel(sock, host, port, origEmit) {
46
- const targetHost = rewriteHost(host)
47
- sock.write(`CONNECT ${targetHost}:${port} HTTP/1.1\r\nHost: ${targetHost}:${port}\r\n\r\n`)
48
- let buf = Buffer.alloc(0)
49
- sock.on('data', function onData(chunk) {
50
- buf = Buffer.concat([buf, chunk])
51
- const i = buf.indexOf('\r\n\r\n')
52
- if (i === -1) return
53
- sock.removeListener('data', onData)
54
- if (!buf.slice(0, i).toString().includes(' 200 ')) {
55
- sock.emit = origEmit
56
- sock.destroy(new Error(`CONNECT ${host}:${port} failed: ${buf.slice(0, buf.indexOf('\r\n')).toString()}`))
57
- return
58
- }
59
- const rest = buf.slice(i + 4)
60
- if (rest.length) sock.unshift(rest)
61
- sock.emit = origEmit
62
- origEmit('connect')
63
- })
64
- }
65
-
66
- function patchEmitAndConnect(sock, host, port, connectFn) {
67
- let tunnelReady = false
68
- const origEmit = sock.emit.bind(sock)
69
-
70
- sock.emit = function(type) {
71
- if (type === 'connect' && !tunnelReady) {
72
- tunnelReady = true
73
- setupTunnel(sock, host, port, origEmit)
74
- return false
75
- }
76
- return origEmit.apply(null, arguments)
77
- }
78
-
79
- return connectFn()
80
- }
81
-
82
- // ── patch net.Socket.prototype.connect (Node.js v22+ TLS / undici) ───────────
83
- const _origSocketConnect = net.Socket.prototype.connect
84
-
85
- net.Socket.prototype.connect = function(options) {
86
- const isObj = options !== null && typeof options === 'object'
87
- const host = isObj ? String(options.host || options.hostname || '') : String(options || '')
88
- const port = isObj ? Number(options.port || 0) : Number(arguments[1] || 0)
89
-
90
- const action = shouldProxy(host, port)
91
- if (!action) return _origSocketConnect.apply(this, arguments)
92
- if (action === 'block') {
93
- // 阻断直连 Anthropic,触发连接失败,Claude Code 会回退到 ANTHROPIC_BASE_URL
94
- const sock = this
95
- process.nextTick(() => sock.destroy(new Error(`ECONNREFUSED: blocked direct connection to ${host}`)))
96
- return sock
97
- }
98
-
99
- const sock = this
100
- const proxyOpts = isObj
101
- ? { ...options, host: PROXY_HOST, port: PROXY_PORT }
102
- : { host: PROXY_HOST, port: PROXY_PORT }
103
-
104
- return patchEmitAndConnect(sock, host, port, () =>
105
- _origSocketConnect.call(sock, proxyOpts)
106
- )
107
- }
108
-
109
- // ── patch net.createConnection (兼容直接调用的旧代码) ─────────────────────────
110
- const _origCreate = net.createConnection
111
-
112
- function proxied(options, cb) {
113
- const isObj = options !== null && typeof options === 'object'
114
- const host = isObj ? String(options.host || options.hostname || 'localhost') : String(options || 'localhost')
115
- const port = isObj ? Number(options.port || 0) : Number(arguments[1] || 0)
116
-
117
- const action = shouldProxy(host, port)
118
- if (!action) return _origCreate.apply(this, arguments)
119
- if (action === 'block') {
120
- const sock = new net.Socket()
121
- process.nextTick(() => sock.destroy(new Error(`ECONNREFUSED: blocked direct connection to ${host}`)))
122
- return sock
123
- }
124
-
125
- const sock = _origCreate({ host: PROXY_HOST, port: PROXY_PORT })
126
- if (typeof cb === 'function') sock.once('connect', cb)
127
-
128
- let tunnelReady = false
129
- const origEmit = sock.emit.bind(sock)
130
- sock.emit = function(type) {
131
- if (type === 'connect' && !tunnelReady) {
132
- tunnelReady = true
133
- setupTunnel(sock, host, port, origEmit)
134
- return false
135
- }
136
- return origEmit.apply(null, arguments)
137
- }
138
-
139
- return sock
140
- }
141
-
142
- net.createConnection = net.connect = proxied
@@ -1,54 +0,0 @@
1
- /**
2
- * 本地配置管理 — 存储 API Key 和用户偏好
3
- * 配置文件: ~/.holysheep/config.json
4
- */
5
- const fs = require('fs')
6
- const path = require('path')
7
- const os = require('os')
8
-
9
- const CONFIG_DIR = path.join(os.homedir(), '.holysheep')
10
- const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json')
11
-
12
- const BASE_URL_ANTHROPIC = 'https://api.holysheep.ai' // 不带 /v1 (Anthropic SDK)
13
- const BASE_URL_OPENAI = 'https://api.holysheep.ai/v1' // 带 /v1 (OpenAI 兼容)
14
- const BASE_URL_CLAUDE_RELAY = process.env.HOLYSHEEP_CLAUDE_RELAY_URL || 'https://api.holysheep.ai/claude-relay'
15
- const SHOP_URL = 'https://holysheep.ai'
16
-
17
- function ensureDir() {
18
- if (!fs.existsSync(CONFIG_DIR)) {
19
- fs.mkdirSync(CONFIG_DIR, { recursive: true })
20
- }
21
- }
22
-
23
- function loadConfig() {
24
- try {
25
- if (fs.existsSync(CONFIG_FILE)) {
26
- return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'))
27
- }
28
- } catch {}
29
- return {}
30
- }
31
-
32
- function saveConfig(data) {
33
- ensureDir()
34
- const current = loadConfig()
35
- const merged = { ...current, ...data }
36
- fs.writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2))
37
- return merged
38
- }
39
-
40
- function getApiKey() {
41
- return loadConfig().apiKey || process.env.HOLYSHEEP_API_KEY || ''
42
- }
43
-
44
- module.exports = {
45
- CONFIG_DIR,
46
- CONFIG_FILE,
47
- BASE_URL_ANTHROPIC,
48
- BASE_URL_OPENAI,
49
- BASE_URL_CLAUDE_RELAY,
50
- SHOP_URL,
51
- loadConfig,
52
- saveConfig,
53
- getApiKey,
54
- }