natureco-cli 4.9.0 → 5.0.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.
@@ -0,0 +1,97 @@
1
+ /**
2
+ * skills_autoload - Otomatik skill yukleme (v5.0.0)
3
+ *
4
+ * Parton'un vizyonu: "Ihtiyaca gore skill'ler otomatik yuklensin"
5
+ *
6
+ * Mantik:
7
+ * 1. Kullanici bir istek yapar
8
+ * 2. REPL anahtar kelimeleri tarar (seo, telegram, git commit, vb.)
9
+ * 3. Ilgili skill varsa otomatik yuklenir
10
+ * 4. System prompt'a eklenir
11
+ */
12
+
13
+ const fs = require("fs");
14
+ const path = require("path");
15
+ const os = require("os");
16
+
17
+ const SKILLS_DIR = path.join(os.homedir(), ".natureco", "skills");
18
+
19
+ /**
20
+ * Anahtar kelime -> skill mapping
21
+ */
22
+ const KEYWORD_MAP = {
23
+ // SEO
24
+ "seo": ["seo", "audit", "site", "website", "ranking", "meta", "search engine", "url"],
25
+ "telegram": ["telegram", "bot", "botfather", "t.me"],
26
+ "git-commit": ["commit", "git commit", "conventional commit", "kaydet", "gönder"],
27
+ "code-review": ["review", "incele", "kod incele", "bug", "security check"],
28
+ "morning-briefing": ["briefing", "morning", "sabah brifingi", "günlük özet"],
29
+ };
30
+
31
+ function loadAllSkills() {
32
+ const loaded = [];
33
+ try {
34
+ if (!fs.existsSync(SKILLS_DIR)) return loaded;
35
+ const dirs = fs.readdirSync(SKILLS_DIR, { withFileTypes: true });
36
+ for (const dir of dirs) {
37
+ if (!dir.isDirectory()) continue;
38
+ const skillFile = path.join(SKILLS_DIR, dir.name, "SKILL.md");
39
+ if (fs.existsSync(skillFile)) {
40
+ loaded.push({ name: dir.name, content: fs.readFileSync(skillFile, "utf8") });
41
+ }
42
+ }
43
+ } catch {}
44
+ return loaded;
45
+ }
46
+
47
+ /**
48
+ * Mesaj icindeki anahtar kelimeleri tara, ilgili skill'leri bul
49
+ */
50
+ function detectRelevantSkills(message, availableSkills) {
51
+ const lower = message.toLowerCase();
52
+ const detected = new Set();
53
+
54
+ for (const [skill, keywords] of Object.entries(KEYWORD_MAP)) {
55
+ for (const kw of keywords) {
56
+ if (lower.includes(kw)) {
57
+ detected.add(skill);
58
+ break;
59
+ }
60
+ }
61
+ }
62
+
63
+ // Yuklu skill'lerle kesistir
64
+ return availableSkills.filter(s => detected.has(s.name));
65
+ }
66
+
67
+ function autoLoad(message) {
68
+ const available = loadAllSkills();
69
+ const relevant = detectRelevantSkills(message, available);
70
+ if (relevant.length === 0) return [];
71
+
72
+ return relevant.map(s => ({
73
+ name: s.name,
74
+ summary: s.content.slice(0, 500).split("\n").slice(0, 3).join("\n"),
75
+ }));
76
+ }
77
+
78
+ module.exports = {
79
+ name: "skills_autoload",
80
+ description: "Kullanici istegine gore otomatik skill yukle. Mesaj analiz edilir, ilgili skill sistem prompt'a eklenir.",
81
+ inputSchema: {
82
+ type: "object",
83
+ properties: {
84
+ message: { type: "string", description: "Kullanici mesaji / istegi" },
85
+ },
86
+ required: ["message"],
87
+ },
88
+ async execute(params) {
89
+ const loaded = autoLoad(params.message);
90
+ return {
91
+ success: true,
92
+ message: params.message,
93
+ detectedSkills: loaded.map(s => s.name),
94
+ skillContext: loaded.map(s => s.content).join("\n\n"),
95
+ };
96
+ },
97
+ };
@@ -0,0 +1,233 @@
1
+ /**
2
+ * skills_marketplace - Skill marketplace (v5.0.0)
3
+ *
4
+ * Parton'un vizyonu: "herkes kendi skill'ini paylassin, CLI otomatik yuklesin"
5
+ *
6
+ * Format: ~/.natureco/marketplace/<skill_name>.json
7
+ * Source: NatureCo GitHub repo (community-contributed) veya local
8
+ */
9
+
10
+ const fs = require("fs");
11
+ const path = require("path");
12
+ const os = require("os");
13
+ const https = require("https");
14
+
15
+ const MARKETPLACE_DIR = path.join(os.homedir(), ".natureco", "marketplace");
16
+ const SKILLS_DIR = path.join(os.homedir(), ".natureco", "skills");
17
+
18
+ /**
19
+ * Marketplace URL'leri — Parton kendi GitHub repo'sunu koyacak
20
+ */
21
+ const MARKETPLACE_SOURCES = [
22
+ {
23
+ name: "NatureCo Official",
24
+ url: "https://raw.githubusercontent.com/naturecoofficial/natureco-skills/main/index.json",
25
+ enabled: true,
26
+ },
27
+ {
28
+ name: "Community",
29
+ url: "https://raw.githubusercontent.com/natureco-community/skills/main/index.json",
30
+ enabled: true,
31
+ },
32
+ ];
33
+
34
+ /**
35
+ * Built-in skill paketleri — Parton'un NatureCo vizyonu icin onemli
36
+ */
37
+ const BUILTIN_SKILLS = {
38
+ "seo-audit": {
39
+ name: "SEO Audit",
40
+ description: "Web sitesi SEO denetimi - meta tags, headings, schema.org, performance",
41
+ author: "NatureCo Team",
42
+ version: "1.0.0",
43
+ tags: ["seo", "web", "audit"],
44
+ instructions: "Kullanici bir URL isteyince, bu skill devreye girer. http_request ile sayfayi cek, og:title, og:description, og:image, h1/h2 sayisi, schema.org/l* varligi, robots.txt, sitemap.xml kontrol et. Skor 0-100 dondur.",
45
+ },
46
+ "code-review": {
47
+ name: "Code Review",
48
+ description: "Kod inceleme - style, best practices, security, performance",
49
+ author: "NatureCo Team",
50
+ version: "1.0.0",
51
+ tags: ["code", "review", "security"],
52
+ instructions: "Kullanici kod gonderdiginde, grep_search ile TODO/FIXME/security issue bul, linting kontrolu yap, ozellikle XSS, SQL injection, hardcoded secret'lere bak. Olumlu/olumsuz yonleri listele.",
53
+ },
54
+ "git-commit": {
55
+ name: "Smart Git Commit",
56
+ description: "AI ile conventional commit mesaji uret",
57
+ author: "NatureCo Team",
58
+ version: "1.0.0",
59
+ tags: ["git", "workflow"],
60
+ instructions: "git diff ciktisini oku, degisiklik tipine gore (feat/fix/docs/style/refactor/test/chore) conventional commit formatinda mesaj uret. Turkce veya Ingilizce gore dili ayarla.",
61
+ },
62
+ "telegram-bot": {
63
+ name: "Telegram Bot Setup",
64
+ description: "Telegram bot kurulum wizard - BotFather adim adim",
65
+ author: "NatureCo Team",
66
+ version: "1.0.0",
67
+ tags: ["telegram", "integration", "tutorial"],
68
+ instructions: "Kullanici 'Telegram bot kur' dediginde, sirasiyla: 1) @BotFather'a git, 2) /newbot, 3) isim ve username, 4) token al, 5) natureco telegram connect ile gir, 6) @userinfobot'tan user ID al, 7) natureco gateway start. Adim adim Turkce yonlendir.",
69
+ },
70
+ "morning-briefing": {
71
+ name: "Morning Briefing",
72
+ description: "Her sabah 9'da ozet: hava, takvim, todo'lar, RSS",
73
+ author: "NatureCo Team",
74
+ version: "1.0.0",
75
+ tags: ["productivity", "cron", "daily"],
76
+ instructions: "cron_create ile her sabah 9'da calisan bir setup kur. natureco weather, natureco calendar today, natureco todo_write list, natureco memory_search - tum bunlari ozetleyen bir gunluk briefing ver.",
77
+ },
78
+ };
79
+
80
+ /**
81
+ * Local skill yukle (skill dosyasi -> ~/.natureco/skills/<name>/)
82
+ */
83
+ function installLocal(skillData) {
84
+ if (!skillData.name) return { success: false, error: "Skill name gerekli" };
85
+ const skillDir = path.join(SKILLS_DIR, skillData.name);
86
+ fs.mkdirSync(skillDir, { recursive: true });
87
+
88
+ const skillFile = path.join(skillDir, "SKILL.md");
89
+ const content = skillData.instructions || skillData.content || `# ${skillData.name}\n\n${skillData.description || ""}`;
90
+ fs.writeFileSync(skillFile, content, "utf8");
91
+
92
+ // Metadata
93
+ const meta = {
94
+ name: skillData.name,
95
+ description: skillData.description,
96
+ author: skillData.author || "Unknown",
97
+ version: skillData.version || "1.0.0",
98
+ tags: skillData.tags || [],
99
+ installedAt: new Date().toISOString(),
100
+ };
101
+ fs.writeFileSync(path.join(skillDir, "metadata.json"), JSON.stringify(meta, null, 2), "utf8");
102
+
103
+ return { success: true, path: skillDir, skill: meta };
104
+ }
105
+
106
+ /**
107
+ * URL'den skill yukle (GitHub raw content)
108
+ */
109
+ function installFromUrl(url) {
110
+ return new Promise((resolve) => {
111
+ https.get(url, (res) => {
112
+ let data = "";
113
+ res.on("data", d => data += d);
114
+ res.on("end", () => {
115
+ try {
116
+ const skill = JSON.parse(data);
117
+ resolve(installLocal(skill));
118
+ } catch (e) {
119
+ resolve({ success: false, error: "Skill JSON parse hatasi: " + e.message });
120
+ }
121
+ });
122
+ }).on("error", e => resolve({ success: false, error: e.message }));
123
+ });
124
+ }
125
+
126
+ /**
127
+ * Marketplace listele
128
+ */
129
+ async function listMarketplace() {
130
+ const skills = { ...BUILTIN_SKILLS };
131
+
132
+ // Remote sources'dan da cekmeyi dene
133
+ for (const source of MARKETPLACE_SOURCES.filter(s => s.enabled)) {
134
+ try {
135
+ const remote = await new Promise((resolve) => {
136
+ const req = https.get(source.url, { timeout: 5000 }, (res) => {
137
+ let data = "";
138
+ res.on("data", d => data += d);
139
+ res.on("end", () => {
140
+ try { resolve(JSON.parse(data)); } catch { resolve({}); }
141
+ });
142
+ });
143
+ req.on("error", () => resolve({}));
144
+ req.on("timeout", () => { req.destroy(); resolve({}); });
145
+ });
146
+ Object.assign(skills, remote);
147
+ } catch {}
148
+ }
149
+
150
+ return skills;
151
+ }
152
+
153
+ /**
154
+ * Tool definitions
155
+ */
156
+ module.exports = {
157
+ name: "skills_marketplace",
158
+ description: "Skill marketplace - topluluk tarafindan paylasilan NatureCo CLI skill'leri. action: list, install, uninstall, search.",
159
+ inputSchema: {
160
+ type: "object",
161
+ properties: {
162
+ action: { type: "string", description: "list/install/uninstall/search", enum: ["list", "install", "uninstall", "search"] },
163
+ skillName: { type: "string", description: "Skill adi (install/uninstall icin)" },
164
+ query: { type: "string", description: "Arama sorgusu (search icin)" },
165
+ source: { type: "string", description: "Marketplace URL (custom source icin)" },
166
+ },
167
+ required: ["action"],
168
+ },
169
+ async execute(params) {
170
+ const { action, skillName, query, source } = params;
171
+
172
+ if (action === "list") {
173
+ const all = await listMarketplace();
174
+ return {
175
+ success: true,
176
+ count: Object.keys(all).length,
177
+ skills: Object.entries(all).map(([name, s]) => ({
178
+ name,
179
+ description: s.description,
180
+ author: s.author,
181
+ version: s.version,
182
+ tags: s.tags,
183
+ })),
184
+ };
185
+ }
186
+
187
+ if (action === "search") {
188
+ if (!query) return { success: false, error: "query gerekli" };
189
+ const all = await listMarketplace();
190
+ const q = query.toLowerCase();
191
+ const matches = Object.entries(all).filter(([_, s]) =>
192
+ s.description?.toLowerCase().includes(q) ||
193
+ s.tags?.some(t => t.toLowerCase().includes(q)) ||
194
+ s.name.toLowerCase().includes(q)
195
+ );
196
+ return { success: true, query, count: matches.length, results: matches.map(([n, s]) => ({ name: n, ...s })) };
197
+ }
198
+
199
+ if (action === "install") {
200
+ if (!skillName) return { success: false, error: "skillName gerekli" };
201
+
202
+ // Once local BUILTIN'den dene
203
+ if (BUILTIN_SKILLS[skillName]) {
204
+ return installLocal(BUILTIN_SKILLS[skillName]);
205
+ }
206
+
207
+ // Sonra URL'den dene
208
+ if (source) {
209
+ return await installFromUrl(source);
210
+ }
211
+
212
+ // Marketplace'ten dene
213
+ const all = await listMarketplace();
214
+ if (all[skillName]) {
215
+ return installLocal(all[skillName]);
216
+ }
217
+
218
+ return { success: false, error: `Skill bulunamadi: ${skillName}. Once 'list' calistirin.` };
219
+ }
220
+
221
+ if (action === "uninstall") {
222
+ if (!skillName) return { success: false, error: "skillName gerekli" };
223
+ const skillDir = path.join(SKILLS_DIR, skillName);
224
+ if (!fs.existsSync(skillDir)) {
225
+ return { success: false, error: `Skill yuklu degil: ${skillName}` };
226
+ }
227
+ fs.rmSync(skillDir, { recursive: true });
228
+ return { success: true, message: `Skill kaldirildi: ${skillName}` };
229
+ }
230
+
231
+ return { success: false, error: `Bilinmeyen action: ${action}` };
232
+ },
233
+ };
@@ -1,105 +1,91 @@
1
- const { getConfig } = require('../utils/config');
1
+ /**
2
+ * text_to_speech - TTS (v4.9.0)
3
+ *
4
+ * Hermes TTS'ine benzer. Edge TTS veya OpenAI TTS.
5
+ * macOS'ta 'say' komutu fallback.
6
+ */
7
+
8
+ const { spawn } = require("child_process");
9
+ const fs = require("fs");
10
+ const path = require("path");
11
+ const os = require("os");
12
+
13
+ async function speak({ text, provider = "auto", voice = "tr-TR", savePath = null }) {
14
+ if (!text) return { success: false, error: "text gerekli" };
15
+
16
+ // Once dosyaya kaydet, sonra caldir
17
+ if (provider === "auto" || provider === "edge") {
18
+ return await edgeTTS(text, voice, savePath);
19
+ }
20
+ if (provider === "say") {
21
+ return await macSay(text);
22
+ }
23
+ if (provider === "save") {
24
+ return await saveToFile(text, savePath);
25
+ }
26
+ return { success: false, error: `Desteklenmeyen provider: ${provider}` };
27
+ }
28
+
29
+ async function edgeTTS(text, voice, savePath) {
30
+ return new Promise((resolve) => {
31
+ const out = savePath || path.join(os.tmpdir(), `tts-${Date.now()}.mp3`);
32
+ const proc = spawn("python3", ["-c", `
33
+ import asyncio, sys
34
+ try:
35
+ import edge_tts
36
+ except ImportError:
37
+ print("ERROR: pip install edge-tts", file=sys.stderr)
38
+ sys.exit(1)
39
+ async def main():
40
+ tts = edge_tts.Communicate("""${text.replace(/"/g, "'")}""", "${voice}")
41
+ await tts.save("${out}")
42
+ asyncio.run(main())
43
+ `], { timeout: 30000 });
44
+ let stderr = "";
45
+ proc.stderr.on("data", d => stderr += d);
46
+ proc.on("close", code => {
47
+ if (code === 0) resolve({ success: true, provider: "edge", path: out, message: `Ses kaydedildi: ${out}` });
48
+ else resolve({ success: false, error: stderr || `edge-tts hata ${code}. Kur: pip install edge-tts` });
49
+ });
50
+ proc.on("error", e => resolve({ success: false, error: e.message + " (pip install edge-tts)" }));
51
+ });
52
+ }
53
+
54
+ async function macSay(text) {
55
+ return new Promise((resolve) => {
56
+ const proc = spawn("say", ["-v", "Yelda", text]);
57
+ proc.on("close", code => {
58
+ if (code === 0) resolve({ success: true, provider: "mac-say", message: "Seslendirildi" });
59
+ else resolve({ success: false, error: `say hata ${code}` });
60
+ });
61
+ proc.on("error", e => resolve({ success: false, error: e.message }));
62
+ });
63
+ }
64
+
65
+ async function saveToFile(text, savePath) {
66
+ if (!savePath) return { success: false, error: "savePath gerekli" };
67
+ try {
68
+ fs.writeFileSync(savePath, text, "utf8");
69
+ return { success: true, provider: "save", path: savePath, message: `Metin kaydedildi: ${savePath}` };
70
+ } catch (e) {
71
+ return { success: false, error: e.message };
72
+ }
73
+ }
2
74
 
