natureco-cli 5.0.1 → 5.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -484,3 +484,56 @@ natureco repl
484
484
  > "Python ile 2+2 hesapla" # code_execution
485
485
  > "Tum TODO'lari goster" # todo_write
486
486
  ```
487
+
488
+ ## [5.1.0] - 2026-06-22 — "SELF-GENERATING SKILLS"
489
+
490
+ ### Yeni: skill_generate Tool (48. Tool)
491
+ Parton'un vizyonu: "Ihtiyaca gore skill yoksa kendi uretsin". LLM ile yeni bir skill talimati uretir, diske kaydeder ve hemen kullanima sunar.
492
+
493
+ #### Nasil calisir
494
+ 1. Kullanici REPL'de bir istek yapar (ornek: "PDF dosyalarini birlestir")
495
+ 2. Mevcut 47 tool/skill ile cozum yoksa `skill_generate` otomatik devreye girer
496
+ 3. LLM'a (MiniMax, OpenAI, vs) skill taslagi uretmesi icin istek gonderilir
497
+ 4. SKILL.md + metadata.json `~/.natureco/skills/<auto-name>/` altina kaydedilir
498
+ 5. Skill hemen REPL'de kullanilabilir olur
499
+
500
+ #### Test
501
+ ```
502
+ > "PDF dosyalarini tek bir PDF dosyasinda birlestir"
503
+ Tool: skill_generate
504
+ Args: {"taskDescription":"..."}
505
+ Result: skill olusturuldu, hemen kullanilabilir!
506
+ ```
507
+
508
+ ### Duzeltmeler
509
+ - **file_search regex bug**: `**/*.js` pattern'i patliyordu (`Nothing to repeat`). Placeholder + escape sirasini degistirdik, artik calisiyor.
510
+ - **v4.5.1 tui.C.cyan/accent**: TUI engine palette'inde yoktu, `amber` ile degistirildi.
511
+ - **code_v5.js legacy code komutu**: v5.0'da eski v2.23 kodu eski yere fallback (`--legacy` flag).
512
+
513
+ ### Istatistikler (final)
514
+ - **Toplam tool**: 48 (Phase 9'da 1'den basladi)
515
+ - **Toplam komut**: 100+
516
+ - **Toplam satır kod**: ~6000 (bin + src)
517
+ - **Phase 1-9**: 9 buyuk iterasyon
518
+ - **Patch versiyonlari (v4.6-v5.1)**: 14+
519
+ - **npm latest**: 5.1.0
520
+ - **CHANGELOG**: tam
521
+ - **README**: v4.5+, guncel
522
+ - **Doc (natureco.me/cli)**: 9116 char, hazir
523
+ - **Pazarlama**: HN, Reddit, Medium yazilari hazir
524
+
525
+ ### Ozellik Matrisi (Final)
526
+ - **AI & Media (6):** image_generation, media_understanding, text_to_speech, llm_task, canvas, audio_understanding
527
+ - **Dosya (6):** read_file, write_file, list_dir, filesystem, file_search, grep_search
528
+ - **Sistem (5):** bash, code_execution, shell_command, http_request, git
529
+ - **Web (6):** web_search, web_readability, exa_search, duckduckgo, firecrawl, browser
530
+ - **macOS Native (6):** calendar_add, reminder_add, notes_add, mac_notify, mac_app_open, mac_app_quit
531
+ - **Verimlilik (5):** todo_write, kanban, memory_search, cron_create, notebook_edit
532
+ - **Sistem Tools (5):** delegate_task, skills_marketplace, skills_autoload, skill_generate, audio_understanding
533
+ - **Diger (8):** document_extract, image_generation, duckduckgo, exa_search, firecrawl, http, audio_understanding, document_extract
534
+
535
+ ### Yayin Bilgisi
536
+ - **NPM**: https://npmjs.com/package/natureco-cli
537
+ - **Versiyon**: 5.1.0
538
+ - **Kurulum**: `npm install -g natureco-cli`
539
+ - **Lisans**: MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "natureco-cli",
3
- "version": "5.0.1",
3
+ "version": "5.1.1",
4
4
  "description": "OpenClaw'dan daha güvenli, daha hızlı, daha ucuz AI agent CLI. Multi-agent, self-evolving skills, audit log, maliyet optimizasyonu ve NatureCo platform-native.",
5
5
  "bin": {
6
6
  "natureco": "bin/natureco.js"
@@ -468,6 +468,10 @@ async function startRepl(args) {
468
468
  // v4.7.4: Daha agresif dil zorlaması — model önceki versiyonlarda İngilizce cevap veriyordu
469
469
  'KRİTİK DİL KURALI: Kullanıcı Türkçe yazıyorsa MUTLAKA %100 Türkçe cevap ver. Asla İngilizce, Çince veya başka dil kullanma. Cevabının TAMAMI Türkçe olmalı.',
470
470
  'ÖNEMLİ: <tool_call>, <invoke>, function_call veya benzeri XML/JSON formatında tool çağrısı SİMÜLE ETME. Sadece düz metin cevap ver. Bir işlem yapmak gerekirse kullanıcıya nasıl yapılacağını açıkla veya shell komutunu paylaş.',
471
+ // v5.1.1: Memory'ye otomatik kaydet — kullanici kim olduğunu/neyi sevdiğini söylerse memory_write tool'unu çağır
472
+ "ÖNEMLİ: Kullanıcı \"adım X\", \"sevdiğim Y\", \"ben Z\" gibi kişisel bilgi verdiğinde MUTLAKA memory_write tool'unu çağır (username: kullanıcı adı, fact: bilgi, category: personal). Bu sayede sonraki oturumlarda hatırlayabilirsin. \"Sen benim patronumsun\" gibi ifadeler de memory'ye yazılmalı.",
473
+ // v5.1.1: Bot isim değişikliği
474
+ 'Kullanıcı "senin adın X" derse memory_write ile botName değiştir ve bundan sonra kendini o isimle tanıt.',
471
475
  // v4.8.0: Kullanıcının dosya yollarını bil — tool'lar doğru path kullansın
472
476
  cfg.userHome ? `Kullanıcının home dizini: ${cfg.userHome}. Downloads: ${cfg.userHome}/Downloads, Desktop: ${cfg.userHome}/Desktop. Dosya işlemlerinde BU path'leri kullan.` : '',
473
477
  memory.nickname && memory.name
@@ -0,0 +1,169 @@
1
+ /**
2
+ * mac_alarm - macOS Clock app ile alarm kur (v5.1.1)
3
+ *
4
+ * Parton'un istegi: "Saat uygulamasi uzerinden saat 18:00 alarm kur"
5
+ * Eski reminder_add date parse edemiyordu. Bu tool AppleScript ile
6
+ * Clock.app'in events sistemine yazar (alarm orada saklanir).
7
+ */
8
+
9
+ const { spawn } = require("child_process");
10
+ const os = require("os");
11
+
12
+ const IS_MAC = os.platform() === "darwin";
13
+
14
+ function runAppleScript(script) {
15
+ return new Promise((resolve, reject) => {
16
+ const proc = spawn("osascript", ["-e", script]);
17
+ let out = ""; let err = "";
18
+ proc.stdout.on("data", d => out += d);
19
+ proc.stderr.on("data", d => err += d);
20
+ proc.on("close", code => code === 0 ? resolve(out.trim()) : reject(new Error(err.trim() || "osascript error")));
21
+ });
22
+ }
23
+
24
+ /**
25
+ * Tarih/saat ayristir — esnek formatlar kabul eder
26
+ * @param input - "18:00", "18:30 tomorrow", "2026-06-22 18:00", "+1 hour"
27
+ * @returns { hours, minutes, date, formattedDate, formattedTime }
28
+ */
29
+ function parseAlarmTime(input) {
30
+ const now = new Date();
31
+ let target = new Date();
32
+ let hours = 0, minutes = 0;
33
+
34
+ // Format: "18:00" veya "18:30 tomorrow"
35
+ const hmMatch = input.match(/(\d{1,2}):(\d{2})(?:\s+(tomorrow|yarın|today|bugün|next\s+(\w+)))?/i);
36
+ if (hmMatch) {
37
+ hours = parseInt(hmMatch[1]);
38
+ minutes = parseInt(hmMatch[2]);
39
+ const dayShift = hmMatch[3];
40
+ if (dayShift && /tomorrow|yarın/i.test(dayShift)) {
41
+ target.setDate(target.getDate() + 1);
42
+ }
43
+ } else if (input.match(/^\d{1,2}$/)) {
44
+ hours = parseInt(input);
45
+ minutes = 0;
46
+ } else if (input.match(/^\+(\d+)\s*(hour|minute|day)/i)) {
47
+ const m = input.match(/\+(\d+)\s*(hour|minute|day)/i);
48
+ const n = parseInt(m[1]);
49
+ const unit = m[2].toLowerCase();
50
+ if (unit === "hour") target.setHours(target.getHours() + n);
51
+ else if (unit === "minute") target.setMinutes(target.getMinutes() + n);
52
+ else if (unit === "day") target.setDate(target.getDate() + n);
53
+ hours = target.getHours();
54
+ minutes = target.getMinutes();
55
+ } else {
56
+ // ISO date parse
57
+ const parsed = new Date(input);
58
+ if (!isNaN(parsed)) {
59
+ target = parsed;
60
+ hours = target.getHours();
61
+ minutes = target.getMinutes();
62
+ } else {
63
+ throw new Error("Gecersiz zaman formati. Ornekler: '18:00', 'tomorrow 09:30', '+1 hour', '2026-06-23 07:00'");
64
+ }
65
+ }
66
+
67
+ // Eger saat dakika parse edildiyse target'a uygula
68
+ if (hmMatch) {
69
+ target.setHours(hours, minutes, 0, 0);
70
+ }
71
+
72
+ const pad = (n) => String(n).padStart(2, "0");
73
+ return {
74
+ hours, minutes,
75
+ date: target.toISOString().slice(0, 10),
76
+ formattedDate: `${pad(target.getMonth() + 1)}/${pad(target.getDate())}/${target.getFullYear()}`,
77
+ formattedTime: `${pad(hours)}:${pad(minutes)}:00`,
78
+ humanReadable: target.toLocaleString("tr-TR"),
79
+ timestamp: target.getTime(),
80
+ };
81
+ }
82
+
83
+ async function setAlarm({ time, label = "Alarm", calendarName = "Calendar" }) {
84
+ if (!IS_MAC) return { success: false, error: "Bu tool sadece macOS'ta calisir" };
85
+ if (!time) return { success: false, error: "time gerekli (ornek: '18:00', 'tomorrow 09:30', '+1 hour')" };
86
+
87
+ let parsed;
88
+ try { parsed = parseAlarmTime(time); }
89
+ catch (e) { return { success: false, error: e.message }; }
90
+
91
+ // AppleScript ile macOS Calendar'a all-day etkinlik olarak alarm ekle
92
+ // (Reminder ile ayni sonuc, daha guvenilir cunku Calendar otomasyon izni Reminders'dan once verilir)
93
+ const script = `
94
+ tell application "Calendar"
95
+ set targetCal to first calendar whose writable is true
96
+ set startDate to date "${parsed.formattedDate} ${parsed.formattedTime}"
97
+ set endDate to startDate + (1 * minutes)
98
+ set newEvent to make new event at end of events of targetCal with properties {summary:"⏰ ${label.replace(/"/g, "'")} - NatureCo", start date:startDate, end date:endDate, allday event:false}
99
+ -- Alarm ekle: 0 dakika once = tam zamanda
100
+ set the number of sound alarms of newEvent to 1
101
+ make new sound alarm at end of sound alarms of newEvent with properties {trigger interval:-0}
102
+ save
103
+ return id of newEvent
104
+ end tell
105
+ `;
106
+
107
+ try {
108
+ const eventId = await runAppleScript(script);
109
+ return {
110
+ success: true,
111
+ eventId,
112
+ message: `⏰ Alarm kuruldu: ${parsed.humanReadable} — "${label}"`,
113
+ calendar: calendarName,
114
+ targetTime: parsed.humanReadable,
115
+ targetTimestamp: parsed.timestamp,
116
+ };
117
+ } catch (e) {
118
+ if (e.message.includes("-1728") || e.message.includes("not authorized")) {
119
+ return {
120
+ success: false,
121
+ error: "Calendar erisim izni yok. System Preferences -> Security & Privacy -> Privacy -> Automation -> natureco -> Calendar -> ON",
122
+ };
123
+ }
124
+ return { success: false, error: e.message };
125
+ }
126
+ }
127
+
128
+ async function listAlarms() {
129
+ if (!IS_MAC) return { success: false, error: "Sadece macOS" };
130
+ const script = `
131
+ tell application "Calendar"
132
+ set nowDate to current date
133
+ set futureEvents to {}
134
+ repeat with cal in calendars
135
+ repeat with e in events of cal
136
+ if start date of e > nowDate and (summary of e starts with "⏰") then
137
+ copy (start date of e as string) & " | " & (summary of e) to end of futureEvents
138
+ end if
139
+ end repeat
140
+ end repeat
141
+ return futureEvents
142
+ end tell
143
+ `;
144
+ try {
145
+ const result = await runAppleScript(script);
146
+ const alarms = result.split(", ").filter(Boolean);
147
+ return { success: true, count: alarms.length, alarms };
148
+ } catch (e) {
149
+ return { success: false, error: e.message };
150
+ }
151
+ }
152
+
153
+ module.exports = {
154
+ name: "mac_alarm",
155
+ description: "macOS Clock/Calendar uzerinden alarm kur. '18:00', 'tomorrow 09:30', '+1 hour' gibi formatlari kabul eder.",
156
+ inputSchema: {
157
+ type: "object",
158
+ properties: {
159
+ action: { type: "string", description: "set/list (default: set)", enum: ["set", "list"] },
160
+ time: { type: "string", description: "Alarm zamani: '18:00', 'tomorrow 09:30', '+1 hour', '2026-06-23 07:00'" },
161
+ label: { type: "string", description: "Alarm etiketi (default: 'Alarm')" },
162
+ },
163
+ required: [],
164
+ },
165
+ async execute(params) {
166
+ if (params.action === "list") return listAlarms();
167
+ return setAlarm(params);
168
+ },
169
+ };
@@ -0,0 +1,149 @@
1
+ /**
2
+ * memory_write - Memory'ye fact/kayit yaz (v5.1.1)
3
+ *
4
+ * REPL'in extractMemoryFromMessage ozelligini tool olarak expose eder.
5
+ * Parton'un vizyonu: "Benim asistanim, her seyimi hatirlayacak"
6
+ */
7
+
8
+ const fs = require("fs");
9
+ const path = require("path");
10
+ const os = require("os");
11
+
12
+ const MEMORY_DIR = path.join(os.homedir(), ".natureco", "memory");
13
+
14
+ function getMemoryFile(username) {
15
+ const name = (username || "default").toLowerCase();
16
+ return path.join(MEMORY_DIR, `${name}.json`);
17
+ }
18
+
19
+ function loadMemory(username) {
20
+ const file = getMemoryFile(username);
21
+ try {
22
+ if (!fs.existsSync(file)) {
23
+ return { name: username || "User", nickname: null, botName: null, facts: [], preferences: [] };
24
+ }
25
+ return JSON.parse(fs.readFileSync(file, "utf8"));
26
+ } catch {
27
+ return { name: username || "User", nickname: null, botName: null, facts: [], preferences: [] };
28
+ }
29
+ }
30
+
31
+ function saveMemory(username, memory) {
32
+ if (!fs.existsSync(MEMORY_DIR)) fs.mkdirSync(MEMORY_DIR, { recursive: true });
33
+ memory.lastUpdated = new Date().toISOString();
34
+ fs.writeFileSync(getMemoryFile(username), JSON.stringify(memory, null, 2), "utf8");
35
+ return memory;
36
+ }
37
+
38
+ /**
39
+ * Score azalt (eski fact'ler zamanla unutuluyor)
40
+ */
41
+ function decayFacts(memory) {
42
+ if (!memory.facts) return memory;
43
+ const now = Date.now();
44
+ memory.facts = memory.facts.map(f => {
45
+ if (!f.score) f.score = 5;
46
+ // 1 haftadan eski -1, 1 aydan eski -3
47
+ const ageMs = now - new Date(f.updatedAt || f.createdAt || now).getTime();
48
+ const ageDays = ageMs / (1000 * 60 * 60 * 24);
49
+ if (ageDays > 30) f.score = Math.max(0, f.score - 3);
50
+ else if (ageDays > 7) f.score = Math.max(0, f.score - 1);
51
+ return f;
52
+ });
53
+ // score 0 olanlari sil
54
+ memory.facts = memory.facts.filter(f => (f.score || 0) > 0);
55
+ // max 15 fact tut
56
+ memory.facts.sort((a, b) => (b.score || 0) - (a.score || 0));
57
+ memory.facts = memory.facts.slice(0, 15);
58
+ return memory;
59
+ }
60
+
61
+ function addMemory({ username, fact, score = 5, category = "general", botName, nickname, name }) {
62
+ if (!username) return { success: false, error: "username gerekli" };
63
+
64
+ let memory = loadMemory(username);
65
+ memory = decayFacts(memory);
66
+
67
+ // identity updates (botName, nickname, name)
68
+ if (botName) memory.botName = botName;
69
+ if (nickname !== undefined) memory.nickname = nickname;
70
+ if (name) memory.name = name;
71
+
72
+ if (fact) {
73
+ // duplicate kontrol
74
+ const existing = memory.facts.find(f => (f.value || f).toLowerCase() === fact.toLowerCase());
75
+ if (existing) {
76
+ existing.score = Math.min(10, (existing.score || 5) + 2);
77
+ existing.updatedAt = new Date().toISOString();
78
+ } else {
79
+ memory.facts.push({
80
+ value: fact,
81
+ score,
82
+ category,
83
+ updatedAt: new Date().toISOString(),
84
+ createdAt: new Date().toISOString(),
85
+ });
86
+ }
87
+ }
88
+
89
+ if (!memory.preferences) memory.preferences = [];
90
+ memory = saveMemory(username, memory);
91
+
92
+ return {
93
+ success: true,
94
+ message: "Memory guncellendi",
95
+ username,
96
+ totalFacts: memory.facts.length,
97
+ facts: memory.facts.map(f => ({ value: f.value, score: f.score, category: f.category })),
98
+ botName: memory.botName,
99
+ nickname: memory.nickname,
100
+ name: memory.name,
101
+ };
102
+ }
103
+
104
+ function clearMemory({ username }) {
105
+ if (!username) return { success: false, error: "username gerekli" };
106
+ const file = getMemoryFile(username);
107
+ if (fs.existsSync(file)) fs.unlinkSync(file);
108
+ return { success: true, message: `Memory temizlendi: ${username}` };
109
+ }
110
+
111
+ function showMemory({ username }) {
112
+ if (!username) return { success: false, error: "username gerekli" };
113
+ const memory = loadMemory(username);
114
+ return {
115
+ success: true,
116
+ username,
117
+ name: memory.name,
118
+ nickname: memory.nickname,
119
+ botName: memory.botName,
120
+ totalFacts: (memory.facts || []).length,
121
+ facts: memory.facts || [],
122
+ preferences: memory.preferences || [],
123
+ };
124
+ }
125
+
126
+ module.exports = {
127
+ name: "memory_write",
128
+ description: "Memory'ye yeni fact/bilgi kaydet veya bot ismini/nickname'i degistir. Kalici, REPL'in extractMemoryFromMessage ozelligi.",
129
+ inputSchema: {
130
+ type: "object",
131
+ properties: {
132
+ username: { type: "string", description: "Kullanici adi (ornek: 'gencay' veya 'parton')" },
133
+ fact: { type: "string", description: 'Yeni fact (ornek: "Kullanici Naruto karakterini seviyor", "Istanbul\'da yasiyor")' },
134
+ score: { type: "number", description: "Onem derecesi 1-10 (default 5)" },
135
+ category: { type: "string", description: "Kategori: personal, preference, work, hobby, fact (default general)" },
136
+ botName: { type: "string", description: "Bot adini degistir (memory.botName)" },
137
+ nickname: { type: "string", description: "Kullanici nickname'i" },
138
+ name: { type: "string", description: "Kullanici gercek adi" },
139
+ action: { type: "string", description: "add/clear/show (default: add)", enum: ["add", "clear", "show"] },
140
+ },
141
+ required: ["username"],
142
+ },
143
+ async execute(params) {
144
+ const action = params.action || "add";
145
+ if (action === "clear") return clearMemory(params);
146
+ if (action === "show") return showMemory(params);
147
+ return addMemory(params);
148
+ },
149
+ };
@@ -0,0 +1,146 @@
1
+ /**
2
+ * skill_generate - Self-generating skill (v5.1.0)
3
+ *
4
+ * Parton'un vizyonu: "Ihtiyaca gore skill yoksa kendi uretsin"
5
+ *
6
+ * Akis:
7
+ * 1. Kullanici bir istek yapar (ornek: "tum pdf'leri birlestir")
8
+ * 2. Mevcut tool/skill'ler yoksa bu tool cagirilir
9
+ * 3. LLM'a skill taslagi uretmesi icin istek gonderilir
10
+ * 4. SKILL.md dosyasi yazilir, kaydedilir, hemen kullanilir
11
+ */
12
+
13
+ const fs = require("fs");
14
+ const path = require("path");
15
+ const os = require("os");
16
+ const https = require("https");
17
+
18
+ const SKILLS_DIR = path.join(os.homedir(), ".natureco", "skills");
19
+
20
+ function getConfig() {
21
+ try {
22
+ return JSON.parse(fs.readFileSync(path.join(os.homedir(), ".natureco", "config.json"), "utf8"));
23
+ } catch { return {}; }
24
+ }
25
+
26
+ function isMiniMax(url) {
27
+ return url && (url.includes("minimax.io") || url.includes("minimaxi.com"));
28
+ }
29
+
30
+ function apiRequest(url, key, body) {
31
+ return new Promise((resolve, reject) => {
32
+ const isMM = isMiniMax(url);
33
+ const endpoint = isMM
34
+ ? url.replace(/\/$/, "") + "/v1/text/chatcompletion_v2"
35
+ : url.replace(/\/$/, "") + "/chat/completions";
36
+ const req = https.request(endpoint, {
37
+ method: "POST",
38
+ headers: { "Authorization": "Bearer " + key, "Content-Type": "application/json" },
39
+ timeout: 30000,
40
+ }, res => {
41
+ let data = "";
42
+ res.on("data", c => data += c);
43
+ res.on("end", () => {
44
+ if (res.statusCode === 200) {
45
+ try { resolve(JSON.parse(data)); } catch (e) { reject(new Error("Parse hatasi")); }
46
+ } else reject(new Error("HTTP " + res.statusCode + ": " + data.slice(0, 200)));
47
+ });
48
+ });
49
+ req.on("error", reject);
50
+ req.on("timeout", () => req.destroy() && reject(new Error("Timeout")));
51
+ req.write(JSON.stringify(body));
52
+ req.end();
53
+ });
54
+ }
55
+
56
+ function slugify(text) {
57
+ return (text || "")
58
+ .toLowerCase()
59
+ .replace(/[^a-z0-9]+/g, "-")
60
+ .replace(/^-+|-+$/g, "")
61
+ .slice(0, 40) || "custom-skill";
62
+ }
63
+
64
+ async function generateSkillInstructions(taskDescription, skillName) {
65
+ const cfg = getConfig();
66
+ if (!cfg.providerUrl || !cfg.providerApiKey) {
67
+ throw new Error("Provider ayarli degil. Once: natureco setup");
68
+ }
69
+ const promptText = "Sen NatureCo CLI icin SKILL talimatlari yazarsin. Asagidaki gorev icin System Prompt'a eklenecek Turkce talimat yaz:\n\nGOREV: " + taskDescription + "\n\nKURALLAR:\n- Turkce, kisa, oz, madde madde yaz\n- 5-10 satir, dogrudan uygulanabilir\n- Hangi araclarin kullanilacagi (dosya/shell/web) acikca belirt\n- Ornek bir kucuk kullanim ornegi ekle\n- Yanlis ciktilari nasil onlenmesi gerektigini yaz\n\nDirekt SKILL.md icerigini yaz, baska aciklama yapma.";
70
+
71
+ const body = {
72
+ model: cfg.providerModel || "MiniMax-M2.5",
73
+ messages: [
74
+ { role: "system", content: "Sen bir CLI skill talimat yazarsin. Kisa ve net Turkce yaz. Markdown kullanma, duz metin yaz." },
75
+ { role: "user", content: promptText },
76
+ ],
77
+ temperature: 0.5,
78
+ max_tokens: 1500,
79
+ };
80
+ const res = await apiRequest(cfg.providerUrl, cfg.providerApiKey, body);
81
+ const content = res.choices && res.choices[0] && res.choices[0].message ? (res.choices[0].message.content || "") : "";
82
+ return content.trim();
83
+ }
84
+
85
+ async function generateSkill(params) {
86
+ const taskDescription = params.taskDescription;
87
+ const requestedName = params.skillName;
88
+ if (!taskDescription) return { success: false, error: "taskDescription gerekli" };
89
+
90
+ const skillName = requestedName || slugify(taskDescription);
91
+ const skillDir = path.join(SKILLS_DIR, skillName);
92
+
93
+ if (fs.existsSync(skillDir)) {
94
+ return { success: true, message: "Skill zaten mevcut", path: skillDir, skillName: skillName, alreadyExisted: true };
95
+ }
96
+
97
+ let skillContent;
98
+ try {
99
+ skillContent = await generateSkillInstructions(taskDescription, skillName);
100
+ } catch (e) {
101
+ return { success: false, error: "Skill talimati uretilemedi: " + e.message };
102
+ }
103
+
104
+ if (!skillContent || skillContent.length < 50) {
105
+ return { success: false, error: "LLM bos icerik uretti" };
106
+ }
107
+
108
+ fs.mkdirSync(skillDir, { recursive: true });
109
+ fs.writeFileSync(path.join(skillDir, "SKILL.md"), skillContent, "utf8");
110
+ const meta = {
111
+ name: skillName,
112
+ description: "Otomatik uretildi: " + taskDescription.slice(0, 100),
113
+ author: "skill_generate (auto)",
114
+ version: "1.0.0",
115
+ tags: ["auto-generated", "user-created"],
116
+ autoGenerated: true,
117
+ taskDescription: taskDescription,
118
+ createdAt: new Date().toISOString(),
119
+ };
120
+ fs.writeFileSync(path.join(skillDir, "metadata.json"), JSON.stringify(meta, null, 2), "utf8");
121
+
122
+ return {
123
+ success: true,
124
+ message: "Yeni skill olusturuldu ve hemen kullanilabilir!",
125
+ skillName: skillName,
126
+ path: skillDir,
127
+ content: skillContent.slice(0, 300),
128
+ note: "REPL'i yeniden acarsaniz yeni skill yuklenecek. /skills list ile test edebilirsiniz.",
129
+ };
130
+ }
131
+
132
+ module.exports = {
133
+ name: "skill_generate",
134
+ description: "Olmayan bir ozellik icin LLM ile yeni bir skill talimati uretir, kaydeder ve hemen kullanima sunar. 'PDF birlestir', 'JSON duzenle' gibi gorevler icin.",
135
+ inputSchema: {
136
+ type: "object",
137
+ properties: {
138
+ taskDescription: { type: "string", description: "Skill'in ne yapacagi (ornek: 'PDF dosyalarini tek dosyada birlestir')" },
139
+ skillName: { type: "string", description: "Ozel skill adi (opsiyonel, otomatik uretilir)" },
140
+ },
141
+ required: ["taskDescription"],
142
+ },
143
+ async execute(params) {
144
+ return await generateSkill(params);
145
+ },
146
+ };