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,46 @@
1
+ FROM node:22-bookworm-slim
2
+
3
+ # System packages for native Node modules (canvas, sharp, etc.)
4
+ RUN apt-get update && apt-get install -y --no-install-recommends \
5
+ build-essential \
6
+ python3 \
7
+ git \
8
+ ca-certificates \
9
+ curl \
10
+ && rm -rf /var/lib/apt/lists/*
11
+
12
+ # Use pnpm via corepack (handles native modules better than npm)
13
+ RUN corepack enable && corepack prepare pnpm@latest --activate
14
+ ENV PNPM_HOME=/usr/local/share/pnpm
15
+ ENV PATH=$PNPM_HOME:$PATH
16
+ RUN mkdir -p $PNPM_HOME
17
+
18
+ # OpenClaw version — pinned to the version tested with this OpenVoiceUI release.
19
+ # Override at build time: docker compose build --build-arg OPENCLAW_VERSION=2026.3.7
20
+ ARG OPENCLAW_VERSION=2026.3.13
21
+ RUN pnpm add -g openclaw@${OPENCLAW_VERSION} && pnpm approve-builds -g
22
+
23
+ # Optional: install a coding CLI so the coding-agent skill is available.
24
+ # Set CODING_CLI at build time: docker compose build --build-arg CODING_CLI=codex
25
+ # Or set CODING_CLI=codex in your .env and use the docker-compose args block.
26
+ #
27
+ # Options (same as openclaw's setup wizard):
28
+ # codex — OpenAI Codex CLI (requires OPENAI_API_KEY)
29
+ # claude — Anthropic Claude Code (requires ANTHROPIC_API_KEY)
30
+ # opencode — OpenCode (bring your own provider key)
31
+ # pi — Pi coding agent (bring your own provider key)
32
+ # none — skip (coding-agent skill will be inactive)
33
+ ARG CODING_CLI=none
34
+ RUN if [ "$CODING_CLI" = "codex" ]; then \
35
+ pnpm add -g @openai/codex; \
36
+ elif [ "$CODING_CLI" = "claude" ]; then \
37
+ pnpm add -g @anthropic-ai/claude-code; \
38
+ elif [ "$CODING_CLI" = "opencode" ]; then \
39
+ pnpm add -g opencode-ai; \
40
+ elif [ "$CODING_CLI" = "pi" ]; then \
41
+ pnpm add -g @mariozechner/pi-coding-agent; \
42
+ fi
43
+
44
+ EXPOSE 18791
45
+
46
+ CMD ["openclaw", "gateway", "--port", "18791"]
@@ -0,0 +1,30 @@
1
+ # =============================================================================
2
+ # OpenVoiceUI systemd service template
3
+ #
4
+ # !! EDIT BEFORE USING !!
5
+ # Replace all /path/to/OpenVoiceUI and YOUR_USER with real values, then:
6
+ #
7
+ # sudo cp deploy/openvoiceui.service /etc/systemd/system/
8
+ # sudo systemctl daemon-reload
9
+ # sudo systemctl enable openvoiceui
10
+ # sudo systemctl start openvoiceui
11
+ #
12
+ # Or use deploy/setup-sudo.sh which fills these in automatically.
13
+ # =============================================================================
14
+
15
+ [Unit]
16
+ Description=OpenVoiceUI Voice Agent Server
17
+ After=network.target
18
+
19
+ [Service]
20
+ Type=simple
21
+ User=YOUR_USER # ← your Linux username
22
+ WorkingDirectory=/path/to/OpenVoiceUI # ← full path to repo
23
+ ExecStart=/path/to/OpenVoiceUI/venv/bin/python3 /path/to/OpenVoiceUI/server.py
24
+ Restart=always
25
+ RestartSec=10
26
+ Environment=PATH=/usr/bin:/usr/local/bin
27
+ EnvironmentFile=/path/to/OpenVoiceUI/.env # ← full path to .env
28
+
29
+ [Install]
30
+ WantedBy=multi-user.target
@@ -0,0 +1,50 @@
1
+ #!/bin/bash
2
+ # =============================================================================
3
+ # OpenVoiceUI — quick nginx config for a single domain
4
+ #
5
+ # For a full setup (SSL, systemd service, www dirs) use deploy/setup-sudo.sh instead.
6
+ # This script is for manual nginx-only configuration.
7
+ #
8
+ # Edit DOMAIN, PORT, and EMAIL before running.
9
+ # Run as: sudo bash setup-nginx.sh
10
+ # =============================================================================
11
+
12
+ DOMAIN="your-domain.com" # ← EDIT: your actual domain
13
+ PORT=5001 # ← match PORT in your .env (default: 5001)
14
+ EMAIL="your-email@example.com" # ← EDIT: for Let's Encrypt notifications
15
+
16
+ # Guard: refuse to run with placeholder values
17
+ if [ "$DOMAIN" = "your-domain.com" ] || [ "$EMAIL" = "your-email@example.com" ]; then
18
+ echo "ERROR: Edit DOMAIN and EMAIL at the top of this script before running."
19
+ exit 1
20
+ fi
21
+
22
+ cat << NGINX | sudo tee /etc/nginx/sites-available/${DOMAIN}
23
+ server {
24
+ listen 80;
25
+ listen [::]:80;
26
+ server_name ${DOMAIN};
27
+
28
+ location / {
29
+ proxy_pass http://127.0.0.1:${PORT};
30
+ proxy_http_version 1.1;
31
+ proxy_set_header Upgrade \$http_upgrade;
32
+ proxy_set_header Connection 'upgrade';
33
+ proxy_set_header Host \$host;
34
+ proxy_set_header X-Real-IP \$remote_addr;
35
+ proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
36
+ proxy_set_header X-Forwarded-Proto \$scheme;
37
+ proxy_cache_bypass \$http_upgrade;
38
+ proxy_read_timeout 86400;
39
+ }
40
+
41
+ client_max_body_size 100M;
42
+ }
43
+ NGINX
44
+
45
+ sudo ln -sf /etc/nginx/sites-available/${DOMAIN} /etc/nginx/sites-enabled/
46
+ sudo nginx -t && sudo systemctl reload nginx
47
+ sudo certbot --nginx -d ${DOMAIN} --non-interactive --agree-tos -m ${EMAIL}
48
+
49
+ echo ""
50
+ echo "Done. OpenVoiceUI accessible at https://${DOMAIN}"
@@ -0,0 +1,306 @@
1
+ #!/bin/bash
2
+ # =============================================================================
3
+ # OpenVoiceUI — generic sudo setup script
4
+ # Creates: nginx config, Let's Encrypt SSL, systemd service, prestart script
5
+ # Run as: sudo bash deploy/setup-sudo.sh
6
+ # =============================================================================
7
+ set -e
8
+
9
+ # ── Configure these before running ──────────────────────────────────────────
10
+ INSTALL_DIR="$(cd "$(dirname "$0")" && pwd)"
11
+ DOMAIN="your-domain.com" # ← EDIT: your actual domain
12
+ PORT=5001 # ← match PORT in your .env (default: 5001)
13
+ EMAIL="your@email.com" # ← EDIT: for Let's Encrypt notifications
14
+ SERVICE_NAME="openvoiceui"
15
+ RUN_USER="${SUDO_USER:-$(whoami)}"
16
+ WWW_DIR="/var/www/${SERVICE_NAME}" # canvas pages + any web assets
17
+ # ────────────────────────────────────────────────────────────────────────────
18
+
19
+ # Guard: refuse to run with placeholder values
20
+ if [ "$DOMAIN" = "your-domain.com" ] || [ "$EMAIL" = "your@email.com" ]; then
21
+ echo "ERROR: Edit DOMAIN and EMAIL at the top of this script before running."
22
+ echo " Open deploy/setup-sudo.sh in a text editor and set your real domain and email."
23
+ exit 1
24
+ fi
25
+
26
+ # Check .env exists
27
+ if [ ! -f "${INSTALL_DIR}/.env" ]; then
28
+ echo "ERROR: No .env file found at ${INSTALL_DIR}/.env"
29
+ echo " Run: cp ${INSTALL_DIR}/.env.example ${INSTALL_DIR}/.env"
30
+ echo " Then edit .env and set your API keys before running this script."
31
+ exit 1
32
+ fi
33
+
34
+ # ── OpenClaw gateway detection ────────────────────────────────────────────────
35
+ # Helper: is OpenClaw currently listening on its default port?
36
+ _openclaw_running() {
37
+ ss -tlnp 2>/dev/null | grep -q ':18791'
38
+ }
39
+
40
+ # Helper: is the openclaw binary installed anywhere?
41
+ _openclaw_installed() {
42
+ command -v openclaw >/dev/null 2>&1 \
43
+ || [ -f "/usr/local/bin/openclaw" ] \
44
+ || [ -f "${HOME}/.local/bin/openclaw" ]
45
+ }
46
+
47
+ # Helper: try to start openclaw and wait up to 5s for port to open
48
+ _openclaw_start() {
49
+ echo " Attempting to start OpenClaw..."
50
+ if command -v openclaw >/dev/null 2>&1; then
51
+ openclaw start 2>/dev/null &
52
+ elif [ -f "/usr/local/bin/openclaw" ]; then
53
+ /usr/local/bin/openclaw start 2>/dev/null &
54
+ else
55
+ return 1
56
+ fi
57
+ local i=0
58
+ while [ $i -lt 5 ]; do
59
+ sleep 1
60
+ _openclaw_running && return 0
61
+ i=$((i+1))
62
+ done
63
+ return 1
64
+ }
65
+
66
+ echo ""
67
+ echo "── OpenClaw Gateway ──────────────────────────────────────────────────────"
68
+
69
+ OPENCLAW_GATEWAY_URL=$(grep -E "^CLAWDBOT_GATEWAY_URL=" "${INSTALL_DIR}/.env" 2>/dev/null | cut -d= -f2 | tr -d '"' | tr -d "'")
70
+ OPENCLAW_GATEWAY_URL="${OPENCLAW_GATEWAY_URL:-ws://127.0.0.1:18791}"
71
+ OPENCLAW_TOKEN=$(grep -E "^CLAWDBOT_AUTH_TOKEN=" "${INSTALL_DIR}/.env" 2>/dev/null | cut -d= -f2 | tr -d '"' | tr -d "'")
72
+ OPENCLAW_CONFIGURED=false
73
+
74
+ if _openclaw_running; then
75
+ echo " ✓ OpenClaw is running (port 18791 active)"
76
+ printf " Use this OpenClaw instance for OpenVoiceUI? [Y/n] "
77
+ read -r REPLY
78
+ if [[ $REPLY =~ ^[Nn]$ ]]; then
79
+ printf " Enter your gateway WebSocket URL [${OPENCLAW_GATEWAY_URL}]: "
80
+ read -r CUSTOM_URL
81
+ if [ -n "$CUSTOM_URL" ]; then
82
+ sed -i "s|^CLAWDBOT_GATEWAY_URL=.*|CLAWDBOT_GATEWAY_URL=${CUSTOM_URL}|" "${INSTALL_DIR}/.env"
83
+ OPENCLAW_GATEWAY_URL="$CUSTOM_URL"
84
+ echo " Updated CLAWDBOT_GATEWAY_URL in .env"
85
+ fi
86
+ fi
87
+ OPENCLAW_CONFIGURED=true
88
+
89
+ elif _openclaw_installed; then
90
+ echo " OpenClaw is installed but not running."
91
+ printf " Start OpenClaw now? [Y/n] "
92
+ read -r REPLY
93
+ if [[ ! $REPLY =~ ^[Nn]$ ]]; then
94
+ if _openclaw_start; then
95
+ echo " ✓ OpenClaw started successfully."
96
+ OPENCLAW_CONFIGURED=true
97
+ else
98
+ echo " ⚠ Could not start OpenClaw automatically."
99
+ echo " Start it manually (e.g. 'openclaw start' or restart its service),"
100
+ echo " then re-run this script — or continue and start it later."
101
+ printf " Continue anyway? [y/N] "
102
+ read -r REPLY
103
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
104
+ echo "Stopping. Start OpenClaw and re-run this script."
105
+ exit 1
106
+ fi
107
+ fi
108
+ else
109
+ OPENCLAW_CONFIGURED=false
110
+ fi
111
+
112
+ else
113
+ echo " OpenClaw is not installed on this system."
114
+ echo " OpenClaw is the AI gateway that processes all voice conversations."
115
+ echo ""
116
+ printf " Install OpenClaw now? [Y/n] "
117
+ read -r REPLY
118
+ if [[ ! $REPLY =~ ^[Nn]$ ]]; then
119
+ echo ""
120
+ echo " ── OpenClaw Installation ─────────────────────────────────────"
121
+ echo " 1. Visit https://openclaw.ai and follow the install guide"
122
+ echo " 2. Run the installer for your OS"
123
+ echo " 3. Start OpenClaw (it will listen on ws://127.0.0.1:18791)"
124
+ echo " 4. Create an agent workspace and copy your auth token"
125
+ echo " ──────────────────────────────────────────────────────────────"
126
+ echo ""
127
+ printf " Press Enter once OpenClaw is installed and running (Ctrl+C to abort)... "
128
+ read -r
129
+ if _openclaw_running; then
130
+ echo " ✓ OpenClaw detected — continuing."
131
+ OPENCLAW_CONFIGURED=true
132
+ else
133
+ echo " ⚠ OpenClaw port 18791 is still not responding."
134
+ printf " Continue anyway and configure OpenClaw later? [y/N] "
135
+ read -r REPLY
136
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
137
+ echo "Stopping. Install and start OpenClaw, then re-run this script."
138
+ exit 1
139
+ fi
140
+ fi
141
+ else
142
+ echo ""
143
+ echo " ┌────────────────────────────────────────────────────────────────┐"
144
+ echo " │ OpenVoiceUI requires a gateway framework to process voice │"
145
+ echo " │ conversations. Without one the server will start but the │"
146
+ echo " │ voice agent will not respond to any input. │"
147
+ echo " │ │"
148
+ echo " │ You can install a compatible gateway later and point .env at │"
149
+ echo " │ it via CLAWDBOT_GATEWAY_URL + CLAWDBOT_AUTH_TOKEN. │"
150
+ echo " │ See plugins/README.md for building a custom gateway plugin. │"
151
+ echo " └────────────────────────────────────────────────────────────────┘"
152
+ echo ""
153
+ printf " Continue setup without a gateway? [y/N] "
154
+ read -r REPLY
155
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
156
+ echo "Stopping. Visit https://openclaw.ai to get started."
157
+ exit 1
158
+ fi
159
+ echo " Continuing — the server will be installed but conversations will not work"
160
+ echo " until a gateway is configured."
161
+ fi
162
+ fi
163
+
164
+ # Check auth token is set and not placeholder
165
+ if [ "$OPENCLAW_CONFIGURED" = "true" ]; then
166
+ if [ -z "$OPENCLAW_TOKEN" ] || [ "$OPENCLAW_TOKEN" = "your-openclaw-gateway-token" ]; then
167
+ echo ""
168
+ echo " ⚠ CLAWDBOT_AUTH_TOKEN is not set in .env."
169
+ echo " OpenClaw is running but OpenVoiceUI cannot authenticate to it."
170
+ echo " Copy your agent auth token from your OpenClaw workspace and set:"
171
+ echo " CLAWDBOT_AUTH_TOKEN=your-token-here"
172
+ echo " in ${INSTALL_DIR}/.env, then restart the service."
173
+ else
174
+ echo " ✓ Auth token configured. Gateway: ${OPENCLAW_GATEWAY_URL}"
175
+ fi
176
+ echo ""
177
+ echo " Note: The AI model (LLM) is configured inside your OpenClaw agent"
178
+ echo " workspace — OpenVoiceUI works with any model OpenClaw is connected to."
179
+ echo " Voice settings (TTS, profile, wake words) are in the admin dashboard:"
180
+ echo " https://${DOMAIN}/src/admin.html"
181
+ fi
182
+ echo ""
183
+
184
+ echo "=== OpenVoiceUI setup: ${DOMAIN} on port ${PORT} ==="
185
+ echo " Install dir : ${INSTALL_DIR}"
186
+ echo " Service user: ${RUN_USER}"
187
+ echo " WWW dir : ${WWW_DIR}"
188
+ echo ""
189
+
190
+ # 0. Per-instance www directory (canvas pages, isolated from other users)
191
+ echo "[0/5] Creating www directory for ${RUN_USER}..."
192
+ mkdir -p "${WWW_DIR}/canvas-pages"
193
+ chown -R "${RUN_USER}:${RUN_USER}" "${WWW_DIR}"
194
+ chmod -R 755 "${WWW_DIR}"
195
+
196
+ # 1. Prestart script (kills stale process on port before service starts)
197
+ echo "[1/5] Creating prestart script..."
198
+ cat > /usr/local/bin/prestart-${SERVICE_NAME}.sh << PRESTART
199
+ #!/bin/bash
200
+ PORT=${PORT}
201
+ LOG=/var/log/${SERVICE_NAME}.log
202
+ PID=\$(fuser \${PORT}/tcp 2>/dev/null)
203
+ if [ -n "\$PID" ]; then
204
+ echo "\$(date): Found stale process \$PID on port \$PORT, killing..." | tee -a \$LOG
205
+ kill \$PID 2>/dev/null
206
+ sleep 2
207
+ if kill -0 \$PID 2>/dev/null; then
208
+ kill -9 \$PID 2>/dev/null
209
+ sleep 1
210
+ fi
211
+ fi
212
+ exit 0
213
+ PRESTART
214
+ chmod +x /usr/local/bin/prestart-${SERVICE_NAME}.sh
215
+
216
+ # 2. Systemd service
217
+ echo "[2/5] Creating systemd service..."
218
+ cat > /etc/systemd/system/${SERVICE_NAME}.service << SERVICE
219
+ [Unit]
220
+ Description=OpenVoiceUI Voice Agent (${DOMAIN})
221
+ After=network.target
222
+
223
+ [Service]
224
+ Type=simple
225
+ User=${RUN_USER}
226
+ WorkingDirectory=${INSTALL_DIR}
227
+ ExecStartPre=/usr/local/bin/prestart-${SERVICE_NAME}.sh
228
+ ExecStart=${INSTALL_DIR}/venv/bin/python3 ${INSTALL_DIR}/server.py
229
+ Restart=always
230
+ RestartSec=10
231
+ Environment=PATH=/usr/bin:/usr/local/bin
232
+ EnvironmentFile=${INSTALL_DIR}/.env
233
+
234
+ [Install]
235
+ WantedBy=multi-user.target
236
+ SERVICE
237
+
238
+ # 3. Nginx config
239
+ echo "[3/5] Creating nginx config..."
240
+ cat > /etc/nginx/sites-available/${DOMAIN} << NGINX
241
+ server {
242
+ listen 80;
243
+ listen [::]:80;
244
+ server_name ${DOMAIN};
245
+ return 301 https://\$host\$request_uri;
246
+ }
247
+
248
+ server {
249
+ listen 443 ssl http2;
250
+ listen [::]:443 ssl http2;
251
+ server_name ${DOMAIN};
252
+
253
+ ssl_certificate /etc/letsencrypt/live/${DOMAIN}/fullchain.pem;
254
+ ssl_certificate_key /etc/letsencrypt/live/${DOMAIN}/privkey.pem;
255
+ include /etc/letsencrypt/options-ssl-nginx.conf;
256
+ ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
257
+
258
+ add_header X-Frame-Options "SAMEORIGIN" always;
259
+ add_header X-Content-Type-Options "nosniff" always;
260
+ add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
261
+
262
+ location / {
263
+ proxy_pass http://127.0.0.1:${PORT};
264
+ proxy_http_version 1.1;
265
+ proxy_set_header Upgrade \$http_upgrade;
266
+ proxy_set_header Connection 'upgrade';
267
+ proxy_set_header Host \$host;
268
+ proxy_set_header X-Real-IP \$remote_addr;
269
+ proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
270
+ proxy_set_header X-Forwarded-Proto \$scheme;
271
+ proxy_cache_bypass \$http_upgrade;
272
+ proxy_read_timeout 86400;
273
+ }
274
+
275
+ client_max_body_size 100M;
276
+ }
277
+ NGINX
278
+
279
+ ln -sf /etc/nginx/sites-available/${DOMAIN} /etc/nginx/sites-enabled/${DOMAIN}
280
+
281
+ # 4. SSL cert
282
+ echo "[4/5] Obtaining SSL certificate..."
283
+ if [ ! -f "/etc/letsencrypt/live/${DOMAIN}/fullchain.pem" ]; then
284
+ certbot certonly --nginx -d ${DOMAIN} --non-interactive --agree-tos -m ${EMAIL}
285
+ else
286
+ echo " SSL cert already exists, skipping."
287
+ fi
288
+
289
+ # 5. Enable and start service
290
+ echo "[5/5] Enabling and starting service..."
291
+ nginx -t
292
+ systemctl reload nginx
293
+ systemctl daemon-reload
294
+ systemctl enable ${SERVICE_NAME}.service
295
+ systemctl restart ${SERVICE_NAME}.service
296
+
297
+ sleep 3
298
+ systemctl status ${SERVICE_NAME}.service --no-pager
299
+
300
+ echo ""
301
+ echo "=== Done! OpenVoiceUI running at https://${DOMAIN} ==="
302
+ echo ""
303
+ echo "Useful commands:"
304
+ echo " sudo systemctl status ${SERVICE_NAME}"
305
+ echo " sudo systemctl restart ${SERVICE_NAME}"
306
+ echo " sudo journalctl -u ${SERVICE_NAME} -f"
@@ -0,0 +1,19 @@
1
+ FROM python:3.12-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install OS deps needed by lxml (python-docx/pptx) and matplotlib
6
+ RUN apt-get update && apt-get install -y --no-install-recommends \
7
+ libxml2 \
8
+ libxslt1.1 \
9
+ libfreetype6 \
10
+ && rm -rf /var/lib/apt/lists/*
11
+
12
+ COPY requirements.txt .
13
+ RUN pip install --no-cache-dir -r requirements.txt
14
+
15
+ COPY server.py .
16
+
17
+ EXPOSE 8900
18
+
19
+ CMD ["python", "server.py"]
@@ -0,0 +1,14 @@
1
+ # skill-runner — shared heavy-dependency service for all JamBot users
2
+ # All packages live here so the openvoiceui/openclaw containers stay lean.
3
+
4
+ flask>=3.0.0
5
+
6
+ # Document text extraction
7
+ pypdf>=4.0.0
8
+ python-docx>=1.1.0
9
+ openpyxl>=3.1.0
10
+ python-pptx>=0.6.23
11
+
12
+ # CSV / data analysis
13
+ pandas>=2.0.0
14
+ matplotlib>=3.7.0