3
75
  module.exports = {
4
- name: 'text_to_speech',
5
- description: 'Convert text to speech using ElevenLabs, OpenAI TTS, or local CLI TTS',
76
+ name: "text_to_speech",
77
+ description: "Metin seslendir. macOS say, edge-tts (pip install edge-tts), veya dosyaya kaydet.",
6
78
  inputSchema: {
7
- type: 'object',
79
+ type: "object",
8
80
  properties: {
9
- text: { type: 'string', description: 'Text to convert to speech' },
10
- provider: { type: 'string', description: 'TTS provider: elevenlabs, openai, local (default: elevenlabs)', enum: ['elevenlabs', 'openai', 'local'] },
11
- voice: { type: 'string', description: 'Voice ID or name (ElevenLabs: Rachel, Adam, etc; OpenAI: alloy, echo, fable, nova, shimmer)' },
12
- outputPath: { type: 'string', description: 'Save audio to file path (optional)' }
81
+ text: { type: "string", description: "Seslendirilecek metin" },
82
+ provider: { type: "string", description: "edge, say, save (default: auto)", enum: ["auto", "edge", "say", "save"] },
83
+ voice: { type: "string", description: "Ses (ornek: tr-TR-EmelNeural, en-US-AriaNeural)" },
84
+ savePath: { type: "string", description: "Kayıt yolu (edge/save provider için)" },
13
85
  },
14
- required: ['text']
86
+ required: ["text"],
15
87
  },
16
-
17
88
  async execute(params) {
18
- try {
19
- const config = getConfig();
20
- const provider = params.provider || config.ttsProvider || 'elevenlabs';
21
- const outputPath = params.outputPath;
22
-
23
- if (provider === 'elevenlabs') {
24
- const apiKey = params.apiKey || config.elevenlabsApiKey || process.env.ELEVENLABS_API_KEY;
25
- if (!apiKey) {
26
- return { success: false, error: 'ElevenLabs API key gerekli.\nKur: natureco config set elevenlabsApiKey <key>' };
27
- }
28
-
29
- const voice = params.voice || config.elevenlabsVoice || '21m00Tcm4TlvDq8ikWAM';
30
- const response = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${voice}`, {
31
- method: 'POST',
32
- headers: { 'Content-Type': 'application/json', 'xi-api-key': apiKey },
33
- body: JSON.stringify({
34
- text: params.text,
35
- model_id: 'eleven_monolingual_v1',
36
- voice_settings: { stability: 0.5, similarity_boost: 0.5 }
37
- })
38
- });
39
-
40
- if (!response.ok) throw new Error(`ElevenLabs error ${response.status}`);
41
-
42
- const audioBuffer = await response.arrayBuffer();
43
- if (outputPath) {
44
- require('fs').writeFileSync(outputPath, Buffer.from(audioBuffer));
45
- return { success: true, provider: 'elevenlabs', outputPath, size: audioBuffer.byteLength };
46
- }
47
-
48
- return {
49
- success: true,
50
- provider: 'elevenlabs',
51
- audioData: Buffer.from(audioBuffer).toString('base64'),
52
- format: 'mp3',
53
- message: 'Audio generated. Use outputPath to save to file.'
54
- };
55
- }
56
-
57
- if (provider === 'openai') {
58
- const apiKey = params.apiKey || config.openaiApiKey || process.env.OPENAI_API_KEY;
59
- if (!apiKey) {
60
- return { success: false, error: 'OpenAI API key gerekli.' };
61
- }
62
-
63
- const voice = params.voice || 'alloy';
64
- const response = await fetch('https://api.openai.com/v1/audio/speech', {
65
- method: 'POST',
66
- headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },
67
- body: JSON.stringify({ model: 'tts-1', input: params.text, voice })
68
- });
69
-
70
- if (!response.ok) throw new Error(`OpenAI TTS error ${response.status}`);
71
-
72
- const audioBuffer = await response.arrayBuffer();
73
- if (outputPath) {
74
- require('fs').writeFileSync(outputPath, Buffer.from(audioBuffer));
75
- return { success: true, provider: 'openai', outputPath, size: audioBuffer.byteLength };
76
- }
77
-
78
- return {
79
- success: true, provider: 'openai',
80
- audioData: Buffer.from(audioBuffer).toString('base64'), format: 'mp3'
81
- };
82
- }
83
-
84
- if (provider === 'local') {
85
- const { execSync } = require('child_process');
86
- try {
87
- execSync('which say || which espeak || which festival', { stdio: 'ignore' });
88
- } catch {
89
- return { success: false, error: 'Local TTS aracı bulunamadı. macOS: "say", Linux: "espeak"' };
90
- }
91
-
92
- const cmd = process.platform === 'darwin'
93
- ? `say "${params.text.replace(/"/g, '\\"')}"${outputPath ? ` -o "${outputPath}"` : ''}`
94
- : `espeak "${params.text.replace(/"/g, '\\"')}"${outputPath ? ` -w "${outputPath}"` : ''}`;
95
-
96
- execSync(cmd, { timeout: 30000 });
97
- return { success: true, provider: 'local', outputPath: outputPath || 'played via speakers', message: 'Text spoken via local TTS' };
98
- }
99
-
100
- return { success: false, error: `Unknown provider: ${provider}` };
101
- } catch (error) {
102
- return { success: false, error: error.message };
103
- }
104
- }
105
- };
89
+ return await speak(params);
90
+ },
91
+ };
@@ -0,0 +1,88 @@
1
+ /**
2
+ * todo_write - Yapilacaklar listesi (v4.9.0)
3
+ *
4
+ * Hermes todo_write'una benzer.
5
+ */
6
+
7
+ const fs = require("fs");
8
+ const path = require("path");
9
+ const os = require("os");
10
+
11
+ const TODO_FILE = path.join(os.homedir(), ".natureco", "todos.json");
12
+
13
+ function loadTodos() {
14
+ try {
15
+ if (!fs.existsSync(TODO_FILE)) return [];
16
+ return JSON.parse(fs.readFileSync(TODO_FILE, "utf8"));
17
+ } catch { return []; }
18
+ }
19
+
20
+ function saveTodos(todos) {
21
+ fs.mkdirSync(path.dirname(TODO_FILE), { recursive: true });
22
+ fs.writeFileSync(TODO_FILE, JSON.stringify(todos, null, 2), "utf8");
23
+ }
24
+
25
+ function genId() {
26
+ return Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
27
+ }
28
+
29
+ async function todoAction({ action = "list", content, id, priority = "medium", status }) {
30
+ let todos = loadTodos();
31
+
32
+ if (action === "list") {
33
+ const pending = todos.filter(t => t.status === "pending");
34
+ const done = todos.filter(t => t.status === "done");
35
+ return { success: true, total: todos.length, pending: pending.length, done: done.length, todos: pending };
36
+ }
37
+
38
+ if (action === "add") {
39
+ if (!content) return { success: false, error: "content gerekli" };
40
+ const todo = { id: genId(), content, priority, status: "pending", createdAt: new Date().toISOString() };
41
+ todos.push(todo);
42
+ saveTodos(todos);
43
+ return { success: true, todo, message: `Todo eklendi: ${content}` };
44
+ }
45
+
46
+ if (action === "done") {
47
+ if (!id) return { success: false, error: "id gerekli" };
48
+ const todo = todos.find(t => t.id === id);
49
+ if (!todo) return { success: false, error: `Todo bulunamadi: ${id}` };
50
+ todo.status = "done";
51
+ todo.completedAt = new Date().toISOString();
52
+ saveTodos(todos);
53
+ return { success: true, todo, message: `Tamamlandi: ${todo.content}` };
54
+ }
55
+
56
+ if (action === "remove") {
57
+ if (!id) return { success: false, error: "id gerekli" };
58
+ const before = todos.length;
59
+ todos = todos.filter(t => t.id !== id);
60
+ saveTodos(todos);
61
+ return { success: true, removed: before - todos.length };
62
+ }
63
+
64
+ if (action === "clear") {
65
+ saveTodos([]);
66
+ return { success: true, cleared: todos.length, message: "Tum todolar temizlendi" };
67
+ }
68
+
69
+ return { success: false, error: `Bilinmeyen action: ${action}` };
70
+ }
71
+
72
+ module.exports = {
73
+ name: "todo_write",
74
+ description: "Yapilacaklar listesi. action: list, add, done, remove, clear.",
75
+ inputSchema: {
76
+ type: "object",
77
+ properties: {
78
+ action: { type: "string", description: "list/add/done/remove/clear (default: list)", enum: ["list", "add", "done", "remove", "clear"] },
79
+ content: { type: "string", description: "Todo icerigi (add icin)" },
80
+ id: { type: "string", description: "Todo ID (done/remove icin)" },
81
+ priority: { type: "string", description: "Oncelik: low/medium/high (add icin)", enum: ["low", "medium", "high"] },
82
+ status: { type: "string", description: "Durum filtresi: pending/done" },
83
+ },
84
+ },
85
+ async execute(params) {
86
+ return await todoAction(params);
87
+ },
88
+ };