dulus 0.2.53__tar.gz → 0.2.55__tar.gz
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.
- {dulus-0.2.53/dulus.egg-info → dulus-0.2.55}/PKG-INFO +1 -1
- {dulus-0.2.53 → dulus-0.2.55}/data/context.json +27 -25
- {dulus-0.2.53 → dulus-0.2.55/dulus.egg-info}/PKG-INFO +1 -1
- {dulus-0.2.53 → dulus-0.2.55}/dulus.egg-info/SOURCES.txt +3 -1
- {dulus-0.2.53 → dulus-0.2.55}/dulus.py +333 -2
- {dulus-0.2.53 → dulus-0.2.55}/input.py +90 -26
- {dulus-0.2.53 → dulus-0.2.55}/pyproject.toml +1 -1
- {dulus-0.2.53 → dulus-0.2.55}/tests/test_voice.py +56 -0
- {dulus-0.2.53 → dulus-0.2.55}/voice/__init__.py +8 -0
- dulus-0.2.55/voice/audio_utils.py +40 -0
- {dulus-0.2.53 → dulus-0.2.55}/voice/recorder.py +11 -7
- {dulus-0.2.53 → dulus-0.2.55}/voice/stt.py +20 -0
- {dulus-0.2.53 → dulus-0.2.55}/voice/tts.py +113 -41
- dulus-0.2.55/voice/wake_word.py +374 -0
- {dulus-0.2.53 → dulus-0.2.55}/LICENSE +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/MANIFEST.in +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/README.md +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/agent.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/backend/__init__.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/backend/agents_bridge.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/backend/compressor.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/backend/context.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/backend/githook.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/backend/marketplace.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/backend/mempalace_bridge.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/backend/personas.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/backend/plugins.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/backend/server.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/backend/tasks.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/batch_api.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/checkpoint/__init__.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/checkpoint/hooks.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/checkpoint/store.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/checkpoint/types.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/claude_code_watcher.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/clipboard_utils.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/cloudsave.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/common.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/compaction.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/config.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/context.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/data/__init__.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/data/active_persona.json +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/data/marketplace.json +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/data/personas.json +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/data/plugins/__init__.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/data/plugins/composio/__init__.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/data/plugins/composio/composio_plugin/__init__.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/data/plugins/composio/composio_plugin/session_manager.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/data/plugins/composio/composio_plugin/tool_generator.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/data/plugins/composio/plugin.json +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/data/plugins/composio/plugin_tool.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/data/tasks.json +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/docs/README.md +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/docs/__init__.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/docs/api.html +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/docs/architecture.md +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/docs/azure-speech-template.json +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/docs/dashboard/index.html +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/docs/divider.svg +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/docs/generate.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/docs/hero.svg +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/docs/index.html +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/docs/news.md +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/docs/nvidia-models.svg +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/docs/particle-playground.html +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/docs/personas/index.html +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/docs/poetry-banner.png +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/docs/preview.html +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/docs/sec-agents.svg +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/docs/sec-brainstorm.svg +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/docs/sec-bridges.svg +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/docs/sec-features.svg +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/docs/sec-freetier.svg +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/docs/sec-memory.svg +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/docs/sec-models.svg +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/docs/sec-perms.svg +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/docs/sec-plugins.svg +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/docs/sec-quickstart.svg +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/docs/sec-ssj.svg +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/docs/spinners.svg +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/docs/split-pane.svg +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/docs/terminal-boot.svg +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/docs/uploads/particle-playground.html +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/dulus.egg-info/dependency_links.txt +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/dulus.egg-info/entry_points.txt +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/dulus.egg-info/requires.txt +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/dulus.egg-info/top_level.txt +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/dulus_gui.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/dulus_mcp/__init__.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/dulus_mcp/client.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/dulus_mcp/config.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/dulus_mcp/tools.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/dulus_mcp/types.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/gui/__init__.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/gui/agent_bridge.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/gui/chat_widget.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/gui/main_window.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/gui/personas.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/gui/session_utils.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/gui/settings_dialog.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/gui/sidebar.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/gui/tasks_view.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/gui/themes.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/gui/tool_panel.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/license_manager.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/memory/__init__.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/memory/audit.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/memory/consolidator.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/memory/context.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/memory/offload.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/memory/palace.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/memory/scan.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/memory/sessions.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/memory/store.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/memory/tools.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/memory/types.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/memory/vector_search.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/multi_agent/__init__.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/multi_agent/subagent.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/multi_agent/tools.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/offload_helper.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/plugin/__init__.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/plugin/autoadapter.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/plugin/loader.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/plugin/recommend.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/plugin/store.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/plugin/types.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/providers.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/README.md +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/__init__.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/components.json +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/dist/assets/index-DE51D6wI.css +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/dist/assets/index-DMCCNE9Y.js +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/dist/index.html +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/dist/wallpaper-default.jpg +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/dist/wallpapers/default.jpeg +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/dist/wallpapers/light.jpeg +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/dist/wallpapers/nature.jpeg +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/dist/wallpapers/tech.jpeg +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/eslint.config.js +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/index.html +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/info.md +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/package-lock.json +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/package.json +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/postcss.config.js +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/public/wallpaper-default.jpg +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/public/wallpapers/default.jpeg +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/public/wallpapers/light.jpeg +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/public/wallpapers/nature.jpeg +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/public/wallpapers/tech.jpeg +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/App.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/AgentMonitor.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/ApiTester.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/AppRouter.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/ArchiveManager.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/AsciiArt.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/Base64Tool.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/Browser.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/Calculator.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/Calendar.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/Chat.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/Chess.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/Clock.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/CodeEditor.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/ColorPalette.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/ColorPicker.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/Contacts.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/DocumentViewer.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/Drawing.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/Email.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/FileManager.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/FlappyBird.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/FtpClient.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/Game2048.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/GitClient.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/ImageGallery.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/ImageViewer.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/JsonFormatter.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/MarkdownPreview.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/MatrixRain.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/MediaConverter.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/Memory.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/MemoryManager.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/Minesweeper.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/MusicPlayer.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/NetworkTools.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/Notes.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/PasswordManager.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/PhotoEditor.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/Pong.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/RegexTester.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/Reminders.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/RssReader.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/ScreenRecorder.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/Settings.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/SkillsLauncher.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/Snake.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/Solitaire.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/Spreadsheet.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/Sudoku.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/SystemMonitor.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/TaskManager.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/Terminal.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/Tetris.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/TextEditor.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/TicTacToe.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/Todo.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/VideoPlayer.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/VoiceRecorder.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/Weather.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/Whiteboard.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/apps/registry.ts +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/AppContainer.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/AppLauncher.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/BootSequence.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ContextMenu.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/Desktop.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/Dock.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/LoginScreen.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/NotImplemented.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/NotificationCenter.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/NotificationSystem.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/TopPanel.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/WindowFrame.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/WindowManager.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/accordion.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/alert-dialog.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/alert.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/aspect-ratio.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/avatar.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/badge.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/breadcrumb.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/button-group.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/button.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/calendar.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/card.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/carousel.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/chart.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/checkbox.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/collapsible.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/command.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/context-menu.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/dialog.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/drawer.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/dropdown-menu.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/empty.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/field.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/form.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/hover-card.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/input-group.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/input-otp.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/input.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/item.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/kbd.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/label.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/menubar.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/navigation-menu.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/pagination.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/popover.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/progress.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/radio-group.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/resizable.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/scroll-area.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/select.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/separator.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/sheet.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/sidebar.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/skeleton.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/slider.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/sonner.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/spinner.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/switch.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/table.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/tabs.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/textarea.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/toggle-group.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/toggle.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/components/ui/tooltip.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/hooks/index.ts +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/hooks/use-mobile.ts +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/hooks/useAutoOpenChat.ts +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/hooks/useDulusAgents.ts +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/hooks/useDulusChat.ts +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/hooks/useDulusEvents.ts +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/hooks/useDulusHealth.ts +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/hooks/useDulusMemory.ts +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/hooks/useDulusSkills.ts +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/hooks/useDulusTasks.ts +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/hooks/useFileSystem.ts +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/hooks/useOSStore.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/hooks/useSkillBridge.ts +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/hooks/useSystemBattery.ts +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/hooks/useSystemNetwork.ts +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/hooks/useSystemVolume.ts +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/index.css +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/lib/dulus-api.ts +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/lib/utils.ts +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/main.tsx +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/types/index.ts +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/src/utils/assets.ts +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/tailwind.config.js +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/tsconfig.app.json +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/tsconfig.json +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/tsconfig.node.json +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/sandbox/vite.config.ts +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/setup.cfg +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/skill/__init__.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/skill/builtin.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/skill/clawhub.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/skill/executor.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/skill/loader.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/skill/tools.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/skills.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/spinner.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/string_utils.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/subagent.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/task/__init__.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/task/store.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/task/tools.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/task/types.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tests/test_afk_yolo.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tests/test_approval_runtime.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tests/test_background_task_tools.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tests/test_background_tasks.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tests/test_checkpoint.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tests/test_clipboard_utils.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tests/test_compaction.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tests/test_diff_view.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tests/test_diff_visualization.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tests/test_display_blocks.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tests/test_export_import.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tests/test_hook_engine.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tests/test_injection_fix.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tests/test_license.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tests/test_mcp.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tests/test_memory.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tests/test_notification_manager.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tests/test_plugin.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tests/test_session_fork.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tests/test_shell_mode.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tests/test_skills.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tests/test_steer_input.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tests/test_subagent.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tests/test_task.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tests/test_telegram_buffer.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tests/test_think_tool.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tests/test_todo_tool.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tests/test_todo_visualization.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tests/test_tool_registry.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tests/test_wire_events.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tmux_offloader.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tmux_tools.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tool_registry.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/tools.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/ui/__init__.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/ui/input.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/ui/render.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/voice/keyterms.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/webchat.py +0 -0
- {dulus-0.2.53 → dulus-0.2.55}/webchat_server.py +0 -0
|
@@ -9,21 +9,22 @@
|
|
|
9
9
|
"project": {
|
|
10
10
|
"name": "Dulus Command Center",
|
|
11
11
|
"repo_stats": {
|
|
12
|
-
"files":
|
|
13
|
-
"lines":
|
|
12
|
+
"files": 439,
|
|
13
|
+
"lines": 206577,
|
|
14
14
|
"languages": {
|
|
15
15
|
".example": 5,
|
|
16
|
-
"no_ext":
|
|
17
|
-
".py":
|
|
16
|
+
"no_ext": 1471,
|
|
17
|
+
".py": 61567,
|
|
18
18
|
".html": 14944,
|
|
19
19
|
".in": 3,
|
|
20
20
|
".toml": 129,
|
|
21
|
-
".md":
|
|
22
|
-
".txt":
|
|
21
|
+
".md": 2170,
|
|
22
|
+
".txt": 473,
|
|
23
23
|
".lock": 1,
|
|
24
|
-
".json":
|
|
25
|
-
".
|
|
26
|
-
".
|
|
24
|
+
".json": 9515,
|
|
25
|
+
".TAG": 4,
|
|
26
|
+
".whl": 21792,
|
|
27
|
+
".gz": 21068,
|
|
27
28
|
".svg": 1123,
|
|
28
29
|
".png": 31639,
|
|
29
30
|
".sh": 144,
|
|
@@ -38,41 +39,42 @@
|
|
|
38
39
|
},
|
|
39
40
|
"recent_commits": [
|
|
40
41
|
{
|
|
41
|
-
"hash": "
|
|
42
|
-
"subject": "
|
|
42
|
+
"hash": "d9c870b",
|
|
43
|
+
"subject": "bump: 0.2.53 → 0.2.54",
|
|
43
44
|
"author": "KevRojo",
|
|
44
|
-
"date": "2026-05-
|
|
45
|
+
"date": "2026-05-13"
|
|
45
46
|
},
|
|
46
47
|
{
|
|
47
|
-
"hash": "
|
|
48
|
-
"subject": "
|
|
48
|
+
"hash": "3c7daf7",
|
|
49
|
+
"subject": "feat(voice): add wake word detection, audio utils, and voice module improvements",
|
|
49
50
|
"author": "KevRojo",
|
|
50
|
-
"date": "2026-05-
|
|
51
|
+
"date": "2026-05-13"
|
|
51
52
|
},
|
|
52
53
|
{
|
|
53
|
-
"hash": "
|
|
54
|
-
"subject": "
|
|
54
|
+
"hash": "14c4f42",
|
|
55
|
+
"subject": "bump: 0.2.53",
|
|
55
56
|
"author": "KevRojo",
|
|
56
57
|
"date": "2026-05-12"
|
|
57
58
|
},
|
|
58
59
|
{
|
|
59
|
-
"hash": "
|
|
60
|
-
"subject": "feat: skill
|
|
60
|
+
"hash": "979df03",
|
|
61
|
+
"subject": "feat: skill system overhaul — awesome-remote install, interactive use, descriptions",
|
|
61
62
|
"author": "KevRojo",
|
|
62
63
|
"date": "2026-05-12"
|
|
63
64
|
},
|
|
64
65
|
{
|
|
65
|
-
"hash": "
|
|
66
|
-
"subject": "
|
|
66
|
+
"hash": "39ee671",
|
|
67
|
+
"subject": "chore(release): bump v0.2.52",
|
|
67
68
|
"author": "KevRojo",
|
|
68
69
|
"date": "2026-05-12"
|
|
69
70
|
}
|
|
70
71
|
],
|
|
71
72
|
"recent_changes": [
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
"
|
|
73
|
+
"input.py",
|
|
74
|
+
"pyproject.toml",
|
|
75
|
+
"requirements.txt",
|
|
76
|
+
"voice/audio_utils.py",
|
|
77
|
+
"voice/tts.py"
|
|
76
78
|
]
|
|
77
79
|
},
|
|
78
80
|
"tasks": {
|
|
@@ -90,6 +90,12 @@ Slash commands in REPL:
|
|
|
90
90
|
/voice Record voice input, transcribe, and submit
|
|
91
91
|
/voice status Show available recording and STT backends
|
|
92
92
|
/voice lang <code> Set STT language (e.g. zh, en, ja — default: auto)
|
|
93
|
+
/wake on|off Toggle wake-word (hotword) detection — say "Hey Dulus"
|
|
94
|
+
/wake status Show wake-word listener state
|
|
95
|
+
/wake phrases List recognised wake phrases
|
|
96
|
+
/wake calibrate Measure your mic levels for 5s and suggest a threshold
|
|
97
|
+
/wake test Debug mode — shows RMS + STT output for 10 seconds
|
|
98
|
+
/wake threshold <n> Tune mic sensitivity (0.001-1.0, default 0.020)
|
|
93
99
|
/proactive [dur] Background sentinel polling (e.g. /proactive 5m)
|
|
94
100
|
/proactive off Disable proactive polling
|
|
95
101
|
/cloudsave setup <token> Configure GitHub token for cloud sync
|
|
@@ -193,7 +199,7 @@ import time
|
|
|
193
199
|
import uuid
|
|
194
200
|
from datetime import datetime
|
|
195
201
|
from pathlib import Path
|
|
196
|
-
from typing import Optional, Union
|
|
202
|
+
from typing import Optional, Union, Any, Callable
|
|
197
203
|
|
|
198
204
|
if sys.platform == "win32":
|
|
199
205
|
os.system("") # Enable ANSI escape codes on Windows CMD
|
|
@@ -736,6 +742,7 @@ def cmd_help(_args: str, _state, config) -> bool:
|
|
|
736
742
|
("lite_mode", False, "/lite", "Lite mode (smaller system prompt)"),
|
|
737
743
|
("brave_search_enabled", False, "/brave", "Brave Search API integration"),
|
|
738
744
|
("tts_enabled", False, "/tts", "Automatic Text-to-Speech"),
|
|
745
|
+
("wake_enabled", False, "/wake", "Wake-word hotword detection (Hey Dulus)"),
|
|
739
746
|
("daemon", False, "/daemon", "Allow external triggers when no REPL is open"),
|
|
740
747
|
("afk_mode", False, "/afk", "AFK mode (auto-dismiss, auto-approve tools)"),
|
|
741
748
|
("yolo_mode", False, "/yolo", "YOLO mode (auto-approve ALL actions)"),
|
|
@@ -6426,6 +6433,230 @@ def cmd_voice(args: str, state, config) -> bool:
|
|
|
6426
6433
|
return ("__voice__", text)
|
|
6427
6434
|
|
|
6428
6435
|
|
|
6436
|
+
# Global handle to the running wake-word listener (managed by repl)
|
|
6437
|
+
_wake_listener: Any = None
|
|
6438
|
+
|
|
6439
|
+
|
|
6440
|
+
def cmd_wake(args: str, state, config) -> bool:
|
|
6441
|
+
"""Wake-word (hotword) detection — hands-free voice activation.
|
|
6442
|
+
|
|
6443
|
+
/wake on — start listening for "Hey Dulus" in background
|
|
6444
|
+
/wake off — stop the background listener
|
|
6445
|
+
/wake status — show whether listener is active
|
|
6446
|
+
/wake phrases — list recognised wake phrases
|
|
6447
|
+
/wake threshold <0.01-0.20> — tune mic sensitivity (default 0.035)
|
|
6448
|
+
"""
|
|
6449
|
+
global _wake_listener
|
|
6450
|
+
|
|
6451
|
+
subcmd = args.strip().lower().split()[0] if args.strip() else ""
|
|
6452
|
+
rest = args.strip()[len(subcmd):].strip()
|
|
6453
|
+
|
|
6454
|
+
# ── /wake threshold ──
|
|
6455
|
+
if subcmd == "threshold":
|
|
6456
|
+
if not rest:
|
|
6457
|
+
current = config.get("wake_threshold", 0.020)
|
|
6458
|
+
info(f"Current wake threshold: {current} (higher = less sensitive)")
|
|
6459
|
+
return True
|
|
6460
|
+
try:
|
|
6461
|
+
val = float(rest)
|
|
6462
|
+
if not 0.001 <= val <= 1.0:
|
|
6463
|
+
raise ValueError
|
|
6464
|
+
config["wake_threshold"] = val
|
|
6465
|
+
ok(f"Wake threshold set to {val}")
|
|
6466
|
+
try:
|
|
6467
|
+
from config import save_config
|
|
6468
|
+
save_config(config)
|
|
6469
|
+
except Exception as e:
|
|
6470
|
+
warn(f"Could not save config: {e}")
|
|
6471
|
+
except ValueError:
|
|
6472
|
+
err("Threshold must be a number between 0.001 and 1.0")
|
|
6473
|
+
return True
|
|
6474
|
+
|
|
6475
|
+
# ── /wake phrases ──
|
|
6476
|
+
if subcmd == "phrases":
|
|
6477
|
+
try:
|
|
6478
|
+
from voice.wake_word import WAKE_PHRASES
|
|
6479
|
+
except ImportError:
|
|
6480
|
+
err("voice/wake_word.py not found")
|
|
6481
|
+
return True
|
|
6482
|
+
print(clr(" Recognised wake phrases:", "cyan", "bold"))
|
|
6483
|
+
for p in WAKE_PHRASES:
|
|
6484
|
+
print(f" • {p}")
|
|
6485
|
+
return True
|
|
6486
|
+
|
|
6487
|
+
# ── /wake calibrate ──
|
|
6488
|
+
if subcmd == "calibrate":
|
|
6489
|
+
try:
|
|
6490
|
+
from voice import check_voice_deps
|
|
6491
|
+
except ImportError:
|
|
6492
|
+
err("voice package not available")
|
|
6493
|
+
return True
|
|
6494
|
+
available, reason = check_voice_deps()
|
|
6495
|
+
if not available:
|
|
6496
|
+
err(f"Voice deps missing: {reason}")
|
|
6497
|
+
return True
|
|
6498
|
+
print(clr(" 🎙 Calibrating mic — speak normally for 5 seconds…", "cyan"))
|
|
6499
|
+
print(clr(" Press Ctrl+C when done.\n", "dim"))
|
|
6500
|
+
try:
|
|
6501
|
+
import sounddevice as sd
|
|
6502
|
+
import numpy as np
|
|
6503
|
+
_chunk = int(16000 * 0.3)
|
|
6504
|
+
_bars = " ▁▂▃▄▅▆▇█"
|
|
6505
|
+
_max_rms = 0.0
|
|
6506
|
+
with sd.InputStream(
|
|
6507
|
+
samplerate=16000, channels=1, dtype="int16",
|
|
6508
|
+
blocksize=_chunk,
|
|
6509
|
+
device=config.get("voice_device_index", config.get("_voice_device_index")),
|
|
6510
|
+
) as stream:
|
|
6511
|
+
import time as _time
|
|
6512
|
+
_t0 = _time.monotonic()
|
|
6513
|
+
while _time.monotonic() - _t0 < 5.0:
|
|
6514
|
+
pcm, _ = stream.read(_chunk)
|
|
6515
|
+
arr = np.frombuffer(pcm.tobytes(), dtype=np.int16).astype(np.float32)
|
|
6516
|
+
if arr.size:
|
|
6517
|
+
rms = float(np.sqrt(np.mean(arr ** 2))) / 32768.0
|
|
6518
|
+
_max_rms = max(_max_rms, rms)
|
|
6519
|
+
lvl = min(int(rms * 8 / 0.08), 8)
|
|
6520
|
+
bar = _bars[lvl]
|
|
6521
|
+
print(f"\r RMS: {rms:.4f} {bar} (max {_max_rms:.4f})", end="", flush=True)
|
|
6522
|
+
_time.sleep(0.05)
|
|
6523
|
+
print()
|
|
6524
|
+
print(clr(f"\n Max RMS detected: {_max_rms:.4f}", "cyan", "bold"))
|
|
6525
|
+
rec = _max_rms * 0.7
|
|
6526
|
+
if rec < 0.005:
|
|
6527
|
+
rec = 0.010
|
|
6528
|
+
info(f" Recommended threshold: ~{rec:.3f}")
|
|
6529
|
+
info(f" Current threshold: {config.get('wake_threshold', 0.020)}")
|
|
6530
|
+
info(" Use '/wake threshold <n>' to adjust.")
|
|
6531
|
+
except KeyboardInterrupt:
|
|
6532
|
+
print()
|
|
6533
|
+
except Exception as e:
|
|
6534
|
+
err(f"Calibration failed: {e}")
|
|
6535
|
+
return True
|
|
6536
|
+
|
|
6537
|
+
# ── /wake test ──
|
|
6538
|
+
if subcmd == "test":
|
|
6539
|
+
try:
|
|
6540
|
+
from voice import check_voice_deps
|
|
6541
|
+
from voice.wake_word import WakeWordListener
|
|
6542
|
+
except ImportError as e:
|
|
6543
|
+
err(f"Wake-word module not available: {e}")
|
|
6544
|
+
return True
|
|
6545
|
+
available, reason = check_voice_deps()
|
|
6546
|
+
if not available:
|
|
6547
|
+
err(f"Voice input not available:\n{reason}")
|
|
6548
|
+
return True
|
|
6549
|
+
print(clr(" 🎙 Wake-word TEST mode — debug output ON for 10 seconds", "cyan", "bold"))
|
|
6550
|
+
print(clr(" Say 'Hey Dulus' now. Press Ctrl+C to stop early.\n", "dim"))
|
|
6551
|
+
_test_listener = WakeWordListener(
|
|
6552
|
+
rms_threshold=config.get("wake_threshold", 0.020),
|
|
6553
|
+
device_index=config.get("voice_device_index", config.get("_voice_device_index")),
|
|
6554
|
+
language=_voice_language,
|
|
6555
|
+
debug=True,
|
|
6556
|
+
)
|
|
6557
|
+
_found: list[str] = []
|
|
6558
|
+
|
|
6559
|
+
def _test_wake(phrase: str) -> None:
|
|
6560
|
+
print(clr(f"\n ✅ WAKE DETECTED: '{phrase}'", "green", "bold"))
|
|
6561
|
+
|
|
6562
|
+
def _test_cmd(text: str) -> None:
|
|
6563
|
+
_found.append(text)
|
|
6564
|
+
print(clr(f'\n 🎙️ COMMAND: "{text}"', "green", "bold"))
|
|
6565
|
+
|
|
6566
|
+
_test_listener.start(on_wake=_test_wake, on_command=_test_cmd)
|
|
6567
|
+
try:
|
|
6568
|
+
import time as _time
|
|
6569
|
+
_time.sleep(10)
|
|
6570
|
+
except KeyboardInterrupt:
|
|
6571
|
+
print()
|
|
6572
|
+
finally:
|
|
6573
|
+
_test_listener.stop()
|
|
6574
|
+
if not _found:
|
|
6575
|
+
warn(" No wake word detected in 10s. Try '/wake calibrate' to check your mic levels.")
|
|
6576
|
+
return True
|
|
6577
|
+
|
|
6578
|
+
# ── /wake status ──
|
|
6579
|
+
if subcmd == "status":
|
|
6580
|
+
try:
|
|
6581
|
+
from voice import check_voice_deps
|
|
6582
|
+
except ImportError:
|
|
6583
|
+
err("voice package not available")
|
|
6584
|
+
return True
|
|
6585
|
+
available, reason = check_voice_deps()
|
|
6586
|
+
if not available:
|
|
6587
|
+
err(f"Voice deps missing: {reason}")
|
|
6588
|
+
return True
|
|
6589
|
+
active = _wake_listener is not None and getattr(_wake_listener, "is_running", lambda: False)()
|
|
6590
|
+
state_str = clr("ACTIVE", "green", "bold") if active else clr("OFF", "gray")
|
|
6591
|
+
info(f"Wake-word listener: {state_str}")
|
|
6592
|
+
info(f"Threshold: {config.get('wake_threshold', 0.020)}")
|
|
6593
|
+
if active:
|
|
6594
|
+
info("Say 'Hey Dulus' followed by your command.")
|
|
6595
|
+
else:
|
|
6596
|
+
info("Use '/wake on' to start listening.")
|
|
6597
|
+
return True
|
|
6598
|
+
|
|
6599
|
+
# ── /wake off ──
|
|
6600
|
+
if subcmd in ["off", "false", "disable", "stop"]:
|
|
6601
|
+
config["wake_enabled"] = False
|
|
6602
|
+
try:
|
|
6603
|
+
from config import save_config
|
|
6604
|
+
save_config(config)
|
|
6605
|
+
except Exception:
|
|
6606
|
+
pass
|
|
6607
|
+
if _wake_listener is not None:
|
|
6608
|
+
try:
|
|
6609
|
+
_wake_listener.stop()
|
|
6610
|
+
except Exception as e:
|
|
6611
|
+
warn(f"Error stopping wake listener: {e}")
|
|
6612
|
+
_wake_listener = None
|
|
6613
|
+
ok("Wake-word listener stopped.")
|
|
6614
|
+
else:
|
|
6615
|
+
info("Wake-word listener was not running.")
|
|
6616
|
+
return True
|
|
6617
|
+
|
|
6618
|
+
# ── /wake on ──
|
|
6619
|
+
if subcmd in ["on", "true", "enable", "start"]:
|
|
6620
|
+
if _wake_listener is not None and getattr(_wake_listener, "is_running", lambda: False)():
|
|
6621
|
+
info("Wake-word listener is already active.")
|
|
6622
|
+
return True
|
|
6623
|
+
|
|
6624
|
+
try:
|
|
6625
|
+
from voice import check_voice_deps
|
|
6626
|
+
from voice.wake_word import WakeWordListener
|
|
6627
|
+
except ImportError as e:
|
|
6628
|
+
err(f"Wake-word module not available: {e}")
|
|
6629
|
+
return True
|
|
6630
|
+
|
|
6631
|
+
available, reason = check_voice_deps()
|
|
6632
|
+
if not available:
|
|
6633
|
+
err(f"Voice input not available:\n{reason}")
|
|
6634
|
+
return True
|
|
6635
|
+
|
|
6636
|
+
# The actual on_wake / on_command callbacks are injected by repl()
|
|
6637
|
+
# via the _wake_listener global handle. Here we just create the
|
|
6638
|
+
# object; repl() wires the queue when it sees the handle change.
|
|
6639
|
+
_wake_listener = WakeWordListener(
|
|
6640
|
+
rms_threshold=config.get("wake_threshold", 0.035),
|
|
6641
|
+
device_index=config.get("voice_device_index", config.get("_voice_device_index")),
|
|
6642
|
+
language=_voice_language,
|
|
6643
|
+
)
|
|
6644
|
+
config["wake_enabled"] = True
|
|
6645
|
+
try:
|
|
6646
|
+
from config import save_config
|
|
6647
|
+
save_config(config)
|
|
6648
|
+
except Exception:
|
|
6649
|
+
pass
|
|
6650
|
+
ok("Wake-word listener starting… Say 'Hey Dulus' to activate.")
|
|
6651
|
+
return True
|
|
6652
|
+
|
|
6653
|
+
# ── Toggle ──
|
|
6654
|
+
if _wake_listener is not None and getattr(_wake_listener, "is_running", lambda: False)():
|
|
6655
|
+
return cmd_wake("off", state, config)
|
|
6656
|
+
else:
|
|
6657
|
+
return cmd_wake("on", state, config)
|
|
6658
|
+
|
|
6659
|
+
|
|
6429
6660
|
def cmd_image(args: str, state, config) -> Union[bool, tuple]:
|
|
6430
6661
|
"""Grab image from clipboard and send to vision model with optional prompt."""
|
|
6431
6662
|
import sys as _sys
|
|
@@ -7539,6 +7770,7 @@ COMMANDS = {
|
|
|
7539
7770
|
"lite": cmd_lite,
|
|
7540
7771
|
"cloudsave": cmd_cloudsave,
|
|
7541
7772
|
"voice": cmd_voice,
|
|
7773
|
+
"wake": cmd_wake,
|
|
7542
7774
|
"git": cmd_git,
|
|
7543
7775
|
"webchat": cmd_webchat,
|
|
7544
7776
|
"sandbox": cmd_sandbox,
|
|
@@ -7650,6 +7882,7 @@ _CMD_META: dict[str, tuple[str, list[str]]] = {
|
|
|
7650
7882
|
"cloudsave": ("Cloud-sync sessions to GitHub Gist", ["setup", "auto", "list", "load", "push"]),
|
|
7651
7883
|
"tts": ("Toggle automatic TTS + lang/provider/auto", ["lang", "provider", "voice", "auto"]),
|
|
7652
7884
|
"voice": ("Voice input (record → STT)", ["lang", "status", "device"]),
|
|
7885
|
+
"wake": ("Wake-word hotword detection", ["on", "off", "status", "phrases", "calibrate", "test", "threshold"]),
|
|
7653
7886
|
"image": ("Send clipboard image to model", []),
|
|
7654
7887
|
"img": ("Send clipboard image (alias)", []),
|
|
7655
7888
|
"batch": ("Manage Kimi Batch tasks", ["status", "list", "fetch"]),
|
|
@@ -7781,6 +8014,10 @@ def repl(config: dict, initial_prompt: str = None):
|
|
|
7781
8014
|
verbose = config.get("verbose", False)
|
|
7782
8015
|
config["_tg_send_callback"] = _tg_send
|
|
7783
8016
|
|
|
8017
|
+
# ── Wake-word queue ──
|
|
8018
|
+
import queue as _queue
|
|
8019
|
+
_wake_queue: "_queue.Queue[str]" = _queue.Queue()
|
|
8020
|
+
|
|
7784
8021
|
def _render_toolbar() -> str:
|
|
7785
8022
|
"""Return ANSI toolbar string for prompt_toolkit bottom bar.
|
|
7786
8023
|
|
|
@@ -8876,6 +9113,88 @@ def repl(config: dict, initial_prompt: str = None):
|
|
|
8876
9113
|
"Please review the results and report back to the user.")
|
|
8877
9114
|
except Exception:
|
|
8878
9115
|
pass
|
|
9116
|
+
|
|
9117
|
+
# ── Wake-word listener lifecycle ──
|
|
9118
|
+
global _wake_listener
|
|
9119
|
+
|
|
9120
|
+
# [Autostart] If enabled in config but listener not created yet
|
|
9121
|
+
if _wake_listener is None and config.get("wake_enabled"):
|
|
9122
|
+
try:
|
|
9123
|
+
from voice.wake_word import WakeWordListener
|
|
9124
|
+
_wake_listener = WakeWordListener(
|
|
9125
|
+
rms_threshold=config.get("wake_threshold", 0.035),
|
|
9126
|
+
device_index=config.get("voice_device_index", config.get("_voice_device_index")),
|
|
9127
|
+
language=_voice_language,
|
|
9128
|
+
)
|
|
9129
|
+
# Pre-load Whisper in background so detection is fast
|
|
9130
|
+
def _preload():
|
|
9131
|
+
try:
|
|
9132
|
+
from voice.stt import _get_faster_whisper_model
|
|
9133
|
+
_get_faster_whisper_model()
|
|
9134
|
+
except Exception:
|
|
9135
|
+
pass
|
|
9136
|
+
threading.Thread(target=_preload, daemon=True).start()
|
|
9137
|
+
except ImportError:
|
|
9138
|
+
pass
|
|
9139
|
+
|
|
9140
|
+
if _wake_listener is not None and not _wake_listener.is_running():
|
|
9141
|
+
def _on_wake(phrase: str) -> None:
|
|
9142
|
+
# Use safe_print_notification to avoid ghosting/re-printing in sticky mode
|
|
9143
|
+
import input as _dulus_input
|
|
9144
|
+
_dulus_input.safe_print_notification(clr(f"\x1b[32m ✅ WAKE DETECTED: '{phrase}'\x1b[0m", "bold"))
|
|
9145
|
+
_dulus_input.set_toolbar_status(clr("Waking up...", "cyan"))
|
|
9146
|
+
|
|
9147
|
+
# Immediate audible feedback — universal beep
|
|
9148
|
+
try:
|
|
9149
|
+
from voice import beep
|
|
9150
|
+
beep(880, 150)
|
|
9151
|
+
except Exception:
|
|
9152
|
+
pass
|
|
9153
|
+
# TTS feedback
|
|
9154
|
+
# NOTE: say() is blocking, which correctly delays the command recording
|
|
9155
|
+
# in WakeWordListener until after the response is finished.
|
|
9156
|
+
try:
|
|
9157
|
+
from voice import say
|
|
9158
|
+
_resp = config.get("wake_response", "¿Dime, papi?")
|
|
9159
|
+
say(_resp, provider=config.get("tts_provider", "auto"))
|
|
9160
|
+
except Exception:
|
|
9161
|
+
pass
|
|
9162
|
+
|
|
9163
|
+
def _on_command(text: str) -> None:
|
|
9164
|
+
# Filter common Whisper hallucinations on silence/noise
|
|
9165
|
+
# NOTE: We allow "gracias" as it's a valid thing a user might say.
|
|
9166
|
+
_HALLUC = {
|
|
9167
|
+
"thank you.", "thank you", "thanks for watching.",
|
|
9168
|
+
"thanks for watching!", "thanks.", ".", "you",
|
|
9169
|
+
"subtitles by the amara.org community",
|
|
9170
|
+
}
|
|
9171
|
+
_norm = text.strip().lower()
|
|
9172
|
+
if not _norm or _norm in _HALLUC:
|
|
9173
|
+
# Ignore hallucinations silently
|
|
9174
|
+
import input as _dulus_input
|
|
9175
|
+
_dulus_input.set_toolbar_status("")
|
|
9176
|
+
return
|
|
9177
|
+
|
|
9178
|
+
# Always put in queue so the main loop can pick it up
|
|
9179
|
+
_wake_queue.put(text)
|
|
9180
|
+
|
|
9181
|
+
# Signal the active prompt to exit (unblocks dulus_input.read_line_split)
|
|
9182
|
+
import input as _dulus_input
|
|
9183
|
+
_dulus_input.request_exit()
|
|
9184
|
+
|
|
9185
|
+
_dulus_input.set_toolbar_status("") # Clear toolbar on success
|
|
9186
|
+
_dulus_input.safe_print_notification(clr(f"\n 🎙️ COMMAND: \"{text}\"", "cyan", "bold"))
|
|
9187
|
+
|
|
9188
|
+
_wake_listener.start(on_wake=_on_wake, on_command=_on_command)
|
|
9189
|
+
|
|
9190
|
+
# ── Check for wake-word command before blocking on keyboard ──
|
|
9191
|
+
user_input = ""
|
|
9192
|
+
_wake_cmd: str | None = None
|
|
9193
|
+
try:
|
|
9194
|
+
_wake_cmd = _wake_queue.get_nowait()
|
|
9195
|
+
except _queue.Empty:
|
|
9196
|
+
_wake_cmd = None
|
|
9197
|
+
|
|
8879
9198
|
try:
|
|
8880
9199
|
cwd_short = Path.cwd().name
|
|
8881
9200
|
# Live context-usage indicator: "[73%]" — green<60, yellow<85, red otherwise.
|
|
@@ -8900,7 +9219,12 @@ def repl(config: dict, initial_prompt: str = None):
|
|
|
8900
9219
|
prompt = _rl_safe(clr(f"\n[{cwd_short}] ", "dim") + ctx_tag + clr("» ", "cyan", "bold"))
|
|
8901
9220
|
if in_batch_mode:
|
|
8902
9221
|
prompt = _rl_safe(clr(f" batch[{len(batch_buffer)}] » ", "yellow", "bold"))
|
|
8903
|
-
|
|
9222
|
+
|
|
9223
|
+
if _wake_cmd is not None:
|
|
9224
|
+
user_input = _wake_cmd
|
|
9225
|
+
import input as _dulus_input
|
|
9226
|
+
_dulus_input.safe_print_notification(clr(f"\n 🦅 [Wake] » {user_input}\n", "green", "bold"))
|
|
9227
|
+
elif config.pop("_auto_voice_next", False) and not in_batch_mode:
|
|
8904
9228
|
print(clr(" 🎙 [auto-voice] Listening… (Ctrl+C to type instead)", "cyan"))
|
|
8905
9229
|
try:
|
|
8906
9230
|
from voice import voice_input as _av_voice_input
|
|
@@ -8935,6 +9259,13 @@ def repl(config: dict, initial_prompt: str = None):
|
|
|
8935
9259
|
user_input = _read_input(prompt)
|
|
8936
9260
|
except (EOFError, KeyboardInterrupt):
|
|
8937
9261
|
print()
|
|
9262
|
+
# ── Stop wake-word listener on exit ──
|
|
9263
|
+
try:
|
|
9264
|
+
if _wake_listener is not None:
|
|
9265
|
+
_wake_listener.stop()
|
|
9266
|
+
globals()["_wake_listener"] = None
|
|
9267
|
+
except Exception:
|
|
9268
|
+
pass
|
|
8938
9269
|
# ── Sleep Trigger: Ask to consolidate before final exit ─────────
|
|
8939
9270
|
try:
|
|
8940
9271
|
# Only ask if there's actually a session worth saving
|