natureco-cli 5.20.4 → 5.22.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,360 +1,361 @@
1
- /**
2
- * NatureCo CLI — Cost Tracker & Model Router (Phase 4)
3
- *
4
- * Token kullanımını USD maliyete çevirir, model router ile
5
- * basit soruları ucuz modellere yönlendirir, bütçe aşımında uyarır.
6
- *
7
- * OpenClaw: $50-200/ay token patlaması.
8
- * NatureCo: Hedef $5-15/ay akıllı routing ile.
9
- *
10
- * Fiyatlar 2026 Haziran itibarıyladır, değişebilir.
11
- */
12
-
13
- // Fiyatlar (USD / 1M token). Provider+model bazlı.
14
- const PRICING = {
15
- // MiniMax — pay-as-you-go fiyatları (tahmini, güncellenebilir)
16
- 'minimax:MiniMax-M3': { input: 0.30, output: 1.20 },
17
- 'minimax:MiniMax-M2.7': { input: 0.15, output: 0.60 },
18
- 'minimax:MiniMax-M2.7-highspeed': { input: 0.20, output: 0.80 },
19
- 'minimax:MiniMax-M2.5': { input: 0.15, output: 0.60 },
20
- 'minimax:MiniMax-M2.5-highspeed': { input: 0.20, output: 0.80 },
21
- 'minimax:MiniMax-M2.1': { input: 0.10, output: 0.40 },
22
- 'minimax:MiniMax-M2.1-highspeed': { input: 0.12, output: 0.50 },
23
- 'minimax:MiniMax-M2': { input: 0.10, output: 0.40 },
24
-
25
- // Groq — genelde ücretsiz tier var, indirimli
26
- 'groq:llama-3.1-8b-instant': { input: 0.05, output: 0.08 },
27
- 'groq:llama-3.3-70b-versatile': { input: 0.59, output: 0.79 },
28
- 'groq:mixtral-8x7b-32768': { input: 0.27, output: 0.27 },
29
- 'groq:llama-3.2-90b-vision-preview': { input: 0.90, output: 0.90 },
30
-
31
- // OpenAI
32
- 'openai:gpt-4o': { input: 2.50, output: 10.00 },
33
- 'openai:gpt-4o-mini': { input: 0.15, output: 0.60 },
34
- 'openai:gpt-4-turbo': { input: 10.00, output: 30.00 },
35
- 'openai:o1-mini': { input: 3.00, output: 12.00 },
36
- 'openai:o1': { input: 15.00, output: 60.00 },
37
-
38
- // Anthropic
39
- 'anthropic:claude-sonnet-4-6': { input: 3.00, output: 15.00 },
40
- 'anthropic:claude-haiku-4-5': { input: 0.80, output: 4.00 },
41
- 'anthropic:claude-opus-4-5': { input: 15.00, output: 75.00 },
42
-
43
- // DeepSeek (çok ucuz)
44
- 'deepseek:deepseek-chat': { input: 0.14, output: 0.28 },
45
- 'deepseek:deepseek-coder': { input: 0.14, output: 0.28 },
46
-
47
- // Together AI
48
- 'together:meta-llama/Llama-3.3-70B-Instruct-Turbo': { input: 0.88, output: 0.88 },
49
-
50
- // Fireworks
51
- 'fireworks:accounts/fireworks/models/llama-v3p3-70b-instruct': { input: 0.90, output: 0.90 },
52
-
53
- // Local (ücretsiz)
54
- 'ollama:llama3.3': { input: 0, output: 0 },
55
- 'ollama:qwen2.5-coder': { input: 0, output: 0 },
56
- 'lmstudio:local': { input: 0, output: 0 },
57
- };
58
-
59
- const DEFAULT_PRICING = { input: 1.00, output: 3.00 };
60
-
61
- function getPricingKey(provider, model) {
62
- if (!provider || !model) return null;
63
- const p = provider.toLowerCase().replace(/^https?:\/\/[^/]+\//, '');
64
- return `${p}:${model}`;
65
- }
66
-
67
- function getPricing(provider, model) {
68
- const key = getPricingKey(provider, model);
69
- if (key && PRICING[key]) return PRICING[key];
70
- // Provider'ı bul, ilk modelini kullan
71
- const prefix = `${provider.toLowerCase()}:`;
72
- for (const [k, v] of Object.entries(PRICING)) {
73
- if (k.startsWith(prefix)) return v;
74
- }
75
- return DEFAULT_PRICING;
76
- }
77
-
78
- /**
79
- * Token kullanımını USD'ye çevir.
80
- * @param {object} usage - { input: number, output: number }
81
- * @param {string} provider
82
- * @param {string} model
83
- * @returns {number} USD
84
- */
85
- function calculateCost(usage, provider, model) {
86
- const pricing = getPricing(provider, model);
87
- const inputCost = (usage.input || 0) / 1_000_000 * pricing.input;
88
- const outputCost = (usage.output || 0) / 1_000_000 * pricing.output;
89
- return inputCost + outputCost;
90
- }
91
-
92
- /**
93
- * Model router — görev tipine göre en uygun modeli öner.
94
- *
95
- * Karmaşıklık seviyesi:
96
- * - "simple": Basit soru, kısa cevap, sınıflandırma
97
- * - "medium": Genel sohbet, doküman özetleme
98
- * - "complex": Kod yazma, mimari karar, çok adımlı reasoning
99
- * - "creative": Yaratıcı yazı, hikaye, içerik üretimi
100
- */
101
- const ROUTING = {
102
- simple: {
103
- description: 'Basit sorular — sınıflandırma, kısa cevaplar',
104
- models: [
105
- { provider: 'groq', model: 'llama-3.1-8b-instant', reason: 'En ucuz, hızlı, çoğu basit görev için yeterli' },
106
- { provider: 'groq', model: 'llama-3.3-70b-versatile', reason: 'Yedek: daha kaliteli ama ucuz' },
107
- { provider: 'anthropic', model: 'claude-haiku-4-5', reason: 'Yüksek kalite gerekiyorsa' },
108
- ],
109
- },
110
- medium: {
111
- description: 'Genel sohbet, özetleme, doküman analizi',
112
- models: [
113
- { provider: 'groq', model: 'llama-3.3-70b-versatile', reason: 'Dengeli fiyat/kalite' },
114
- { provider: 'anthropic', model: 'claude-haiku-4-5', reason: 'Daha kaliteli cevap' },
115
- { provider: 'openai', model: 'gpt-4o-mini', reason: 'OpenAI istiyorsan' },
116
- ],
117
- },
118
- complex: {
119
- description: 'Kod yazma, mimari, çok adımlı reasoning',
120
- models: [
121
- { provider: 'groq', model: 'llama-3.3-70b-versatile', reason: 'Hızlı, yeterince akıllı' },
122
- { provider: 'anthropic', model: 'claude-sonnet-4-6', reason: 'Kod için en iyi kalite' },
123
- { provider: 'openai', model: 'gpt-4o', reason: 'OpenAI istiyorsan' },
124
- ],
125
- },
126
- creative: {
127
- description: 'Yaratıcı yazı, hikaye, içerik üretimi',
128
- models: [
129
- { provider: 'anthropic', model: 'claude-sonnet-4-6', reason: 'Yaratıcı yazıda güçlü' },
130
- { provider: 'openai', model: 'gpt-4o', reason: 'OpenAI yaratıcı kalitesi' },
131
- { provider: 'groq', model: 'llama-3.3-70b-versatile', reason: 'Ücretsiz denemek için' },
132
- ],
133
- },
134
- };
135
-
136
- /**
137
- * Basit karmaşıklık tahmini — prompt içeriğine bakarak.
138
- */
139
- function estimateComplexity(prompt) {
140
- if (!prompt) return 'simple';
141
- const text = prompt.toLowerCase();
142
- const len = text.length;
143
-
144
- // Code işaretleri
145
- const codeIndicators = (text.match(/```|function|class|import|const |let |var |=>|\{|\}/g) || []).length;
146
- if (codeIndicators >= 3) return 'complex';
147
-
148
- // Karmaşık soru işaretleri
149
- const complexKeywords = ['mimari', 'tasarla', 'analiz', 'karşılaştır', 'açıkla', 'optimize et', 'refactor', 'debug', 'neden', 'nasıl çalışır'];
150
- if (complexKeywords.some(k => text.includes(k))) return 'complex';
151
-
152
- // Yaratıcı işaretler
153
- const creativeKeywords = ['yaz', 'hikaye', 'şiir', 'blog', 'içerik', 'makale', 'yaratıcı', 'senaryo'];
154
- if (creativeKeywords.some(k => text.includes(k)) && len > 100) return 'creative';
155
-
156
- // Uzun metin → medium
157
- if (len > 500) return 'medium';
158
-
159
- return 'simple';
160
- }
161
-
162
- /**
163
- * Bir prompt için önerilen modeli döner.
164
- */
165
- function suggestModel(prompt, options = {}) {
166
- const { forceComplexity, currentProvider, currentModel } = options;
167
-
168
- // Mevcut model zaten pahalıysa ve görev basitse, uyar
169
- const complexity = forceComplexity || estimateComplexity(prompt);
170
- const route = ROUTING[complexity];
171
-
172
- if (!route) return null;
173
-
174
- // Mevcut model routing listesinde mi?
175
- if (currentProvider && currentModel) {
176
- const matched = route.models.find(m => m.provider === currentProvider.toLowerCase() && m.model === currentModel);
177
- if (matched) {
178
- return { complexity, ...matched, optimal: true };
179
- }
180
- }
181
-
182
- // İlk öneriyi döndür
183
- return { complexity, ...route.models[0], optimal: false };
184
- }
185
-
186
- /**
187
- * Bir kullanım kaydını cost-tracker'a ekle.
188
- */
189
- const fs = require('fs');
190
- const path = require('path');
191
- const os = require('os');
192
-
193
- const COST_FILE = path.join(os.homedir(), '.natureco', 'cost-tracking.json');
194
- const BUDGET_CONFIG_FILE = path.join(os.homedir(), '.natureco', 'budget-config.json');
195
-
196
- const DEFAULT_BUDGET = {
197
- dailyLimit: 5.00, // USD/gün
198
- monthlyLimit: 100.00, // USD/ay
199
- warnAt: 0.75, // %75'inde uyar
200
- downgradeAt: 0.90, // %90'ında otomatik downgrade
201
- };
202
-
203
- function loadBudget() {
204
- try {
205
- if (fs.existsSync(BUDGET_CONFIG_FILE)) {
206
- return { ...DEFAULT_BUDGET, ...JSON.parse(fs.readFileSync(BUDGET_CONFIG_FILE, 'utf8')) };
207
- }
208
- } catch {}
209
- return { ...DEFAULT_BUDGET };
210
- }
211
-
212
- function saveBudget(budget) {
213
- const dir = path.dirname(BUDGET_CONFIG_FILE);
214
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
215
- fs.writeFileSync(BUDGET_CONFIG_FILE, JSON.stringify(budget, null, 2), 'utf8');
216
- }
217
-
218
- function loadCosts() {
219
- try {
220
- if (fs.existsSync(COST_FILE)) {
221
- return JSON.parse(fs.readFileSync(COST_FILE, 'utf8'));
222
- }
223
- } catch {}
224
- return { entries: [] };
225
- }
226
-
227
- function saveCosts(data) {
228
- const dir = path.dirname(COST_FILE);
229
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
230
- fs.writeFileSync(COST_FILE, JSON.stringify(data, null, 2), 'utf8');
231
- }
232
-
233
- /**
234
- * Yeni bir kullanım kaydı ekle.
235
- */
236
- function recordUsage({ provider, model, input, output, sessionId, command }) {
237
- const data = loadCosts();
238
- const cost = calculateCost({ input, output }, provider, model);
239
- const entry = {
240
- ts: new Date().toISOString(),
241
- provider,
242
- model,
243
- input: input || 0,
244
- output: output || 0,
245
- cost,
246
- sessionId: sessionId || null,
247
- command: command || null,
248
- };
249
- data.entries.push(entry);
250
- // Maksimum 10.000 entry tut (eski dosyaları rotate et)
251
- if (data.entries.length > 10000) {
252
- data.entries = data.entries.slice(-10000);
253
- }
254
- saveCosts(data);
255
- return entry;
256
- }
257
-
258
- /**
259
- * Belirli bir dönem için toplam maliyet.
260
- */
261
- function totalForPeriod(period = 'today') {
262
- const data = loadCosts();
263
- const now = new Date();
264
- let cutoff;
265
-
266
- switch (period) {
267
- case 'today':
268
- cutoff = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
269
- break;
270
- case 'week':
271
- cutoff = now.getTime() - 7 * 86400000;
272
- break;
273
- case 'month':
274
- cutoff = new Date(now.getFullYear(), now.getMonth(), 1).getTime();
275
- break;
276
- case 'all':
277
- cutoff = 0;
278
- break;
279
- default:
280
- cutoff = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
281
- }
282
-
283
- const filtered = data.entries.filter(e => new Date(e.ts).getTime() >= cutoff);
284
- let totalCost = 0;
285
- let totalInput = 0;
286
- let totalOutput = 0;
287
- const byProvider = {};
288
- const byModel = {};
289
-
290
- for (const e of filtered) {
291
- totalCost += e.cost;
292
- totalInput += e.input;
293
- totalOutput += e.output;
294
- byProvider[e.provider] = (byProvider[e.provider] || 0) + e.cost;
295
- byModel[`${e.provider}:${e.model}`] = (byModel[`${e.provider}:${e.model}`] || 0) + e.cost;
296
- }
297
-
298
- return {
299
- period,
300
- entries: filtered.length,
301
- totalCost,
302
- totalInput,
303
- totalOutput,
304
- byProvider,
305
- byModel,
306
- topModel: Object.entries(byModel).sort((a, b) => b[1] - a[1])[0]?.[0] || null,
307
- };
308
- }
309
-
310
- /**
311
- * Bütçe kontrolü — limit aşıldı mı?
312
- */
313
- function checkBudget() {
314
- const budget = loadBudget();
315
- const today = totalForPeriod('today');
316
- const month = totalForPeriod('month');
317
-
318
- const dailyUsage = today.totalCost / budget.dailyLimit;
319
- const monthlyUsage = month.totalCost / budget.monthlyLimit;
320
-
321
- return {
322
- daily: {
323
- spent: today.totalCost,
324
- limit: budget.dailyLimit,
325
- usage: dailyUsage,
326
- exceeded: dailyUsage >= 1.0,
327
- warning: dailyUsage >= budget.warnAt,
328
- },
329
- monthly: {
330
- spent: month.totalCost,
331
- limit: budget.monthlyLimit,
332
- usage: monthlyUsage,
333
- exceeded: monthlyUsage >= 1.0,
334
- warning: monthlyUsage >= budget.warnAt,
335
- },
336
- shouldDowngrade: dailyUsage >= budget.downgradeAt || monthlyUsage >= budget.downgradeAt,
337
- };
338
- }
339
-
340
- function formatUSD(amount) {
341
- if (amount === 0) return '$0.00';
342
- if (amount < 0.01) return `$${(amount * 100).toFixed(2)}¢`;
343
- return `$${amount.toFixed(4)}`;
344
- }
345
-
346
- module.exports = {
347
- PRICING,
348
- ROUTING,
349
- DEFAULT_BUDGET,
350
- getPricing,
351
- calculateCost,
352
- estimateComplexity,
353
- suggestModel,
354
- loadBudget,
355
- saveBudget,
356
- recordUsage,
357
- totalForPeriod,
358
- checkBudget,
359
- formatUSD,
360
- };
1
+ /**
2
+ * NatureCo CLI — Cost Tracker & Model Router (Phase 4)
3
+ *
4
+ * Token kullanımını USD maliyete çevirir, model router ile
5
+ * basit soruları ucuz modellere yönlendirir, bütçe aşımında uyarır.
6
+ *
7
+ * OpenClaw: $50-200/ay token patlaması.
8
+ * NatureCo: Hedef $5-15/ay akıllı routing ile.
9
+ *
10
+ * Fiyatlar 2026 Haziran itibarıyladır, değişebilir.
11
+ */
12
+
13
+ // Fiyatlar (USD / 1M token). Provider+model bazlı.
14
+ const PRICING = {
15
+ // MiniMax — pay-as-you-go fiyatları (tahmini, güncellenebilir)
16
+ 'minimax:MiniMax-M3': { input: 0.30, output: 1.20 },
17
+ 'minimax:MiniMax-M2.7': { input: 0.15, output: 0.60 },
18
+ 'minimax:MiniMax-M2.7-highspeed': { input: 0.20, output: 0.80 },
19
+ 'minimax:MiniMax-M2.5': { input: 0.15, output: 0.60 },
20
+ 'minimax:MiniMax-M2.5-highspeed': { input: 0.20, output: 0.80 },
21
+ 'minimax:MiniMax-M2.1': { input: 0.10, output: 0.40 },
22
+ 'minimax:MiniMax-M2.1-highspeed': { input: 0.12, output: 0.50 },
23
+ 'minimax:MiniMax-M2': { input: 0.10, output: 0.40 },
24
+
25
+ // Groq — genelde ücretsiz tier var, indirimli
26
+ 'groq:llama-3.1-8b-instant': { input: 0.05, output: 0.08 },
27
+ 'groq:llama-3.3-70b-versatile': { input: 0.59, output: 0.79 },
28
+ 'groq:mixtral-8x7b-32768': { input: 0.27, output: 0.27 },
29
+ 'groq:llama-3.2-90b-vision-preview': { input: 0.90, output: 0.90 },
30
+
31
+ // OpenAI
32
+ 'openai:gpt-4o': { input: 2.50, output: 10.00 },
33
+ 'openai:gpt-4o-mini': { input: 0.15, output: 0.60 },
34
+ 'openai:gpt-4-turbo': { input: 10.00, output: 30.00 },
35
+ 'openai:o1-mini': { input: 3.00, output: 12.00 },
36
+ 'openai:o1': { input: 15.00, output: 60.00 },
37
+
38
+ // Anthropic
39
+ 'anthropic:claude-sonnet-4-6': { input: 3.00, output: 15.00 },
40
+ 'anthropic:claude-haiku-4-5': { input: 0.80, output: 4.00 },
41
+ 'anthropic:claude-opus-4-5': { input: 15.00, output: 75.00 },
42
+
43
+ // DeepSeek (çok ucuz)
44
+ 'deepseek:deepseek-chat': { input: 0.14, output: 0.28 },
45
+ 'deepseek:deepseek-coder': { input: 0.14, output: 0.28 },
46
+
47
+ // Together AI
48
+ 'together:meta-llama/Llama-3.3-70B-Instruct-Turbo': { input: 0.88, output: 0.88 },
49
+
50
+ // Fireworks
51
+ 'fireworks:accounts/fireworks/models/llama-v3p3-70b-instruct': { input: 0.90, output: 0.90 },
52
+
53
+ // Local (ücretsiz)
54
+ 'ollama:llama3.3': { input: 0, output: 0 },
55
+ 'ollama:qwen2.5-coder': { input: 0, output: 0 },
56
+ 'lmstudio:local': { input: 0, output: 0 },
57
+ };
58
+
59
+ const DEFAULT_PRICING = { input: 1.00, output: 3.00 };
60
+
61
+ function getPricingKey(provider, model) {
62
+ if (!provider || !model) return null;
63
+ const p = provider.toLowerCase().replace(/^https?:\/\/[^/]+\//, '');
64
+ return `${p}:${model}`;
65
+ }
66
+
67
+ function getPricing(provider, model) {
68
+ const key = getPricingKey(provider, model);
69
+ if (key && PRICING[key]) return PRICING[key];
70
+ // Provider'ı bul, ilk modelini kullan
71
+ const prefix = `${provider.toLowerCase()}:`;
72
+ for (const [k, v] of Object.entries(PRICING)) {
73
+ if (k.startsWith(prefix)) return v;
74
+ }
75
+ return DEFAULT_PRICING;
76
+ }
77
+
78
+ /**
79
+ * Token kullanımını USD'ye çevir.
80
+ * @param {object} usage - { input: number, output: number }
81
+ * @param {string} provider
82
+ * @param {string} model
83
+ * @returns {number} USD
84
+ */
85
+ function calculateCost(usage, provider, model) {
86
+ const pricing = getPricing(provider, model);
87
+ const inputCost = (usage.input || 0) / 1_000_000 * pricing.input;
88
+ const outputCost = (usage.output || 0) / 1_000_000 * pricing.output;
89
+ return inputCost + outputCost;
90
+ }
91
+
92
+ /**
93
+ * Model router — görev tipine göre en uygun modeli öner.
94
+ *
95
+ * Karmaşıklık seviyesi:
96
+ * - "simple": Basit soru, kısa cevap, sınıflandırma
97
+ * - "medium": Genel sohbet, doküman özetleme
98
+ * - "complex": Kod yazma, mimari karar, çok adımlı reasoning
99
+ * - "creative": Yaratıcı yazı, hikaye, içerik üretimi
100
+ */
101
+ const ROUTING = {
102
+ simple: {
103
+ description: 'Basit sorular — sınıflandırma, kısa cevaplar',
104
+ models: [
105
+ { provider: 'groq', model: 'llama-3.1-8b-instant', reason: 'En ucuz, hızlı, çoğu basit görev için yeterli' },
106
+ { provider: 'groq', model: 'llama-3.3-70b-versatile', reason: 'Yedek: daha kaliteli ama ucuz' },
107
+ { provider: 'anthropic', model: 'claude-haiku-4-5', reason: 'Yüksek kalite gerekiyorsa' },
108
+ ],
109
+ },
110
+ medium: {
111
+ description: 'Genel sohbet, özetleme, doküman analizi',
112
+ models: [
113
+ { provider: 'groq', model: 'llama-3.3-70b-versatile', reason: 'Dengeli fiyat/kalite' },
114
+ { provider: 'anthropic', model: 'claude-haiku-4-5', reason: 'Daha kaliteli cevap' },
115
+ { provider: 'openai', model: 'gpt-4o-mini', reason: 'OpenAI istiyorsan' },
116
+ ],
117
+ },
118
+ complex: {
119
+ description: 'Kod yazma, mimari, çok adımlı reasoning',
120
+ models: [
121
+ { provider: 'groq', model: 'llama-3.3-70b-versatile', reason: 'Hızlı, yeterince akıllı' },
122
+ { provider: 'anthropic', model: 'claude-sonnet-4-6', reason: 'Kod için en iyi kalite' },
123
+ { provider: 'openai', model: 'gpt-4o', reason: 'OpenAI istiyorsan' },
124
+ ],
125
+ },
126
+ creative: {
127
+ description: 'Yaratıcı yazı, hikaye, içerik üretimi',
128
+ models: [
129
+ { provider: 'anthropic', model: 'claude-sonnet-4-6', reason: 'Yaratıcı yazıda güçlü' },
130
+ { provider: 'openai', model: 'gpt-4o', reason: 'OpenAI yaratıcı kalitesi' },
131
+ { provider: 'groq', model: 'llama-3.3-70b-versatile', reason: 'Ücretsiz denemek için' },
132
+ ],
133
+ },
134
+ };
135
+
136
+ /**
137
+ * Basit karmaşıklık tahmini — prompt içeriğine bakarak.
138
+ */
139
+ function estimateComplexity(prompt) {
140
+ if (!prompt) return 'simple';
141
+ const text = prompt.toLowerCase();
142
+ const len = text.length;
143
+
144
+ // Code işaretleri
145
+ const codeIndicators = (text.match(/```|function|class|import|const |let |var |=>|\{|\}/g) || []).length;
146
+ if (codeIndicators >= 3) return 'complex';
147
+
148
+ // Karmaşık soru işaretleri
149
+ const complexKeywords = ['mimari', 'tasarla', 'analiz', 'karşılaştır', 'açıkla', 'optimize et', 'refactor', 'debug', 'neden', 'nasıl çalışır'];
150
+ if (complexKeywords.some(k => text.includes(k))) return 'complex';
151
+
152
+ // Yaratıcı işaretler
153
+ const creativeKeywords = ['yaz', 'hikaye', 'şiir', 'blog', 'içerik', 'makale', 'yaratıcı', 'senaryo'];
154
+ if (creativeKeywords.some(k => text.includes(k)) && len > 100) return 'creative';
155
+
156
+ // Uzun metin → medium
157
+ if (len > 500) return 'medium';
158
+
159
+ return 'simple';
160
+ }
161
+
162
+ /**
163
+ * Bir prompt için önerilen modeli döner.
164
+ */
165
+ function suggestModel(prompt, options = {}) {
166
+ const { forceComplexity, currentProvider, currentModel } = options;
167
+
168
+ // Mevcut model zaten pahalıysa ve görev basitse, uyar
169
+ const complexity = forceComplexity || estimateComplexity(prompt);
170
+ const route = ROUTING[complexity];
171
+
172
+ if (!route) return null;
173
+
174
+ // Mevcut model routing listesinde mi?
175
+ if (currentProvider && currentModel) {
176
+ const matched = route.models.find(m => m.provider === currentProvider.toLowerCase() && m.model === currentModel);
177
+ if (matched) {
178
+ return { complexity, ...matched, optimal: true };
179
+ }
180
+ }
181
+
182
+ // İlk öneriyi döndür
183
+ return { complexity, ...route.models[0], optimal: false };
184
+ }
185
+
186
+ /**
187
+ * Bir kullanım kaydını cost-tracker'a ekle.
188
+ */
189
+ const fs = require('fs');
190
+ const path = require('path');
191
+ const os = require('os');
192
+
193
+ const COST_FILE = path.join(os.homedir(), '.natureco', 'cost-tracking.json');
194
+ const BUDGET_CONFIG_FILE = path.join(os.homedir(), '.natureco', 'budget-config.json');
195
+
196
+ const DEFAULT_BUDGET = {
197
+ dailyLimit: 5.00, // USD/gün
198
+ monthlyLimit: 100.00, // USD/ay
199
+ warnAt: 0.75, // %75'inde uyar
200
+ downgradeAt: 0.90, // %90'ında otomatik downgrade
201
+ };
202
+
203
+ function loadBudget() {
204
+ try {
205
+ if (fs.existsSync(BUDGET_CONFIG_FILE)) {
206
+ return { ...DEFAULT_BUDGET, ...JSON.parse(fs.readFileSync(BUDGET_CONFIG_FILE, 'utf8')) };
207
+ }
208
+ } catch {}
209
+ return { ...DEFAULT_BUDGET };
210
+ }
211
+
212
+ function saveBudget(budget) {
213
+ const dir = path.dirname(BUDGET_CONFIG_FILE);
214
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
215
+ fs.writeFileSync(BUDGET_CONFIG_FILE, JSON.stringify(budget, null, 2), 'utf8');
216
+ }
217
+
218
+ function loadCosts() {
219
+ try {
220
+ if (fs.existsSync(COST_FILE)) {
221
+ return JSON.parse(fs.readFileSync(COST_FILE, 'utf8'));
222
+ }
223
+ } catch {}
224
+ return { entries: [] };
225
+ }
226
+
227
+ function saveCosts(data) {
228
+ const dir = path.dirname(COST_FILE);
229
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
230
+ fs.writeFileSync(COST_FILE, JSON.stringify(data, null, 2), 'utf8');
231
+ }
232
+
233
+ /**
234
+ * Yeni bir kullanım kaydı ekle.
235
+ */
236
+ function recordUsage({ provider, model, input, output, sessionId, command }) {
237
+ const data = loadCosts();
238
+ const cost = calculateCost({ input, output }, provider, model);
239
+ const entry = {
240
+ ts: new Date().toISOString(),
241
+ provider,
242
+ model,
243
+ input: input || 0,
244
+ output: output || 0,
245
+ cost,
246
+ sessionId: sessionId || null,
247
+ command: command || null,
248
+ };
249
+ data.entries.push(entry);
250
+ // Maksimum 10.000 entry tut (eski dosyaları rotate et)
251
+ if (data.entries.length > 10000) {
252
+ data.entries = data.entries.slice(-10000);
253
+ }
254
+ saveCosts(data);
255
+ return entry;
256
+ }
257
+
258
+ /**
259
+ * Belirli bir dönem için toplam maliyet.
260
+ */
261
+ function totalForPeriod(period = 'today') {
262
+ const data = loadCosts();
263
+ const now = new Date();
264
+ let cutoff;
265
+
266
+ switch (period) {
267
+ case 'today':
268
+ cutoff = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
269
+ break;
270
+ case 'week':
271
+ cutoff = now.getTime() - 7 * 86400000;
272
+ break;
273
+ case 'month':
274
+ cutoff = new Date(now.getFullYear(), now.getMonth(), 1).getTime();
275
+ break;
276
+ case 'all':
277
+ cutoff = 0;
278
+ break;
279
+ default:
280
+ cutoff = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
281
+ }
282
+
283
+ const filtered = data.entries.filter(e => new Date(e.ts).getTime() >= cutoff);
284
+ let totalCost = 0;
285
+ let totalInput = 0;
286
+ let totalOutput = 0;
287
+ const byProvider = {};
288
+ const byModel = {};
289
+
290
+ for (const e of filtered) {
291
+ totalCost += e.cost;
292
+ totalInput += e.input;
293
+ totalOutput += e.output;
294
+ byProvider[e.provider] = (byProvider[e.provider] || 0) + e.cost;
295
+ byModel[`${e.provider}:${e.model}`] = (byModel[`${e.provider}:${e.model}`] || 0) + e.cost;
296
+ }
297
+
298
+ return {
299
+ period,
300
+ entries: filtered.length,
301
+ totalCost,
302
+ totalInput,
303
+ totalOutput,
304
+ byProvider,
305
+ byModel,
306
+ topModel: Object.entries(byModel).sort((a, b) => b[1] - a[1])[0]?.[0] || null,
307
+ };
308
+ }
309
+
310
+ /**
311
+ * Bütçe kontrolü — limit aşıldı mı?
312
+ */
313
+ function checkBudget() {
314
+ const budget = loadBudget();
315
+ const today = totalForPeriod('today');
316
+ const month = totalForPeriod('month');
317
+
318
+ const dailyUsage = today.totalCost / budget.dailyLimit;
319
+ const monthlyUsage = month.totalCost / budget.monthlyLimit;
320
+
321
+ return {
322
+ daily: {
323
+ spent: today.totalCost,
324
+ limit: budget.dailyLimit,
325
+ usage: dailyUsage,
326
+ exceeded: dailyUsage >= 1.0,
327
+ warning: dailyUsage >= budget.warnAt,
328
+ },
329
+ monthly: {
330
+ spent: month.totalCost,
331
+ limit: budget.monthlyLimit,
332
+ usage: monthlyUsage,
333
+ exceeded: monthlyUsage >= 1.0,
334
+ warning: monthlyUsage >= budget.warnAt,
335
+ },
336
+ shouldDowngrade: dailyUsage >= budget.downgradeAt || monthlyUsage >= budget.downgradeAt,
337
+ };
338
+ }
339
+
340
+ function formatUSD(amount) {
341
+ if (amount === 0) return '$0.00';
342
+ // 1 sentin altı: yalnızca sent işareti ("0.15¢"), $ ile karıştırma
343
+ if (amount < 0.01) return `${(amount * 100).toFixed(2)}¢`;
344
+ return `$${amount.toFixed(4)}`;
345
+ }
346
+
347
+ module.exports = {
348
+ PRICING,
349
+ ROUTING,
350
+ DEFAULT_BUDGET,
351
+ getPricing,
352
+ calculateCost,
353
+ estimateComplexity,
354
+ suggestModel,
355
+ loadBudget,
356
+ saveBudget,
357
+ recordUsage,
358
+ totalForPeriod,
359
+ checkBudget,
360
+ formatUSD,
361
+ };