natureco-cli 2.23.27 → 2.23.29

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