@simonyea/holysheep-cli 1.0.3 → 1.0.5
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 +1 -1
- package/src/commands/setup.js +10 -1
- package/src/tools/aider.js +1 -4
- package/src/tools/claude-code.js +1 -7
- package/src/tools/codex.js +55 -79
- package/src/tools/gemini-cli.js +1 -4
- package/src/tools/openclaw.js +1 -4
- package/src/tools/opencode.js +1 -4
- package/src/utils/which.js +21 -0
package/package.json
CHANGED
package/src/commands/setup.js
CHANGED
|
@@ -36,10 +36,19 @@ async function tryAutoInstall(tool) {
|
|
|
36
36
|
|
|
37
37
|
const spinner = ora(`正在安装 ${tool.name}...`).start()
|
|
38
38
|
try {
|
|
39
|
-
spawnSync(info.cmd.split(' ')[0], info.cmd.split(' ').slice(1), {
|
|
39
|
+
const ret = spawnSync(info.cmd.split(' ')[0], info.cmd.split(' ').slice(1), {
|
|
40
40
|
stdio: 'inherit',
|
|
41
41
|
shell: true,
|
|
42
42
|
})
|
|
43
|
+
if (ret.status !== 0) {
|
|
44
|
+
spinner.fail(`安装失败,请手动运行: ${chalk.cyan(info.cmd)}`)
|
|
45
|
+
return false
|
|
46
|
+
}
|
|
47
|
+
// Windows PATH 在当前进程内不会刷新,安装成功即认为可用
|
|
48
|
+
if (process.platform === 'win32') {
|
|
49
|
+
spinner.succeed(`${tool.name} 安装完成(请重新开一个终端窗口后运行 hs setup 完成配置)`)
|
|
50
|
+
return false // 返回 false 跳过配置,让用户重开终端再来
|
|
51
|
+
}
|
|
43
52
|
// 安装后重新检测
|
|
44
53
|
if (tool.checkInstalled()) {
|
|
45
54
|
spinner.succeed(`${tool.name} 安装成功`)
|
package/src/tools/aider.js
CHANGED
|
@@ -38,10 +38,7 @@ module.exports = {
|
|
|
38
38
|
name: 'Aider',
|
|
39
39
|
id: 'aider',
|
|
40
40
|
checkInstalled() {
|
|
41
|
-
|
|
42
|
-
require('child_process').execSync('which aider', { stdio: 'ignore' })
|
|
43
|
-
return true
|
|
44
|
-
} catch { return false }
|
|
41
|
+
return require('../utils/which').commandExists('aider')
|
|
45
42
|
},
|
|
46
43
|
isConfigured() {
|
|
47
44
|
const content = readConfig()
|
package/src/tools/claude-code.js
CHANGED
|
@@ -34,13 +34,7 @@ module.exports = {
|
|
|
34
34
|
name: 'Claude Code',
|
|
35
35
|
id: 'claude-code',
|
|
36
36
|
checkInstalled() {
|
|
37
|
-
|
|
38
|
-
try {
|
|
39
|
-
require('child_process').execSync('which claude', { stdio: 'ignore' })
|
|
40
|
-
return true
|
|
41
|
-
} catch {
|
|
42
|
-
return false
|
|
43
|
-
}
|
|
37
|
+
return require('../utils/which').commandExists('claude')
|
|
44
38
|
},
|
|
45
39
|
isConfigured() {
|
|
46
40
|
const s = readSettings()
|
package/src/tools/codex.js
CHANGED
|
@@ -1,117 +1,93 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* 配置文件: ~/.codex/config.yaml 或 ~/.codex/config.json
|
|
4
|
-
* 环境变量: OPENAI_API_KEY, OPENAI_BASE_URL
|
|
2
|
+
* Codex CLI 适配器 (@openai/codex v0.46+)
|
|
5
3
|
*
|
|
6
|
-
*
|
|
7
|
-
* providers:
|
|
8
|
-
* - name: HolySheep
|
|
9
|
-
* baseURL: https://api.holysheep.ai/v1
|
|
10
|
-
* envKey: OPENAI_API_KEY
|
|
4
|
+
* 配置文件: ~/.codex/config.json(JSON 格式,不是 yaml)
|
|
11
5
|
*
|
|
12
|
-
*
|
|
6
|
+
* 正确格式:
|
|
7
|
+
* {
|
|
8
|
+
* "model": "claude-sonnet-4-5",
|
|
9
|
+
* "provider": "holysheep", // 指定默认 provider
|
|
10
|
+
* "providers": {
|
|
11
|
+
* "holysheep": {
|
|
12
|
+
* "name": "HolySheep",
|
|
13
|
+
* "baseURL": "https://api.holysheep.ai/v1",
|
|
14
|
+
* "envKey": "OPENAI_API_KEY"
|
|
15
|
+
* }
|
|
16
|
+
* }
|
|
17
|
+
* }
|
|
18
|
+
*
|
|
19
|
+
* 环境变量: OPENAI_API_KEY(通过 envKey 指定)
|
|
20
|
+
* 注意: Codex 会优先使用账号登录,需要设置 provider 才能绕过
|
|
13
21
|
*/
|
|
14
|
-
const fs
|
|
22
|
+
const fs = require('fs')
|
|
15
23
|
const path = require('path')
|
|
16
|
-
const os
|
|
24
|
+
const os = require('os')
|
|
17
25
|
|
|
18
26
|
const CONFIG_DIR = path.join(os.homedir(), '.codex')
|
|
19
|
-
const
|
|
20
|
-
const CONFIG_JSON = path.join(CONFIG_DIR, 'config.json')
|
|
27
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json')
|
|
21
28
|
|
|
22
29
|
function readConfig() {
|
|
23
30
|
try {
|
|
24
|
-
if (fs.existsSync(
|
|
25
|
-
|
|
31
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
32
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'))
|
|
33
|
+
}
|
|
26
34
|
} catch {}
|
|
27
|
-
return {
|
|
35
|
+
return {}
|
|
28
36
|
}
|
|
29
37
|
|
|
30
|
-
function
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const result = []
|
|
34
|
-
let skip = false
|
|
35
|
-
for (const line of lines) {
|
|
36
|
-
if (line.includes('HolySheep') || line.includes('holysheep')) {
|
|
37
|
-
skip = true
|
|
38
|
-
// 也移除上一行的 `- name:` 前缀
|
|
39
|
-
if (result.length && result[result.length - 1].trim().startsWith('- name:')) {
|
|
40
|
-
result.pop()
|
|
41
|
-
}
|
|
42
|
-
continue
|
|
43
|
-
}
|
|
44
|
-
if (skip && (line.startsWith(' ') || line.trim() === '')) {
|
|
45
|
-
if (line.trim() === '') skip = false
|
|
46
|
-
continue
|
|
47
|
-
}
|
|
48
|
-
skip = false
|
|
49
|
-
result.push(line)
|
|
50
|
-
}
|
|
51
|
-
return result.join('\n')
|
|
38
|
+
function writeConfig(data) {
|
|
39
|
+
if (!fs.existsSync(CONFIG_DIR)) fs.mkdirSync(CONFIG_DIR, { recursive: true })
|
|
40
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(data, null, 2), 'utf8')
|
|
52
41
|
}
|
|
53
42
|
|
|
54
43
|
module.exports = {
|
|
55
44
|
name: 'Codex CLI',
|
|
56
45
|
id: 'codex',
|
|
57
46
|
checkInstalled() {
|
|
58
|
-
|
|
59
|
-
require('child_process').execSync('which codex', { stdio: 'ignore' })
|
|
60
|
-
return true
|
|
61
|
-
} catch { return false }
|
|
47
|
+
return require('../utils/which').commandExists('codex')
|
|
62
48
|
},
|
|
63
49
|
isConfigured() {
|
|
64
|
-
const
|
|
65
|
-
return
|
|
50
|
+
const c = readConfig()
|
|
51
|
+
return c.provider === 'holysheep' &&
|
|
52
|
+
!!c.providers?.holysheep?.baseURL?.includes('holysheep')
|
|
66
53
|
},
|
|
67
|
-
configure(apiKey, baseUrlOpenAI) {
|
|
68
|
-
|
|
54
|
+
configure(apiKey, _baseUrlAnthropicNoV1, baseUrlOpenAI) {
|
|
55
|
+
const config = readConfig()
|
|
69
56
|
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
content = fs.readFileSync(CONFIG_YAML, 'utf8')
|
|
74
|
-
content = removeHolysheepProvider(content)
|
|
75
|
-
}
|
|
57
|
+
// 设置 HolySheep 为默认 provider
|
|
58
|
+
config.provider = 'holysheep'
|
|
59
|
+
config.model = config.model || 'claude-sonnet-4-5'
|
|
76
60
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
baseURL: ${baseUrlOpenAI}
|
|
83
|
-
envKey: OPENAI_API_KEY
|
|
84
|
-
model: claude-sonnet-4-5
|
|
85
|
-
`
|
|
86
|
-
// 如果已有 providers 块,改为追加到列表
|
|
87
|
-
if (content.includes('providers:')) {
|
|
88
|
-
content = content.replace('providers:', `providers:\n - name: HolySheep\n baseURL: ${baseUrlOpenAI}\n envKey: OPENAI_API_KEY`)
|
|
89
|
-
} else {
|
|
90
|
-
content += providerBlock
|
|
61
|
+
if (!config.providers) config.providers = {}
|
|
62
|
+
config.providers.holysheep = {
|
|
63
|
+
name: 'HolySheep',
|
|
64
|
+
baseURL: baseUrlOpenAI, // https://api.holysheep.ai/v1
|
|
65
|
+
envKey: 'OPENAI_API_KEY',
|
|
91
66
|
}
|
|
92
67
|
|
|
93
|
-
|
|
68
|
+
writeConfig(config)
|
|
94
69
|
|
|
95
|
-
// 同时写入环境变量(Codex 通过 envKey 读取)
|
|
96
70
|
return {
|
|
97
|
-
file:
|
|
98
|
-
hot:
|
|
71
|
+
file: CONFIG_FILE,
|
|
72
|
+
hot: false,
|
|
73
|
+
// 需要同时设置环境变量,供 envKey 读取
|
|
99
74
|
envVars: {
|
|
100
|
-
OPENAI_API_KEY:
|
|
101
|
-
OPENAI_BASE_URL:
|
|
75
|
+
OPENAI_API_KEY: apiKey,
|
|
76
|
+
OPENAI_BASE_URL: baseUrlOpenAI,
|
|
102
77
|
},
|
|
103
78
|
}
|
|
104
79
|
},
|
|
105
80
|
reset() {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
81
|
+
const config = readConfig()
|
|
82
|
+
if (config.provider === 'holysheep') {
|
|
83
|
+
delete config.provider
|
|
84
|
+
delete config.providers?.holysheep
|
|
110
85
|
}
|
|
86
|
+
writeConfig(config)
|
|
111
87
|
},
|
|
112
|
-
getConfigPath() { return
|
|
113
|
-
hint: '
|
|
88
|
+
getConfigPath() { return CONFIG_FILE },
|
|
89
|
+
hint: '切换后重开终端生效;用 codex --provider holysheep 指定',
|
|
114
90
|
installCmd: 'npm install -g @openai/codex',
|
|
115
91
|
docsUrl: 'https://github.com/openai/codex',
|
|
116
|
-
envVarFormat: 'openai',
|
|
92
|
+
envVarFormat: 'openai',
|
|
117
93
|
}
|
package/src/tools/gemini-cli.js
CHANGED
|
@@ -38,10 +38,7 @@ module.exports = {
|
|
|
38
38
|
name: 'Gemini CLI',
|
|
39
39
|
id: 'gemini-cli',
|
|
40
40
|
checkInstalled() {
|
|
41
|
-
|
|
42
|
-
require('child_process').execSync('which gemini', { stdio: 'ignore' })
|
|
43
|
-
return true
|
|
44
|
-
} catch { return false }
|
|
41
|
+
return require('../utils/which').commandExists('gemini')
|
|
45
42
|
},
|
|
46
43
|
isConfigured() {
|
|
47
44
|
const s = readSettings()
|
package/src/tools/openclaw.js
CHANGED
|
@@ -44,10 +44,7 @@ module.exports = {
|
|
|
44
44
|
name: 'OpenClaw',
|
|
45
45
|
id: 'openclaw',
|
|
46
46
|
checkInstalled() {
|
|
47
|
-
|
|
48
|
-
require('child_process').execSync('which openclaw', { stdio: 'ignore' })
|
|
49
|
-
return true
|
|
50
|
-
} catch { return false }
|
|
47
|
+
return require('../utils/which').commandExists('openclaw')
|
|
51
48
|
},
|
|
52
49
|
isConfigured() {
|
|
53
50
|
const file = getSettingsFile()
|
package/src/tools/opencode.js
CHANGED
|
@@ -53,10 +53,7 @@ module.exports = {
|
|
|
53
53
|
name: 'OpenCode',
|
|
54
54
|
id: 'opencode',
|
|
55
55
|
checkInstalled() {
|
|
56
|
-
|
|
57
|
-
require('child_process').execSync('which opencode', { stdio: 'ignore' })
|
|
58
|
-
return true
|
|
59
|
-
} catch { return false }
|
|
56
|
+
return require('../utils/which').commandExists('opencode')
|
|
60
57
|
},
|
|
61
58
|
isConfigured() {
|
|
62
59
|
const c = readConfig()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 跨平台检测命令是否存在
|
|
3
|
+
* Windows 用 where,Unix 用 which,兜底用 --version
|
|
4
|
+
*/
|
|
5
|
+
const { execSync } = require('child_process')
|
|
6
|
+
|
|
7
|
+
function commandExists(cmd) {
|
|
8
|
+
const finder = process.platform === 'win32' ? `where ${cmd}` : `which ${cmd}`
|
|
9
|
+
try {
|
|
10
|
+
execSync(finder, { stdio: 'ignore' })
|
|
11
|
+
return true
|
|
12
|
+
} catch {
|
|
13
|
+
// 兜底:直接跑 --version
|
|
14
|
+
try {
|
|
15
|
+
execSync(`${cmd} --version`, { stdio: 'ignore', timeout: 3000 })
|
|
16
|
+
return true
|
|
17
|
+
} catch { return false }
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = { commandExists }
|