@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,354 +0,0 @@
1
- /**
2
- * Hermes Agent (Nous Research) 适配器
3
- * Project: https://github.com/NousResearch/hermes-agent
4
- *
5
- * Hermes 是 Python 项目,上游推荐用 uv 安装:
6
- * curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash -s -- --skip-setup
7
- *
8
- * 配置文件:~/.hermes/config.toml(Python 的 TOML 风格)。我们注入一个
9
- * [providers.holysheep] block 并把 default_provider 设成 holysheep,让
10
- * `hermes` 启动后默认使用 HolySheep。
11
- *
12
- * 写 TOML 是侵入式操作,宁可保守:
13
- * 1. 如果文件不存在,整个写一个最小可用的 HolySheep-only 配置。
14
- * 2. 如果已存在,只替换 `[providers.holysheep]` 段和一个 `default_provider = "..."`
15
- * 行,其余内容(用户自己加的 other providers / tools / memory 配置)原样保留。
16
- *
17
- * Windows:官方安装脚本(bash + uv)不支持原生 Windows。hs setup 里我们把
18
- * `installCmd` 返回 manual 标记,引导用户到 WSL2。
19
- */
20
- const fs = require('fs')
21
- const path = require('path')
22
- const os = require('os')
23
- const { commandExists } = require('../utils/which')
24
-
25
- const CONFIG_DIR = path.join(os.homedir(), '.hermes')
26
- const CONFIG_FILE = path.join(CONFIG_DIR, 'config.toml')
27
- // [HolySheep fork v2.1.28 / hs21] Hermes's agent runtime (the `hermes acp`
28
- // process) reads ~/.hermes/config.yaml — NOT config.toml — for `model.base_url`
29
- // and `providers.*`. The TOML is only used by the hermes CLI wrapper for
30
- // provider selection; the actual outbound HTTP requests come out of config.yaml.
31
- //
32
- // Historically a user or earlier setup script wrote `model.base_url:
33
- // http://127.0.0.1:18788/v1` into config.yaml (that's the OpenClaw local
34
- // bridge port). If AionUi is running but the user isn't using OpenClaw,
35
- // :18788 isn't listening, so every hermes prompt hangs on `provider=custom`
36
- // connection until the AcpAgentManager fallback fires and fabricates an
37
- // empty end_turn. User-visible symptom: hermes "thinks forever" then emits
38
- // no response.
39
- //
40
- // Fix: on every hermes.configure(), also rewrite config.yaml's
41
- // `model.base_url` + api_key + providers.custom to point at HolySheep
42
- // directly. This is idempotent and preserves every other config.yaml
43
- // section (toolsets, agent, memory, ...).
44
- const CONFIG_YAML = path.join(CONFIG_DIR, 'config.yaml')
45
-
46
- const PROVIDER_SECTION_RE = /^\[providers\.holysheep\]\s*$([\s\S]*?)(?=^\[|\Z)/m
47
- const DEFAULT_PROVIDER_RE = /^default_provider\s*=\s*"[^"]*"\s*$/m
48
-
49
- function readConfig() {
50
- try {
51
- if (fs.existsSync(CONFIG_FILE)) {
52
- return fs.readFileSync(CONFIG_FILE, 'utf8')
53
- }
54
- } catch {}
55
- return ''
56
- }
57
-
58
- // [HolySheep fork v2.1.28 / hs21] Minimal YAML patcher for ~/.hermes/config.yaml.
59
- //
60
- // We don't want to pull a full YAML dependency into the holysheep-cli bundle,
61
- // so we do targeted line-level rewrites:
62
- // - model.base_url : overwritten
63
- // - model.api_key : inserted if absent, else overwritten
64
- // - model.default : overwritten (only if primaryModel provided)
65
- // - providers : if currently `{}` or missing, write an explicit
66
- // `custom` provider pointing at HolySheep
67
- //
68
- // Everything else in config.yaml (toolsets, agent, memory, summary_*, ...)
69
- // is preserved verbatim.
70
- function patchConfigYaml(apiKey, baseUrlOpenAI, primaryModel) {
71
- if (!fs.existsSync(CONFIG_YAML)) {
72
- // If the user never ran `hermes` (so no config.yaml), do nothing —
73
- // hermes creates one on first launch; our next `hs setup` will patch it.
74
- return
75
- }
76
- const cleanBase = String(baseUrlOpenAI || 'https://api.holysheep.ai/v1').replace(/\/+$/, '')
77
- const model = primaryModel || 'claude-sonnet-4-6'
78
- const src = fs.readFileSync(CONFIG_YAML, 'utf8')
79
- const lines = src.replace(/\r\n/g, '\n').split('\n')
80
- const out = []
81
- let inModel = false
82
- let modelBlockLines = []
83
- let modelIndent = ' '
84
- let modelBlockStart = -1
85
- // First pass: find model: block
86
- for (let i = 0; i < lines.length; i++) {
87
- const line = lines[i]
88
- if (/^model:\s*$/.test(line)) {
89
- out.push(line)
90
- inModel = true
91
- modelBlockStart = out.length
92
- modelBlockLines = []
93
- continue
94
- }
95
- if (inModel) {
96
- // Model section ends at next top-level key (non-indented non-blank) or EOF
97
- if (line.length > 0 && !/^\s/.test(line)) {
98
- // finalize model block first
99
- out.push(...rewriteModelBlock(modelBlockLines, cleanBase, apiKey, model, modelIndent))
100
- inModel = false
101
- out.push(line)
102
- continue
103
- }
104
- modelBlockLines.push(line)
105
- // detect indent from first key-line
106
- const m = line.match(/^(\s+)[^\s]/)
107
- if (m && modelBlockLines.length === 1) modelIndent = m[1]
108
- continue
109
- }
110
- out.push(line)
111
- }
112
- if (inModel) {
113
- // File ended inside model block
114
- out.push(...rewriteModelBlock(modelBlockLines, cleanBase, apiKey, model, modelIndent))
115
- }
116
- // Second pass: rewrite the `providers:` block so `custom` always points
117
- // at HolySheep. We locate the block by line offset so we can replace the
118
- // entire block regardless of whether it's inline `{}`, empty, or a
119
- // pre-existing nested map (which would otherwise duplicate `custom:`).
120
- const resultLines = out.join('\n').split('\n')
121
- const providersBlock = [
122
- 'providers:',
123
- ' custom:',
124
- ` base_url: ${cleanBase}`,
125
- ` api_key: ${apiKey}`,
126
- ` default_model: ${model}`,
127
- ' type: openai',
128
- ]
129
- let start = -1, end = -1
130
- for (let i = 0; i < resultLines.length; i++) {
131
- if (/^providers:\s*($|\{\s*\}\s*$)/.test(resultLines[i])) { start = i; break }
132
- }
133
- if (start >= 0) {
134
- // Find end = next top-level key or EOF (preserve whitespace-only lines inside)
135
- end = resultLines.length
136
- for (let j = start + 1; j < resultLines.length; j++) {
137
- const ln = resultLines[j]
138
- if (ln.length > 0 && !/^\s/.test(ln)) { end = j; break }
139
- }
140
- resultLines.splice(start, end - start, ...providersBlock)
141
- }
142
- // Third pass: hermes 0.8 also has a `custom_providers:` LIST (different
143
- // from the `providers:` map). Line-by-line patch each item's base_url/api_key
144
- // whose `name:` includes "holysheep" (case-insensitive) or whose base_url
145
- // pointed at the stale OpenClaw bridge :18788.
146
- const finalLines = resultLines.slice()
147
- let inCustom = false
148
- let itemStart = -1
149
- let itemLines = []
150
- const flushItem = () => {
151
- if (itemStart < 0) return
152
- const blob = itemLines.join('\n')
153
- const isHolySheep = /name:\s*holysheep/i.test(blob) || /base_url:\s*http:\/\/127\.0\.0\.1:18788/.test(blob)
154
- if (isHolySheep) {
155
- for (let i = 0; i < itemLines.length; i++) {
156
- if (/^\s+-?\s*base_url:/.test(itemLines[i])) {
157
- itemLines[i] = itemLines[i].replace(/base_url:.*$/, `base_url: ${cleanBase}`)
158
- } else if (/^\s+api_key:/.test(itemLines[i])) {
159
- itemLines[i] = itemLines[i].replace(/api_key:.*$/, `api_key: ${apiKey}`)
160
- }
161
- }
162
- finalLines.splice(itemStart, itemLines.length, ...itemLines)
163
- }
164
- itemStart = -1
165
- itemLines = []
166
- }
167
- for (let i = 0; i < finalLines.length; i++) {
168
- const ln = finalLines[i]
169
- if (/^custom_providers:\s*$/.test(ln)) { inCustom = true; continue }
170
- if (inCustom) {
171
- // End of custom_providers: next top-level key (non-dash, non-indent, non-blank)
172
- if (ln.length > 0 && !/^\s/.test(ln) && !/^-/.test(ln)) { flushItem(); inCustom = false; continue }
173
- // New list item starts with dash at any indentation
174
- if (/^\s*-\s/.test(ln)) { flushItem(); itemStart = i; itemLines = [ln]; continue }
175
- if (itemStart >= 0) itemLines.push(ln)
176
- }
177
- }
178
- if (inCustom) flushItem()
179
- fs.writeFileSync(CONFIG_YAML, finalLines.join('\n'), { encoding: 'utf8', mode: 0o600 })
180
- }
181
-
182
- function rewriteModelBlock(blockLines, cleanBase, apiKey, model, indent) {
183
- let sawBase = false, sawKey = false, sawDefault = false
184
- const rewritten = []
185
- for (const line of blockLines) {
186
- if (/^\s*base_url\s*:/.test(line)) {
187
- rewritten.push(`${indent}base_url: ${cleanBase}`)
188
- sawBase = true
189
- continue
190
- }
191
- if (/^\s*api_key\s*:/.test(line)) {
192
- rewritten.push(`${indent}api_key: ${apiKey}`)
193
- sawKey = true
194
- continue
195
- }
196
- if (/^\s*default\s*:/.test(line)) {
197
- rewritten.push(`${indent}default: ${model}`)
198
- sawDefault = true
199
- continue
200
- }
201
- rewritten.push(line)
202
- }
203
- // Append missing keys at the end of the model block
204
- // (preserve trailing blank line if present)
205
- let insertAt = rewritten.length
206
- while (insertAt > 0 && rewritten[insertAt - 1].trim() === '') insertAt--
207
- const toInsert = []
208
- if (!sawBase) toInsert.push(`${indent}base_url: ${cleanBase}`)
209
- if (!sawKey) toInsert.push(`${indent}api_key: ${apiKey}`)
210
- if (!sawDefault) toInsert.push(`${indent}default: ${model}`)
211
- if (toInsert.length) rewritten.splice(insertAt, 0, ...toInsert)
212
- return rewritten
213
- }
214
-
215
- function writeConfig(content) {
216
- if (!fs.existsSync(CONFIG_DIR)) {
217
- fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 })
218
- }
219
- fs.writeFileSync(CONFIG_FILE, content, { encoding: 'utf8', mode: 0o600 })
220
- if (process.platform !== 'win32') {
221
- try { fs.chmodSync(CONFIG_FILE, 0o600) } catch {}
222
- }
223
- }
224
-
225
- /** Build the [providers.holysheep] block + default_provider pointing at it. */
226
- function buildHolySheepBlock(apiKey, baseUrlOpenAI, primaryModel) {
227
- const cleanBase = String(baseUrlOpenAI || 'https://api.holysheep.ai/v1').replace(/\/+$/, '')
228
- const model = primaryModel || 'gpt-5.4'
229
- return [
230
- '[providers.holysheep]',
231
- '# Managed by @simonyea/holysheep-cli. Do not edit by hand — run `hs setup`.',
232
- 'type = "openai"',
233
- `base_url = "${cleanBase}"`,
234
- `api_key = "${apiKey}"`,
235
- `default_model = "${model}"`,
236
- '',
237
- ].join('\n')
238
- }
239
-
240
- /**
241
- * Strip a previous HolySheep-managed block from the TOML content. Everything
242
- * else the user wrote is preserved verbatim. Non-TOML-aware line splicing —
243
- * the hermes config uses standard TOML sections which are blank-line separated,
244
- * so detecting a section header that starts with `[…]` is sufficient.
245
- */
246
- function stripExisting(content) {
247
- const lines = content.replace(/\r\n/g, '\n').split('\n')
248
- const out = []
249
- let inBlock = false
250
- for (const line of lines) {
251
- if (/^\[providers\.holysheep\]\s*$/.test(line)) {
252
- inBlock = true
253
- continue
254
- }
255
- if (inBlock) {
256
- if (/^\[[^\]]+\]\s*$/.test(line)) {
257
- inBlock = false
258
- out.push(line)
259
- continue
260
- }
261
- continue
262
- }
263
- out.push(line)
264
- }
265
- return out.join('\n').replace(/\n{3,}/g, '\n\n').trim()
266
- }
267
-
268
- function mergeConfig(apiKey, baseUrlOpenAI, primaryModel) {
269
- const existing = stripExisting(readConfig())
270
- const block = buildHolySheepBlock(apiKey, baseUrlOpenAI, primaryModel)
271
-
272
- // Replace an existing `default_provider` line, or insert one near the top.
273
- let withDefault = existing
274
- if (DEFAULT_PROVIDER_RE.test(withDefault)) {
275
- withDefault = withDefault.replace(DEFAULT_PROVIDER_RE, 'default_provider = "holysheep"')
276
- } else {
277
- withDefault = `default_provider = "holysheep"\n` + withDefault
278
- }
279
-
280
- const final = [withDefault.trim(), '', block].filter(Boolean).join('\n')
281
- return final.endsWith('\n') ? final : final + '\n'
282
- }
283
-
284
- function isConfiguredInToml(content) {
285
- if (!content) return false
286
- return /\[providers\.holysheep\]/.test(content) &&
287
- /api\.holysheep\.ai/.test(content) &&
288
- /api_key\s*=\s*"cr_/.test(content)
289
- }
290
-
291
- function installCmdForPlatform() {
292
- if (process.platform === 'win32') {
293
- return '' // handled as manual in server.js
294
- }
295
- return 'curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash -s -- --skip-setup'
296
- }
297
-
298
- module.exports = {
299
- name: 'Hermes Agent',
300
- id: 'hermes',
301
- checkInstalled() {
302
- return commandExists('hermes')
303
- },
304
- isConfigured() {
305
- return isConfiguredInToml(readConfig())
306
- },
307
- configure(apiKey, _baseUrlAnthropic, baseUrlOpenAI, primaryModel /*, _selectedModels */) {
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)
322
- writeConfig(merged)
323
- // [HolySheep fork v2.1.28 / hs21] Also patch config.yaml so the actual
324
- // agent runtime uses the right base_url. See CONFIG_YAML comment.
325
- try {
326
- patchConfigYaml(apiKey, baseUrlOpenAI, hermesPrimaryModel)
327
- } catch (e) {
328
- // Non-fatal: hermes can still run on config.toml alone if the user
329
- // manually edits config.yaml. Surface via stderr for diagnosability.
330
- try { process.stderr.write(`[hermes.configure] config.yaml patch skipped: ${e.message}\n`) } catch {}
331
- }
332
- return {
333
- file: CONFIG_FILE,
334
- hot: true, // next `hermes` run picks up the TOML on load
335
- }
336
- },
337
- reset() {
338
- const existing = readConfig()
339
- if (!existing) return
340
- const stripped = stripExisting(existing).replace(DEFAULT_PROVIDER_RE, '').replace(/\n{3,}/g, '\n\n').trim()
341
- writeConfig(stripped ? stripped + '\n' : '')
342
- },
343
- getConfigPath() { return CONFIG_FILE },
344
- hint: process.platform === 'win32'
345
- ? 'Hermes 官方安装脚本仅支持 macOS/Linux/WSL2,Windows 请先启用 WSL2。'
346
- : '已写入 ~/.hermes/config.toml;运行 `hermes` 默认使用 HolySheep。',
347
- launchCmd: 'hermes',
348
- installCmd: installCmdForPlatform(),
349
- docsUrl: 'https://hermes-agent.nousresearch.com/docs/',
350
- // Internal helpers — re-exported for tests / inspection.
351
- _stripExisting: stripExisting,
352
- _mergeConfig: mergeConfig,
353
- _buildHolySheepBlock: buildHolySheepBlock,
354
- }
@@ -1,13 +0,0 @@
1
- /**
2
- * 所有工具适配器的统一入口
3
- */
4
- module.exports = [
5
- require('./claude-code'),
6
- require('./codex'),
7
- require('./droid'),
8
- require('./opencode'),
9
- require('./openclaw'),
10
- // 2.1.14: Hermes Agent (Nous Research) — Python/uv based, macOS/Linux/WSL2 only.
11
- require('./hermes'),
12
- require('./env-config'),
13
- ]