dulus 3.7.0__tar.gz → 3.8.0__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.
Files changed (333) hide show
  1. {dulus-3.7.0/dulus.egg-info → dulus-3.8.0}/PKG-INFO +3 -2
  2. {dulus-3.7.0 → dulus-3.8.0}/README.md +2 -1
  3. {dulus-3.7.0 → dulus-3.8.0}/analytics.py +164 -164
  4. {dulus-3.7.0 → dulus-3.8.0}/common.py +21 -0
  5. {dulus-3.7.0 → dulus-3.8.0}/docs/news.md +2 -0
  6. {dulus-3.7.0 → dulus-3.8.0/dulus.egg-info}/PKG-INFO +3 -2
  7. {dulus-3.7.0 → dulus-3.8.0}/dulus.egg-info/SOURCES.txt +2 -2
  8. {dulus-3.7.0 → dulus-3.8.0}/dulus.py +6 -6
  9. {dulus-3.7.0 → dulus-3.8.0}/dulus_gui_web.py +825 -825
  10. dulus-3.8.0/dulus_tools/tree_ls.py +156 -0
  11. {dulus-3.7.0 → dulus-3.8.0}/gui/dulus_orchestrator/App.tsx +196 -196
  12. {dulus-3.7.0 → dulus-3.8.0}/gui/dulus_orchestrator/THIRD_PARTY_NOTICES.md +10 -10
  13. {dulus-3.7.0 → dulus-3.8.0}/gui/dulus_orchestrator/dist/index.html +188 -188
  14. {dulus-3.7.0 → dulus-3.8.0}/gui/dulus_orchestrator/dist/standalone.html +188 -188
  15. {dulus-3.7.0 → dulus-3.8.0}/gui/dulus_orchestrator/index.html +120 -120
  16. {dulus-3.7.0 → dulus-3.8.0}/gui/dulus_orchestrator/index.tsx +16 -16
  17. {dulus-3.7.0 → dulus-3.8.0}/gui/dulus_orchestrator/metadata.json +6 -6
  18. {dulus-3.7.0 → dulus-3.8.0}/gui/dulus_orchestrator/package-lock.json +3143 -3143
  19. {dulus-3.7.0 → dulus-3.8.0}/gui/dulus_orchestrator/package.json +30 -30
  20. {dulus-3.7.0 → dulus-3.8.0}/gui/dulus_orchestrator/types.ts +169 -169
  21. {dulus-3.7.0 → dulus-3.8.0}/gui/dulus_orchestrator/utils.ts +74 -74
  22. {dulus-3.7.0 → dulus-3.8.0}/gui/dulus_orchestrator/vertex-ai-proxy-interceptor.js +154 -154
  23. {dulus-3.7.0 → dulus-3.8.0}/gui/dulus_orchestrator/vite.config.ts +44 -44
  24. {dulus-3.7.0 → dulus-3.8.0}/plugin/store.py +2 -1
  25. {dulus-3.7.0 → dulus-3.8.0}/pyproject.toml +1 -1
  26. dulus-3.7.0/tests/test_fs_compass.py → dulus-3.8.0/tests/test_tree_ls.py +48 -40
  27. {dulus-3.7.0 → dulus-3.8.0}/tools.py +3 -3
  28. {dulus-3.7.0 → dulus-3.8.0}/voice/stt.py +80 -5
  29. {dulus-3.7.0 → dulus-3.8.0}/voice/tts.py +101 -11
  30. {dulus-3.7.0 → dulus-3.8.0}/welcome.py +1 -1
  31. dulus-3.7.0/dulus_tools/fs_compass.py +0 -472
  32. {dulus-3.7.0 → dulus-3.8.0}/LICENSE +0 -0
  33. {dulus-3.7.0 → dulus-3.8.0}/MANIFEST.in +0 -0
  34. {dulus-3.7.0 → dulus-3.8.0}/agent.py +0 -0
  35. {dulus-3.7.0 → dulus-3.8.0}/backend/__init__.py +0 -0
  36. {dulus-3.7.0 → dulus-3.8.0}/backend/agents_bridge.py +0 -0
  37. {dulus-3.7.0 → dulus-3.8.0}/backend/compressor.py +0 -0
  38. {dulus-3.7.0 → dulus-3.8.0}/backend/context.py +0 -0
  39. {dulus-3.7.0 → dulus-3.8.0}/backend/githook.py +0 -0
  40. {dulus-3.7.0 → dulus-3.8.0}/backend/marketplace.py +0 -0
  41. {dulus-3.7.0 → dulus-3.8.0}/backend/mempalace_bridge.py +0 -0
  42. {dulus-3.7.0 → dulus-3.8.0}/backend/personas.py +0 -0
  43. {dulus-3.7.0 → dulus-3.8.0}/backend/plugins.py +0 -0
  44. {dulus-3.7.0 → dulus-3.8.0}/backend/server.py +0 -0
  45. {dulus-3.7.0 → dulus-3.8.0}/backend/tasks.py +0 -0
  46. {dulus-3.7.0 → dulus-3.8.0}/batch_api.py +0 -0
  47. {dulus-3.7.0 → dulus-3.8.0}/checkpoint/__init__.py +0 -0
  48. {dulus-3.7.0 → dulus-3.8.0}/checkpoint/hooks.py +0 -0
  49. {dulus-3.7.0 → dulus-3.8.0}/checkpoint/store.py +0 -0
  50. {dulus-3.7.0 → dulus-3.8.0}/checkpoint/types.py +0 -0
  51. {dulus-3.7.0 → dulus-3.8.0}/claude_code_watcher.py +0 -0
  52. {dulus-3.7.0 → dulus-3.8.0}/clipboard_utils.py +0 -0
  53. {dulus-3.7.0 → dulus-3.8.0}/cloudsave.py +0 -0
  54. {dulus-3.7.0 → dulus-3.8.0}/compaction.py +0 -0
  55. {dulus-3.7.0 → dulus-3.8.0}/config.py +0 -0
  56. {dulus-3.7.0 → dulus-3.8.0}/context.py +0 -0
  57. {dulus-3.7.0 → dulus-3.8.0}/data/__init__.py +0 -0
  58. {dulus-3.7.0 → dulus-3.8.0}/data/active_persona.json +0 -0
  59. {dulus-3.7.0 → dulus-3.8.0}/data/marketplace.json +0 -0
  60. {dulus-3.7.0 → dulus-3.8.0}/data/personas.json +0 -0
  61. {dulus-3.7.0 → dulus-3.8.0}/data/plugins/__init__.py +0 -0
  62. {dulus-3.7.0 → dulus-3.8.0}/data/plugins/composio/__init__.py +0 -0
  63. {dulus-3.7.0 → dulus-3.8.0}/data/plugins/composio/composio_plugin/__init__.py +0 -0
  64. {dulus-3.7.0 → dulus-3.8.0}/data/plugins/composio/composio_plugin/session_manager.py +0 -0
  65. {dulus-3.7.0 → dulus-3.8.0}/data/plugins/composio/composio_plugin/tool_generator.py +0 -0
  66. {dulus-3.7.0 → dulus-3.8.0}/data/plugins/composio/plugin.json +0 -0
  67. {dulus-3.7.0 → dulus-3.8.0}/data/plugins/composio/plugin_tool.py +0 -0
  68. {dulus-3.7.0 → dulus-3.8.0}/data/tasks.json +0 -0
  69. {dulus-3.7.0 → dulus-3.8.0}/docs/API.md +0 -0
  70. {dulus-3.7.0 → dulus-3.8.0}/docs/BRAND_GUIDE.md +0 -0
  71. {dulus-3.7.0 → dulus-3.8.0}/docs/CHANGELOG.md +0 -0
  72. {dulus-3.7.0 → dulus-3.8.0}/docs/CONTRIBUTING.md +0 -0
  73. {dulus-3.7.0 → dulus-3.8.0}/docs/DEPLOYMENT.md +0 -0
  74. {dulus-3.7.0 → dulus-3.8.0}/docs/Dulus_AI_Whitepaper_(v2.0).pdf +0 -0
  75. {dulus-3.7.0 → dulus-3.8.0}/docs/FAQ.md +0 -0
  76. {dulus-3.7.0 → dulus-3.8.0}/docs/GETTING_STARTED.md +0 -0
  77. {dulus-3.7.0 → dulus-3.8.0}/docs/INVESTOR_BRIEF.md +0 -0
  78. {dulus-3.7.0 → dulus-3.8.0}/docs/README.md +0 -0
  79. {dulus-3.7.0 → dulus-3.8.0}/docs/README_AR.md +0 -0
  80. {dulus-3.7.0 → dulus-3.8.0}/docs/README_EN.md +0 -0
  81. {dulus-3.7.0 → dulus-3.8.0}/docs/README_ES.md +0 -0
  82. {dulus-3.7.0 → dulus-3.8.0}/docs/README_FR.md +0 -0
  83. {dulus-3.7.0 → dulus-3.8.0}/docs/README_JA.md +0 -0
  84. {dulus-3.7.0 → dulus-3.8.0}/docs/README_KO.md +0 -0
  85. {dulus-3.7.0 → dulus-3.8.0}/docs/README_PT.md +0 -0
  86. {dulus-3.7.0 → dulus-3.8.0}/docs/README_RU.md +0 -0
  87. {dulus-3.7.0 → dulus-3.8.0}/docs/README_ZH.md +0 -0
  88. {dulus-3.7.0 → dulus-3.8.0}/docs/SECURITY.md +0 -0
  89. {dulus-3.7.0 → dulus-3.8.0}/docs/__init__.py +0 -0
  90. {dulus-3.7.0 → dulus-3.8.0}/docs/api.html +0 -0
  91. {dulus-3.7.0 → dulus-3.8.0}/docs/architecture.md +0 -0
  92. {dulus-3.7.0 → dulus-3.8.0}/docs/azure-speech-template.json +0 -0
  93. {dulus-3.7.0 → dulus-3.8.0}/docs/dashboard/index.html +0 -0
  94. {dulus-3.7.0 → dulus-3.8.0}/docs/divider.svg +0 -0
  95. {dulus-3.7.0 → dulus-3.8.0}/docs/dulus-bird.png +0 -0
  96. {dulus-3.7.0 → dulus-3.8.0}/docs/generate.py +0 -0
  97. {dulus-3.7.0 → dulus-3.8.0}/docs/hero.svg +0 -0
  98. {dulus-3.7.0 → dulus-3.8.0}/docs/index.html +0 -0
  99. {dulus-3.7.0 → dulus-3.8.0}/docs/nvidia-models.svg +0 -0
  100. {dulus-3.7.0 → dulus-3.8.0}/docs/particle-playground.html +0 -0
  101. {dulus-3.7.0 → dulus-3.8.0}/docs/personas/index.html +0 -0
  102. {dulus-3.7.0 → dulus-3.8.0}/docs/poetry-banner.png +0 -0
  103. {dulus-3.7.0 → dulus-3.8.0}/docs/preview.html +0 -0
  104. {dulus-3.7.0 → dulus-3.8.0}/docs/sec-agents.svg +0 -0
  105. {dulus-3.7.0 → dulus-3.8.0}/docs/sec-brainstorm.svg +0 -0
  106. {dulus-3.7.0 → dulus-3.8.0}/docs/sec-bridges.svg +0 -0
  107. {dulus-3.7.0 → dulus-3.8.0}/docs/sec-features.svg +0 -0
  108. {dulus-3.7.0 → dulus-3.8.0}/docs/sec-freetier.svg +0 -0
  109. {dulus-3.7.0 → dulus-3.8.0}/docs/sec-memory.svg +0 -0
  110. {dulus-3.7.0 → dulus-3.8.0}/docs/sec-models.svg +0 -0
  111. {dulus-3.7.0 → dulus-3.8.0}/docs/sec-perms.svg +0 -0
  112. {dulus-3.7.0 → dulus-3.8.0}/docs/sec-plugins.svg +0 -0
  113. {dulus-3.7.0 → dulus-3.8.0}/docs/sec-quickstart.svg +0 -0
  114. {dulus-3.7.0 → dulus-3.8.0}/docs/sec-ssj.svg +0 -0
  115. {dulus-3.7.0 → dulus-3.8.0}/docs/spinners.svg +0 -0
  116. {dulus-3.7.0 → dulus-3.8.0}/docs/split-pane.svg +0 -0
  117. {dulus-3.7.0 → dulus-3.8.0}/docs/terminal-boot.svg +0 -0
  118. {dulus-3.7.0 → dulus-3.8.0}/dulus.egg-info/dependency_links.txt +0 -0
  119. {dulus-3.7.0 → dulus-3.8.0}/dulus.egg-info/entry_points.txt +0 -0
  120. {dulus-3.7.0 → dulus-3.8.0}/dulus.egg-info/requires.txt +0 -0
  121. {dulus-3.7.0 → dulus-3.8.0}/dulus.egg-info/top_level.txt +0 -0
  122. {dulus-3.7.0 → dulus-3.8.0}/dulus_gui.py +0 -0
  123. {dulus-3.7.0 → dulus-3.8.0}/dulus_mcp/__init__.py +0 -0
  124. {dulus-3.7.0 → dulus-3.8.0}/dulus_mcp/client.py +0 -0
  125. {dulus-3.7.0 → dulus-3.8.0}/dulus_mcp/config.py +0 -0
  126. {dulus-3.7.0 → dulus-3.8.0}/dulus_mcp/tools.py +0 -0
  127. {dulus-3.7.0 → dulus-3.8.0}/dulus_mcp/types.py +0 -0
  128. {dulus-3.7.0 → dulus-3.8.0}/dulus_tools/__init__.py +0 -0
  129. {dulus-3.7.0 → dulus-3.8.0}/dulus_tools/add_dir_manager.py +0 -0
  130. {dulus-3.7.0 → dulus-3.8.0}/dulus_tools/afk_mode.py +0 -0
  131. {dulus-3.7.0 → dulus-3.8.0}/dulus_tools/approval_runtime.py +0 -0
  132. {dulus-3.7.0 → dulus-3.8.0}/dulus_tools/background_tasks.py +0 -0
  133. {dulus-3.7.0 → dulus-3.8.0}/dulus_tools/clipboard_utils.py +0 -0
  134. {dulus-3.7.0 → dulus-3.8.0}/dulus_tools/diff_visualizer.py +0 -0
  135. {dulus-3.7.0 → dulus-3.8.0}/dulus_tools/export_import.py +0 -0
  136. {dulus-3.7.0 → dulus-3.8.0}/dulus_tools/hook_engine.py +0 -0
  137. {dulus-3.7.0 → dulus-3.8.0}/dulus_tools/notification_manager.py +0 -0
  138. {dulus-3.7.0 → dulus-3.8.0}/dulus_tools/session_fork.py +0 -0
  139. {dulus-3.7.0 → dulus-3.8.0}/dulus_tools/shell_mode.py +0 -0
  140. {dulus-3.7.0 → dulus-3.8.0}/dulus_tools/todo_visualizer.py +0 -0
  141. {dulus-3.7.0 → dulus-3.8.0}/dulus_tools/wire_events.py +0 -0
  142. {dulus-3.7.0 → dulus-3.8.0}/dulus_tools/yolo_mode.py +0 -0
  143. {dulus-3.7.0 → dulus-3.8.0}/governance.py +0 -0
  144. {dulus-3.7.0 → dulus-3.8.0}/gui/__init__.py +0 -0
  145. {dulus-3.7.0 → dulus-3.8.0}/gui/agent_bridge.py +0 -0
  146. {dulus-3.7.0 → dulus-3.8.0}/gui/chat_widget.py +0 -0
  147. {dulus-3.7.0 → dulus-3.8.0}/gui/main_window.py +0 -0
  148. {dulus-3.7.0 → dulus-3.8.0}/gui/personas.py +0 -0
  149. {dulus-3.7.0 → dulus-3.8.0}/gui/sandbox_server.py +0 -0
  150. {dulus-3.7.0 → dulus-3.8.0}/gui/session_utils.py +0 -0
  151. {dulus-3.7.0 → dulus-3.8.0}/gui/settings_dialog.py +0 -0
  152. {dulus-3.7.0 → dulus-3.8.0}/gui/sidebar.py +0 -0
  153. {dulus-3.7.0 → dulus-3.8.0}/gui/tasks_view.py +0 -0
  154. {dulus-3.7.0 → dulus-3.8.0}/gui/themes.py +0 -0
  155. {dulus-3.7.0 → dulus-3.8.0}/gui/tool_panel.py +0 -0
  156. {dulus-3.7.0 → dulus-3.8.0}/input.py +0 -0
  157. {dulus-3.7.0 → dulus-3.8.0}/license_manager.py +0 -0
  158. {dulus-3.7.0 → dulus-3.8.0}/memory/__init__.py +0 -0
  159. {dulus-3.7.0 → dulus-3.8.0}/memory/audit.py +0 -0
  160. {dulus-3.7.0 → dulus-3.8.0}/memory/consolidator.py +0 -0
  161. {dulus-3.7.0 → dulus-3.8.0}/memory/context.py +0 -0
  162. {dulus-3.7.0 → dulus-3.8.0}/memory/offload.py +0 -0
  163. {dulus-3.7.0 → dulus-3.8.0}/memory/palace.py +0 -0
  164. {dulus-3.7.0 → dulus-3.8.0}/memory/scan.py +0 -0
  165. {dulus-3.7.0 → dulus-3.8.0}/memory/sessions.py +0 -0
  166. {dulus-3.7.0 → dulus-3.8.0}/memory/store.py +0 -0
  167. {dulus-3.7.0 → dulus-3.8.0}/memory/tools.py +0 -0
  168. {dulus-3.7.0 → dulus-3.8.0}/memory/types.py +0 -0
  169. {dulus-3.7.0 → dulus-3.8.0}/memory/vector_search.py +0 -0
  170. {dulus-3.7.0 → dulus-3.8.0}/multi_agent/__init__.py +0 -0
  171. {dulus-3.7.0 → dulus-3.8.0}/multi_agent/subagent.py +0 -0
  172. {dulus-3.7.0 → dulus-3.8.0}/multi_agent/tools.py +0 -0
  173. {dulus-3.7.0 → dulus-3.8.0}/offload_helper.py +0 -0
  174. {dulus-3.7.0 → dulus-3.8.0}/personality_config.py +0 -0
  175. {dulus-3.7.0 → dulus-3.8.0}/plugin/__init__.py +0 -0
  176. {dulus-3.7.0 → dulus-3.8.0}/plugin/autoadapter.py +0 -0
  177. {dulus-3.7.0 → dulus-3.8.0}/plugin/loader.py +0 -0
  178. {dulus-3.7.0 → dulus-3.8.0}/plugin/recommend.py +0 -0
  179. {dulus-3.7.0 → dulus-3.8.0}/plugin/types.py +0 -0
  180. {dulus-3.7.0 → dulus-3.8.0}/providers.py +0 -0
  181. {dulus-3.7.0 → dulus-3.8.0}/sandbox/README.md +0 -0
  182. {dulus-3.7.0 → dulus-3.8.0}/sandbox/__init__.py +0 -0
  183. {dulus-3.7.0 → dulus-3.8.0}/sandbox/components.json +0 -0
  184. {dulus-3.7.0 → dulus-3.8.0}/sandbox/dist/assets/index-DE51D6wI.css +0 -0
  185. {dulus-3.7.0 → dulus-3.8.0}/sandbox/dist/assets/index-DMCCNE9Y.js +0 -0
  186. {dulus-3.7.0 → dulus-3.8.0}/sandbox/dist/index.html +0 -0
  187. {dulus-3.7.0 → dulus-3.8.0}/sandbox/dist/wallpaper-default.jpg +0 -0
  188. {dulus-3.7.0 → dulus-3.8.0}/sandbox/dist/wallpapers/default.jpeg +0 -0
  189. {dulus-3.7.0 → dulus-3.8.0}/sandbox/dist/wallpapers/light.jpeg +0 -0
  190. {dulus-3.7.0 → dulus-3.8.0}/sandbox/dist/wallpapers/nature.jpeg +0 -0
  191. {dulus-3.7.0 → dulus-3.8.0}/sandbox/dist/wallpapers/tech.jpeg +0 -0
  192. {dulus-3.7.0 → dulus-3.8.0}/sandbox/eslint.config.js +0 -0
  193. {dulus-3.7.0 → dulus-3.8.0}/sandbox/index.html +0 -0
  194. {dulus-3.7.0 → dulus-3.8.0}/sandbox/info.md +0 -0
  195. {dulus-3.7.0 → dulus-3.8.0}/sandbox/package-lock.json +0 -0
  196. {dulus-3.7.0 → dulus-3.8.0}/sandbox/package.json +0 -0
  197. {dulus-3.7.0 → dulus-3.8.0}/sandbox/postcss.config.js +0 -0
  198. {dulus-3.7.0 → dulus-3.8.0}/sandbox/tailwind.config.js +0 -0
  199. {dulus-3.7.0 → dulus-3.8.0}/sandbox/tsconfig.app.json +0 -0
  200. {dulus-3.7.0 → dulus-3.8.0}/sandbox/tsconfig.json +0 -0
  201. {dulus-3.7.0 → dulus-3.8.0}/sandbox/tsconfig.node.json +0 -0
  202. {dulus-3.7.0 → dulus-3.8.0}/sandbox/vite.config.ts +0 -0
  203. {dulus-3.7.0 → dulus-3.8.0}/sandbox_bootstrap.py +0 -0
  204. {dulus-3.7.0 → dulus-3.8.0}/setup.cfg +0 -0
  205. {dulus-3.7.0 → dulus-3.8.0}/skill/__init__.py +0 -0
  206. {dulus-3.7.0 → dulus-3.8.0}/skill/builtin.py +0 -0
  207. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/LICENSE-obsidian-skills +0 -0
  208. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/README.md +0 -0
  209. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/defuddle/SKILL.md +0 -0
  210. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/json-canvas/SKILL.md +0 -0
  211. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/json-canvas/references/EXAMPLES.md +0 -0
  212. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/obsidian-bases/SKILL.md +0 -0
  213. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/obsidian-bases/references/FUNCTIONS_REFERENCE.md +0 -0
  214. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/obsidian-cli/SKILL.md +0 -0
  215. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/obsidian-markdown/SKILL.md +0 -0
  216. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/obsidian-markdown/references/CALLOUTS.md +0 -0
  217. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/obsidian-markdown/references/EMBEDS.md +0 -0
  218. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/obsidian-markdown/references/PROPERTIES.md +0 -0
  219. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/README.md +0 -0
  220. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/SKILL.md +0 -0
  221. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/references/brokers/addresses.json +0 -0
  222. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/references/brokers/advancedbackgroundchecks.json +0 -0
  223. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/references/brokers/beenverified.json +0 -0
  224. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/references/brokers/clustal.json +0 -0
  225. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/references/brokers/clustrmaps.json +0 -0
  226. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/references/brokers/cyberbackgroundchecks.json +0 -0
  227. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/references/brokers/familytreenow.json +0 -0
  228. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/references/brokers/fastpeoplesearch.json +0 -0
  229. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/references/brokers/intelius.json +0 -0
  230. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/references/brokers/mylife.json +0 -0
  231. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/references/brokers/nuwber.json +0 -0
  232. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/references/brokers/peekyou.json +0 -0
  233. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/references/brokers/peoplefinders.json +0 -0
  234. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/references/brokers/radaris.json +0 -0
  235. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/references/brokers/rehold.json +0 -0
  236. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/references/brokers/searchpeoplefree.json +0 -0
  237. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/references/brokers/socialcatfish.json +0 -0
  238. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/references/brokers/spokeo.json +0 -0
  239. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/references/brokers/thatsthem.json +0 -0
  240. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/references/brokers/truepeoplesearch.json +0 -0
  241. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/references/brokers/usphonebook.json +0 -0
  242. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/references/brokers/whitepages.json +0 -0
  243. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/references/legal/ccpa.md +0 -0
  244. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/references/legal/drop.md +0 -0
  245. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/references/legal/gdpr.md +0 -0
  246. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/references/methods.md +0 -0
  247. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/references/site-playbooks.md +0 -0
  248. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/references/state-machine.md +0 -0
  249. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/scripts/autopilot.py +0 -0
  250. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/scripts/badbool.py +0 -0
  251. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/scripts/brokers.py +0 -0
  252. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/scripts/cdp.py +0 -0
  253. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/scripts/config.py +0 -0
  254. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/scripts/crypto.py +0 -0
  255. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/scripts/dossier.py +0 -0
  256. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/scripts/email_modes.py +0 -0
  257. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/scripts/emailer.py +0 -0
  258. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/scripts/ledger.py +0 -0
  259. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/scripts/legal.py +0 -0
  260. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/scripts/paths.py +0 -0
  261. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/scripts/pdd.py +0 -0
  262. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/scripts/registry.py +0 -0
  263. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/scripts/report.py +0 -0
  264. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/scripts/scan.py +0 -0
  265. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/scripts/storage.py +0 -0
  266. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/scripts/tiers.py +0 -0
  267. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/scripts/vectors.py +0 -0
  268. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/templates/consent/authorization.md +0 -0
  269. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/templates/emails/ccpa-authorized-agent.txt +0 -0
  270. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/templates/emails/ccpa-deletion.txt +0 -0
  271. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/templates/emails/ccpa-indirect-deletion.txt +0 -0
  272. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/templates/emails/gdpr-erasure.txt +0 -0
  273. {dulus-3.7.0 → dulus-3.8.0}/skill/bundled/unbroker/templates/emails/generic-optout.txt +0 -0
  274. {dulus-3.7.0 → dulus-3.8.0}/skill/clawhub.py +0 -0
  275. {dulus-3.7.0 → dulus-3.8.0}/skill/executor.py +0 -0
  276. {dulus-3.7.0 → dulus-3.8.0}/skill/loader.py +0 -0
  277. {dulus-3.7.0 → dulus-3.8.0}/skill/tools.py +0 -0
  278. {dulus-3.7.0 → dulus-3.8.0}/skills.py +0 -0
  279. {dulus-3.7.0 → dulus-3.8.0}/soul.py +0 -0
  280. {dulus-3.7.0 → dulus-3.8.0}/spinner.py +0 -0
  281. {dulus-3.7.0 → dulus-3.8.0}/string_utils.py +0 -0
  282. {dulus-3.7.0 → dulus-3.8.0}/subagent.py +0 -0
  283. {dulus-3.7.0 → dulus-3.8.0}/task/__init__.py +0 -0
  284. {dulus-3.7.0 → dulus-3.8.0}/task/store.py +0 -0
  285. {dulus-3.7.0 → dulus-3.8.0}/task/tools.py +0 -0
  286. {dulus-3.7.0 → dulus-3.8.0}/task/types.py +0 -0
  287. {dulus-3.7.0 → dulus-3.8.0}/tests/test_afk_yolo.py +0 -0
  288. {dulus-3.7.0 → dulus-3.8.0}/tests/test_approval_runtime.py +0 -0
  289. {dulus-3.7.0 → dulus-3.8.0}/tests/test_background_task_tools.py +0 -0
  290. {dulus-3.7.0 → dulus-3.8.0}/tests/test_background_tasks.py +0 -0
  291. {dulus-3.7.0 → dulus-3.8.0}/tests/test_checkpoint.py +0 -0
  292. {dulus-3.7.0 → dulus-3.8.0}/tests/test_clipboard_utils.py +0 -0
  293. {dulus-3.7.0 → dulus-3.8.0}/tests/test_compaction.py +0 -0
  294. {dulus-3.7.0 → dulus-3.8.0}/tests/test_diff_view.py +0 -0
  295. {dulus-3.7.0 → dulus-3.8.0}/tests/test_diff_visualization.py +0 -0
  296. {dulus-3.7.0 → dulus-3.8.0}/tests/test_display_blocks.py +0 -0
  297. {dulus-3.7.0 → dulus-3.8.0}/tests/test_export_import.py +0 -0
  298. {dulus-3.7.0 → dulus-3.8.0}/tests/test_hook_engine.py +0 -0
  299. {dulus-3.7.0 → dulus-3.8.0}/tests/test_injection_fix.py +0 -0
  300. {dulus-3.7.0 → dulus-3.8.0}/tests/test_license.py +0 -0
  301. {dulus-3.7.0 → dulus-3.8.0}/tests/test_mcp.py +0 -0
  302. {dulus-3.7.0 → dulus-3.8.0}/tests/test_memory.py +0 -0
  303. {dulus-3.7.0 → dulus-3.8.0}/tests/test_notification_manager.py +0 -0
  304. {dulus-3.7.0 → dulus-3.8.0}/tests/test_plugin.py +0 -0
  305. {dulus-3.7.0 → dulus-3.8.0}/tests/test_session_fork.py +0 -0
  306. {dulus-3.7.0 → dulus-3.8.0}/tests/test_shell_mode.py +0 -0
  307. {dulus-3.7.0 → dulus-3.8.0}/tests/test_skills.py +0 -0
  308. {dulus-3.7.0 → dulus-3.8.0}/tests/test_steer_input.py +0 -0
  309. {dulus-3.7.0 → dulus-3.8.0}/tests/test_subagent.py +0 -0
  310. {dulus-3.7.0 → dulus-3.8.0}/tests/test_task.py +0 -0
  311. {dulus-3.7.0 → dulus-3.8.0}/tests/test_telegram_buffer.py +0 -0
  312. {dulus-3.7.0 → dulus-3.8.0}/tests/test_think_tool.py +0 -0
  313. {dulus-3.7.0 → dulus-3.8.0}/tests/test_todo_tool.py +0 -0
  314. {dulus-3.7.0 → dulus-3.8.0}/tests/test_todo_visualization.py +0 -0
  315. {dulus-3.7.0 → dulus-3.8.0}/tests/test_tool_registry.py +0 -0
  316. {dulus-3.7.0 → dulus-3.8.0}/tests/test_voice.py +0 -0
  317. {dulus-3.7.0 → dulus-3.8.0}/tests/test_wire_events.py +0 -0
  318. {dulus-3.7.0 → dulus-3.8.0}/tmux_offloader.py +0 -0
  319. {dulus-3.7.0 → dulus-3.8.0}/tmux_tools.py +0 -0
  320. {dulus-3.7.0 → dulus-3.8.0}/tool_registry.py +0 -0
  321. {dulus-3.7.0 → dulus-3.8.0}/ui/__init__.py +0 -0
  322. {dulus-3.7.0 → dulus-3.8.0}/ui/input.py +0 -0
  323. {dulus-3.7.0 → dulus-3.8.0}/ui/render.py +0 -0
  324. {dulus-3.7.0 → dulus-3.8.0}/voice/__init__.py +0 -0
  325. {dulus-3.7.0 → dulus-3.8.0}/voice/audio_utils.py +0 -0
  326. {dulus-3.7.0 → dulus-3.8.0}/voice/keyterms.py +0 -0
  327. {dulus-3.7.0 → dulus-3.8.0}/voice/recorder.py +0 -0
  328. {dulus-3.7.0 → dulus-3.8.0}/voice/wake_word.py +0 -0
  329. {dulus-3.7.0 → dulus-3.8.0}/webbridge/__init__.py +0 -0
  330. {dulus-3.7.0 → dulus-3.8.0}/webbridge/core.py +0 -0
  331. {dulus-3.7.0 → dulus-3.8.0}/webbridge/tools.py +0 -0
  332. {dulus-3.7.0 → dulus-3.8.0}/webchat.py +0 -0
  333. {dulus-3.7.0 → dulus-3.8.0}/webchat_server.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dulus
