natureco-cli 5.53.0 → 5.55.0

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.
@@ -1,622 +1,624 @@
1
1
  const chalk = require('chalk');
2
- const { getConfig, saveConfig } = require('../utils/config');
3
- const { NatureCoError, handleError } = require('../utils/errors');
4
-
5
- const PROVIDER_MODELS = {
6
- 'api.groq.com': [
7
- { id: 'llama-3.3-70b-versatile', label: 'Llama 3.3 70B Versatile', features: ['tool', 'vision'] },
8
- { id: 'llama-3.1-8b-instant', label: 'Llama 3.1 8B Instant', features: ['tool'] },
9
- { id: 'llama-3.2-90b-vision-preview', label: 'Llama 3.2 90B Vision', features: ['tool', 'vision'] },
10
- { id: 'llama-3.1-70b-versatile', label: 'Llama 3.1 70B Versatile', features: ['tool'] },
11
- { id: 'mixtral-8x7b-32768', label: 'Mixtral 8x7B', features: ['tool'] },
12
- { id: 'gemma2-9b-it', label: 'Gemma 2 9B', features: [] },
13
- { id: 'llama-guard-3-8b', label: 'Llama Guard 3 8B', features: [] },
14
- { id: 'llama-3.2-1b-preview', label: 'Llama 3.2 1B Preview', features: [] },
15
- { id: 'llama-3.2-3b-preview', label: 'Llama 3.2 3B Preview', features: [] },
16
- { id: 'distil-whisper-large-v3-en', label: 'Distil Whisper v3', features: ['audio'] },
17
- ],
18
- 'api.openai.com': [
19
- { id: 'gpt-4o', label: 'GPT-4o', features: ['tool', 'vision'] },
20
- { id: 'gpt-4o-mini', label: 'GPT-4o Mini', features: ['tool', 'vision'] },
21
- { id: 'gpt-4-turbo', label: 'GPT-4 Turbo', features: ['tool', 'vision'] },
22
- { id: 'gpt-4', label: 'GPT-4', features: ['tool'] },
23
- { id: 'gpt-3.5-turbo', label: 'GPT-3.5 Turbo', features: ['tool'] },
24
- { id: 'o1-preview', label: 'o1 Preview', features: [] },
25
- { id: 'o1-mini', label: 'o1 Mini', features: [] },
26
- { id: 'dall-e-3', label: 'DALL-E 3', features: ['image'] },
27
- { id: 'tts-1', label: 'TTS-1', features: ['audio'] },
28
- { id: 'whisper-1', label: 'Whisper-1', features: ['audio'] },
29
- ],
30
- 'api.anthropic.com': [
31
- { id: 'claude-opus-4-7', label: 'Claude Opus 4.7', features: ['tool', 'vision'] },
32
- { id: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6', features: ['tool', 'vision'] },
33
- { id: 'claude-haiku-4-5-20251001', label: 'Claude Haiku 4.5', features: ['tool', 'vision'] },
34
- { id: 'claude-3-5-sonnet-20241022', label: 'Claude 3.5 Sonnet', features: ['tool', 'vision'] },
35
- { id: 'claude-3-haiku-20240307', label: 'Claude 3 Haiku', features: ['tool', 'vision'] },
36
- ],
37
- 'api.together.xyz': [
38
- { id: 'meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo', label: 'Llama 3.1 70B Turbo', features: ['tool'] },
39
- { id: 'meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo', label: 'Llama 3.1 8B Turbo', features: ['tool'] },
40
- { id: 'meta-llama/Llama-3.2-90B-Vision-Instruct-Turbo', label: 'Llama 3.2 90B Vision', features: ['tool', 'vision'] },
41
- { id: 'mistralai/Mixtral-8x7B-Instruct-v0.1', label: 'Mixtral 8x7B', features: ['tool'] },
42
- { id: 'deepseek-ai/deepseek-coder-33b-instruct', label: 'DeepSeek Coder 33B', features: [] },
43
- ],
44
- 'api.deepseek.com': [
45
- { id: 'deepseek-chat', label: 'DeepSeek Chat', features: ['tool'] },
46
- { id: 'deepseek-coder', label: 'DeepSeek Coder', features: ['tool'] },
47
- { id: 'deepseek-reasoner', label: 'DeepSeek Reasoner', features: [] },
48
- ],
49
- 'api.mistral.ai': [
50
- { id: 'mistral-large-latest', label: 'Mistral Large', features: ['tool'] },
51
- { id: 'mistral-medium-latest', label: 'Mistral Medium', features: ['tool'] },
52
- { id: 'mistral-small-latest', label: 'Mistral Small', features: ['tool'] },
53
- { id: 'codestral-latest', label: 'Codestral', features: ['tool'] },
54
- ],
55
- 'api.perplexity.ai': [
56
- { id: 'sonar-pro', label: 'Sonar Pro', features: ['tool'] },
57
- { id: 'sonar', label: 'Sonar', features: ['tool'] },
58
- { id: 'sonar-reasoning-pro', label: 'Sonar Reasoning Pro', features: [] },
59
- ],
60
- 'api.x.ai': [
61
- { id: 'grok-beta', label: 'Grok Beta', features: ['tool'] },
62
- { id: 'grok-vision-beta', label: 'Grok Vision Beta', features: ['tool', 'vision'] },
63
- ],
64
- 'api.deepinfra.com': [
65
- { id: 'meta-llama/Meta-Llama-3.1-70B-Instruct', label: 'Llama 3.1 70B Instruct', features: ['tool'] },
66
- { id: 'meta-llama/Meta-Llama-3.1-8B-Instruct', label: 'Llama 3.1 8B Instruct', features: ['tool'] },
67
- { id: 'mistralai/Mixtral-8x22B-Instruct-v0.1', label: 'Mixtral 8x22B', features: ['tool'] },
68
- ],
69
- 'fireworks.ai': [
70
- { id: 'accounts/fireworks/models/llama-v3p1-70b-instruct', label: 'Llama 3.1 70B Instruct', features: ['tool'] },
71
- { id: 'accounts/fireworks/models/llama-v3p1-8b-instruct', label: 'Llama 3.1 8B Instruct', features: ['tool'] },
72
- ],
73
- 'natureco.me': [
74
- { id: 'natureco-default', label: 'NatureCo Default (otomatik)', features: ['tool'] },
75
- { id: 'natureco-fast', label: 'NatureCo Fast (hızlı)', features: ['tool'] },
76
- { id: 'natureco-reasoner', label: 'NatureCo Reasoner', features: [] },
77
- ],
78
- 'openrouter.ai': [
79
- { id: 'openrouter/auto', label: 'OpenRouter Auto (otomatik seçim)', features: ['tool', 'vision'] },
80
- ],
81
- };
82
-
83
- const PROVIDER_API_PATTERNS = [
84
- { match: 'openai.com', modelsEndpoint: 'https://api.openai.com/v1/models', type: 'openai' },
85
- { match: 'groq.com', modelsEndpoint: null, type: 'groq' },
86
- { match: 'anthropic.com', modelsEndpoint: null, type: 'anthropic' },
87
- { match: 'together.xyz', modelsEndpoint: null, type: 'openai' },
88
- { match: 'deepseek.com', modelsEndpoint: 'https://api.deepseek.com/v1/models', type: 'openai' },
89
- { match: 'mistral.ai', modelsEndpoint: 'https://api.mistral.ai/v1/models', type: 'openai' },
90
- { match: 'perplexity.ai', modelsEndpoint: 'https://api.perplexity.ai/v1/models', type: 'openai' },
91
- { match: 'x.ai', modelsEndpoint: 'https://api.x.ai/v1/models', type: 'openai' },
92
- { match: 'deepinfra.com', modelsEndpoint: 'https://api.deepinfra.com/v1/models', type: 'openai' },
93
- { match: 'fireworks.ai', modelsEndpoint: 'https://api.fireworks.ai/v1/models', type: 'openai' },
94
- { match: 'openrouter.ai', modelsEndpoint: 'https://openrouter.ai/api/v1/models', type: 'openrouter' },
95
- { match: 'natureco.me', modelsEndpoint: null, type: 'natureco' },
96
- ];
97
-
98
- const FEATURE_ICONS = {
99
- tool: '🔧',
100
- vision: '👁',
101
- audio: '🎤',
102
- image: '🎨',
103
- };
104
-
105
- async function models(args) {
106
- const [action, ...params] = (args || []);
107
-
108
- if (!action || action === 'list' || action === 'status') {
109
- const opts = parseFlags(params);
110
- return listModels(opts);
111
- }
112
- if (action === 'set') return setModel(params[0]);
113
- if (action === 'scan') {
114
- const opts = parseFlags(params);
115
- return scanModels(opts);
116
- }
117
- if (action === 'aliases') return manageAliases(params);
118
- if (action === 'fallbacks') return manageFallbacks(params);
119
- if (action === 'set-image') return setImageModel(params[0]);
120
- if (action === 'image-fallbacks') return manageImageFallbacks(params);
121
-
122
- console.log(chalk.red(`\n ❌ Bilinmeyen komut: ${action}\n`));
123
- console.log(chalk.gray(' Kullanım: natureco models [list|set|scan|aliases|fallbacks|set-image|image-fallbacks]\n'));
124
- process.exit(1);
125
- }
126
-
127
- function parseFlags(params) {
128
- return {
129
- refresh: params.includes('--refresh') || params.includes('-r'),
130
- probe: params.includes('--probe') || params.includes('-p'),
131
- json: params.includes('--json') || params.includes('-j'),
132
- all: params.includes('--all') || params.includes('-a'),
133
- provider: extractFlag(params, '--provider'),
134
- timeout: parseInt(extractFlag(params, '--timeout') || '10000', 10),
135
- };
136
- }
137
-
138
- function extractFlag(params, name) {
139
- const idx = params.indexOf(name);
140
- return idx >= 0 && idx + 1 < params.length ? params[idx + 1] : null;
141
- }
142
-
143
- async function listModels(opts) {
144
- const config = getConfig();
145
- const providerUrl = config.providerUrl || '';
146
- const currentModel = config.providerModel || '';
147
- const fallbackModel = config.fallbackModel || '';
148
- const count = opts.refresh ? 50 : 0;
149
-
150
- const providerHost = providerUrl.replace('https://', '').split('/')[0] || 'yapılandırılmamış';
151
- let liveModels = [];
152
-
153
- if (opts.refresh) {
154
- const endpoint = findModelsEndpoint(providerUrl);
155
- if (endpoint) {
156
- console.log(chalk.gray('\n Canlı modeller taranıyor...\n'));
157
- try {
158
- liveModels = await fetchLiveModels(endpoint, config.providerApiKey, opts);
159
- if (liveModels.length > 0) {
160
- console.log(chalk.gray(` ${liveModels.length} model bulundu\n`));
161
- }
162
- } catch (err) {
163
- console.log(chalk.yellow(` ⚠ ${err.message}\n`));
164
- }
165
- }
166
- }
167
-
168
- if (opts.json) {
169
- const data = {
170
- provider: providerHost,
171
- currentModel,
172
- fallbackModel,
173
- models: liveModels.length > 0 ? liveModels : getKnownModels(providerUrl),
174
- liveModels: liveModels.length > 0,
175
- };
176
- console.log(JSON.stringify(data, null, 2));
177
- return;
178
- }
179
-
180
- console.log('');
181
- console.log(chalk.gray(' ' + '─'.repeat(48)));
182
- console.log(chalk.cyan.bold('\n Model Yapılandırması\n'));
183
- console.log(chalk.gray(' Provider : ') + chalk.white(providerHost));
184
- console.log(chalk.gray(' Aktif : ') + chalk.cyan(currentModel || 'ayarlanmamış'));
185
- if (fallbackModel) {
186
- console.log(chalk.gray(' Yedek : ') + chalk.yellow(fallbackModel));
187
- }
188
-
189
- if (config.modelAliases && Object.keys(config.modelAliases).length > 0) {
190
- console.log(chalk.gray(' Takma ad : ') + chalk.white(
191
- Object.entries(config.modelAliases).map(([k, v]) => `${k}→${v}`).join(', ')
192
- ));
193
- }
194
-
195
- if (liveModels.length > 0) {
196
- console.log(chalk.cyan.bold(`\n Canlı Modeller (${liveModels.length})\n`));
197
- liveModels.slice(0, count || 30).forEach(m => {
198
- const active = m.id === currentModel ? chalk.green(' aktif') : '';
199
- const fallback = m.id === fallbackModel ? chalk.yellow(' yedek') : '';
200
- console.log(chalk.white(` ${m.id}`) + active + fallback);
201
- });
202
- if (liveModels.length > 30 && !count) {
203
- console.log(chalk.gray(` ... ve ${liveModels.length - 30} model daha (--refresh ile tümü)`));
204
- }
205
- } else if (!opts.refresh) {
206
- const knownModels = getKnownModels(providerUrl);
207
- if (knownModels.length > 0) {
208
- console.log(chalk.cyan.bold('\n Bilinen Modeller\n'));
209
- knownModels.forEach(m => {
210
- const active = m.id === currentModel ? chalk.green(' aktif') : '';
211
- const fallback = m.id === fallbackModel ? chalk.yellow(' yedek') : '';
212
- const featureIcons = (m.features || []).map(f => FEATURE_ICONS[f] || '').join(' ');
213
- console.log(chalk.white(` ${m.id}`) + active + fallback);
214
- console.log(chalk.gray(` ${m.label}`) + (featureIcons ? ` ${featureIcons}` : ''));
215
- });
216
- }
217
- }
218
-
219
- if (opts.probe && config.providerUrl) {
220
- console.log(chalk.cyan.bold('\n Canlı Sınama\n'));
221
- await probeProvider(config.providerUrl, config.providerApiKey, currentModel, opts);
222
- }
223
-
224
- console.log('');
225
- console.log(chalk.gray(' ' + '─'.repeat(48)));
226
- console.log(chalk.gray(' Değiştirmek : ') + chalk.cyan('natureco models set <model-id>'));
227
- console.log(chalk.gray(' Taramak : ') + chalk.cyan('natureco models scan'));
228
- console.log(chalk.gray(' Yedek model : ') + chalk.cyan('natureco models fallbacks set <model-id>'));
229
- console.log(chalk.gray(' Canlı sınama : ') + chalk.cyan('natureco models list --probe\n'));
230
- }
231
-
232
- async function setModel(modelId) {
233
- const config = getConfig();
234
-
235
- if (!modelId) {
236
- const knownModels = getKnownModels(config.providerUrl || '');
237
- if (knownModels.length === 0) {
238
- console.log(chalk.red('\n ❌ Bu provider için bilinen model yok. Model ID\'sini parametre olarak verin.\n'));
239
- console.log(chalk.gray(' Kullanım: natureco models set <model-id>\n'));
240
- process.exit(1);
241
- }
242
- console.log(chalk.cyan('\n Mevcut Modeller\n'));
243
- knownModels.forEach((m, i) => {
244
- console.log(chalk.white(` ${i + 1}. ${m.id}`));
245
- console.log(chalk.gray(` ${m.label}`));
246
- });
247
- console.log('');
248
- console.log(chalk.gray(' Kullanım: natureco models set <model-id>\n'));
249
- return;
250
- }
251
-
252
- config.providerModel = modelId;
253
- saveConfig(config);
254
- console.log(chalk.green(`\n ✓ Model güncellendi: ${modelId}\n`));
255
- }
256
-
257
- async function scanModels(opts) {
258
- const config = getConfig();
259
- const providerUrl = config.providerUrl || '';
260
- const providerHost = providerUrl.replace('https://', '').split('/')[0] || '';
261
-
262
- const spinner = ['\\', '|', '/', '-'];
263
- let si = 0;
264
- const spin = setInterval(() => {
265
- process.stdout.write(`\r ${chalk.gray(spinner[si % spinner.length])} Modeller taranıyor...`);
266
- si++;
267
- }, 150);
268
-
269
- const allModels = [];
270
- const errors = [];
271
-
272
- // 1. Provider /v1/models API'sinden canlı tarama
273
- const endpoint = findModelsEndpoint(providerUrl);
274
- if (endpoint) {
275
- try {
276
- const live = await fetchLiveModels(endpoint, config.providerApiKey, { timeout: opts.timeout || 10000 });
277
- allModels.push(...live.map(m => ({ ...m, source: 'live' })));
278
- } catch (err) {
279
- errors.push(`API: ${err.message}`);
280
- }
281
- }
282
-
283
- // 2. OpenRouter ücretsiz model kataloğu
284
- if (!providerUrl || providerUrl.includes('openrouter')) {
285
- try {
286
- const res = await fetch('https://openrouter.ai/api/v1/models', {
287
- signal: AbortSignal.timeout(opts.timeout || 10000),
288
- });
289
- if (res.ok) {
290
- const data = await res.json();
291
- const free = (data.data || []).filter(m => m.pricing?.prompt === '0');
292
- free.forEach(m => {
293
- if (!allModels.some(ex => ex.id === m.id)) {
294
- allModels.push({
295
- id: m.id,
296
- label: m.name || m.id,
297
- features: [],
298
- source: 'openrouter-free',
299
- context: m.context_length || null,
300
- pricing: m.pricing,
301
- });
302
- }
303
- });
304
- }
305
- } catch (err) {
306
- errors.push(`OpenRouter: ${err.message}`);
307
- }
308
- }
309
-
310
- // 3. Bilinen statik modeller
311
- const known = getKnownModels(providerUrl);
312
- known.forEach(m => {
313
- if (!allModels.some(ex => ex.id === m.id)) {
314
- allModels.push({ ...m, source: 'static' });
315
- }
316
- });
317
-
318
- clearInterval(spin);
319
- process.stdout.write('\r' + ' '.repeat(50) + '\r');
320
-
321
- if (opts.json) {
322
- console.log(JSON.stringify({
323
- models: allModels,
324
- total: allModels.length,
325
- sources: {
326
- live: allModels.filter(m => m.source === 'live').length,
327
- openrouter_free: allModels.filter(m => m.source === 'openrouter-free').length,
328
- static: allModels.filter(m => m.source === 'static').length,
329
- },
330
- errors: errors.length > 0 ? errors : undefined,
331
- }, null, 2));
332
- return;
333
- }
334
-
335
- console.log('');
336
- console.log(chalk.gray(' ' + '─'.repeat(48)));
337
-
338
- if (errors.length > 0) {
339
- console.log(chalk.yellow(`\n ⚠ ${errors.length} uyarı:`));
340
- errors.forEach(e => console.log(chalk.gray(` ${e}`)));
341
- }
342
-
343
- const liveCount = allModels.filter(m => m.source === 'live').length;
344
- const freeCount = allModels.filter(m => m.source === 'openrouter-free').length;
345
- const staticCount = allModels.filter(m => m.source === 'static').length;
346
-
347
- console.log(chalk.cyan.bold(`\n Modeller (${allModels.length} bulundu)\n`));
348
- console.log(chalk.gray(` Kaynak: ${liveCount} canlı, ${freeCount} ücretsiz, ${staticCount} statik\n`));
349
-
350
- if (liveCount > 0) {
351
- console.log(chalk.cyan(' Canlı Modeller\n'));
352
- allModels.filter(m => m.source === 'live').slice(0, 20).forEach(m => {
353
- console.log(chalk.white(` ${m.id}`));
354
- if (m.context) console.log(chalk.gray(` Bağlam: ${m.context} token`));
355
- });
356
- if (allModels.filter(m => m.source === 'live').length > 20) {
357
- console.log(chalk.gray(` ... ve ${allModels.filter(m => m.source === 'live').length - 20} model daha`));
358
- }
359
- }
360
-
361
- if (freeCount > 0) {
362
- console.log(chalk.cyan('\n OpenRouter Ücretsiz Modeller\n'));
363
- allModels.filter(m => m.source === 'openrouter-free').slice(0, 15).forEach(m => {
364
- console.log(chalk.white(` ${m.id}`));
365
- if (m.label && m.label !== m.id) console.log(chalk.gray(` ${m.label}`));
366
- });
367
- if (allModels.filter(m => m.source === 'openrouter-free').length > 15) {
368
- console.log(chalk.gray(` ... ve ${allModels.filter(m => m.source === 'openrouter-free').length - 15} model daha`));
369
- }
370
- }
371
-
372
- if (staticCount > 0 && liveCount === 0) {
373
- console.log(chalk.cyan('\n Statik Modeller\n'));
374
- allModels.filter(m => m.source === 'static').forEach(m => {
375
- const featureIcons = (m.features || []).map(f => FEATURE_ICONS[f] || '').join(' ');
376
- console.log(chalk.white(` ${m.id}`) + (featureIcons ? ` ${featureIcons}` : ''));
377
- console.log(chalk.gray(` ${m.label}`));
378
- });
379
- }
380
-
381
- const currentModel = config.providerModel || '';
382
- if (currentModel && !allModels.some(m => m.id === currentModel)) {
383
- console.log(chalk.yellow(`\n ⚠ Mevcut model "${currentModel}" bu provider için bulunamadı.`));
384
- console.log(chalk.gray(' Güncellemek için: ') + chalk.cyan('natureco models set <model-id>'));
385
- }
386
-
387
- console.log('');
388
- }
389
-
390
- function manageAliases(params) {
391
- const config = getConfig();
392
- const aliases = config.modelAliases || {};
393
-
394
- if (params.length === 0) {
395
- console.log(chalk.cyan.bold('\n Model Takma Adları\n'));
396
- if (Object.keys(aliases).length === 0) {
397
- console.log(chalk.gray(' Takma ad yok.\n'));
398
- console.log(chalk.gray(' Eklemek için: ') + chalk.cyan('natureco models aliases <takma-ad> <model-id>\n'));
399
- return;
400
- }
401
- Object.entries(aliases).forEach(([alias, model]) => {
402
- console.log(chalk.white(` ${alias.padEnd(15)} → `) + chalk.cyan(model));
403
- });
404
- console.log('');
405
- return;
406
- }
407
-
408
- if (params.length === 2) {
409
- const [alias, modelId] = params;
410
- aliases[alias] = modelId;
411
- config.modelAliases = aliases;
412
- saveConfig(config);
413
- console.log(chalk.green(`\n ✓ Takma ad eklendi: ${alias} → ${modelId}\n`));
414
- return;
415
- }
416
-
417
- if (params.length === 1 && (params[0] === 'clear' || params[0] === '--clear')) {
418
- config.modelAliases = {};
419
- saveConfig(config);
420
- console.log(chalk.green('\n ✓ Tüm takma adlar temizlendi.\n'));
421
- return;
422
- }
423
-
424
- if (params.length === 1 && aliases[params[0]]) {
425
- delete aliases[params[0]];
426
- config.modelAliases = aliases;
427
- saveConfig(config);
428
- console.log(chalk.green(`\n ✓ Takma ad silindi: ${params[0]}\n`));
429
- return;
430
- }
431
-
432
- if (params.length === 1) {
433
- const resolved = resolveModel(params[0]);
434
- if (resolved) {
435
- console.log(chalk.cyan(`\n ${params[0]} → ${resolved}\n`));
436
- } else {
437
- console.log(chalk.yellow(`\n "${params[0]}" çözülemedi.\n`));
438
- }
439
- return;
440
- }
441
-
442
- console.log(chalk.gray('\n Kullanım: natureco models aliases [<takma-ad> <model-id>]\n'));
443
- }
444
-
445
- function manageFallbacks(params) {
446
- const config = getConfig();
447
-
448
- if (params.length === 0 || params[0] === 'list') {
449
- console.log(chalk.cyan.bold('\n Model Yedekleri\n'));
450
- console.log(chalk.gray(' Birincil : ') + chalk.white(config.providerModel || 'ayarlanmamış'));
451
- console.log(chalk.gray(' Yedek : ') + chalk.white(config.fallbackModel || 'ayarlanmamış'));
452
- console.log('');
453
- console.log(chalk.gray(' Ayarlamak için: ') + chalk.cyan('natureco models fallbacks set <model-id>\n'));
454
- return;
455
- }
456
-
457
- if (params[0] === 'set' && params[1]) {
458
- config.fallbackModel = params[1];
459
- saveConfig(config);
460
- console.log(chalk.green(`\n ✓ Yedek model ayarlandı: ${params[1]}\n`));
461
- return;
462
- }
463
-
464
- if (params[0] === 'clear') {
465
- delete config.fallbackModel;
466
- saveConfig(config);
467
- console.log(chalk.green('\n ✓ Yedek model temizlendi.\n'));
468
- return;
469
- }
470
-
471
- console.log(chalk.gray('\n Kullanım: natureco models fallbacks [list|set <model-id>|clear]\n'));
472
- }
473
-
474
- function setImageModel(modelId) {
475
- if (!modelId) {
476
- console.log(chalk.cyan('\n Current image model: ') + chalk.white(getConfig().imageModel || 'not set'));
477
- console.log(chalk.gray('\n Usage: natureco models set-image <model-id>\n'));
478
- return;
479
- }
480
- const config = getConfig();
481
- config.imageModel = modelId;
482
- saveConfig(config);
483
- console.log(chalk.green(`\n ✓ Image model set: ${modelId}\n`));
484
- }
485
-
486
- function manageImageFallbacks(params) {
487
- const config = getConfig();
488
- if (params.length === 0 || params[0] === 'list') {
489
- console.log(chalk.cyan.bold('\n Image Model Fallbacks\n'));
490
- console.log(chalk.gray(' Primary : ') + chalk.white(config.imageModel || 'not set'));
491
- console.log(chalk.gray(' Fallback : ') + chalk.white(config.imageFallbackModel || 'not set'));
492
- console.log(chalk.gray('\n Set: natureco models image-fallbacks set <model-id>'));
493
- console.log(chalk.gray(' Clear: natureco models image-fallbacks clear\n'));
494
- return;
495
- }
496
- if (params[0] === 'set' && params[1]) {
497
- config.imageFallbackModel = params[1];
498
- saveConfig(config);
499
- console.log(chalk.green(`\n ✓ Image fallback set: ${params[1]}\n`));
500
- return;
501
- }
502
- if (params[0] === 'clear') {
503
- delete config.imageFallbackModel;
504
- saveConfig(config);
505
- console.log(chalk.green('\n ✓ Image fallback cleared\n'));
506
- return;
507
- }
508
- }
509
-
510
- function resolveModel(input) {
511
- const config = getConfig();
512
- const aliases = config.modelAliases || {};
513
- if (aliases[input]) return aliases[input];
514
-
515
- const knownModels = getKnownModels(config.providerUrl || '');
516
- const match = knownModels.find(m => m.id === input);
517
- if (match) return match.id;
518
-
519
- if (input.includes('/')) return input;
520
- return null;
521
- }
522
-
523
- function getKnownModels(providerUrl) {
524
- for (const [domain, list] of Object.entries(PROVIDER_MODELS)) {
525
- if (providerUrl.includes(domain)) {
526
- return list;
527
- }
528
- }
529
- if (providerUrl.includes('openai') || providerUrl.includes('v1')) {
530
- return PROVIDER_MODELS['api.openai.com'];
531
- }
532
- return [];
533
- }
534
-
535
- function findModelsEndpoint(providerUrl) {
536
- if (!providerUrl) return null;
537
- for (const pattern of PROVIDER_API_PATTERNS) {
538
- if (providerUrl.includes(pattern.match)) {
539
- return pattern.modelsEndpoint || (providerUrl.replace(/\/v1\/.*$|\/$/, '') + '/v1/models');
540
- }
541
- }
542
- if (providerUrl.includes('/v1')) {
543
- return providerUrl.replace(/\/v1\/.*$/, '/v1/models');
544
- }
545
- return null;
546
- }
547
-
548
- async function fetchLiveModels(endpoint, apiKey, opts) {
549
- const headers = { 'Content-Type': 'application/json' };
550
- if (apiKey) {
551
- headers['Authorization'] = `Bearer ${apiKey}`;
552
- }
553
-
554
- const res = await fetch(endpoint, {
555
- headers,
556
- signal: AbortSignal.timeout(opts.timeout || 10000),
557
- });
558
-
559
- if (!res.ok) {
560
- if (res.status === 401 || res.status === 403) {
561
- throw new Error('API key geçersiz veya yetkisiz erişim');
562
- }
563
- if (res.status === 404) {
564
- throw new Error('Provider bu endpoint\'i desteklemiyor (/v1/models)');
565
- }
566
- throw new Error(`HTTP ${res.status}: ${res.statusText}`);
567
- }
568
-
569
- const data = await res.json();
570
- const rawModels = data.data || data.models || [];
571
-
572
- return rawModels.map(m => ({
573
- id: m.id || m.name || String(m),
574
- label: m.name || m.id || String(m),
575
- context: m.context_length || m.max_tokens || m.context_window || null,
576
- features: [],
577
- }));
578
- }
579
-
580
- async function probeProvider(providerUrl, apiKey, modelId, opts) {
581
- const testModel = modelId || 'gpt-4o-mini';
582
- const testUrl = providerUrl.includes('/v1/')
583
- ? providerUrl
584
- : providerUrl.replace(/\/$/, '') + '/v1/chat/completions';
585
-
586
- const start = Date.now();
587
- try {
588
- const res = await fetch(testUrl, {
589
- method: 'POST',
590
- headers: {
591
- 'Content-Type': 'application/json',
592
- ...(apiKey ? { 'Authorization': `Bearer ${apiKey}` } : {}),
593
- },
594
- body: JSON.stringify({
595
- model: testModel,
596
- messages: [{ role: 'user', content: 'Respond with only the word "ok".' }],
597
- max_tokens: 5,
598
- }),
599
- signal: AbortSignal.timeout(opts.timeout || 10000),
600
- });
601
-
602
- const ms = Date.now() - start;
603
- if (res.ok) {
604
- console.log(chalk.green(` ✓ ${testModel}: ${ms}ms (yanıt alındı)`));
605
- } else if (res.status === 401 || res.status === 403) {
606
- console.log(chalk.red(` ${testModel}: Yetkisiz (${res.status})`));
607
- } else if (res.status === 429) {
608
- console.log(chalk.yellow(` ${testModel}: Rate limit (${res.status})`));
609
- } else {
610
- console.log(chalk.yellow(` ⚠ ${testModel}: HTTP ${res.status} (${ms}ms)`));
611
- }
612
- } catch (err) {
613
- const ms = Date.now() - start;
614
- if (err.name === 'TimeoutError' || err.name === 'AbortError') {
615
- console.log(chalk.red(` ✗ ${testModel}: Zaman aşımı (${opts.timeout}ms)`));
616
- } else {
617
- console.log(chalk.red(` ✗ ${testModel}: ${err.message}`));
618
- }
619
- }
620
- }
621
-
622
- module.exports = models;
2
+ const { getLang: _gl } = require('../utils/i18n');
3
+ const L = (tr, en) => (_gl() === 'en' ? en : tr);
4
+ const { getConfig, saveConfig } = require('../utils/config');
5
+ const { NatureCoError, handleError } = require('../utils/errors');
6
+
7
+ const PROVIDER_MODELS = {
8
+ 'api.groq.com': [
9
+ { id: 'llama-3.3-70b-versatile', label: 'Llama 3.3 70B Versatile', features: ['tool', 'vision'] },
10
+ { id: 'llama-3.1-8b-instant', label: 'Llama 3.1 8B Instant', features: ['tool'] },
11
+ { id: 'llama-3.2-90b-vision-preview', label: 'Llama 3.2 90B Vision', features: ['tool', 'vision'] },
12
+ { id: 'llama-3.1-70b-versatile', label: 'Llama 3.1 70B Versatile', features: ['tool'] },
13
+ { id: 'mixtral-8x7b-32768', label: 'Mixtral 8x7B', features: ['tool'] },
14
+ { id: 'gemma2-9b-it', label: 'Gemma 2 9B', features: [] },
15
+ { id: 'llama-guard-3-8b', label: 'Llama Guard 3 8B', features: [] },
16
+ { id: 'llama-3.2-1b-preview', label: 'Llama 3.2 1B Preview', features: [] },
17
+ { id: 'llama-3.2-3b-preview', label: 'Llama 3.2 3B Preview', features: [] },
18
+ { id: 'distil-whisper-large-v3-en', label: 'Distil Whisper v3', features: ['audio'] },
19
+ ],
20
+ 'api.openai.com': [
21
+ { id: 'gpt-4o', label: 'GPT-4o', features: ['tool', 'vision'] },
22
+ { id: 'gpt-4o-mini', label: 'GPT-4o Mini', features: ['tool', 'vision'] },
23
+ { id: 'gpt-4-turbo', label: 'GPT-4 Turbo', features: ['tool', 'vision'] },
24
+ { id: 'gpt-4', label: 'GPT-4', features: ['tool'] },
25
+ { id: 'gpt-3.5-turbo', label: 'GPT-3.5 Turbo', features: ['tool'] },
26
+ { id: 'o1-preview', label: 'o1 Preview', features: [] },
27
+ { id: 'o1-mini', label: 'o1 Mini', features: [] },
28
+ { id: 'dall-e-3', label: 'DALL-E 3', features: ['image'] },
29
+ { id: 'tts-1', label: 'TTS-1', features: ['audio'] },
30
+ { id: 'whisper-1', label: 'Whisper-1', features: ['audio'] },
31
+ ],
32
+ 'api.anthropic.com': [
33
+ { id: 'claude-opus-4-7', label: 'Claude Opus 4.7', features: ['tool', 'vision'] },
34
+ { id: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6', features: ['tool', 'vision'] },
35
+ { id: 'claude-haiku-4-5-20251001', label: 'Claude Haiku 4.5', features: ['tool', 'vision'] },
36
+ { id: 'claude-3-5-sonnet-20241022', label: 'Claude 3.5 Sonnet', features: ['tool', 'vision'] },
37
+ { id: 'claude-3-haiku-20240307', label: 'Claude 3 Haiku', features: ['tool', 'vision'] },
38
+ ],
39
+ 'api.together.xyz': [
40
+ { id: 'meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo', label: 'Llama 3.1 70B Turbo', features: ['tool'] },
41
+ { id: 'meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo', label: 'Llama 3.1 8B Turbo', features: ['tool'] },
42
+ { id: 'meta-llama/Llama-3.2-90B-Vision-Instruct-Turbo', label: 'Llama 3.2 90B Vision', features: ['tool', 'vision'] },
43
+ { id: 'mistralai/Mixtral-8x7B-Instruct-v0.1', label: 'Mixtral 8x7B', features: ['tool'] },
44
+ { id: 'deepseek-ai/deepseek-coder-33b-instruct', label: 'DeepSeek Coder 33B', features: [] },
45
+ ],
46
+ 'api.deepseek.com': [
47
+ { id: 'deepseek-chat', label: 'DeepSeek Chat', features: ['tool'] },
48
+ { id: 'deepseek-coder', label: 'DeepSeek Coder', features: ['tool'] },
49
+ { id: 'deepseek-reasoner', label: 'DeepSeek Reasoner', features: [] },
50
+ ],
51
+ 'api.mistral.ai': [
52
+ { id: 'mistral-large-latest', label: 'Mistral Large', features: ['tool'] },
53
+ { id: 'mistral-medium-latest', label: 'Mistral Medium', features: ['tool'] },
54
+ { id: 'mistral-small-latest', label: 'Mistral Small', features: ['tool'] },
55
+ { id: 'codestral-latest', label: 'Codestral', features: ['tool'] },
56
+ ],
57
+ 'api.perplexity.ai': [
58
+ { id: 'sonar-pro', label: 'Sonar Pro', features: ['tool'] },
59
+ { id: 'sonar', label: 'Sonar', features: ['tool'] },
60
+ { id: 'sonar-reasoning-pro', label: 'Sonar Reasoning Pro', features: [] },
61
+ ],
62
+ 'api.x.ai': [
63
+ { id: 'grok-beta', label: 'Grok Beta', features: ['tool'] },
64
+ { id: 'grok-vision-beta', label: 'Grok Vision Beta', features: ['tool', 'vision'] },
65
+ ],
66
+ 'api.deepinfra.com': [
67
+ { id: 'meta-llama/Meta-Llama-3.1-70B-Instruct', label: 'Llama 3.1 70B Instruct', features: ['tool'] },
68
+ { id: 'meta-llama/Meta-Llama-3.1-8B-Instruct', label: 'Llama 3.1 8B Instruct', features: ['tool'] },
69
+ { id: 'mistralai/Mixtral-8x22B-Instruct-v0.1', label: 'Mixtral 8x22B', features: ['tool'] },
70
+ ],
71
+ 'fireworks.ai': [
72
+ { id: 'accounts/fireworks/models/llama-v3p1-70b-instruct', label: 'Llama 3.1 70B Instruct', features: ['tool'] },
73
+ { id: 'accounts/fireworks/models/llama-v3p1-8b-instruct', label: 'Llama 3.1 8B Instruct', features: ['tool'] },
74
+ ],
75
+ 'natureco.me': [
76
+ { id: 'natureco-default', label: L('NatureCo Default (otomatik)', 'NatureCo Default (auto)'), features: ['tool'] },
77
+ { id: 'natureco-fast', label: L('NatureCo Fast (hızlı)', 'NatureCo Fast (fast)'), features: ['tool'] },
78
+ { id: 'natureco-reasoner', label: 'NatureCo Reasoner', features: [] },
79
+ ],
80
+ 'openrouter.ai': [
81
+ { id: 'openrouter/auto', label: L('OpenRouter Auto (otomatik seçim)', 'OpenRouter Auto (auto-select)'), features: ['tool', 'vision'] },
82
+ ],
83
+ };
84
+
85
+ const PROVIDER_API_PATTERNS = [
86
+ { match: 'openai.com', modelsEndpoint: 'https://api.openai.com/v1/models', type: 'openai' },
87
+ { match: 'groq.com', modelsEndpoint: null, type: 'groq' },
88
+ { match: 'anthropic.com', modelsEndpoint: null, type: 'anthropic' },
89
+ { match: 'together.xyz', modelsEndpoint: null, type: 'openai' },
90
+ { match: 'deepseek.com', modelsEndpoint: 'https://api.deepseek.com/v1/models', type: 'openai' },
91
+ { match: 'mistral.ai', modelsEndpoint: 'https://api.mistral.ai/v1/models', type: 'openai' },
92
+ { match: 'perplexity.ai', modelsEndpoint: 'https://api.perplexity.ai/v1/models', type: 'openai' },
93
+ { match: 'x.ai', modelsEndpoint: 'https://api.x.ai/v1/models', type: 'openai' },
94
+ { match: 'deepinfra.com', modelsEndpoint: 'https://api.deepinfra.com/v1/models', type: 'openai' },
95
+ { match: 'fireworks.ai', modelsEndpoint: 'https://api.fireworks.ai/v1/models', type: 'openai' },
96
+ { match: 'openrouter.ai', modelsEndpoint: 'https://openrouter.ai/api/v1/models', type: 'openrouter' },
97
+ { match: 'natureco.me', modelsEndpoint: null, type: 'natureco' },
98
+ ];
99
+
100
+ const FEATURE_ICONS = {
101
+ tool: '🔧',
102
+ vision: '👁',
103
+ audio: '🎤',
104
+ image: '🎨',
105
+ };
106
+
107
+ async function models(args) {
108
+ const [action, ...params] = (args || []);
109
+
110
+ if (!action || action === 'list' || action === 'status') {
111
+ const opts = parseFlags(params);
112
+ return listModels(opts);
113
+ }
114
+ if (action === 'set') return setModel(params[0]);
115
+ if (action === 'scan') {
116
+ const opts = parseFlags(params);
117
+ return scanModels(opts);
118
+ }
119
+ if (action === 'aliases') return manageAliases(params);
120
+ if (action === 'fallbacks') return manageFallbacks(params);
121
+ if (action === 'set-image') return setImageModel(params[0]);
122
+ if (action === 'image-fallbacks') return manageImageFallbacks(params);
123
+
124
+ console.log(chalk.red(`\n ❌ ${L('Bilinmeyen komut', 'Unknown command')}: ${action}\n`));
125
+ console.log(chalk.gray(L(' Kullanım: natureco models [list|set|scan|aliases|fallbacks|set-image|image-fallbacks]\n', ' Usage: natureco models [list|set|scan|aliases|fallbacks|set-image|image-fallbacks]\n')));
126
+ process.exit(1);
127
+ }
128
+
129
+ function parseFlags(params) {
130
+ return {
131
+ refresh: params.includes('--refresh') || params.includes('-r'),
132
+ probe: params.includes('--probe') || params.includes('-p'),
133
+ json: params.includes('--json') || params.includes('-j'),
134
+ all: params.includes('--all') || params.includes('-a'),
135
+ provider: extractFlag(params, '--provider'),
136
+ timeout: parseInt(extractFlag(params, '--timeout') || '10000', 10),
137
+ };
138
+ }
139
+
140
+ function extractFlag(params, name) {
141
+ const idx = params.indexOf(name);
142
+ return idx >= 0 && idx + 1 < params.length ? params[idx + 1] : null;
143
+ }
144
+
145
+ async function listModels(opts) {
146
+ const config = getConfig();
147
+ const providerUrl = config.providerUrl || '';
148
+ const currentModel = config.providerModel || '';
149
+ const fallbackModel = config.fallbackModel || '';
150
+ const count = opts.refresh ? 50 : 0;
151
+
152
+ const providerHost = providerUrl.replace('https://', '').split('/')[0] || L('yapılandırılmamış', 'not configured');
153
+ let liveModels = [];
154
+
155
+ if (opts.refresh) {
156
+ const endpoint = findModelsEndpoint(providerUrl);
157
+ if (endpoint) {
158
+ console.log(chalk.gray(L('\n Canlı modeller taranıyor...\n', '\n Scanning live models...\n')));
159
+ try {
160
+ liveModels = await fetchLiveModels(endpoint, config.providerApiKey, opts);
161
+ if (liveModels.length > 0) {
162
+ console.log(chalk.gray(` ${liveModels.length} ${L('model bulundu', 'models found')}\n`));
163
+ }
164
+ } catch (err) {
165
+ console.log(chalk.yellow(` ⚠ ${err.message}\n`));
166
+ }
167
+ }
168
+ }
169
+
170
+ if (opts.json) {
171
+ const data = {
172
+ provider: providerHost,
173
+ currentModel,
174
+ fallbackModel,
175
+ models: liveModels.length > 0 ? liveModels : getKnownModels(providerUrl),
176
+ liveModels: liveModels.length > 0,
177
+ };
178
+ console.log(JSON.stringify(data, null, 2));
179
+ return;
180
+ }
181
+
182
+ console.log('');
183
+ console.log(chalk.gray(' ' + '─'.repeat(48)));
184
+ console.log(chalk.cyan.bold(L('\n Model Yapılandırması\n', '\n Model Configuration\n')));
185
+ console.log(chalk.gray(' Provider : ') + chalk.white(providerHost));
186
+ console.log(chalk.gray(L(' Aktif : ', ' Active : ')) + chalk.cyan(currentModel || L('ayarlanmamış', 'not set')));
187
+ if (fallbackModel) {
188
+ console.log(chalk.gray(L(' Yedek : ', ' Fallback : ')) + chalk.yellow(fallbackModel));
189
+ }
190
+
191
+ if (config.modelAliases && Object.keys(config.modelAliases).length > 0) {
192
+ console.log(chalk.gray(L(' Takma ad : ', ' Alias : ')) + chalk.white(
193
+ Object.entries(config.modelAliases).map(([k, v]) => `${k}→${v}`).join(', ')
194
+ ));
195
+ }
196
+
197
+ if (liveModels.length > 0) {
198
+ console.log(chalk.cyan.bold(`\n ${L('Canlı Modeller', 'Live Models')} (${liveModels.length})\n`));
199
+ liveModels.slice(0, count || 30).forEach(m => {
200
+ const active = m.id === currentModel ? chalk.green(L(' aktif', ' ← active')) : '';
201
+ const fallback = m.id === fallbackModel ? chalk.yellow(L(' ← yedek', ' ← fallback')) : '';
202
+ console.log(chalk.white(` ${m.id}`) + active + fallback);
203
+ });
204
+ if (liveModels.length > 30 && !count) {
205
+ console.log(chalk.gray(` ... ${L('ve', 'and')} ${liveModels.length - 30} ${L('model daha (--refresh ile tümü)', 'more models (--refresh for all)')}`));
206
+ }
207
+ } else if (!opts.refresh) {
208
+ const knownModels = getKnownModels(providerUrl);
209
+ if (knownModels.length > 0) {
210
+ console.log(chalk.cyan.bold(L('\n Bilinen Modeller\n', '\n Known Models\n')));
211
+ knownModels.forEach(m => {
212
+ const active = m.id === currentModel ? chalk.green(L(' aktif', ' ← active')) : '';
213
+ const fallback = m.id === fallbackModel ? chalk.yellow(L(' yedek', ' fallback')) : '';
214
+ const featureIcons = (m.features || []).map(f => FEATURE_ICONS[f] || '').join(' ');
215
+ console.log(chalk.white(` ${m.id}`) + active + fallback);
216
+ console.log(chalk.gray(` ${m.label}`) + (featureIcons ? ` ${featureIcons}` : ''));
217
+ });
218
+ }
219
+ }
220
+
221
+ if (opts.probe && config.providerUrl) {
222
+ console.log(chalk.cyan.bold(L('\n Canlı Sınama\n', '\n Live Probe\n')));
223
+ await probeProvider(config.providerUrl, config.providerApiKey, currentModel, opts);
224
+ }
225
+
226
+ console.log('');
227
+ console.log(chalk.gray(' ' + '─'.repeat(48)));
228
+ console.log(chalk.gray(L(' Değiştirmek : ', ' Change : ')) + chalk.cyan('natureco models set <model-id>'));
229
+ console.log(chalk.gray(L(' Taramak : ', ' Scan : ')) + chalk.cyan('natureco models scan'));
230
+ console.log(chalk.gray(L(' Yedek model : ', ' Fallback : ')) + chalk.cyan('natureco models fallbacks set <model-id>'));
231
+ console.log(chalk.gray(L(' Canlı sınama : ', ' Live probe : ')) + chalk.cyan('natureco models list --probe\n'));
232
+ }
233
+
234
+ async function setModel(modelId) {
235
+ const config = getConfig();
236
+
237
+ if (!modelId) {
238
+ const knownModels = getKnownModels(config.providerUrl || '');
239
+ if (knownModels.length === 0) {
240
+ console.log(chalk.red(L('\n ❌ Bu provider için bilinen model yok. Model ID\'sini parametre olarak verin.\n', '\n ❌ No known models for this provider. Pass the model ID as a parameter.\n')));
241
+ console.log(chalk.gray(L(' Kullanım: natureco models set <model-id>\n', ' Usage: natureco models set <model-id>\n')));
242
+ process.exit(1);
243
+ }
244
+ console.log(chalk.cyan(L('\n Mevcut Modeller\n', '\n Available Models\n')));
245
+ knownModels.forEach((m, i) => {
246
+ console.log(chalk.white(` ${i + 1}. ${m.id}`));
247
+ console.log(chalk.gray(` ${m.label}`));
248
+ });
249
+ console.log('');
250
+ console.log(chalk.gray(L(' Kullanım: natureco models set <model-id>\n', ' Usage: natureco models set <model-id>\n')));
251
+ return;
252
+ }
253
+
254
+ config.providerModel = modelId;
255
+ saveConfig(config);
256
+ console.log(chalk.green(`\n ✓ ${L('Model güncellendi', 'Model updated')}: ${modelId}\n`));
257
+ }
258
+
259
+ async function scanModels(opts) {
260
+ const config = getConfig();
261
+ const providerUrl = config.providerUrl || '';
262
+ const providerHost = providerUrl.replace('https://', '').split('/')[0] || '';
263
+
264
+ const spinner = ['\\', '|', '/', '-'];
265
+ let si = 0;
266
+ const spin = setInterval(() => {
267
+ process.stdout.write(`\r ${chalk.gray(spinner[si % spinner.length])} ${L('Modeller taranıyor...', 'Scanning models...')}`);
268
+ si++;
269
+ }, 150);
270
+
271
+ const allModels = [];
272
+ const errors = [];
273
+
274
+ // 1. Provider /v1/models API'sinden canlı tarama
275
+ const endpoint = findModelsEndpoint(providerUrl);
276
+ if (endpoint) {
277
+ try {
278
+ const live = await fetchLiveModels(endpoint, config.providerApiKey, { timeout: opts.timeout || 10000 });
279
+ allModels.push(...live.map(m => ({ ...m, source: 'live' })));
280
+ } catch (err) {
281
+ errors.push(`API: ${err.message}`);
282
+ }
283
+ }
284
+
285
+ // 2. OpenRouter ücretsiz model kataloğu
286
+ if (!providerUrl || providerUrl.includes('openrouter')) {
287
+ try {
288
+ const res = await fetch('https://openrouter.ai/api/v1/models', {
289
+ signal: AbortSignal.timeout(opts.timeout || 10000),
290
+ });
291
+ if (res.ok) {
292
+ const data = await res.json();
293
+ const free = (data.data || []).filter(m => m.pricing?.prompt === '0');
294
+ free.forEach(m => {
295
+ if (!allModels.some(ex => ex.id === m.id)) {
296
+ allModels.push({
297
+ id: m.id,
298
+ label: m.name || m.id,
299
+ features: [],
300
+ source: 'openrouter-free',
301
+ context: m.context_length || null,
302
+ pricing: m.pricing,
303
+ });
304
+ }
305
+ });
306
+ }
307
+ } catch (err) {
308
+ errors.push(`OpenRouter: ${err.message}`);
309
+ }
310
+ }
311
+
312
+ // 3. Bilinen ${L('statik', 'static')} modeller
313
+ const known = getKnownModels(providerUrl);
314
+ known.forEach(m => {
315
+ if (!allModels.some(ex => ex.id === m.id)) {
316
+ allModels.push({ ...m, source: 'static' });
317
+ }
318
+ });
319
+
320
+ clearInterval(spin);
321
+ process.stdout.write('\r' + ' '.repeat(50) + '\r');
322
+
323
+ if (opts.json) {
324
+ console.log(JSON.stringify({
325
+ models: allModels,
326
+ total: allModels.length,
327
+ sources: {
328
+ live: allModels.filter(m => m.source === 'live').length,
329
+ openrouter_free: allModels.filter(m => m.source === 'openrouter-free').length,
330
+ static: allModels.filter(m => m.source === 'static').length,
331
+ },
332
+ errors: errors.length > 0 ? errors : undefined,
333
+ }, null, 2));
334
+ return;
335
+ }
336
+
337
+ console.log('');
338
+ console.log(chalk.gray(' ' + '─'.repeat(48)));
339
+
340
+ if (errors.length > 0) {
341
+ console.log(chalk.yellow(`\n ⚠ ${errors.length} ${L('uyarı', 'warning(s)')}:`));
342
+ errors.forEach(e => console.log(chalk.gray(` ${e}`)));
343
+ }
344
+
345
+ const liveCount = allModels.filter(m => m.source === 'live').length;
346
+ const freeCount = allModels.filter(m => m.source === 'openrouter-free').length;
347
+ const staticCount = allModels.filter(m => m.source === 'static').length;
348
+
349
+ console.log(chalk.cyan.bold(`\n ${L('Modeller', 'Models')} (${allModels.length} ${L('bulundu', 'found')})\n`));
350
+ console.log(chalk.gray(` ${L('Kaynak', 'Source')}: ${liveCount} ${L('canlı', 'live')}, ${freeCount} ${L('ücretsiz', 'free')}, ${staticCount} ${L('statik', 'static')}\n`));
351
+
352
+ if (liveCount > 0) {
353
+ console.log(chalk.cyan(L(' Canlı Modeller\n', ' Live Models\n')));
354
+ allModels.filter(m => m.source === 'live').slice(0, 20).forEach(m => {
355
+ console.log(chalk.white(` ${m.id}`));
356
+ if (m.context) console.log(chalk.gray(` ${L('Bağlam', 'Context')}: ${m.context} token`));
357
+ });
358
+ if (allModels.filter(m => m.source === 'live').length > 20) {
359
+ console.log(chalk.gray(` ... ${L('ve', 'and')} ${allModels.filter(m => m.source === 'live').length - 20} ${L('model daha', 'more models')}`));
360
+ }
361
+ }
362
+
363
+ if (freeCount > 0) {
364
+ console.log(chalk.cyan(L('\n OpenRouter Ücretsiz Modeller\n', '\n OpenRouter Free Models\n')));
365
+ allModels.filter(m => m.source === 'openrouter-free').slice(0, 15).forEach(m => {
366
+ console.log(chalk.white(` ${m.id}`));
367
+ if (m.label && m.label !== m.id) console.log(chalk.gray(` ${m.label}`));
368
+ });
369
+ if (allModels.filter(m => m.source === 'openrouter-free').length > 15) {
370
+ console.log(chalk.gray(` ... ${L('ve', 'and')} ${allModels.filter(m => m.source === 'openrouter-free').length - 15} ${L('model daha', 'more models')}`));
371
+ }
372
+ }
373
+
374
+ if (staticCount > 0 && liveCount === 0) {
375
+ console.log(chalk.cyan(L('\n Statik Modeller\n', '\n Static Models\n')));
376
+ allModels.filter(m => m.source === 'static').forEach(m => {
377
+ const featureIcons = (m.features || []).map(f => FEATURE_ICONS[f] || '').join(' ');
378
+ console.log(chalk.white(` ${m.id}`) + (featureIcons ? ` ${featureIcons}` : ''));
379
+ console.log(chalk.gray(` ${m.label}`));
380
+ });
381
+ }
382
+
383
+ const currentModel = config.providerModel || '';
384
+ if (currentModel && !allModels.some(m => m.id === currentModel)) {
385
+ console.log(chalk.yellow(`\n ⚠ ${L('Mevcut model', 'Current model')} "${currentModel}" ${L('bu provider için bulunamadı.', 'was not found for this provider.')}`));
386
+ console.log(chalk.gray(L(' Güncellemek için: ', ' To update: ')) + chalk.cyan('natureco models set <model-id>'));
387
+ }
388
+
389
+ console.log('');
390
+ }
391
+
392
+ function manageAliases(params) {
393
+ const config = getConfig();
394
+ const aliases = config.modelAliases || {};
395
+
396
+ if (params.length === 0) {
397
+ console.log(chalk.cyan.bold(L('\n Model Takma Adları\n', '\n Model Aliases\n')));
398
+ if (Object.keys(aliases).length === 0) {
399
+ console.log(chalk.gray(L(' Takma ad yok.\n', ' No aliases.\n')));
400
+ console.log(chalk.gray(L(' Eklemek için: ', ' To add: ')) + chalk.cyan('natureco models aliases <alias> <model-id>\n'));
401
+ return;
402
+ }
403
+ Object.entries(aliases).forEach(([alias, model]) => {
404
+ console.log(chalk.white(` ${alias.padEnd(15)} → `) + chalk.cyan(model));
405
+ });
406
+ console.log('');
407
+ return;
408
+ }
409
+
410
+ if (params.length === 2) {
411
+ const [alias, modelId] = params;
412
+ aliases[alias] = modelId;
413
+ config.modelAliases = aliases;
414
+ saveConfig(config);
415
+ console.log(chalk.green(`\n ✓ ${L('Takma ad eklendi', 'Alias added')}: ${alias} → ${modelId}\n`));
416
+ return;
417
+ }
418
+
419
+ if (params.length === 1 && (params[0] === 'clear' || params[0] === '--clear')) {
420
+ config.modelAliases = {};
421
+ saveConfig(config);
422
+ console.log(chalk.green(L('\n ✓ Tüm takma adlar temizlendi.\n', '\n ✓ All aliases cleared.\n')));
423
+ return;
424
+ }
425
+
426
+ if (params.length === 1 && aliases[params[0]]) {
427
+ delete aliases[params[0]];
428
+ config.modelAliases = aliases;
429
+ saveConfig(config);
430
+ console.log(chalk.green(`\n ✓ ${L('Takma ad silindi', 'Alias removed')}: ${params[0]}\n`));
431
+ return;
432
+ }
433
+
434
+ if (params.length === 1) {
435
+ const resolved = resolveModel(params[0]);
436
+ if (resolved) {
437
+ console.log(chalk.cyan(`\n ${params[0]} → ${resolved}\n`));
438
+ } else {
439
+ console.log(chalk.yellow(`\n "${params[0]}" ${L('çözülemedi.', "couldn't be resolved.")}\n`));
440
+ }
441
+ return;
442
+ }
443
+
444
+ console.log(chalk.gray(L('\n Kullanım: natureco models aliases [<takma-ad> <model-id>]\n', '\n Usage: natureco models aliases [<alias> <model-id>]\n')));
445
+ }
446
+
447
+ function manageFallbacks(params) {
448
+ const config = getConfig();
449
+
450
+ if (params.length === 0 || params[0] === 'list') {
451
+ console.log(chalk.cyan.bold(L('\n Model Yedekleri\n', '\n Model Fallbacks\n')));
452
+ console.log(chalk.gray(L(' Birincil : ', ' Primary : ')) + chalk.white(config.providerModel || L('ayarlanmamış', 'not set')));
453
+ console.log(chalk.gray(L(' Yedek : ', ' Fallback : ')) + chalk.white(config.fallbackModel || L('ayarlanmamış', 'not set')));
454
+ console.log('');
455
+ console.log(chalk.gray(L(' Ayarlamak için: ', ' To set: ')) + chalk.cyan('natureco models fallbacks set <model-id>\n'));
456
+ return;
457
+ }
458
+
459
+ if (params[0] === 'set' && params[1]) {
460
+ config.fallbackModel = params[1];
461
+ saveConfig(config);
462
+ console.log(chalk.green(`\n ✓ ${L('Yedek model ayarlandı', 'Fallback model set')}: ${params[1]}\n`));
463
+ return;
464
+ }
465
+
466
+ if (params[0] === 'clear') {
467
+ delete config.fallbackModel;
468
+ saveConfig(config);
469
+ console.log(chalk.green(L('\n ✓ Yedek model temizlendi.\n', '\n ✓ Fallback model cleared.\n')));
470
+ return;
471
+ }
472
+
473
+ console.log(chalk.gray(L('\n Kullanım: natureco models fallbacks [list|set <model-id>|clear]\n', '\n Usage: natureco models fallbacks [list|set <model-id>|clear]\n')));
474
+ }
475
+
476
+ function setImageModel(modelId) {
477
+ if (!modelId) {
478
+ console.log(chalk.cyan('\n Current image model: ') + chalk.white(getConfig().imageModel || 'not set'));
479
+ console.log(chalk.gray('\n Usage: natureco models set-image <model-id>\n'));
480
+ return;
481
+ }
482
+ const config = getConfig();
483
+ config.imageModel = modelId;
484
+ saveConfig(config);
485
+ console.log(chalk.green(`\n ✓ Image model set: ${modelId}\n`));
486
+ }
487
+
488
+ function manageImageFallbacks(params) {
489
+ const config = getConfig();
490
+ if (params.length === 0 || params[0] === 'list') {
491
+ console.log(chalk.cyan.bold('\n Image Model Fallbacks\n'));
492
+ console.log(chalk.gray(' Primary : ') + chalk.white(config.imageModel || 'not set'));
493
+ console.log(chalk.gray(' Fallback : ') + chalk.white(config.imageFallbackModel || 'not set'));
494
+ console.log(chalk.gray('\n Set: natureco models image-fallbacks set <model-id>'));
495
+ console.log(chalk.gray(' Clear: natureco models image-fallbacks clear\n'));
496
+ return;
497
+ }
498
+ if (params[0] === 'set' && params[1]) {
499
+ config.imageFallbackModel = params[1];
500
+ saveConfig(config);
501
+ console.log(chalk.green(`\n ✓ Image fallback set: ${params[1]}\n`));
502
+ return;
503
+ }
504
+ if (params[0] === 'clear') {
505
+ delete config.imageFallbackModel;
506
+ saveConfig(config);
507
+ console.log(chalk.green('\n ✓ Image fallback cleared\n'));
508
+ return;
509
+ }
510
+ }
511
+
512
+ function resolveModel(input) {
513
+ const config = getConfig();
514
+ const aliases = config.modelAliases || {};
515
+ if (aliases[input]) return aliases[input];
516
+
517
+ const knownModels = getKnownModels(config.providerUrl || '');
518
+ const match = knownModels.find(m => m.id === input);
519
+ if (match) return match.id;
520
+
521
+ if (input.includes('/')) return input;
522
+ return null;
523
+ }
524
+
525
+ function getKnownModels(providerUrl) {
526
+ for (const [domain, list] of Object.entries(PROVIDER_MODELS)) {
527
+ if (providerUrl.includes(domain)) {
528
+ return list;
529
+ }
530
+ }
531
+ if (providerUrl.includes('openai') || providerUrl.includes('v1')) {
532
+ return PROVIDER_MODELS['api.openai.com'];
533
+ }
534
+ return [];
535
+ }
536
+
537
+ function findModelsEndpoint(providerUrl) {
538
+ if (!providerUrl) return null;
539
+ for (const pattern of PROVIDER_API_PATTERNS) {
540
+ if (providerUrl.includes(pattern.match)) {
541
+ return pattern.modelsEndpoint || (providerUrl.replace(/\/v1\/.*$|\/$/, '') + '/v1/models');
542
+ }
543
+ }
544
+ if (providerUrl.includes('/v1')) {
545
+ return providerUrl.replace(/\/v1\/.*$/, '/v1/models');
546
+ }
547
+ return null;
548
+ }
549
+
550
+ async function fetchLiveModels(endpoint, apiKey, opts) {
551
+ const headers = { 'Content-Type': 'application/json' };
552
+ if (apiKey) {
553
+ headers['Authorization'] = `Bearer ${apiKey}`;
554
+ }
555
+
556
+ const res = await fetch(endpoint, {
557
+ headers,
558
+ signal: AbortSignal.timeout(opts.timeout || 10000),
559
+ });
560
+
561
+ if (!res.ok) {
562
+ if (res.status === 401 || res.status === 403) {
563
+ throw new Error(L('API key geçersiz veya yetkisiz erişim', 'Invalid API key or unauthorized'));
564
+ }
565
+ if (res.status === 404) {
566
+ throw new Error(L('Provider bu endpoint\'i desteklemiyor (/v1/models)', 'Provider does not support this endpoint (/v1/models)'));
567
+ }
568
+ throw new Error(`HTTP ${res.status}: ${res.statusText}`);
569
+ }
570
+
571
+ const data = await res.json();
572
+ const rawModels = data.data || data.models || [];
573
+
574
+ return rawModels.map(m => ({
575
+ id: m.id || m.name || String(m),
576
+ label: m.name || m.id || String(m),
577
+ context: m.context_length || m.max_tokens || m.context_window || null,
578
+ features: [],
579
+ }));
580
+ }
581
+
582
+ async function probeProvider(providerUrl, apiKey, modelId, opts) {
583
+ const testModel = modelId || 'gpt-4o-mini';
584
+ const testUrl = providerUrl.includes('/v1/')
585
+ ? providerUrl
586
+ : providerUrl.replace(/\/$/, '') + '/v1/chat/completions';
587
+
588
+ const start = Date.now();
589
+ try {
590
+ const res = await fetch(testUrl, {
591
+ method: 'POST',
592
+ headers: {
593
+ 'Content-Type': 'application/json',
594
+ ...(apiKey ? { 'Authorization': `Bearer ${apiKey}` } : {}),
595
+ },
596
+ body: JSON.stringify({
597
+ model: testModel,
598
+ messages: [{ role: 'user', content: 'Respond with only the word "ok".' }],
599
+ max_tokens: 5,
600
+ }),
601
+ signal: AbortSignal.timeout(opts.timeout || 10000),
602
+ });
603
+
604
+ const ms = Date.now() - start;
605
+ if (res.ok) {
606
+ console.log(chalk.green(` ${testModel}: ${ms}ms ${L('(yanıt alındı)', '(response received)')}`));
607
+ } else if (res.status === 401 || res.status === 403) {
608
+ console.log(chalk.red(` ${testModel}: ${L('Yetkisiz', 'Unauthorized')} (${res.status})`));
609
+ } else if (res.status === 429) {
610
+ console.log(chalk.yellow(` ⚠ ${testModel}: Rate limit (${res.status})`));
611
+ } else {
612
+ console.log(chalk.yellow(` ⚠ ${testModel}: HTTP ${res.status} (${ms}ms)`));
613
+ }
614
+ } catch (err) {
615
+ const ms = Date.now() - start;
616
+ if (err.name === 'TimeoutError' || err.name === 'AbortError') {
617
+ console.log(chalk.red(` ✗ ${testModel}: ${L('Zaman aşımı', 'Timeout')} (${opts.timeout}ms)`));
618
+ } else {
619
+ console.log(chalk.red(` ✗ ${testModel}: ${err.message}`));
620
+ }
621
+ }
622
+ }
623
+
624
+ module.exports = models;