openvoiceui 1.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.
Files changed (185) hide show
  1. package/.env.example +104 -0
  2. package/Dockerfile +30 -0
  3. package/LICENSE +21 -0
  4. package/README.md +638 -0
  5. package/SETUP.md +360 -0
  6. package/app.py +232 -0
  7. package/auto-approve-devices.js +111 -0
  8. package/cli/index.js +372 -0
  9. package/config/__init__.py +4 -0
  10. package/config/default.yaml +43 -0
  11. package/config/flags.yaml +67 -0
  12. package/config/loader.py +203 -0
  13. package/config/providers.yaml +71 -0
  14. package/config/speech_normalization.yaml +182 -0
  15. package/config/theme.json +4 -0
  16. package/data/greetings.json +25 -0
  17. package/default-pages/ai-image-creator.html +915 -0
  18. package/default-pages/bulk-image-uploader.html +492 -0
  19. package/default-pages/desktop.html +2865 -0
  20. package/default-pages/file-explorer.html +854 -0
  21. package/default-pages/interactive-map.html +655 -0
  22. package/default-pages/style-guide.html +1005 -0
  23. package/default-pages/website-setup.html +1623 -0
  24. package/deploy/openclaw/Dockerfile +46 -0
  25. package/deploy/openvoiceui.service +30 -0
  26. package/deploy/setup-nginx.sh +50 -0
  27. package/deploy/setup-sudo.sh +306 -0
  28. package/deploy/skill-runner/Dockerfile +19 -0
  29. package/deploy/skill-runner/requirements.txt +14 -0
  30. package/deploy/skill-runner/server.py +269 -0
  31. package/deploy/supertonic/Dockerfile +22 -0
  32. package/deploy/supertonic/server.py +79 -0
  33. package/docker-compose.pinokio.yml +11 -0
  34. package/docker-compose.yml +59 -0
  35. package/greetings.json +25 -0
  36. package/index.html +65 -0
  37. package/inject-device-identity.js +142 -0
  38. package/package.json +82 -0
  39. package/profiles/default.json +114 -0
  40. package/profiles/manager.py +354 -0
  41. package/profiles/schema.json +337 -0
  42. package/prompts/voice-system-prompt.md +149 -0
  43. package/providers/__init__.py +39 -0
  44. package/providers/base.py +63 -0
  45. package/providers/llm/__init__.py +12 -0
  46. package/providers/llm/base.py +71 -0
  47. package/providers/llm/clawdbot_provider.py +112 -0
  48. package/providers/llm/zai_provider.py +115 -0
  49. package/providers/registry.py +320 -0
  50. package/providers/stt/__init__.py +12 -0
  51. package/providers/stt/base.py +58 -0
  52. package/providers/stt/webspeech_provider.py +49 -0
  53. package/providers/stt/whisper_provider.py +100 -0
  54. package/providers/tts/__init__.py +20 -0
  55. package/providers/tts/base.py +91 -0
  56. package/providers/tts/groq_provider.py +74 -0
  57. package/providers/tts/supertonic_provider.py +72 -0
  58. package/requirements.txt +38 -0
  59. package/routes/__init__.py +10 -0
  60. package/routes/admin.py +515 -0
  61. package/routes/canvas.py +1315 -0
  62. package/routes/chat.py +51 -0
  63. package/routes/conversation.py +2158 -0
  64. package/routes/elevenlabs_hybrid.py +306 -0
  65. package/routes/greetings.py +98 -0
  66. package/routes/icons.py +279 -0
  67. package/routes/image_gen.py +364 -0
  68. package/routes/instructions.py +190 -0
  69. package/routes/music.py +838 -0
  70. package/routes/onboarding.py +43 -0
  71. package/routes/pi.py +62 -0
  72. package/routes/profiles.py +215 -0
  73. package/routes/report_issue.py +68 -0
  74. package/routes/static_files.py +533 -0
  75. package/routes/suno.py +664 -0
  76. package/routes/theme.py +81 -0
  77. package/routes/transcripts.py +199 -0
  78. package/routes/vision.py +348 -0
  79. package/routes/workspace.py +288 -0
  80. package/server.py +1510 -0
  81. package/services/__init__.py +1 -0
  82. package/services/auth.py +143 -0
  83. package/services/canvas_versioning.py +239 -0
  84. package/services/db_pool.py +107 -0
  85. package/services/gateway.py +16 -0
  86. package/services/gateway_manager.py +333 -0
  87. package/services/gateways/__init__.py +12 -0
  88. package/services/gateways/base.py +110 -0
  89. package/services/gateways/compat.py +264 -0
  90. package/services/gateways/openclaw.py +1134 -0
  91. package/services/health.py +100 -0
  92. package/services/memory_client.py +455 -0
  93. package/services/paths.py +26 -0
  94. package/services/speech_normalizer.py +285 -0
  95. package/services/tts.py +270 -0
  96. package/setup-config.js +262 -0
  97. package/sounds/air_horn.mp3 +0 -0
  98. package/sounds/bruh.mp3 +0 -0
  99. package/sounds/crowd_cheer.mp3 +0 -0
  100. package/sounds/gunshot.mp3 +0 -0
  101. package/sounds/impact.mp3 +0 -0
  102. package/sounds/lets_go.mp3 +0 -0
  103. package/sounds/record_stop.mp3 +0 -0
  104. package/sounds/rewind.mp3 +0 -0
  105. package/sounds/sad_trombone.mp3 +0 -0
  106. package/sounds/scratch_long.mp3 +0 -0
  107. package/sounds/yeah.mp3 +0 -0
  108. package/src/adapters/ClawdBotAdapter.js +264 -0
  109. package/src/adapters/_template.js +133 -0
  110. package/src/adapters/elevenlabs-classic.js +841 -0
  111. package/src/adapters/elevenlabs-hybrid.js +812 -0
  112. package/src/adapters/hume-evi.js +676 -0
  113. package/src/admin.html +1339 -0
  114. package/src/app.js +8802 -0
  115. package/src/core/Config.js +173 -0
  116. package/src/core/EmotionEngine.js +307 -0
  117. package/src/core/EventBridge.js +180 -0
  118. package/src/core/EventBus.js +117 -0
  119. package/src/core/VoiceSession.js +607 -0
  120. package/src/face/BaseFace.js +259 -0
  121. package/src/face/EyeFace.js +208 -0
  122. package/src/face/HaloSmokeFace.js +509 -0
  123. package/src/face/manifest.json +27 -0
  124. package/src/face/previews/eyes.svg +16 -0
  125. package/src/face/previews/orb.svg +29 -0
  126. package/src/features/MusicPlayer.js +620 -0
  127. package/src/features/Soundboard.js +128 -0
  128. package/src/providers/DeepgramSTT.js +472 -0
  129. package/src/providers/DeepgramStreamingSTT.js +766 -0
  130. package/src/providers/GroqSTT.js +559 -0
  131. package/src/providers/TTSPlayer.js +323 -0
  132. package/src/providers/WebSpeechSTT.js +479 -0
  133. package/src/providers/tts/BaseTTSProvider.js +81 -0
  134. package/src/providers/tts/HumeProvider.js +77 -0
  135. package/src/providers/tts/SupertonicProvider.js +174 -0
  136. package/src/providers/tts/index.js +140 -0
  137. package/src/shell/adapter-registry.js +154 -0
  138. package/src/shell/caller-bridge.js +35 -0
  139. package/src/shell/camera-bridge.js +28 -0
  140. package/src/shell/canvas-bridge.js +32 -0
  141. package/src/shell/commercial-bridge.js +44 -0
  142. package/src/shell/face-bridge.js +44 -0
  143. package/src/shell/music-bridge.js +60 -0
  144. package/src/shell/orchestrator.js +233 -0
  145. package/src/shell/profile-discovery.js +303 -0
  146. package/src/shell/sounds-bridge.js +28 -0
  147. package/src/shell/transcript-bridge.js +61 -0
  148. package/src/shell/waveform-bridge.js +33 -0
  149. package/src/styles/base.css +2862 -0
  150. package/src/styles/face.css +417 -0
  151. package/src/styles/pi-overrides.css +89 -0
  152. package/src/styles/theme-dark.css +67 -0
  153. package/src/test-tts.html +175 -0
  154. package/src/ui/AppShell.js +544 -0
  155. package/src/ui/ProfileSwitcher.js +228 -0
  156. package/src/ui/SessionControl.js +240 -0
  157. package/src/ui/face/FacePicker.js +195 -0
  158. package/src/ui/face/FaceRenderer.js +309 -0
  159. package/src/ui/settings/PlaylistEditor.js +366 -0
  160. package/src/ui/settings/SettingsPanel.css +684 -0
  161. package/src/ui/settings/SettingsPanel.js +419 -0
  162. package/src/ui/settings/TTSVoicePreview.js +210 -0
  163. package/src/ui/themes/ThemeManager.js +213 -0
  164. package/src/ui/visualizers/BaseVisualizer.js +29 -0
  165. package/src/ui/visualizers/PartyFXVisualizer.css +291 -0
  166. package/src/ui/visualizers/PartyFXVisualizer.js +637 -0
  167. package/static/emulators/jsdos/js-dos.css +1 -0
  168. package/static/emulators/jsdos/js-dos.js +22 -0
  169. package/static/favicon.svg +55 -0
  170. package/static/icons/apple-touch-icon.png +0 -0
  171. package/static/icons/favicon-32.png +0 -0
  172. package/static/icons/icon-192.png +0 -0
  173. package/static/icons/icon-512.png +0 -0
  174. package/static/install.html +449 -0
  175. package/static/manifest.json +26 -0
  176. package/static/sw.js +21 -0
  177. package/tts_providers/__init__.py +136 -0
  178. package/tts_providers/base_provider.py +319 -0
  179. package/tts_providers/groq_provider.py +155 -0
  180. package/tts_providers/hume_provider.py +226 -0
  181. package/tts_providers/providers_config.json +119 -0
  182. package/tts_providers/qwen3_provider.py +371 -0
  183. package/tts_providers/resemble_provider.py +315 -0
  184. package/tts_providers/supertonic_provider.py +557 -0
  185. package/tts_providers/supertonic_tts.py +399 -0
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env node
2
+ // auto-approve-devices.js — Approve all pending OpenClaw devices.
3
+ // Runs on the host, execs into the openclaw container to move pending → paired.
4
+ // Called by start.js after containers are healthy.
5
+ //
6
+ // Polls for up to 2 minutes (24 retries × 5s) because OpenVoiceUI doesn't
7
+ // connect to OpenClaw until the user opens the browser — which happens AFTER
8
+ // Pinokio shows the "Open" button. We need to wait for that.
9
+
10
+ const { execSync } = require("child_process");
11
+
12
+ const COMPOSE = "docker compose -f docker-compose.yml -f docker-compose.pinokio.yml";
13
+ const MAX_ATTEMPTS = 24; // 24 × 5s = 2 minutes
14
+ const DELAY_MS = 5000;
15
+
16
+ // MSYS_NO_PATHCONV=1 prevents Git Bash on Windows from converting /container/paths
17
+ const EXEC_ENV = Object.assign({}, process.env, { MSYS_NO_PATHCONV: "1" });
18
+
19
+ // Node one-liner that runs INSIDE the openclaw container
20
+ const script = `
21
+ const fs = require('fs');
22
+ try {
23
+ const pendingPath = '/root/.openclaw/devices/pending.json';
24
+ const pairedPath = '/root/.openclaw/devices/paired.json';
25
+ let pending = {};
26
+ let paired = {};
27
+ try { pending = JSON.parse(fs.readFileSync(pendingPath, 'utf8')); } catch(e) {}
28
+ try { paired = JSON.parse(fs.readFileSync(pairedPath, 'utf8')); } catch(e) {}
29
+ const pairedCount = Object.keys(paired).length;
30
+ let count = 0;
31
+ for (const entry of Object.values(pending)) {
32
+ if (entry.deviceId && entry.publicKey) {
33
+ paired[entry.deviceId] = {
34
+ publicKey: entry.publicKey,
35
+ name: entry.name || 'auto-approved',
36
+ role: entry.role || 'operator',
37
+ roles: entry.roles || ['operator'],
38
+ scopes: entry.scopes || ['operator.read', 'operator.write'],
39
+ paired: true,
40
+ pairedAt: new Date().toISOString(),
41
+ autoApproved: true,
42
+ };
43
+ count++;
44
+ }
45
+ }
46
+ if (count > 0) {
47
+ fs.writeFileSync(pairedPath, JSON.stringify(paired, null, 2));
48
+ fs.writeFileSync(pendingPath, '{}');
49
+ console.log('APPROVED:' + count);
50
+ } else if (pairedCount > 0) {
51
+ console.log('ALREADY_PAIRED:' + pairedCount);
52
+ } else {
53
+ console.log('APPROVED:0');
54
+ }
55
+ } catch(e) {
56
+ console.log('ERROR:' + e.message);
57
+ }
58
+ `.replace(/\n/g, " ").trim();
59
+
60
+ function sleep(ms) {
61
+ return new Promise(resolve => setTimeout(resolve, ms));
62
+ }
63
+
64
+ async function run() {
65
+ console.log(" Waiting for browser to connect (up to 2 minutes)...");
66
+
67
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
68
+ try {
69
+ const result = execSync(
70
+ `${COMPOSE} exec -T openclaw node -e "${script.replace(/"/g, '\\"')}"`,
71
+ { encoding: "utf8", timeout: 15000, env: EXEC_ENV }
72
+ ).trim();
73
+
74
+ // New device(s) approved
75
+ const approvedMatch = result.match(/APPROVED:(\d+)/);
76
+ if (approvedMatch && parseInt(approvedMatch[1]) > 0) {
77
+ console.log(` Auto-approved ${approvedMatch[1]} device(s) — ready to use!`);
78
+ return;
79
+ }
80
+
81
+ // Device already paired from a previous session
82
+ const pairedMatch = result.match(/ALREADY_PAIRED:(\d+)/);
83
+ if (pairedMatch && parseInt(pairedMatch[1]) > 0) {
84
+ console.log(` Device already paired (${pairedMatch[1]} device(s)) — ready to use!`);
85
+ return;
86
+ }
87
+
88
+ // Nothing yet — keep polling
89
+ if (attempt < MAX_ATTEMPTS) {
90
+ // Only log every 4th attempt to avoid spam
91
+ if (attempt % 4 === 0) {
92
+ const remaining = Math.round((MAX_ATTEMPTS - attempt) * DELAY_MS / 1000);
93
+ console.log(` Still waiting for browser connection... (${remaining}s remaining)`);
94
+ }
95
+ await sleep(DELAY_MS);
96
+ } else {
97
+ console.log(" Timeout: No device connected after 2 minutes.");
98
+ console.log(" If you see NOT_PAIRED errors, open the browser and restart the app.");
99
+ }
100
+ } catch (e) {
101
+ if (attempt < MAX_ATTEMPTS) {
102
+ // Container might still be starting — retry silently
103
+ await sleep(DELAY_MS);
104
+ } else {
105
+ console.log(" Device auto-approve failed:", e.message.split("\n")[0]);
106
+ }
107
+ }
108
+ }
109
+ }
110
+
111
+ run();
package/cli/index.js ADDED
@@ -0,0 +1,372 @@
1
+ #!/usr/bin/env node
2
+ // OpenVoiceUI CLI — zero-dependency entry point
3
+ // Usage: openvoiceui <command> [options]
4
+
5
+ const { execSync, spawn } = require("child_process");
6
+ const fs = require("fs");
7
+ const path = require("path");
8
+
9
+ const VERSION = require("../package.json").version;
10
+ const PROJECT_DIR = path.resolve(__dirname, "..");
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Helpers
14
+ // ---------------------------------------------------------------------------
15
+
16
+ function run(cmd, opts = {}) {
17
+ return execSync(cmd, {
18
+ cwd: PROJECT_DIR,
19
+ stdio: opts.silent ? "pipe" : "inherit",
20
+ ...opts,
21
+ });
22
+ }
23
+
24
+ function runLive(cmd, args = []) {
25
+ const child = spawn(cmd, args, {
26
+ cwd: PROJECT_DIR,
27
+ stdio: "inherit",
28
+ shell: true,
29
+ });
30
+ child.on("error", (err) => {
31
+ console.error(`Failed to run: ${cmd} ${args.join(" ")}`);
32
+ console.error(err.message);
33
+ process.exit(1);
34
+ });
35
+ return child;
36
+ }
37
+
38
+ function hasDocker() {
39
+ try {
40
+ execSync("docker --version", { stdio: "pipe" });
41
+ execSync("docker compose version", { stdio: "pipe" });
42
+ return true;
43
+ } catch {
44
+ return false;
45
+ }
46
+ }
47
+
48
+ function isRunning() {
49
+ try {
50
+ const out = execSync("docker compose ps --format json", {
51
+ cwd: PROJECT_DIR,
52
+ stdio: "pipe",
53
+ }).toString();
54
+ // docker compose ps returns one JSON object per line
55
+ const lines = out.trim().split("\n").filter(Boolean);
56
+ return lines.length > 0;
57
+ } catch {
58
+ return false;
59
+ }
60
+ }
61
+
62
+ function getPort() {
63
+ const envPath = path.join(PROJECT_DIR, ".env");
64
+ if (fs.existsSync(envPath)) {
65
+ const content = fs.readFileSync(envPath, "utf8");
66
+ const match = content.match(/^PORT=(\d+)/m);
67
+ if (match) return match[1];
68
+ }
69
+ return "5001";
70
+ }
71
+
72
+ function printBanner() {
73
+ console.log(`
74
+ ╔══════════════════════════════════════╗
75
+ ║ OpenVoiceUI v${VERSION.padEnd(14)}║
76
+ ║ Voice-Powered AI Assistant ║
77
+ ╚══════════════════════════════════════╝
78
+ `);
79
+ }
80
+
81
+ // ---------------------------------------------------------------------------
82
+ // Commands
83
+ // ---------------------------------------------------------------------------
84
+
85
+ const commands = {
86
+ setup: {
87
+ desc: "Interactive setup — configure API keys and build Docker images",
88
+ run: cmdSetup,
89
+ },
90
+ start: {
91
+ desc: "Start OpenVoiceUI (Docker Compose)",
92
+ run: cmdStart,
93
+ },
94
+ stop: {
95
+ desc: "Stop OpenVoiceUI",
96
+ run: cmdStop,
97
+ },
98
+ restart: {
99
+ desc: "Restart OpenVoiceUI",
100
+ run: cmdRestart,
101
+ },
102
+ status: {
103
+ desc: "Show container status",
104
+ run: cmdStatus,
105
+ },
106
+ logs: {
107
+ desc: "Stream container logs (Ctrl+C to stop)",
108
+ run: cmdLogs,
109
+ },
110
+ update: {
111
+ desc: "Pull latest source and rebuild images",
112
+ run: cmdUpdate,
113
+ },
114
+ config: {
115
+ desc: "Open the OpenClaw control panel (localhost:18791)",
116
+ run: cmdConfig,
117
+ },
118
+ help: {
119
+ desc: "Show this help message",
120
+ run: cmdHelp,
121
+ },
122
+ };
123
+
124
+ // --- setup ---
125
+ async function cmdSetup() {
126
+ printBanner();
127
+ console.log(" Setting up OpenVoiceUI...\n");
128
+
129
+ if (!hasDocker()) {
130
+ console.error(" ERROR: Docker and Docker Compose are required.");
131
+ console.error(" Install Docker: https://docs.docker.com/get-docker/\n");
132
+ process.exit(1);
133
+ }
134
+ console.log(" [OK] Docker found\n");
135
+
136
+ // Interactive key collection
137
+ const readline = require("readline");
138
+ const rl = readline.createInterface({
139
+ input: process.stdin,
140
+ output: process.stdout,
141
+ });
142
+
143
+ const ask = (q) =>
144
+ new Promise((resolve) =>
145
+ rl.question(q, (a) => resolve(a.trim()))
146
+ );
147
+
148
+ console.log(" ── Required Keys ──────────────────────────────────────");
149
+ console.log(" These are needed for the app to function.\n");
150
+
151
+ const groqKey = await ask(
152
+ " Groq API Key (console.groq.com) [REQUIRED]: "
153
+ );
154
+ if (!groqKey) {
155
+ console.error("\n ERROR: Groq API key is required for TTS.\n");
156
+ rl.close();
157
+ process.exit(1);
158
+ }
159
+
160
+ const deepgramKey = await ask(
161
+ " Deepgram API Key (console.deepgram.com) [REQUIRED]: "
162
+ );
163
+ if (!deepgramKey) {
164
+ console.error("\n ERROR: Deepgram API key is required for STT.\n");
165
+ rl.close();
166
+ process.exit(1);
167
+ }
168
+
169
+ console.log("\n ── AI Provider Keys (pick at least one) ──────────────");
170
+ console.log(" Press Enter to skip any provider.\n");
171
+
172
+ const zaiKey = await ask(" Z.AI API Key (z.ai): ");
173
+ const anthropicKey = await ask(" Anthropic API Key (console.anthropic.com): ");
174
+ const openaiKey = await ask(" OpenAI API Key (platform.openai.com): ");
175
+
176
+ if (!zaiKey && !anthropicKey && !openaiKey) {
177
+ console.log(
178
+ "\n WARNING: No AI provider key set. You can add one later in the .env file.\n"
179
+ );
180
+ }
181
+
182
+ console.log("\n ── Optional Keys ─────────────────────────────────────");
183
+ console.log(" Press Enter to skip.\n");
184
+
185
+ const geminiKey = await ask(" Gemini API Key (aistudio.google.com): ");
186
+ const openrouterKey = await ask(" OpenRouter API Key (openrouter.ai): ");
187
+ const sunoKey = await ask(" Suno API Key — AI Music (sunoapi.org): ");
188
+
189
+ const portAnswer = await ask(" Port (default 5001): ");
190
+ const port = portAnswer || "5001";
191
+
192
+ rl.close();
193
+
194
+ // Set env vars for setup-config.js
195
+ const setupEnv = {
196
+ PINOKIO_PORT: port,
197
+ PINOKIO_GROQ_API_KEY: groqKey,
198
+ PINOKIO_DEEPGRAM_API_KEY: deepgramKey,
199
+ PINOKIO_ZAI_API_KEY: zaiKey,
200
+ PINOKIO_ANTHROPIC_API_KEY: anthropicKey,
201
+ PINOKIO_OPENAI_API_KEY: openaiKey,
202
+ PINOKIO_GEMINI_API_KEY: geminiKey,
203
+ PINOKIO_OPENROUTER_API_KEY: openrouterKey,
204
+ PINOKIO_SUNO_API_KEY: sunoKey,
205
+ };
206
+
207
+ console.log("\n Generating configuration files...\n");
208
+ run("node setup-config.js", {
209
+ env: { ...process.env, ...setupEnv },
210
+ });
211
+
212
+ console.log("\n Building Docker images (this may take a few minutes)...\n");
213
+ run("docker compose build");
214
+
215
+ console.log(`
216
+ ════════════════════════════════════════════════════════
217
+ Setup complete!
218
+
219
+ Start OpenVoiceUI: openvoiceui start
220
+ Open in browser: http://localhost:${port}
221
+ OpenClaw control: http://localhost:18791
222
+ View logs: openvoiceui logs
223
+ ════════════════════════════════════════════════════════
224
+ `);
225
+ }
226
+
227
+ // --- start ---
228
+ function cmdStart() {
229
+ printBanner();
230
+ if (!hasDocker()) {
231
+ console.error(" ERROR: Docker not found. Run: openvoiceui setup\n");
232
+ process.exit(1);
233
+ }
234
+
235
+ const envPath = path.join(PROJECT_DIR, ".env");
236
+ if (!fs.existsSync(envPath)) {
237
+ console.error(" ERROR: No .env file found. Run: openvoiceui setup\n");
238
+ process.exit(1);
239
+ }
240
+
241
+ console.log(" Starting OpenVoiceUI...\n");
242
+ run("docker compose up -d");
243
+
244
+ // Inject pre-paired device identity if available
245
+ const prePairedPath = path.join(
246
+ PROJECT_DIR,
247
+ "openclaw-data",
248
+ "pre-paired-device.json"
249
+ );
250
+ if (fs.existsSync(prePairedPath)) {
251
+ try {
252
+ run("node inject-device-identity.js", { silent: true });
253
+ } catch {
254
+ // Non-fatal — device pairing may already be done
255
+ }
256
+ }
257
+
258
+ const port = getPort();
259
+ console.log(`
260
+ OpenVoiceUI is running!
261
+
262
+ App: http://localhost:${port}
263
+ OpenClaw: http://localhost:18791
264
+ Stop: openvoiceui stop
265
+ Logs: openvoiceui logs
266
+ `);
267
+ }
268
+
269
+ // --- stop ---
270
+ function cmdStop() {
271
+ console.log(" Stopping OpenVoiceUI...\n");
272
+ run("docker compose down");
273
+ console.log(" Stopped.\n");
274
+ }
275
+
276
+ // --- restart ---
277
+ function cmdRestart() {
278
+ console.log(" Restarting OpenVoiceUI...\n");
279
+ run("docker compose down");
280
+ run("docker compose up -d");
281
+
282
+ const port = getPort();
283
+ console.log(`\n Restarted. App: http://localhost:${port}\n`);
284
+ }
285
+
286
+ // --- status ---
287
+ function cmdStatus() {
288
+ printBanner();
289
+ if (!hasDocker()) {
290
+ console.error(" Docker not found.\n");
291
+ process.exit(1);
292
+ }
293
+ run("docker compose ps");
294
+ }
295
+
296
+ // --- logs ---
297
+ function cmdLogs() {
298
+ const child = runLive("docker", ["compose", "logs", "-f", "--tail", "100"]);
299
+ process.on("SIGINT", () => {
300
+ child.kill("SIGINT");
301
+ process.exit(0);
302
+ });
303
+ }
304
+
305
+ // --- update ---
306
+ function cmdUpdate() {
307
+ printBanner();
308
+ console.log(" Updating OpenVoiceUI...\n");
309
+
310
+ // Check if this is a git repo (cloned install) or npm install
311
+ const gitDir = path.join(PROJECT_DIR, ".git");
312
+ if (fs.existsSync(gitDir)) {
313
+ console.log(" Pulling latest source...\n");
314
+ run("git pull --ff-only");
315
+ } else {
316
+ console.log(" Installed via npm — update with: npm update -g openvoiceui\n");
317
+ }
318
+
319
+ console.log(" Rebuilding Docker images...\n");
320
+ run("docker compose build --pull");
321
+
322
+ if (isRunning()) {
323
+ console.log(" Restarting containers...\n");
324
+ run("docker compose up -d");
325
+ }
326
+
327
+ console.log(" Update complete!\n");
328
+ }
329
+
330
+ // --- config ---
331
+ function cmdConfig() {
332
+ console.log(" OpenClaw control panel: http://localhost:18791\n");
333
+ console.log(" Open this URL in your browser to change AI models,");
334
+ console.log(" add provider keys, and configure agent behavior.\n");
335
+ }
336
+
337
+ // --- help ---
338
+ function cmdHelp() {
339
+ printBanner();
340
+ console.log(" Usage: openvoiceui <command>\n");
341
+ console.log(" Commands:\n");
342
+ for (const [name, cmd] of Object.entries(commands)) {
343
+ console.log(` ${name.padEnd(12)} ${cmd.desc}`);
344
+ }
345
+ console.log(`
346
+ Quick start:
347
+ openvoiceui setup # configure keys + build images
348
+ openvoiceui start # launch the app
349
+ open http://localhost:5001
350
+ `);
351
+ }
352
+
353
+ // ---------------------------------------------------------------------------
354
+ // Main
355
+ // ---------------------------------------------------------------------------
356
+
357
+ const command = process.argv[2] || "help";
358
+
359
+ if (commands[command]) {
360
+ const result = commands[command].run();
361
+ // Handle async commands (setup uses readline)
362
+ if (result && typeof result.catch === "function") {
363
+ result.catch((err) => {
364
+ console.error(`\n ERROR: ${err.message}\n`);
365
+ process.exit(1);
366
+ });
367
+ }
368
+ } else {
369
+ console.error(` Unknown command: ${command}\n`);
370
+ cmdHelp();
371
+ process.exit(1);
372
+ }
@@ -0,0 +1,4 @@
1
+ # config package
2
+ from config.loader import config
3
+
4
+ __all__ = ["config"]
@@ -0,0 +1,43 @@
1
+ # OpenVoiceUI Default Configuration
2
+ # All values here can be overridden by environment variables.
3
+ # Env var naming: SECTION__KEY (double underscore), e.g. SERVER__PORT=5002
4
+ # Or use the direct env var names listed in comments.
5
+
6
+ # ── Server ────────────────────────────────────────────────────────────────────
7
+ server:
8
+ host: "0.0.0.0"
9
+ port: 5001 # env: PORT
10
+ debug: false
11
+ threaded: true
12
+
13
+ # ── Gateway (OpenClaw) ────────────────────────────────────────────────────────
14
+ gateway:
15
+ url: "ws://127.0.0.1:18791" # env: CLAWDBOT_GATEWAY_URL
16
+ auth_token: "" # env: CLAWDBOT_AUTH_TOKEN (required)
17
+ session_key: "voice-main-1" # env: GATEWAY_SESSION_KEY
18
+
19
+ # ── LLM Models ────────────────────────────────────────────────────────────────
20
+ models:
21
+ whisper: "tiny" # Faster-Whisper model size
22
+ whisper_device: "cpu" # cpu or cuda
23
+ whisper_compute: "float32" # float32, float16, int8
24
+
25
+ # ── TTS ───────────────────────────────────────────────────────────────────────
26
+ tts:
27
+ provider: "groq" # env: TTS_PROVIDER (groq or supertonic)
28
+
29
+ # ── Conversation ──────────────────────────────────────────────────────────────
30
+ conversation:
31
+ max_history_messages: 20 # env: MAX_HISTORY_MESSAGES
32
+ active_profile: "default" # must match a filename in profiles/
33
+
34
+ # ── Logging ───────────────────────────────────────────────────────────────────
35
+ logging:
36
+ level: "INFO" # env: LOG_LEVEL
37
+
38
+ # ── Features (runtime toggles) ────────────────────────────────────────────────
39
+ features:
40
+ enable_fts: true # env: ENABLE_FTS
41
+ enable_briefing: true # env: ENABLE_BRIEFING
42
+ enable_history_reload: true # env: ENABLE_HISTORY_RELOAD
43
+ memory_client: true # whether to use memory_client if available
@@ -0,0 +1,67 @@
1
+ # Feature Flags — ai-eyes2 refactor
2
+ #
3
+ # These flags gate each refactor phase so changes can be rolled out gradually.
4
+ # All flags default to false (old behaviour preserved until explicitly enabled).
5
+ #
6
+ # Override any flag via env var:
7
+ # FEATURE_USE_BLUEPRINTS=true
8
+ # FEATURE_PERSISTENT_WEBSOCKET=true
9
+ # etc.
10
+ #
11
+ # Usage in code:
12
+ # from config.loader import config
13
+ # if config.flag('use_blueprints'):
14
+ # ...
15
+
16
+ flags:
17
+ # ── Phase 1: Foundation ───────────────────────────────────────────────────
18
+ use_yaml_config: true # P1-T2 — YAML config system active
19
+ use_feature_flags: true # P1-T3 — this flag system itself (meta)
20
+ use_health_probes: true # P1-T4 — /health/live and /health/ready endpoints
21
+ use_sqlite_wal: true # P1-T5 — WAL mode + connection pooling
22
+ use_session_reset: false # P1-T7 — POST /api/session/reset endpoint
23
+
24
+ # ── Phase 2: Backend Blueprint Split ─────────────────────────────────────
25
+ use_blueprints: false # P2-T1 — Flask app factory + blueprints
26
+ use_gateway_service: false # P2-T2 — GatewayConnection as services/gateway.py
27
+ use_conversation_blueprint: false # P2-T3 — conversation routes in blueprint
28
+ use_music_blueprint: false # P2-T4 — music routes in blueprint
29
+ use_canvas_blueprint: false # P2-T5 — canvas routes in blueprint
30
+ use_admin_blueprint: false # P2-T6 — admin routes in blueprint
31
+ use_tts_service: false # P2-T7 — TTS providers as service module
32
+
33
+ # ── Phase G: Gateway & Transport ─────────────────────────────────────────
34
+ use_persistent_websocket: false # PG-T2 — persistent WS to Gateway
35
+ use_streaming_events: false # PG-T3 — stream delta events to frontend
36
+ use_webchat_mode: true # PG-T5 — Gateway client mode WEBCHAT
37
+
38
+ # ── Phase 3: Frontend Module Extraction ──────────────────────────────────
39
+ use_eventbus: false # P3-T1 — frontend EventBus pub/sub
40
+ use_css_modules: false # P3-T2 — extracted CSS files
41
+ use_stt_module: false # P3-T3 — WebSpeechSTT as module
42
+ use_tts_player_module: false # P3-T4 — TTSPlayer as module
43
+ use_face_module: false # P3-T5 — face system as module
44
+ use_music_module: false # P3-T6 — MusicPlayer as module
45
+ use_voice_session: false # P3-T7 — VoiceSession orchestrator
46
+ use_emotion_engine: false # P3-T8 — EmotionEngine for mood → face
47
+ use_thin_shell: false # P3-T9 — slim index.html shell
48
+
49
+ # ── Phase 4: Admin UI ────────────────────────────────────────────────────
50
+ use_session_control_ui: false # P4-T1 — Reset Conversation button
51
+ use_playlist_editor: false # P4-T2 — PlaylistEditor UI
52
+ use_face_picker: false # P4-T3 — face picker gallery
53
+ use_theme_editor: false # P4-T4 — color picker / theme editor
54
+ use_tts_preview: false # P4-T5 — TTS voice preview
55
+ use_profile_switcher_ui: false # P4-T6 — profile switcher UI
56
+
57
+ # ── Phase 5: Agent Profiles & Provider Registry ───────────────────────────
58
+ use_provider_registry: false # P5-T2 — provider auto-discovery
59
+ use_profile_system: false # P5-T3 — JSON profile schema
60
+ use_profile_manager: false # P5-T4 — ProfileManager + API
61
+ use_speech_normalization: false # P5-T5 — config-driven speech normalisation
62
+
63
+ # ── Phase 6: Multi-Agent Framework ───────────────────────────────────────
64
+ use_eventbridge: false # P6-T1 — EventBridge implementation
65
+ use_clawdbot_adapter: false # P6-T2 — ClawdBotAdapter module
66
+ use_hume_adapter: false # P6-T3 — Hume EVI adapter
67
+ use_capability_ui: false # P6-T4 — capability-driven UI shell