natureco-cli 5.2.1 → 5.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "natureco-cli",
3
- "version": "5.2.1",
3
+ "version": "5.3.0",
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"
@@ -0,0 +1,178 @@
1
+ /**
2
+ * voice_chat - Sesli asistan (v5.3.0)
3
+ *
4
+ * Parton'un vizyonu: "Bilgisayarla konusayim"
5
+ *
6
+ * Mikrofon → Whisper STT → REPL'e metin olarak gönder
7
+ * Bot cevabı → TTS ile sesli oku
8
+ *
9
+ * macOS icin: afplay + sox/rec + Whisper
10
+ * Cross-platform: openai-whisper API veya local whisper.cpp
11
+ */
12
+
13
+ const fs = require("fs");
14
+ const os = require("os");
15
+ const path = require("path");
16
+ const { spawn } = require("child_process");
17
+ const https = require("https");
18
+
19
+ const IS_MAC = os.platform() === "darwin";
20
+
21
+ /**
22
+ * Whisper API ile sesi metne cevir
23
+ */
24
+ function whisperTranscribe(audioBuffer, provider = "openai") {
25
+ return new Promise((resolve, reject) => {
26
+ const apiKey = process.env.OPENAI_API_KEY || process.env.WHISPER_API_KEY;
27
+ if (!apiKey) {
28
+ reject(new Error("OPENAI_API_KEY gerekli (Whisper API icin)"));
29
+ return;
30
+ }
31
+
32
+ // multipart/form-data olustur
33
+ const boundary = "----formdata-" + Date.now();
34
+ const filename = "/tmp/audio-" + Date.now() + ".wav";
35
+
36
+ // Basit multipart builder
37
+ let body = Buffer.alloc(0);
38
+ body = Buffer.concat([body, Buffer.from(`--${boundary}\r\n`)]);
39
+ body = Buffer.concat([body, Buffer.from(`Content-Disposition: form-data; name="file"; filename="audio.wav"\r\n`)]);
40
+ body = Buffer.concat([body, Buffer.from(`Content-Type: audio/wav\r\n\r\n`)]);
41
+ body = Buffer.concat([body, audioBuffer]);
42
+ body = Buffer.concat([body, Buffer.from(`\r\n--${boundary}\r\n`)]);
43
+ body = Buffer.concat([body, Buffer.from(`Content-Disposition: form-data; name="model"\r\n\r\n`)]);
44
+ body = Buffer.concat([body, Buffer.from(`whisper-1`)]);
45
+ body = Buffer.concat([body, Buffer.from(`\r\n--${boundary}\r\n`)]);
46
+ body = Buffer.concat([body, Buffer.from(`Content-Disposition: form-data; name="language"\r\n\r\n`)]);
47
+ body = Buffer.concat([body, Buffer.from(`tr`)]);
48
+ body = Buffer.concat([body, Buffer.from(`\r\n--${boundary}--\r\n`)]);
49
+
50
+ const req = https.request({
51
+ hostname: "api.openai.com",
52
+ port: 443,
53
+ path: "/v1/audio/transcriptions",
54
+ method: "POST",
55
+ headers: {
56
+ "Authorization": `Bearer ${apiKey}`,
57
+ "Content-Type": `multipart/form-data; boundary=${boundary}`,
58
+ "Content-Length": body.length,
59
+ },
60
+ timeout: 30000,
61
+ }, res => {
62
+ let data = "";
63
+ res.on("data", c => data += c);
64
+ res.on("end", () => {
65
+ try {
66
+ const parsed = JSON.parse(data);
67
+ resolve(parsed.text || "");
68
+ } catch (e) {
69
+ reject(new Error("Whisper API yanit parse hatasi: " + data.slice(0, 200)));
70
+ }
71
+ });
72
+ });
73
+ req.on("error", reject);
74
+ req.on("timeout", () => req.destroy() && reject(new Error("Whisper timeout")));
75
+ req.write(body);
76
+ req.end();
77
+ });
78
+ }
79
+
80
+ /**
81
+ * macOS'ta mikrofondan ses kaydet
82
+ * "sox" veya "rec" komutunu kullanir
83
+ */
84
+ function recordMac(durationSec = 5) {
85
+ return new Promise((resolve, reject) => {
86
+ const outFile = path.join(os.tmpdir(), `voice-${Date.now()}.wav`);
87
+
88
+ // sox varsa onu kullan, yoksa built-in "rec" (sox)
89
+ const proc = spawn("rec", [
90
+ "-r", "16000", // 16kHz (Whisper icin ideal)
91
+ "-c", "1", // mono
92
+ "-b", "16", // 16-bit
93
+ outFile,
94
+ "trim", "0", String(durationSec), // sure
95
+ ], { timeout: durationSec * 1000 + 5000 });
96
+
97
+ proc.on("close", (code) => {
98
+ if (code === 0 && fs.existsSync(outFile)) {
99
+ resolve(outFile);
100
+ } else {
101
+ reject(new Error("Ses kaydi basarisiz. Kur: brew install sox"));
102
+ }
103
+ });
104
+ proc.on("error", (e) => reject(new Error("'rec' komutu bulunamadi. brew install sox ile kur")));
105
+ });
106
+ }
107
+
108
+ /**
109
+ * macOS'ta text-to-speech (say)
110
+ */
111
+ function macSay(text) {
112
+ return new Promise((resolve) => {
113
+ if (!text) return resolve({ success: false, error: "text bos" });
114
+ const proc = spawn("say", ["-v", "Yelda", text]);
115
+ proc.on("close", (code) => {
116
+ if (code === 0) resolve({ success: true, provider: "mac-say" });
117
+ else resolve({ success: false, error: `say exit ${code}` });
118
+ });
119
+ proc.on("error", (e) => resolve({ success: false, error: e.message }));
120
+ });
121
+ }
122
+
123
+ /**
124
+ * Voice chat — record → transcribe → reply → speak
125
+ */
126
+ async function voiceChat({ action = "speak", text, durationSec = 5 } = {}) {
127
+ if (!IS_MAC) return { success: false, error: "Voice chat sadece macOS'ta" };
128
+
129
+ if (action === "record") {
130
+ try {
131
+ const audioFile = await recordMac(durationSec);
132
+ const buffer = fs.readFileSync(audioFile);
133
+ const text = await whisperTranscribe(buffer);
134
+ fs.unlinkSync(audioFile);
135
+ return { success: true, action: "record", text, message: `Algilanan: "${text}"` };
136
+ } catch (e) {
137
+ return { success: false, error: e.message };
138
+ }
139
+ }
140
+
141
+ if (action === "speak" || !action) {
142
+ if (!text) return { success: false, error: "text gerekli (speak icin)" };
143
+ return await macSay(text);
144
+ }
145
+
146
+ if (action === "test") {
147
+ // Setup check
148
+ try {
149
+ const outFile = path.join(os.tmpdir(), "voice-test.wav");
150
+ const proc = spawn("which", ["rec"]);
151
+ proc.on("close", (code) => {
152
+ if (code === 0) return { success: true, message: "Voice chat hazir" };
153
+ });
154
+ return { success: false, error: "'rec' komutu yok. brew install sox" };
155
+ } catch (e) {
156
+ return { success: false, error: e.message };
157
+ }
158
+ }
159
+
160
+ return { success: false, error: `Bilinmeyen action: ${action}` };
161
+ }
162
+
163
+ module.exports = {
164
+ name: "voice_chat",
165
+ description: "Sesli asistan: mikrofonla konus, Whisper ile metne cevir, yaniti sesli oku. macOS + sox gerekli.",
166
+ inputSchema: {
167
+ type: "object",
168
+ properties: {
169
+ action: { type: "string", description: "record (konusarak yaz), speak (yaziyi sesli oku), test (hazir mi?)", enum: ["record", "speak", "test"] },
170
+ text: { type: "string", description: "Sesli okunacak metin (speak icin)" },
171
+ durationSec: { type: "number", description: "Kayit suresi (default 5)" },
172
+ },
173
+ required: [],
174
+ },
175
+ async execute(params) {
176
+ return await voiceChat(params);
177
+ },
178
+ };