3
- Version: 3.7.0
3
+ Version: 3.8.0
4
4
  Summary: Use IA as an agent without an API key. $0. Harvest your browser session for Gemini (guest, no login), Claude.ai, Claude Code, Kimi.com, Qwen, DeepSeek — drive them like an API, no per-token billing. Plus LiteLLM (100+ paid providers), local models via Ollama, /lang in 34 languages, Mesa Redonda, voice, OCR, MemPalace, embedded sandbox OS.
5
5
  Author: KevRojo
6
6
  License: GPL-3.0
@@ -305,7 +305,8 @@ Dulus is proudly supported by these startup programs — powering our infrastruc
305
305
  <a href="https://amplitude.com/startups"><img src="https://www.google.com/s2/favicons?domain=amplitude.com&sz=128" height="38" alt="Amplitude" title="Amplitude Startup Scholarship"></a>&nbsp;&nbsp;&nbsp;&nbsp;
306
306
  <a href="https://www.digitalocean.com/hatch"><img src="https://cdn.simpleicons.org/digitalocean" height="38" alt="DigitalOcean" title="DigitalOcean Hatch"></a>&nbsp;&nbsp;&nbsp;&nbsp;
307
307
  <a href="https://www.notion.com/startups"><img src="https://cdn.simpleicons.org/notion" height="38" alt="Notion" title="Notion for Startups"></a>&nbsp;&nbsp;&nbsp;&nbsp;
