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.
- package/CHANGELOG.md +486 -422
- package/package.json +1 -1
- package/src/commands/skills.js +497 -414
- package/src/tools/calendar_add.js +94 -0
- package/src/tools/code_execution.js +86 -0
- package/src/tools/cron_create.js +105 -0
- package/src/tools/delegate_task.js +61 -0
- package/src/tools/file_search.js +85 -0
- package/src/tools/grep_search.js +127 -0
- package/src/tools/http_request.js +77 -0
- package/src/tools/kanban.js +108 -0
- package/src/tools/mac_app_open.js +38 -0
- package/src/tools/mac_app_quit.js +46 -0
- package/src/tools/mac_notify.js +50 -0
- package/src/tools/memory_search.js +112 -0
- package/src/tools/notebook_edit.js +93 -0
- package/src/tools/notes_add.js +62 -0
- package/src/tools/reminder_add.js +65 -0
- package/src/tools/shell_command.js +55 -0
- package/src/tools/skills_autoload.js +97 -0
- package/src/tools/skills_marketplace.js +233 -0
- package/src/tools/text_to_speech.js +84 -98
- package/src/tools/todo_write.js +88 -0
- package/src/tools/macos.js +0 -324
|
@@ -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
|
-
|
|
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:
|
|
5
|
-
description:
|
|
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:
|
|
79
|
+
type: "object",
|
|
8
80
|
properties: {
|
|
9
|
-
text: { type:
|
|
10
|
-
provider: { type:
|
|
11
|
-
voice: { type:
|
|
12
|
-
|
|
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: [
|
|
86
|
+
required: ["text"],
|
|
15
87
|
},
|
|
16
|
-
|
|
17
88
|
async execute(params) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
+
};
|