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 +1 -1
- package/src/tools/voice_chat.js +178 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "natureco-cli",
|
|
3
|
-
"version": "5.
|
|
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
|
+
};
|