308
- <a href="https://www.zendesk.com/campaign/startups/"><img src="https://cdn.simpleicons.org/zendesk" height="38" alt="Zendesk" title="Zendesk for Startups"></a>
308
+ <a href="https://www.zendesk.com/campaign/startups/"><img src="https://cdn.simpleicons.org/zendesk" height="38" alt="Zendesk" title="Zendesk for Startups"></a>&nbsp;&nbsp;&nbsp;&nbsp;
309
+ <a href="https://deepgram.com/startups"><img src="https://www.google.com/s2/favicons?domain=deepgram.com&sz=128" height="38" alt="Deepgram" title="Deepgram Startup Program — powering Dulus's voice"></a>
309
310
  </p>
310
311
 
311
312
  <p align="center"><i>Want to support Dulus? → <a href="https://github.com/sponsors/KevRojo">GitHub Sponsors</a> 🦅</i></p>
@@ -239,7 +239,8 @@ Dulus is proudly supported by these startup programs — powering our infrastruc
239
239
  <a href="https://amplitude.com/startups"><img src="https://www.google.com/s2/favicons?domain=amplitude.com&sz=128" height="38" alt="Amplitude" title="Amplitude Startup Scholarship"></a>&nbsp;&nbsp;&nbsp;&nbsp;
240
240
  <a href="https://www.digitalocean.com/hatch"><img src="https://cdn.simpleicons.org/digitalocean" height="38" alt="DigitalOcean" title="DigitalOcean Hatch"></a>&nbsp;&nbsp;&nbsp;&nbsp;
