@freeclaude/cli 3.0.6 → 3.0.7

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/dist/cli.mjs CHANGED
@@ -8,7 +8,7 @@ import { spawnSync } from 'node:child_process'
8
8
  import { homedir } from 'node:os'
9
9
  import { join } from 'node:path'
10
10
 
11
- const CONFIG_PATH = join(homedir(), '.freeclaude.json')
11
+ const CONFIG_PATH = process.env.FREECLAUDE_CONFIG_PATH || join(homedir(), '.freeclaude.json')
12
12
  const CLAUDE_SETTINGS_PATH = join(homedir(), '.claude', 'settings.json')
13
13
  const WHISPER_MODEL_PATH = join(
14
14
  homedir(),
@@ -18,6 +18,128 @@ const WHISPER_MODEL_PATH = join(
18
18
  'ggml-small.bin',
19
19
  )
20
20
 
21
+ const KNOWN_PROVIDER_DEFINITIONS = [
22
+ { slug: 'zai', baseUrl: 'https://api.z.ai/api/coding/paas/v4', models: ['glm-5', 'glm-4.7-flash', 'glm-4.7'] },
23
+ { slug: 'gemini', baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai', models: ['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.5-flash-lite'] },
24
+ { slug: 'groq', baseUrl: 'https://api.groq.com/openai/v1', models: ['llama-3.3-70b-versatile', 'llama-4-scout-17b-16e-instruct', 'qwen-qwq-32b'] },
25
+ { slug: 'cerebras', baseUrl: 'https://api.cerebras.ai/v1', models: ['llama-4-scout-17b-16e', 'llama3.1-8b', 'qwen-2.5-32b'] },
26
+ { slug: 'siliconflow', baseUrl: 'https://api.siliconflow.cn/v1', models: ['deepseek-ai/DeepSeek-V3', 'Qwen/Qwen3-8B'] },
27
+ { slug: 'qwen', baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1', models: ['qwen3-235b-a22b', 'qwen-max', 'qwen-plus', 'qwen-turbo', 'qwen-coder-plus'] },
28
+ { slug: 'sambanova', baseUrl: 'https://api.sambanova.ai/v1', models: ['Meta-Llama-3.3-70B-Instruct', 'DeepSeek-R1-Distill-Llama-70B'] },
29
+ { slug: 'ollama', baseUrl: 'http://localhost:11434/v1', models: ['qwen2.5:3b', 'qwen2.5:7b', 'llama3.2', 'deepseek-r1:8b'] },
30
+ { slug: 'lmstudio', baseUrl: 'http://localhost:1234/v1', models: ['(auto-detected)'] },
31
+ { slug: 'openai', baseUrl: 'https://api.openai.com/v1', models: ['gpt-4o', 'gpt-4o-mini', 'o3-mini'] },
32
+ { slug: 'deepseek', baseUrl: 'https://api.deepseek.com/v1', models: ['deepseek-chat', 'deepseek-reasoner', 'deepseek-coder'] },
33
+ { slug: 'mistral', baseUrl: 'https://api.mistral.ai/v1', models: ['mistral-large-latest', 'codestral-latest'] },
34
+ { slug: 'openrouter', baseUrl: 'https://openrouter.ai/api/v1', models: ['anthropic/claude-sonnet-4', 'google/gemini-2.5-flash', 'openai/gpt-4o', 'deepseek/deepseek-chat'] },
35
+ { slug: 'together', baseUrl: 'https://api.together.xyz/v1', models: ['meta-llama/Llama-4-Maverick-17B-128E-Instruct', 'Qwen/Qwen3-235B-A22B'] },
36
+ { slug: 'fireworks', baseUrl: 'https://api.fireworks.ai/inference/v1', models: ['accounts/fireworks/models/llama-v4p-70b-instruct'] },
37
+ { slug: 'deepinfra', baseUrl: 'https://api.deepinfra.com/v1/openai', models: ['meta-llama/Llama-4-Maverick-17B-128E-Instruct', 'Qwen/Qwen3-235B-A22B'] },
38
+ { slug: 'perplexity', baseUrl: 'https://api.perplexity.ai', models: ['sonar-pro', 'sonar-reasoning'] },
39
+ { slug: 'huggingface', baseUrl: 'https://api-inference.huggingface.co/v1', models: ['(any HF model ID)'] },
40
+ ]
41
+
42
+ function normalizeBaseUrl(baseUrl) {
43
+ return (baseUrl || '').trim().replace(/\/+$/, '').toLowerCase()
44
+ }
45
+
46
+ function normalizeName(name) {
47
+ return (name || '').trim().toLowerCase()
48
+ }
49
+
50
+ function findKnownProviderDefinition(provider) {
51
+ const baseUrl = normalizeBaseUrl(provider?.baseUrl)
52
+ if (baseUrl) {
53
+ const byBaseUrl = KNOWN_PROVIDER_DEFINITIONS.find(entry => normalizeBaseUrl(entry.baseUrl) === baseUrl)
54
+ if (byBaseUrl) return byBaseUrl
55
+ }
56
+
57
+ const name = normalizeName(provider?.name)
58
+ if (!name) return undefined
59
+ return KNOWN_PROVIDER_DEFINITIONS.find(entry => normalizeName(entry.slug) === name)
60
+ }
61
+
62
+ function resolveConfiguredModel(provider, model) {
63
+ const trimmedModel = typeof model === 'string' ? model.trim() : ''
64
+ const knownProvider = findKnownProviderDefinition(provider)
65
+
66
+ if (!trimmedModel) {
67
+ return knownProvider?.models?.[0]
68
+ }
69
+
70
+ if (!knownProvider) {
71
+ return trimmedModel
72
+ }
73
+
74
+ if (/^\d+$/.test(trimmedModel)) {
75
+ const index = Number.parseInt(trimmedModel, 10) - 1
76
+ if (index >= 0 && index < knownProvider.models.length) {
77
+ return knownProvider.models[index]
78
+ }
79
+ }
80
+
81
+ return trimmedModel
82
+ }
83
+
84
+ function normalizeConfig(config) {
85
+ const providers = Array.isArray(config?.providers)
86
+ ? config.providers.map(provider => ({ ...provider }))
87
+ : []
88
+ let changed = false
89
+
90
+ for (const provider of providers) {
91
+ const resolvedModel = resolveConfiguredModel(provider, provider.model)
92
+ if (resolvedModel && resolvedModel !== provider.model) {
93
+ provider.model = resolvedModel
94
+ changed = true
95
+ }
96
+ }
97
+
98
+ const normalized = { ...config, providers }
99
+ const activeProvider = normalized.activeProvider
100
+ ? providers.find(provider => provider.name === normalized.activeProvider)
101
+ : providers.length === 1
102
+ ? providers[0]
103
+ : undefined
104
+ const resolvedActiveModel = resolveConfiguredModel(
105
+ activeProvider ?? { name: normalized.activeProvider },
106
+ normalized.activeModel,
107
+ )
108
+
109
+ if (
110
+ resolvedActiveModel &&
111
+ normalized.activeModel &&
112
+ resolvedActiveModel !== normalized.activeModel
113
+ ) {
114
+ normalized.activeModel = resolvedActiveModel
115
+ changed = true
116
+ }
117
+
118
+ return { config: normalized, changed }
119
+ }
120
+
121
+ function getOrderedConfiguredProviders(config) {
122
+ const providers = [...(config.providers || [])].sort(
123
+ (a, b) => (a.priority ?? 99) - (b.priority ?? 99),
124
+ )
125
+
126
+ if (!config.activeProvider) {
127
+ return providers
128
+ }
129
+
130
+ const activeIdx = providers.findIndex(provider => provider.name === config.activeProvider)
131
+ if (activeIdx < 0) {
132
+ return providers
133
+ }
134
+
135
+ const [activeProvider] = providers.splice(activeIdx, 1)
136
+ if (activeProvider && config.activeModel) {
137
+ activeProvider.model = resolveConfiguredModel(activeProvider, config.activeModel) || activeProvider.model
138
+ }
139
+ providers.unshift(activeProvider)
140
+ return providers
141
+ }
142
+
21
143
  function resolveKey(key) {
22
144
  return (typeof key === 'string' && key.startsWith('env:'))
23
145
  ? (process.env[key.slice(4)] || '')
@@ -25,16 +147,20 @@ function resolveKey(key) {
25
147
  }
26
148
 
27
149
  function hasValidProviders() {
28
- if (!existsSync(CONFIG_PATH)) return false
29
- try {
30
- const c = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'))
31
- return Array.isArray(c.providers) && c.providers.some(p => resolveKey(p.apiKey))
32
- } catch { return false }
150
+ const config = loadConfig()
151
+ return Array.isArray(config.providers) && config.providers.some(p => resolveKey(p.apiKey))
33
152
  }
34
153
 
35
154
  function loadConfig() {
36
155
  if (!existsSync(CONFIG_PATH)) return {}
37
- try { return JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')) }
156
+ try {
157
+ const config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'))
158
+ const normalized = normalizeConfig(config)
159
+ if (normalized.changed) {
160
+ saveConfig(normalized.config)
161
+ }
162
+ return normalized.config
163
+ }
38
164
  catch { return {} }
39
165
  }
40
166
 
@@ -50,24 +176,7 @@ function applyConfig() {
50
176
  const config = loadConfig()
51
177
  if (!Array.isArray(config.providers)) return
52
178
 
53
- const providers = config.providers
54
-
55
- // 1. Try activeProvider first (remembers last choice)
56
- if (config.activeProvider) {
57
- const active = providers.find(p => p.name === config.activeProvider && p.model === config.activeModel)
58
- if (active) {
59
- const key = resolveKey(active.apiKey)
60
- if (key) {
61
- process.env.OPENAI_API_KEY = key
62
- process.env.OPENAI_BASE_URL = active.baseUrl
63
- process.env.OPENAI_MODEL = active.model
64
- return
65
- }
66
- }
67
- }
68
-
69
- // 2. Fallback: first valid provider by priority
70
- for (const p of [...providers].sort((a, b) => (a.priority ?? 99) - (b.priority ?? 99))) {
179
+ for (const p of getOrderedConfiguredProviders(config)) {
71
180
  const key = resolveKey(p.apiKey)
72
181
  if (key) {
73
182
  process.env.OPENAI_API_KEY = key
@@ -102,18 +211,7 @@ function getPrimaryConfiguredProvider() {
102
211
  return null
103
212
  }
104
213
 
105
- if (config.activeProvider) {
106
- const active = config.providers.find(
107
- p => p.name === config.activeProvider && p.model === config.activeModel,
108
- )
109
- if (active && (resolveKey(active.apiKey) || isLocalProviderUrl(active.baseUrl))) {
110
- return active
111
- }
112
- }
113
-
114
- for (const provider of [...config.providers].sort(
115
- (a, b) => (a.priority ?? 99) - (b.priority ?? 99),
116
- )) {
214
+ for (const provider of getOrderedConfiguredProviders(config)) {
117
215
  if (resolveKey(provider.apiKey) || isLocalProviderUrl(provider.baseUrl)) {
118
216
  return provider
119
217
  }
@@ -236,7 +334,7 @@ async function main() {
236
334
 
237
335
  // --version
238
336
  if (args.length === 1 && (args[0] === '--version' || args[0] === '-v' || args[0] === '-V')) {
239
- console.log('3.0.5 (FreeClaude)')
337
+ console.log('3.0.6 (FreeClaude)')
240
338
  return
241
339
  }
242
340
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@freeclaude/cli",
3
- "version": "3.0.6",
3
+ "version": "3.0.7",
4
4
  "description": "FreeClaude - Claude Code без авторизации. Работает с ZAI, Qwen, DeepSeek, Ollama и любыми OpenAI-compatible API.",
5
5
  "type": "module",
6
6
  "bin": {