@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simonyea/holysheep-cli",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "一键配置所有 AI 编程工具接入 HolySheep API — Claude Code / Codex / Gemini CLI / OpenCode / OpenClaw / Aider / Cursor",
5
5
  "keywords": [
6
6
  "claude",
@@ -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} 安装成功`)
@@ -38,10 +38,7 @@ module.exports = {
38
38
  name: 'Aider',
39
39
  id: 'aider',
40
40
  checkInstalled() {
41
- try {
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()
@@ -34,13 +34,7 @@ module.exports = {
34
34
  name: 'Claude Code',
35
35
  id: 'claude-code',
36
36
  checkInstalled() {
37
- // 检查是否安装了 claude CLI
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()
@@ -1,117 +1,93 @@
1
1
  /**
2
- * OpenAI Codex CLI 适配器
3
- * 配置文件: ~/.codex/config.yaml 或 ~/.codex/config.json
4
- * 环境变量: OPENAI_API_KEY, OPENAI_BASE_URL
2
+ * Codex CLI 适配器 (@openai/codex v0.46+)
5
3
  *
6
- * Codex 支持 custom provider 配置:
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
- * 注意: Codex 用 OpenAI 兼容格式,baseURL 需带 /v1
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 = require('fs')
22
+ const fs = require('fs')
15
23
  const path = require('path')
16
- const os = require('os')
24
+ const os = require('os')
17
25
 
18
26
  const CONFIG_DIR = path.join(os.homedir(), '.codex')
19
- const CONFIG_YAML = path.join(CONFIG_DIR, 'config.yaml')
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(CONFIG_YAML)) return { type: 'yaml', content: fs.readFileSync(CONFIG_YAML, 'utf8') }
25
- if (fs.existsSync(CONFIG_JSON)) return { type: 'json', content: fs.readFileSync(CONFIG_JSON, 'utf8') }
31
+ if (fs.existsSync(CONFIG_FILE)) {
32
+ return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'))
33
+ }
26
34
  } catch {}
27
- return { type: 'yaml', content: '' }
35
+ return {}
28
36
  }
29
37
 
30
- function removeHolysheepProvider(yamlContent) {
31
- // 简单移除已有的 holysheep provider
32
- const lines = yamlContent.split('\n')
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
- try {
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 { content } = readConfig()
65
- return content.includes('holysheep') || content.includes('HolySheep')
50
+ const c = readConfig()
51
+ return c.provider === 'holysheep' &&
52
+ !!c.providers?.holysheep?.baseURL?.includes('holysheep')
66
53
  },
67
- configure(apiKey, baseUrlOpenAI) {
68
- if (!fs.existsSync(CONFIG_DIR)) fs.mkdirSync(CONFIG_DIR, { recursive: true })
54
+ configure(apiKey, _baseUrlAnthropicNoV1, baseUrlOpenAI) {
55
+ const config = readConfig()
69
56
 
70
- // 生成 YAML 格式配置(Codex 官方推荐)
71
- let content = ''
72
- if (fs.existsSync(CONFIG_YAML)) {
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
- // 追加 holysheep provider + 设为默认 model provider
78
- const providerBlock = `
79
- # HolySheep API — https://shop.holysheep.ai
80
- providers:
81
- - name: HolySheep
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
- fs.writeFileSync(CONFIG_YAML, content.trim() + '\n', 'utf8')
68
+ writeConfig(config)
94
69
 
95
- // 同时写入环境变量(Codex 通过 envKey 读取)
96
70
  return {
97
- file: CONFIG_YAML,
98
- hot: false,
71
+ file: CONFIG_FILE,
72
+ hot: false,
73
+ // 需要同时设置环境变量,供 envKey 读取
99
74
  envVars: {
100
- OPENAI_API_KEY: apiKey,
101
- OPENAI_BASE_URL: baseUrlOpenAI,
75
+ OPENAI_API_KEY: apiKey,
76
+ OPENAI_BASE_URL: baseUrlOpenAI,
102
77
  },
103
78
  }
104
79
  },
105
80
  reset() {
106
- if (fs.existsSync(CONFIG_YAML)) {
107
- let content = fs.readFileSync(CONFIG_YAML, 'utf8')
108
- content = removeHolysheepProvider(content)
109
- fs.writeFileSync(CONFIG_YAML, content, 'utf8')
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 CONFIG_YAML },
113
- hint: '切换后需重启终端或新开 terminal',
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', // 告知 setup 命令写哪些 env vars
92
+ envVarFormat: 'openai',
117
93
  }
@@ -38,10 +38,7 @@ module.exports = {
38
38
  name: 'Gemini CLI',
39
39
  id: 'gemini-cli',
40
40
  checkInstalled() {
41
- try {
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()
@@ -44,10 +44,7 @@ module.exports = {
44
44
  name: 'OpenClaw',
45
45
  id: 'openclaw',
46
46
  checkInstalled() {
47
- try {
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()
@@ -53,10 +53,7 @@ module.exports = {
53
53
  name: 'OpenCode',
54
54
  id: 'opencode',
55
55
  checkInstalled() {
56
- try {
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 }