241
241
  <a href="https://www.notion.com/startups"><img src="https://cdn.simpleicons.org/notion" height="38" alt="Notion" title="Notion for Startups"></a>&nbsp;&nbsp;&nbsp;&nbsp;
242
- <a href="https://www.zendesk.com/campaign/startups/"><img src="https://cdn.simpleicons.org/zendesk" height="38" alt="Zendesk" title="Zendesk for Startups"></a>
242
+ <a href="https://www.zendesk.com/campaign/startups/"><img src="https://cdn.simpleicons.org/zendesk" height="38" alt="Zendesk" title="Zendesk for Startups"></a>&nbsp;&nbsp;&nbsp;&nbsp;
243
+ <a href="https://deepgram.com/startups"><img src="https://www.google.com/s2/favicons?domain=deepgram.com&sz=128" height="38" alt="Deepgram" title="Deepgram Startup Program — powering Dulus's voice"></a>
243
244
  </p>
244
245
 
245
246
  <p align="center"><i>Want to support Dulus? → <a href="https://github.com/sponsors/KevRojo">GitHub Sponsors</a> 🦅</i></p>
@@ -1,164 +1,164 @@
1
- """Dulus anonymous usage telemetry (opt-in, privacy-first).
2
-
3
- Dulus asks ONCE on startup whether you want to share anonymous usage
4
- statistics. Nothing is sent until you explicitly say yes.
5
-
6
- What IS collected (when enabled):
7
- - Event names (session_start, message_sent, tool_used, model_selected)
8
- - Dulus version, OS name, Python version
9
- - Provider/model *names* (e.g. "gemini", "claude-sonnet")
10
- - A random anonymous ID (UUID generated locally — not tied to you)
11
-
12
- What is NEVER collected:
13
- - Prompts, responses, or any conversation content
14
- - File paths, file contents, or code
15
- - Usernames, emails, hostnames, IPs (Mixpanel geo is disabled via $ip=0)
16
- - API keys or tokens
17
-
18
- Where it goes: Mixpanel (https://mixpanel.com) — event analytics only.
19
-
20
- Opt out at any time (any of these):
21
- - Answer "n" at the first-run prompt
22
- - /config telemetry=off (inside Dulus)
23
- - DULUS_TELEMETRY=0 (environment variable)
24
-
25
- Implementation notes: zero third-party dependencies — plain urllib POST to
26
- the Mixpanel ingestion API in a fire-and-forget daemon thread. Failures are
27
- silently ignored; telemetry must never slow down or break Dulus.
28
- """
29
- from __future__ import annotations
30
-
31
- import base64
32
- import json
33
- import os
34
- import platform
35
- import sys
36
- import threading
37
- import time
38
- import uuid
39
-
40
- # Mixpanel PROJECT TOKEN for the public Dulus project.
41
- # NOTE: Mixpanel ingestion tokens are write-only and designed to ship in
42
- # client code (every website using Mixpanel exposes theirs). It cannot be
43
- # used to read any data. Override with DULUS_MP_TOKEN.
44
- MP_TOKEN = os.environ.get("DULUS_MP_TOKEN", "966ce8f5ccb32f06788f51be9f8bf8f5")
45
-
46
- _MP_ENDPOINT = "https://api.mixpanel.com/track"
47
-
48
- # Populated by init_telemetry(); None = not initialised / disabled.
49
- _distinct_id: str | None = None
50
- _enabled: bool = False
51
- _dulus_version: str = ""
52
-
53
-
54
- def _env_disabled() -> bool:
55
- return os.environ.get("DULUS_TELEMETRY", "").strip().lower() in ("0", "off", "false", "no")
56
-
57
-
58
- def is_enabled() -> bool:
59
- return _enabled and not _env_disabled() and bool(MP_TOKEN)
60
-
61
-
62
- CONSENT_NOTICE = """
63
- ── Ayuda a mejorar Dulus / Help improve Dulus ─────────────────────────
64
- Dulus can share ANONYMOUS usage statistics to help us understand
65
- which features matter (event counts only — sent to Mixpanel).
66
-
67
- Collected: event names, Dulus version, OS, model/provider names,
68
- a random anonymous ID generated on this machine.
69
- NEVER: prompts, responses, files, paths, keys, emails, IPs.
70
-
71
- Change your mind anytime: /config telemetry=off or DULUS_TELEMETRY=0
72
- ────────────────────────────────────────────────────────────────────────
73
- """
74
-
75
-
76
- def ask_consent(config: dict) -> dict:
77
- """One-time interactive consent prompt. Mutates + returns config.
78
-
79
- Only call when config['telemetry'] is unset and stdin is a TTY.
80
- """
81
- print(CONSENT_NOTICE)
82
- try:
83
- answer = input(" Share anonymous usage stats? [y/N] ").strip().lower()
84
- except (EOFError, KeyboardInterrupt):
85
- answer = ""
86
- config["telemetry"] = answer in ("y", "yes", "s", "si", "sí")
87
- if config["telemetry"] and not config.get("telemetry_id"):
88
- config["telemetry_id"] = uuid.uuid4().hex
89
- state = "enabled — thank you! 🦅" if config["telemetry"] else "disabled."
90
- print(f" Telemetry {state}\n")
91
- return config
92
-
93
-
94
- def init_telemetry(config: dict, version: str = "") -> None:
95
- """Initialise the module from config. Safe to call multiple times."""
96
- global _distinct_id, _enabled, _dulus_version
97
- _dulus_version = version or _dulus_version
98
- _enabled = bool(config.get("telemetry")) and not _env_disabled()
99
- if _enabled:
100
- _distinct_id = config.get("telemetry_id") or uuid.uuid4().hex
101
- config.setdefault("telemetry_id", _distinct_id)
102
-
103
-
104
- def track(event: str, properties: dict | None = None) -> None:
105
- """Fire-and-forget anonymous event. No-op unless telemetry is enabled."""
106
- if not is_enabled() or not _distinct_id:
107
- return
108
-
109
- payload = {
110
- "event": event,
111
- "properties": {
112
- "token": MP_TOKEN,
113
- "distinct_id": _distinct_id,
114
- "time": int(time.time()),
115
- "$ip": 0, # disable Mixpanel geolocation
116
- "dulus_version": _dulus_version,
117
- "os": platform.system(),
118
- "python": f"{sys.version_info.major}.{sys.version_info.minor}",
119
- **(properties or {}),
120
- },
121
- }
122
-
123
- def _send() -> None:
124
- try:
125
- from urllib.request import Request, urlopen
126
- from urllib.parse import urlencode
127
-
128
- data = urlencode(
129
- {"data": base64.b64encode(json.dumps([payload]).encode()).decode()}
130
- ).encode()
131
- req = Request(_MP_ENDPOINT, data=data, method="POST")
132
- urlopen(req, timeout=4).read()
133
- except Exception:
134
- pass # telemetry must never break Dulus
135
-
136
- threading.Thread(target=_send, daemon=True, name="telemetry").start()
137
-
138
-
139
- def track_session_start(config: dict) -> None:
140
- """Convenience: one event per REPL boot (enough for DAU/MAU counts)."""
141
- track("session_start", {
142
- "provider": str(config.get("provider", "")),
143
- "model": str(config.get("model", "")),
144
- })
145
-
146
-
147
- def track_message_sent(model: str = "") -> None:
148
- """One event per user prompt sent to the LLM (no content — count only)."""
149
- track("message_sent", {"model": model})
150
-
151
-
152
- def track_tool_used(tool_name: str) -> None:
153
- """One event per tool invocation (tool NAME only — never inputs)."""
154
- track("tool_used", {"tool": tool_name})
155
-
156
-
157
- def track_command_used(command: str) -> None:
158
- """One event per slash command (command NAME only — never args)."""
159
- track("command_used", {"command": command})
160
-
161
-
162
- def track_model_selected(model: str, provider: str = "") -> None:
163
- """Fired when the user switches models via /model."""
164
- track("model_selected", {"model": model, "provider": provider})
1
+ """Dulus anonymous usage telemetry (opt-in, privacy-first).
2
+
3
+ Dulus asks ONCE on startup whether you want to share anonymous usage
4
+ statistics. Nothing is sent until you explicitly say yes.
5
+
6
+ What IS collected (when enabled):
7
+ - Event names (session_start, message_sent, tool_used, model_selected)
8
+ - Dulus version, OS name, Python version
9
+ - Provider/model *names* (e.g. "gemini", "claude-sonnet")
10
+ - A random anonymous ID (UUID generated locally — not tied to you)
11
+
12
+ What is NEVER collected:
13
+ - Prompts, responses, or any conversation content
14
+ - File paths, file contents, or code
15
+ - Usernames, emails, hostnames, IPs (Mixpanel geo is disabled via $ip=0)
16
+ - API keys or tokens
17
+
18
+ Where it goes: Mixpanel (https://mixpanel.com) — event analytics only.
19
+
20
+ Opt out at any time (any of these):
21
+ - Answer "n" at the first-run prompt
22
+ - /config telemetry=off (inside Dulus)
23
+ - DULUS_TELEMETRY=0 (environment variable)
24
+
25
+ Implementation notes: zero third-party dependencies — plain urllib POST to
26
+ the Mixpanel ingestion API in a fire-and-forget daemon thread. Failures are
27
+ silently ignored; telemetry must never slow down or break Dulus.
28
+ """
29
+ from __future__ import annotations
30
+
31
+ import base64
32
+ import json
33
+ import os
34
+ import platform
35
+ import sys
36
+ import threading
37
+ import time
38
+ import uuid
39
+
40
+ # Mixpanel PROJECT TOKEN for the public Dulus project.
41
+ # NOTE: Mixpanel ingestion tokens are write-only and designed to ship in
42
+ # client code (every website using Mixpanel exposes theirs). It cannot be
43
+ # used to read any data. Override with DULUS_MP_TOKEN.
44
+ MP_TOKEN = os.environ.get("DULUS_MP_TOKEN", "966ce8f5ccb32f06788f51be9f8bf8f5")
45
+
46
+ _MP_ENDPOINT = "https://api.mixpanel.com/track"
47
+
48
+ # Populated by init_telemetry(); None = not initialised / disabled.
49
+ _distinct_id: str | None = None
50
+ _enabled: bool = False
51
+ _dulus_version: str = ""
52
+
53
+
54
+ def _env_disabled() -> bool:
55
+ return os.environ.get("DULUS_TELEMETRY", "").strip().lower() in ("0", "off", "false", "no")
56
+
57
+
58
+ def is_enabled() -> bool:
59
+ return _enabled and not _env_disabled() and bool(MP_TOKEN)
60
+
61
+
62
+ CONSENT_NOTICE = """
63
+ ── Ayuda a mejorar Dulus / Help improve Dulus ─────────────────────────
64
+ Dulus can share ANONYMOUS usage statistics to help us understand
65
+ which features matter (event counts only — sent to Mixpanel).
66
+
67
+ Collected: event names, Dulus version, OS, model/provider names,
68
+ a random anonymous ID generated on this machine.
69
+ NEVER: prompts, responses, files, paths, keys, emails, IPs.
70
+
71
+ Change your mind anytime: /config telemetry=off or DULUS_TELEMETRY=0
72
+ ────────────────────────────────────────────────────────────────────────
73
+ """
74
+
75
+
76
+ def ask_consent(config: dict) -> dict:
77
+ """One-time interactive consent prompt. Mutates + returns config.
78
+
79
+ Only call when config['telemetry'] is unset and stdin is a TTY.
80
+ """
81
+ print(CONSENT_NOTICE)
82
+ try:
83
+ answer = input(" Share anonymous usage stats? [y/N] ").strip().lower()
84
+ except (EOFError, KeyboardInterrupt):
85
+ answer = ""
86
+ config["telemetry"] = answer in ("y", "yes", "s", "si", "sí")
87
+ if config["telemetry"] and not config.get("telemetry_id"):
88
+ config["telemetry_id"] = uuid.uuid4().hex
89
+ state = "enabled — thank you! 🦅" if config["telemetry"] else "disabled."
90
+ print(f" Telemetry {state}\n")
91
+ return config
92
+
93
+
94
+ def init_telemetry(config: dict, version: str = "") -> None:
95
+ """Initialise the module from config. Safe to call multiple times."""
96
+ global _distinct_id, _enabled, _dulus_version
97
+ _dulus_version = version or _dulus_version
98
+ _enabled = bool(config.get("telemetry")) and not _env_disabled()
99
+ if _enabled:
100
+ _distinct_id = config.get("telemetry_id") or uuid.uuid4().hex
101
+ config.setdefault("telemetry_id", _distinct_id)
102
+
103
+
104
+ def track(event: str, properties: dict | None = None) -> None:
105
+ """Fire-and-forget anonymous event. No-op unless telemetry is enabled."""
106
+ if not is_enabled() or not _distinct_id:
107
+ return
108
+
109
+ payload = {
110
+ "event": event,
111
+ "properties": {
112
+ "token": MP_TOKEN,
113
+ "distinct_id": _distinct_id,
114
+ "time": int(time.time()),
115
+ "$ip": 0, # disable Mixpanel geolocation
116
+ "dulus_version": _dulus_version,
117
+ "os": platform.system(),
118
+ "python": f"{sys.version_info.major}.{sys.version_info.minor}",
119
+ **(properties or {}),
120
+ },
121
+ }
122
+
123
+ def _send() -> None:
124
+ try:
125
+ from urllib.request import Request, urlopen
126
+ from urllib.parse import urlencode
127
+
128
+ data = urlencode(
129
+ {"data": base64.b64encode(json.dumps([payload]).encode()).decode()}
130
+ ).encode()
131
+ req = Request(_MP_ENDPOINT, data=data, method="POST")
132
+ urlopen(req, timeout=4).read()
133
+ except Exception:
134
+ pass # telemetry must never break Dulus
135
+
136
+ threading.Thread(target=_send, daemon=True, name="telemetry").start()
137
+
138
+
139
+ def track_session_start(config: dict) -> None:
140
+ """Convenience: one event per REPL boot (enough for DAU/MAU counts)."""
141
+ track("session_start", {
142
+ "provider": str(config.get("provider", "")),
143
+ "model": str(config.get("model", "")),
144
+ })
145
+
146
+
147
+ def track_message_sent(model: str = "") -> None:
148
+ """One event per user prompt sent to the LLM (no content — count only)."""
149
+ track("message_sent", {"model": model})
150
+
151
+
152
+ def track_tool_used(tool_name: str) -> None:
153
+ """One event per tool invocation (tool NAME only — never inputs)."""
154
+ track("tool_used", {"tool": tool_name})
155
+
156
+
157
+ def track_command_used(command: str) -> None:
158
+ """One event per slash command (command NAME only — never args)."""
159
+ track("command_used", {"command": command})
160
+
161
+
162
+ def track_model_selected(model: str, provider: str = "") -> None:
163
+ """Fired when the user switches models via /model."""
164
+ track("model_selected", {"model": model, "provider": provider})
@@ -1,5 +1,6 @@
1
1
  import sys
