@inetafrica/open-claudia 2.6.44 → 2.6.45

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/bot-agent.js CHANGED
@@ -767,25 +767,9 @@ function transcribeAudio(oggPath) {
767
767
  }
768
768
 
769
769
  // ── Text-to-Speech ────────────────────────────────────────────────
770
+ // Shared with direct mode: ElevenLabs natural voice, falling back to `say`.
770
771
 
771
- const TTS_CMD = process.platform === "darwin" ? "say" : null;
772
-
773
- function textToVoice(text) {
774
- if (!TTS_CMD || !FFMPEG) return null;
775
- try {
776
- const clean = text.replace(/[*_`#>\[\]()]/g, "").replace(/\n{2,}/g, ". ").replace(/\n/g, " ").trim();
777
- if (!clean) return null;
778
- const aiffPath = path.join(TEMP_DIR, `tts-${Date.now()}.aiff`);
779
- const oggPath = aiffPath.replace(".aiff", ".ogg");
780
- execSync(`${TTS_CMD} ${JSON.stringify(clean)} -o "${aiffPath}"`, { timeout: 30000 });
781
- execSync(`"${FFMPEG}" -i "${aiffPath}" -c:a libopus -y "${oggPath}" 2>/dev/null`, { timeout: 30000 });
782
- try { fs.unlinkSync(aiffPath); } catch (e) {}
783
- return oggPath;
784
- } catch (e) {
785
- console.error("TTS error:", e.message);
786
- return null;
787
- }
788
- }
772
+ const { textToVoice } = require("./core/media");
789
773
 
790
774
  async function sendVoice(oggPath) {
791
775
  try {
@@ -1421,9 +1405,9 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
1421
1405
  if (code !== 0 && code !== null) await send(`Exit code: ${code}`);
1422
1406
 
1423
1407
  // Send voice reply if input was a voice note
1424
- if (lastInputWasVoice && TTS_CMD) {
1408
+ if (lastInputWasVoice) {
1425
1409
  lastInputWasVoice = false;
1426
- const voicePath = textToVoice(finalText);
1410
+ const voicePath = await textToVoice(finalText);
1427
1411
  if (voicePath) await sendVoice(voicePath);
1428
1412
  }
1429
1413
  } catch (e) {
package/core/config.js CHANGED
@@ -99,6 +99,9 @@ const TRANSCRIPTS_DIR = config.TRANSCRIPTS_DIR || process.env.TRANSCRIPTS_DIR ||
99
99
  const WHISPER_CLI = config.WHISPER_CLI || "";
100
100
  const WHISPER_MODEL = config.WHISPER_MODEL || "";
101
101
  const FFMPEG = config.FFMPEG || "";
102
+ const ELEVENLABS_API_KEY = config.ELEVENLABS_API_KEY || process.env.ELEVENLABS_API_KEY || "";
103
+ const ELEVENLABS_VOICE_ID = config.ELEVENLABS_VOICE_ID || process.env.ELEVENLABS_VOICE_ID || "EXAVITQu4vr4xnSDxMaL";
104
+ const ELEVENLABS_MODEL = config.ELEVENLABS_MODEL || process.env.ELEVENLABS_MODEL || "eleven_v3";
102
105
  const SOUL_FILE = config.SOUL_FILE || path.join(CONFIG_DIR, "soul.md");
103
106
  const CRONS_FILE = config.CRONS_FILE || path.join(CONFIG_DIR, "crons.json");
104
107
  const JOBS_FILE = config.JOBS_FILE || path.join(CONFIG_DIR, "jobs.json");
@@ -233,6 +236,7 @@ module.exports = {
233
236
  TRANSCRIPT_MAX_ENTRY_CHARS,
234
237
  TRANSCRIPTS_DIR,
235
238
  WHISPER_CLI, WHISPER_MODEL, FFMPEG,
239
+ ELEVENLABS_API_KEY, ELEVENLABS_VOICE_ID, ELEVENLABS_MODEL,
236
240
  SOUL_FILE, CRONS_FILE, JOBS_FILE, TASKS_DIR, VAULT_FILE, AUTH_FILE, IDENTITIES_FILE,
237
241
  PEOPLE_FILE, INTROS_FILE, AUDIT_FILE,
238
242
  STATE_FILE, SESSIONS_FILE,
package/core/media.js CHANGED
@@ -4,7 +4,7 @@
4
4
  const fs = require("fs");
5
5
  const path = require("path");
6
6
  const { execSync } = require("child_process");
7
- const { WHISPER_CLI, WHISPER_MODEL, FFMPEG, TEMP_DIR } = require("./config");
7
+ const { WHISPER_CLI, WHISPER_MODEL, FFMPEG, TEMP_DIR, ELEVENLABS_API_KEY, ELEVENLABS_VOICE_ID, ELEVENLABS_MODEL } = require("./config");
8
8
 
9
9
  const TTS_CMD = process.platform === "darwin" ? "say" : null;
10
10
 
@@ -19,11 +19,14 @@ function transcribeAudio(oggPath) {
19
19
  .join(" ").trim();
20
20
  }
21
21
 
22
- function textToVoice(text) {
22
+ function cleanForTTS(text) {
23
+ return text.replace(/[*_`#>\[\]()]/g, "").replace(/\n{2,}/g, ". ").replace(/\n/g, " ").trim();
24
+ }
25
+
26
+ // macOS `say` fallback. Synchronous. Returns ogg path or null.
27
+ function sayToVoice(clean) {
23
28
  if (!TTS_CMD || !FFMPEG) return null;
24
29
  try {
25
- const clean = text.replace(/[*_`#>\[\]()]/g, "").replace(/\n{2,}/g, ". ").replace(/\n/g, " ").trim();
26
- if (!clean) return null;
27
30
  const aiffPath = path.join(TEMP_DIR, `tts-${Date.now()}.aiff`);
28
31
  const oggPath = aiffPath.replace(".aiff", ".ogg");
29
32
  execSync(`${TTS_CMD} ${JSON.stringify(clean)} -o "${aiffPath}"`, { timeout: 30000 });
@@ -31,9 +34,49 @@ function textToVoice(text) {
31
34
  try { fs.unlinkSync(aiffPath); } catch (e) {}
32
35
  return oggPath;
33
36
  } catch (e) {
34
- console.error("TTS error:", e.message);
37
+ console.error("say TTS error:", e.message);
35
38
  return null;
36
39
  }
37
40
  }
38
41
 
42
+ // Natural TTS via ElevenLabs. Returns ogg path or null on any failure.
43
+ async function elevenLabsToVoice(clean) {
44
+ if (!ELEVENLABS_API_KEY || !FFMPEG) return null;
45
+ try {
46
+ const res = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${ELEVENLABS_VOICE_ID}`, {
47
+ method: "POST",
48
+ headers: { "xi-api-key": ELEVENLABS_API_KEY, "Content-Type": "application/json" },
49
+ body: JSON.stringify({
50
+ text: clean,
51
+ model_id: ELEVENLABS_MODEL,
52
+ voice_settings: { stability: 0.5, similarity_boost: 0.85, style: 0.5, use_speaker_boost: true },
53
+ }),
54
+ });
55
+ if (!res.ok) {
56
+ const body = await res.text().catch(() => "");
57
+ console.error(`ElevenLabs TTS failed: ${res.status} ${body}`.slice(0, 300));
58
+ return null;
59
+ }
60
+ const buf = Buffer.from(await res.arrayBuffer());
61
+ const mp3Path = path.join(TEMP_DIR, `tts-${Date.now()}.mp3`);
62
+ const oggPath = mp3Path.replace(".mp3", ".ogg");
63
+ fs.writeFileSync(mp3Path, buf);
64
+ execSync(`"${FFMPEG}" -i "${mp3Path}" -c:a libopus -y "${oggPath}" 2>/dev/null`, { timeout: 30000 });
65
+ try { fs.unlinkSync(mp3Path); } catch (e) {}
66
+ return oggPath;
67
+ } catch (e) {
68
+ console.error("ElevenLabs TTS error:", e.message);
69
+ return null;
70
+ }
71
+ }
72
+
73
+ // Natural voice via ElevenLabs, falling back to macOS `say` only on no-key/error.
74
+ async function textToVoice(text) {
75
+ const clean = cleanForTTS(text);
76
+ if (!clean) return null;
77
+ const eleven = await elevenLabsToVoice(clean);
78
+ if (eleven) return eleven;
79
+ return sayToVoice(clean);
80
+ }
81
+
39
82
  module.exports = { transcribeAudio, textToVoice, TTS_CMD };
package/core/runner.js CHANGED
@@ -16,7 +16,7 @@ const { chatContext, currentChannelId, currentAdapter } = require("./context");
16
16
  const { buildSystemPrompt, promptWithDynamicContext } = require("./system-prompt");
17
17
  const { redactSensitive } = require("./redact");
18
18
  const { send, editMessage, sendVoice, splitMessage } = require("./io");
19
- const { textToVoice, TTS_CMD } = require("./media");
19
+ const { textToVoice } = require("./media");
20
20
  const { killProcessTree } = require("./process-tree");
21
21
  const {
22
22
  appendProjectTranscript, transcriptProjectInfo,
@@ -1193,9 +1193,9 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
1193
1193
  }
1194
1194
  if (code !== 0 && code !== null) await send(`Exit code: ${code}`);
1195
1195
 
1196
- if (state.lastInputWasVoice && TTS_CMD) {
1196
+ if (state.lastInputWasVoice) {
1197
1197
  state.lastInputWasVoice = false;
1198
- const voicePath = textToVoice(finalText);
1198
+ const voicePath = await textToVoice(finalText);
1199
1199
  if (voicePath) await sendVoice(voicePath);
1200
1200
  }
1201
1201
  } catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "2.6.44",
3
+ "version": "2.6.45",
4
4
  "description": "Your always-on AI coding assistant — Claude Code, Cursor Agent, and OpenAI Codex via Telegram or Kazee Chat",
5
5
  "main": "bot.js",
6
6
  "bin": {