2
2
  import json
3
+ from pathlib import Path
3
4
 
4
5
  # ── Import slash completer helpers ──
5
6
  try:
@@ -131,6 +132,26 @@ def ok(msg: str): print(clr(msg, "green"))
131
132
  def warn(msg: str): print(clr(f"Warning: {msg}", "yellow"))
132
133
  def err(msg: str): print(clr(f"Error: {msg}", "red"), file=sys.stderr)
133
134
 
135
+ def pip_install_cmd(*packages: str) -> list[str]:
136
+ """Build a pip-install command that works on externally-managed Pythons.
137
+
138
+ Debian 12+ / Ubuntu 23.04+ mark the system Python as externally managed
139
+ (PEP 668): any bare `pip install` dies with `externally-managed-environment`
140
+ unless `--break-system-packages` is passed. That flag only exists on
141
+ pip >= 23, so we add it exactly when the marker file is present (which
142
+ implies a modern pip) instead of always.
143
+ """
144
+ import sysconfig
145
+ cmd = [sys.executable, "-m", "pip", "install"]
146
+ try:
147
+ marker = Path(sysconfig.get_path("stdlib")) / "EXTERNALLY-MANAGED"
148
+ if marker.exists():
149
+ cmd.append("--break-system-packages")
150
+ except Exception:
151
+ pass
152
+ return cmd + list(packages)
153
+
154
+
134
155
  def stream_thinking(chunk: str, verbose: bool):
135
156
  if verbose:
136
157
  clean_chunk = chunk.replace("\n", " ")
@@ -2,6 +2,8 @@
2
2
 
3
3
  ## 🔥🔥🔥 News (Pacific Time)
4
4
 
5
+ - July 10, 2026: **🎙️ Dulus has his own voice — Deepgram lands as a first-class voice provider.** Voice on Dulus just leveled up: **Deepgram nova-3** is now the default STT backend the moment a key exists — ~300ms cloud transcription with native keyterm boosting, so your coding vocabulary (`grep`, `MemPalace`, `TypeScript`) comes out spelled right instead of whatever Whisper hallucinates on a noisy mic. And on the speaking side, **Aura-2** joins the TTS chain as the `deepgram` provider with natural per-language voices (`aura-2-celeste-es` for Spanish, `aura-2-thalia-en` for English). Zero SDK, zero heavy model downloads — both talk plain HTTPS, which means voice finally works great on laptops that can't afford a 1GB local Whisper model warming up for 30 seconds. Getting in is stupid simple: create a free account at [deepgram.com](https://deepgram.com) (**$200 in credit, no credit card** — that's roughly 775 hours of transcription), drop the key in `DEEPGRAM_API_KEY` or `deepgram_api_key` in your config, and `/voice`, `/wake` and `/say` are instantly running on it. No key? Nothing changes — local Whisper keeps working as always, and `DULUS_WAKE_FORCE_LOCAL=1` brings it back anytime. The eagle speaks now. 🦅🇩🇴
6
+
5
7
  - July 3, 2026: **🌍 dulus.work — THE HUB, revealed.** The secret is out: dulus.work is a **global hub where every Dulus instance on the planet shares what it learns**. Your Dulus finds a fix, refines a skill, adapts a plugin locally → publishes it to the hub → every other Dulus in the world pulls it down. Not an update server — an **organism**: every installation is a node, every user effectively becomes a contributor. *The fix a Dulus in Tokyo finds at 3AM, the one in Santo Domingo has by sunrise.* Traditional agents scale linearly (N devs improving 1 product); Dulus scales with its network (every user is a dev). The building blocks have been quietly shipping for months — the SelfImprove engine, the Auto-Adapter, the skills format, cloudsave — and dulus.work is where they converge. Domain is live, execution underway. 🦅🇩🇴 → [dulus.work](https://dulus.work)
6
8
 
7
9
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dulus
3
- Version: 3.7.0
3
+ Version: 3.8.0
4
4
  Summary: Use IA as an agent without an API key. $0. Harvest your browser session for Gemini (guest, no login), Claude.ai, Claude Code, Kimi.com, Qwen, DeepSeek — drive them like an API, no per-token billing. Plus LiteLLM (100+ paid providers), local models via Ollama, /lang in 34 languages, Mesa Redonda, voice, OCR, MemPalace, embedded sandbox OS.
5
5
  Author: KevRojo
6
6
  License: GPL-3.0
@@ -305,7 +305,8 @@ Dulus is proudly supported by these startup programs — powering our infrastruc
305
305
  <a href="https://amplitude.com/startups"><img src="https://www.google.com/s2/favicons?domain=amplitude.com&sz=128" height="38" alt="Amplitude" title="Amplitude Startup Scholarship"></a>&nbsp;&nbsp;&nbsp;&nbsp;
306
306
  <a href="https://www.digitalocean.com/hatch"><img src="https://cdn.simpleicons.org/digitalocean" height="38" alt="DigitalOcean" title="DigitalOcean Hatch"></a>&nbsp;&nbsp;&nbsp;&nbsp;
307
307
  <a href="https://www.notion.com/startups"><img src="https://cdn.simpleicons.org/notion" height="38" alt="Notion" title="Notion for Startups"></a>&nbsp;&nbsp;&nbsp;&nbsp;
308
- <a href="https://www.zendesk.com/campaign/startups/"><img src="https://cdn.simpleicons.org/zendesk" height="38" alt="Zendesk" title="Zendesk for Startups"></a>
308
+ <a href="https://www.zendesk.com/campaign/startups/"><img src="https://cdn.simpleicons.org/zendesk" height="38" alt="Zendesk" title="Zendesk for Startups"></a>&nbsp;&nbsp;&nbsp;&nbsp;
309
+ <a href="https://deepgram.com/startups"><img src="https://www.google.com/s2/favicons?domain=deepgram.com&sz=128" height="38" alt="Deepgram" title="Deepgram Startup Program — powering Dulus's voice"></a>
309
310
  </p>
310
311
 
311
312
  <p align="center"><i>Want to support Dulus? → <a href="https://github.com/sponsors/KevRojo">GitHub Sponsors</a> 🦅</i></p>
@@ -130,12 +130,12 @@ dulus_tools/background_tasks.py
130
130
  dulus_tools/clipboard_utils.py
131
131
  dulus_tools/diff_visualizer.py
132
132
  dulus_tools/export_import.py
133
- dulus_tools/fs_compass.py
134
133
  dulus_tools/hook_engine.py
135
134
  dulus_tools/notification_manager.py
136
135
  dulus_tools/session_fork.py
137
136
  dulus_tools/shell_mode.py
138
137
  dulus_tools/todo_visualizer.py
138
+ dulus_tools/tree_ls.py
139
139
  dulus_tools/wire_events.py
140
140
  dulus_tools/yolo_mode.py
141
141
  gui/__init__.py
@@ -294,7 +294,6 @@ tests/test_diff_view.py
294
294
  tests/test_diff_visualization.py
295
295
  tests/test_display_blocks.py
296
296
  tests/test_export_import.py
297
- tests/test_fs_compass.py
298
297
  tests/test_hook_engine.py
299
298
  tests/test_injection_fix.py
300
299
  tests/test_license.py
@@ -313,6 +312,7 @@ tests/test_think_tool.py
313
312
  tests/test_todo_tool.py
314
313
  tests/test_todo_visualization.py
315
314
  tests/test_tool_registry.py
315
+ tests/test_tree_ls.py
316
316
  tests/test_voice.py
317
317
  tests/test_wire_events.py
318
318
  ui/__init__.py
@@ -2743,7 +2743,7 @@ def cmd_harvest(_args: str, _state, config) -> bool:
2743
2743
  except ImportError:
2744
2744
  import os
2745
2745
  info("Installing playwright...")
2746
- os.system("pip install playwright")
2746
+ __import__("subprocess").run(__import__("common").pip_install_cmd("playwright"))
2747
2747
  os.system("playwright install chromium")
2748
2748
  from playwright.sync_api import sync_playwright
2749
2749
 
@@ -2888,7 +2888,7 @@ def cmd_harvest_kimi(_args: str, _state, config) -> bool:
2888
2888
  from playwright.sync_api import sync_playwright
2889
2889
  except ImportError:
2890
2890
  info("Installing playwright...")
2891
- os.system("pip install playwright")
2891
+ __import__("subprocess").run(__import__("common").pip_install_cmd("playwright"))
2892
2892
  os.system("playwright install chromium")
2893
2893
  from playwright.sync_api import sync_playwright
2894
2894
 
@@ -2999,7 +2999,7 @@ def cmd_harvest_gemini(_args: str, _state, config) -> bool:
2999
2999
  from playwright.sync_api import sync_playwright
3000
3000
  except ImportError:
3001
3001
  info("Installing playwright...")
3002
- os.system("pip install playwright")
3002
+ __import__("subprocess").run(__import__("common").pip_install_cmd("playwright"))
3003
3003
  os.system("playwright install chromium")
3004
3004
  from playwright.sync_api import sync_playwright
3005
3005
 
@@ -3169,7 +3169,7 @@ def cmd_harvest_deepseek(_args: str, _state, config) -> bool:
3169
3169
  from playwright.sync_api import sync_playwright
3170
3170
  except ImportError:
3171
3171
  info("Installing playwright...")
3172
- os.system("pip install playwright")
3172
+ __import__("subprocess").run(__import__("common").pip_install_cmd("playwright"))
3173
3173
  os.system("playwright install chromium")
3174
3174
  from playwright.sync_api import sync_playwright
3175
3175
 
@@ -3317,7 +3317,7 @@ def cmd_harvest_qwen(_args: str, _state, config) -> bool:
3317
3317
  from playwright.sync_api import sync_playwright
3318
3318
  except ImportError:
3319
3319
  info("Installing playwright...")
3320
- os.system("pip install playwright")
3320
+ __import__("subprocess").run(__import__("common").pip_install_cmd("playwright"))
3321
3321
  os.system("playwright install chromium")
3322
3322
  from playwright.sync_api import sync_playwright
3323
3323
 
@@ -6991,7 +6991,7 @@ def cmd_tts(args: str, state, config) -> bool:
6991
6991
 
6992
6992
  if parts and parts[0].lower() == "provider":
6993
6993
  name = parts[1].strip().lower() if len(parts) > 1 else ""
6994
- valid = ("auto", "azure", "riva", "openai", "gtts", "pyttsx3", "edge", "elevenlabs")
6994
+ valid = ("auto", "azure", "riva", "openai", "gtts", "pyttsx3", "edge", "elevenlabs", "deepgram")
6995
6995
  if not name:
6996
6996
  current = config.get("tts_provider", "auto")
6997
6997
  info(f"TTS provider: {current}")