dulus 3.13.6__tar.gz → 3.13.7__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 (387) hide show
  1. {dulus-3.13.6/dulus.egg-info → dulus-3.13.7}/PKG-INFO +1 -1
  2. {dulus-3.13.6 → dulus-3.13.7}/docs/CHANGELOG.md +10 -0
  3. {dulus-3.13.6 → dulus-3.13.7}/docs/news.md +4 -0
  4. {dulus-3.13.6 → dulus-3.13.7/dulus.egg-info}/PKG-INFO +1 -1
  5. {dulus-3.13.6 → dulus-3.13.7}/dulus.py +478 -38
  6. {dulus-3.13.6 → dulus-3.13.7}/pyproject.toml +1 -1
  7. {dulus-3.13.6 → dulus-3.13.7}/LICENSE +0 -0
  8. {dulus-3.13.6 → dulus-3.13.7}/MANIFEST.in +0 -0
  9. {dulus-3.13.6 → dulus-3.13.7}/README.md +0 -0
  10. {dulus-3.13.6 → dulus-3.13.7}/agent.py +0 -0
  11. {dulus-3.13.6 → dulus-3.13.7}/algolia_search.py +0 -0
  12. {dulus-3.13.6 → dulus-3.13.7}/analytics.py +0 -0
  13. {dulus-3.13.6 → dulus-3.13.7}/backend/__init__.py +0 -0
  14. {dulus-3.13.6 → dulus-3.13.7}/backend/agents_bridge.py +0 -0
  15. {dulus-3.13.6 → dulus-3.13.7}/backend/compressor.py +0 -0
  16. {dulus-3.13.6 → dulus-3.13.7}/backend/context.py +0 -0
  17. {dulus-3.13.6 → dulus-3.13.7}/backend/githook.py +0 -0
  18. {dulus-3.13.6 → dulus-3.13.7}/backend/marketplace.py +0 -0
  19. {dulus-3.13.6 → dulus-3.13.7}/backend/mempalace_bridge.py +0 -0
  20. {dulus-3.13.6 → dulus-3.13.7}/backend/paths.py +0 -0
  21. {dulus-3.13.6 → dulus-3.13.7}/backend/personas.py +0 -0
  22. {dulus-3.13.6 → dulus-3.13.7}/backend/plugins.py +0 -0
  23. {dulus-3.13.6 → dulus-3.13.7}/backend/server.py +0 -0
  24. {dulus-3.13.6 → dulus-3.13.7}/backend/tasks.py +0 -0
  25. {dulus-3.13.6 → dulus-3.13.7}/banners/__init__.py +0 -0
  26. {dulus-3.13.6 → dulus-3.13.7}/banners/banner_rotator.py +0 -0
  27. {dulus-3.13.6 → dulus-3.13.7}/banners/banners_dulus_ai.txt +0 -0
  28. {dulus-3.13.6 → dulus-3.13.7}/batch_api.py +0 -0
  29. {dulus-3.13.6 → dulus-3.13.7}/checkpoint/__init__.py +0 -0
  30. {dulus-3.13.6 → dulus-3.13.7}/checkpoint/hooks.py +0 -0
  31. {dulus-3.13.6 → dulus-3.13.7}/checkpoint/store.py +0 -0
  32. {dulus-3.13.6 → dulus-3.13.7}/checkpoint/types.py +0 -0
  33. {dulus-3.13.6 → dulus-3.13.7}/cli_animations/__init__.py +0 -0
  34. {dulus-3.13.6 → dulus-3.13.7}/cli_animations/__main__.py +0 -0
  35. {dulus-3.13.6 → dulus-3.13.7}/cli_animations/ansi.py +0 -0
  36. {dulus-3.13.6 → dulus-3.13.7}/cli_animations/assets/ascii/dulus_logo.txt +0 -0
  37. {dulus-3.13.6 → dulus-3.13.7}/cli_animations/assets/ascii/eagle.txt +0 -0
  38. {dulus-3.13.6 → dulus-3.13.7}/cli_animations/assets/ascii/terminal_art.txt +0 -0
  39. {dulus-3.13.6 → dulus-3.13.7}/cli_animations/banners.py +0 -0
  40. {dulus-3.13.6 → dulus-3.13.7}/cli_animations/demo.py +0 -0
  41. {dulus-3.13.6 → dulus-3.13.7}/cli_animations/effects.py +0 -0
  42. {dulus-3.13.6 → dulus-3.13.7}/cli_animations/effort_slider.py +0 -0
  43. {dulus-3.13.6 → dulus-3.13.7}/cli_animations/progress.py +0 -0
  44. {dulus-3.13.6 → dulus-3.13.7}/cli_animations/py.typed +0 -0
  45. {dulus-3.13.6 → dulus-3.13.7}/cli_animations/spinners.py +0 -0
  46. {dulus-3.13.6 → dulus-3.13.7}/cli_animations/status.py +0 -0
  47. {dulus-3.13.6 → dulus-3.13.7}/clipboard_utils.py +0 -0
  48. {dulus-3.13.6 → dulus-3.13.7}/cloudsave.py +0 -0
  49. {dulus-3.13.6 → dulus-3.13.7}/common.py +0 -0
  50. {dulus-3.13.6 → dulus-3.13.7}/compaction.py +0 -0
  51. {dulus-3.13.6 → dulus-3.13.7}/config.py +0 -0
  52. {dulus-3.13.6 → dulus-3.13.7}/context.py +0 -0
  53. {dulus-3.13.6 → dulus-3.13.7}/data/__init__.py +0 -0
  54. {dulus-3.13.6 → dulus-3.13.7}/data/active_persona.json +0 -0
  55. {dulus-3.13.6 → dulus-3.13.7}/data/marketplace.json +0 -0
  56. {dulus-3.13.6 → dulus-3.13.7}/data/personas.json +0 -0
  57. {dulus-3.13.6 → dulus-3.13.7}/data/plugins/__init__.py +0 -0
  58. {dulus-3.13.6 → dulus-3.13.7}/data/plugins/composio/__init__.py +0 -0
  59. {dulus-3.13.6 → dulus-3.13.7}/data/plugins/composio/composio_plugin/__init__.py +0 -0
  60. {dulus-3.13.6 → dulus-3.13.7}/data/plugins/composio/composio_plugin/session_manager.py +0 -0
  61. {dulus-3.13.6 → dulus-3.13.7}/data/plugins/composio/composio_plugin/tool_generator.py +0 -0
  62. {dulus-3.13.6 → dulus-3.13.7}/data/plugins/composio/plugin.json +0 -0
  63. {dulus-3.13.6 → dulus-3.13.7}/data/plugins/composio/plugin_tool.py +0 -0
  64. {dulus-3.13.6 → dulus-3.13.7}/data/tasks.json +0 -0
  65. {dulus-3.13.6 → dulus-3.13.7}/display_blocks.py +0 -0
  66. {dulus-3.13.6 → dulus-3.13.7}/docs/API.md +0 -0
  67. {dulus-3.13.6 → dulus-3.13.7}/docs/BRAND_GUIDE.md +0 -0
  68. {dulus-3.13.6 → dulus-3.13.7}/docs/CONTRIBUTING.md +0 -0
  69. {dulus-3.13.6 → dulus-3.13.7}/docs/DEPLOYMENT.md +0 -0
  70. {dulus-3.13.6 → dulus-3.13.7}/docs/FAQ.md +0 -0
  71. {dulus-3.13.6 → dulus-3.13.7}/docs/GETTING_STARTED.md +0 -0
  72. {dulus-3.13.6 → dulus-3.13.7}/docs/README.md +0 -0
  73. {dulus-3.13.6 → dulus-3.13.7}/docs/README_AR.md +0 -0
  74. {dulus-3.13.6 → dulus-3.13.7}/docs/README_EN.md +0 -0
  75. {dulus-3.13.6 → dulus-3.13.7}/docs/README_ES.md +0 -0
  76. {dulus-3.13.6 → dulus-3.13.7}/docs/README_FR.md +0 -0
  77. {dulus-3.13.6 → dulus-3.13.7}/docs/README_JA.md +0 -0
  78. {dulus-3.13.6 → dulus-3.13.7}/docs/README_KO.md +0 -0
  79. {dulus-3.13.6 → dulus-3.13.7}/docs/README_PT.md +0 -0
  80. {dulus-3.13.6 → dulus-3.13.7}/docs/README_RU.md +0 -0
  81. {dulus-3.13.6 → dulus-3.13.7}/docs/README_ZH.md +0 -0
  82. {dulus-3.13.6 → dulus-3.13.7}/docs/SECURITY.md +0 -0
  83. {dulus-3.13.6 → dulus-3.13.7}/docs/__init__.py +0 -0
  84. {dulus-3.13.6 → dulus-3.13.7}/docs/api.html +0 -0
  85. {dulus-3.13.6 → dulus-3.13.7}/docs/architecture.md +0 -0
  86. {dulus-3.13.6 → dulus-3.13.7}/docs/azure-speech-template.json +0 -0
  87. {dulus-3.13.6 → dulus-3.13.7}/docs/dashboard/index.html +0 -0
  88. {dulus-3.13.6 → dulus-3.13.7}/docs/divider.svg +0 -0
  89. {dulus-3.13.6 → dulus-3.13.7}/docs/dulus-bird.png +0 -0
  90. {dulus-3.13.6 → dulus-3.13.7}/docs/generate.py +0 -0
  91. {dulus-3.13.6 → dulus-3.13.7}/docs/index.html +0 -0
  92. {dulus-3.13.6 → dulus-3.13.7}/docs/nvidia-models.svg +0 -0
  93. {dulus-3.13.6 → dulus-3.13.7}/docs/particle-playground.html +0 -0
  94. {dulus-3.13.6 → dulus-3.13.7}/docs/personas/index.html +0 -0
  95. {dulus-3.13.6 → dulus-3.13.7}/docs/poetry-banner.png +0 -0
  96. {dulus-3.13.6 → dulus-3.13.7}/docs/preview.html +0 -0
  97. {dulus-3.13.6 → dulus-3.13.7}/docs/python-console.md +0 -0
  98. {dulus-3.13.6 → dulus-3.13.7}/docs/readme/auto-adapter.png +0 -0
  99. {dulus-3.13.6 → dulus-3.13.7}/docs/readme/dulus-os.png +0 -0
  100. {dulus-3.13.6 → dulus-3.13.7}/docs/readme/hero.png +0 -0
  101. {dulus-3.13.6 → dulus-3.13.7}/docs/readme/lookback_two_windows.png +0 -0
  102. {dulus-3.13.6 → dulus-3.13.7}/docs/readme/pyc_heap.svg +0 -0
  103. {dulus-3.13.6 → dulus-3.13.7}/docs/readme/pyc_receipts.svg +0 -0
  104. {dulus-3.13.6 → dulus-3.13.7}/docs/readme/pyc_trap.svg +0 -0
  105. {dulus-3.13.6 → dulus-3.13.7}/docs/readme/roundtable.png +0 -0
  106. {dulus-3.13.6 → dulus-3.13.7}/docs/readme/task-board.png +0 -0
  107. {dulus-3.13.6 → dulus-3.13.7}/docs/readme/webchat.png +0 -0
  108. {dulus-3.13.6 → dulus-3.13.7}/docs/sec-agents.svg +0 -0
  109. {dulus-3.13.6 → dulus-3.13.7}/docs/sec-brainstorm.svg +0 -0
  110. {dulus-3.13.6 → dulus-3.13.7}/docs/sec-bridges.svg +0 -0
  111. {dulus-3.13.6 → dulus-3.13.7}/docs/sec-features.svg +0 -0
  112. {dulus-3.13.6 → dulus-3.13.7}/docs/sec-freetier.svg +0 -0
  113. {dulus-3.13.6 → dulus-3.13.7}/docs/sec-memory.svg +0 -0
  114. {dulus-3.13.6 → dulus-3.13.7}/docs/sec-models.svg +0 -0
  115. {dulus-3.13.6 → dulus-3.13.7}/docs/sec-perms.svg +0 -0
  116. {dulus-3.13.6 → dulus-3.13.7}/docs/sec-plugins.svg +0 -0
  117. {dulus-3.13.6 → dulus-3.13.7}/docs/sec-quickstart.svg +0 -0
  118. {dulus-3.13.6 → dulus-3.13.7}/docs/sec-ssj.svg +0 -0
  119. {dulus-3.13.6 → dulus-3.13.7}/docs/spinners.svg +0 -0
  120. {dulus-3.13.6 → dulus-3.13.7}/docs/split-pane.svg +0 -0
  121. {dulus-3.13.6 → dulus-3.13.7}/docs/terminal-boot.svg +0 -0
  122. {dulus-3.13.6 → dulus-3.13.7}/docs/termux-edge.md +0 -0
  123. {dulus-3.13.6 → dulus-3.13.7}/dulus.egg-info/SOURCES.txt +0 -0
  124. {dulus-3.13.6 → dulus-3.13.7}/dulus.egg-info/dependency_links.txt +0 -0
  125. {dulus-3.13.6 → dulus-3.13.7}/dulus.egg-info/entry_points.txt +0 -0
  126. {dulus-3.13.6 → dulus-3.13.7}/dulus.egg-info/requires.txt +0 -0
  127. {dulus-3.13.6 → dulus-3.13.7}/dulus.egg-info/top_level.txt +0 -0
  128. {dulus-3.13.6 → dulus-3.13.7}/dulus_account.py +0 -0
  129. {dulus-3.13.6 → dulus-3.13.7}/dulus_bar_client.py +0 -0
  130. {dulus-3.13.6 → dulus-3.13.7}/dulus_gui.py +0 -0
  131. {dulus-3.13.6 → dulus-3.13.7}/dulus_mcp/__init__.py +0 -0
  132. {dulus-3.13.6 → dulus-3.13.7}/dulus_mcp/client.py +0 -0
  133. {dulus-3.13.6 → dulus-3.13.7}/dulus_mcp/commands.py +0 -0
  134. {dulus-3.13.6 → dulus-3.13.7}/dulus_mcp/config.py +0 -0
  135. {dulus-3.13.6 → dulus-3.13.7}/dulus_mcp/hub.py +0 -0
  136. {dulus-3.13.6 → dulus-3.13.7}/dulus_mcp/tools.py +0 -0
  137. {dulus-3.13.6 → dulus-3.13.7}/dulus_mcp/types.py +0 -0
  138. {dulus-3.13.6 → dulus-3.13.7}/dulus_quick_menu.py +0 -0
  139. {dulus-3.13.6 → dulus-3.13.7}/dulus_tools/__init__.py +0 -0
  140. {dulus-3.13.6 → dulus-3.13.7}/dulus_tools/add_dir_manager.py +0 -0
  141. {dulus-3.13.6 → dulus-3.13.7}/dulus_tools/afk_mode.py +0 -0
  142. {dulus-3.13.6 → dulus-3.13.7}/dulus_tools/approval_runtime.py +0 -0
  143. {dulus-3.13.6 → dulus-3.13.7}/dulus_tools/background_tasks.py +0 -0
  144. {dulus-3.13.6 → dulus-3.13.7}/dulus_tools/clipboard_utils.py +0 -0
  145. {dulus-3.13.6 → dulus-3.13.7}/dulus_tools/diff_visualizer.py +0 -0
  146. {dulus-3.13.6 → dulus-3.13.7}/dulus_tools/export_import.py +0 -0
  147. {dulus-3.13.6 → dulus-3.13.7}/dulus_tools/hook_engine.py +0 -0
  148. {dulus-3.13.6 → dulus-3.13.7}/dulus_tools/notification_manager.py +0 -0
  149. {dulus-3.13.6 → dulus-3.13.7}/dulus_tools/session_fork.py +0 -0
  150. {dulus-3.13.6 → dulus-3.13.7}/dulus_tools/shell_mode.py +0 -0
  151. {dulus-3.13.6 → dulus-3.13.7}/dulus_tools/todo_visualizer.py +0 -0
  152. {dulus-3.13.6 → dulus-3.13.7}/dulus_tools/tree_ls.py +0 -0
  153. {dulus-3.13.6 → dulus-3.13.7}/dulus_tools/wire_events.py +0 -0
  154. {dulus-3.13.6 → dulus-3.13.7}/dulus_tools/yolo_mode.py +0 -0
  155. {dulus-3.13.6 → dulus-3.13.7}/file_filter.py +0 -0
  156. {dulus-3.13.6 → dulus-3.13.7}/fs_compass.py +0 -0
  157. {dulus-3.13.6 → dulus-3.13.7}/governance.py +0 -0
  158. {dulus-3.13.6 → dulus-3.13.7}/gui/__init__.py +0 -0
  159. {dulus-3.13.6 → dulus-3.13.7}/gui/agent_bridge.py +0 -0
  160. {dulus-3.13.6 → dulus-3.13.7}/gui/chat_widget.py +0 -0
  161. {dulus-3.13.6 → dulus-3.13.7}/gui/main_window.py +0 -0
  162. {dulus-3.13.6 → dulus-3.13.7}/gui/personas.py +0 -0
  163. {dulus-3.13.6 → dulus-3.13.7}/gui/sandbox_server.py +0 -0
  164. {dulus-3.13.6 → dulus-3.13.7}/gui/session_utils.py +0 -0
  165. {dulus-3.13.6 → dulus-3.13.7}/gui/settings_dialog.py +0 -0
  166. {dulus-3.13.6 → dulus-3.13.7}/gui/sidebar.py +0 -0
  167. {dulus-3.13.6 → dulus-3.13.7}/gui/tasks_view.py +0 -0
  168. {dulus-3.13.6 → dulus-3.13.7}/gui/themes.py +0 -0
  169. {dulus-3.13.6 → dulus-3.13.7}/gui/tool_panel.py +0 -0
  170. {dulus-3.13.6 → dulus-3.13.7}/input.py +0 -0
  171. {dulus-3.13.6 → dulus-3.13.7}/license_manager.py +0 -0
  172. {dulus-3.13.6 → dulus-3.13.7}/lookback.py +0 -0
  173. {dulus-3.13.6 → dulus-3.13.7}/memory/__init__.py +0 -0
  174. {dulus-3.13.6 → dulus-3.13.7}/memory/audit.py +0 -0
  175. {dulus-3.13.6 → dulus-3.13.7}/memory/consolidator.py +0 -0
  176. {dulus-3.13.6 → dulus-3.13.7}/memory/context.py +0 -0
  177. {dulus-3.13.6 → dulus-3.13.7}/memory/mempalace_bridge.py +0 -0
  178. {dulus-3.13.6 → dulus-3.13.7}/memory/offload.py +0 -0
  179. {dulus-3.13.6 → dulus-3.13.7}/memory/palace.py +0 -0
  180. {dulus-3.13.6 → dulus-3.13.7}/memory/scan.py +0 -0
  181. {dulus-3.13.6 → dulus-3.13.7}/memory/seeds/insights/adaptation_guides.md +0 -0
  182. {dulus-3.13.6 → dulus-3.13.7}/memory/seeds/insights/askuserquestion_after_numbered_list.md +0 -0
  183. {dulus-3.13.6 → dulus-3.13.7}/memory/seeds/insights/background_jobs_no_extra_timers.md +0 -0
  184. {dulus-3.13.6 → dulus-3.13.7}/memory/seeds/insights/cleverness_over_resources.md +0 -0
  185. {dulus-3.13.6 → dulus-3.13.7}/memory/seeds/insights/corporate_tls_breaks_curl_cffi.md +0 -0
  186. {dulus-3.13.6 → dulus-3.13.7}/memory/seeds/insights/hardened_rules.md +0 -0
  187. {dulus-3.13.6 → dulus-3.13.7}/memory/seeds/insights/investigator_workflow_heuristic.md +0 -0
  188. {dulus-3.13.6 → dulus-3.13.7}/memory/seeds/insights/native_code_over_sketchy_plugins.md +0 -0
  189. {dulus-3.13.6 → dulus-3.13.7}/memory/seeds/insights/no_personal_info_in_code.md +0 -0
  190. {dulus-3.13.6 → dulus-3.13.7}/memory/seeds/insights/searches_include_modified_date.md +0 -0
  191. {dulus-3.13.6 → dulus-3.13.7}/memory/seeds/insights/tmux_send_keys_power_patterns.md +0 -0
  192. {dulus-3.13.6 → dulus-3.13.7}/memory/seeds/insights/token_optimization_technique.md +0 -0
  193. {dulus-3.13.6 → dulus-3.13.7}/memory/seeds/insights/tool_chaining_capability.md +0 -0
  194. {dulus-3.13.6 → dulus-3.13.7}/memory/seeds/insights/webbridge_evaluate_first_pattern.md +0 -0
  195. {dulus-3.13.6 → dulus-3.13.7}/memory/seeds/short_memory.md +0 -0
  196. {dulus-3.13.6 → dulus-3.13.7}/memory/sessions.py +0 -0
  197. {dulus-3.13.6 → dulus-3.13.7}/memory/store.py +0 -0
  198. {dulus-3.13.6 → dulus-3.13.7}/memory/tools.py +0 -0
  199. {dulus-3.13.6 → dulus-3.13.7}/memory/types.py +0 -0
  200. {dulus-3.13.6 → dulus-3.13.7}/memory/vector_search.py +0 -0
  201. {dulus-3.13.6 → dulus-3.13.7}/model_params.py +0 -0
  202. {dulus-3.13.6 → dulus-3.13.7}/multi_agent/__init__.py +0 -0
  203. {dulus-3.13.6 → dulus-3.13.7}/multi_agent/subagent.py +0 -0
  204. {dulus-3.13.6 → dulus-3.13.7}/multi_agent/tools.py +0 -0
  205. {dulus-3.13.6 → dulus-3.13.7}/paste_placeholders.py +0 -0
  206. {dulus-3.13.6 → dulus-3.13.7}/personality_config.py +0 -0
  207. {dulus-3.13.6 → dulus-3.13.7}/plugin/__init__.py +0 -0
  208. {dulus-3.13.6 → dulus-3.13.7}/plugin/autoadapter.py +0 -0
  209. {dulus-3.13.6 → dulus-3.13.7}/plugin/loader.py +0 -0
  210. {dulus-3.13.6 → dulus-3.13.7}/plugin/recommend.py +0 -0
  211. {dulus-3.13.6 → dulus-3.13.7}/plugin/store.py +0 -0
  212. {dulus-3.13.6 → dulus-3.13.7}/plugin/types.py +0 -0
  213. {dulus-3.13.6 → dulus-3.13.7}/profiles.py +0 -0
  214. {dulus-3.13.6 → dulus-3.13.7}/providers.py +0 -0
  215. {dulus-3.13.6 → dulus-3.13.7}/pyconsole.py +0 -0
  216. {dulus-3.13.6 → dulus-3.13.7}/sandbox/README.md +0 -0
  217. {dulus-3.13.6 → dulus-3.13.7}/sandbox/__init__.py +0 -0
  218. {dulus-3.13.6 → dulus-3.13.7}/sandbox/dist/assets/index-DE51D6wI.css +0 -0
  219. {dulus-3.13.6 → dulus-3.13.7}/sandbox/dist/assets/index-DMCCNE9Y.js +0 -0
  220. {dulus-3.13.6 → dulus-3.13.7}/sandbox/dist/index.html +0 -0
  221. {dulus-3.13.6 → dulus-3.13.7}/sandbox/dist/wallpaper-default.jpg +0 -0
  222. {dulus-3.13.6 → dulus-3.13.7}/sandbox/dist/wallpapers/default.jpeg +0 -0
  223. {dulus-3.13.6 → dulus-3.13.7}/sandbox/dist/wallpapers/light.jpeg +0 -0
  224. {dulus-3.13.6 → dulus-3.13.7}/sandbox/dist/wallpapers/nature.jpeg +0 -0
  225. {dulus-3.13.6 → dulus-3.13.7}/sandbox/dist/wallpapers/tech.jpeg +0 -0
  226. {dulus-3.13.6 → dulus-3.13.7}/sandbox/info.md +0 -0
  227. {dulus-3.13.6 → dulus-3.13.7}/sandbox_bootstrap.py +0 -0
  228. {dulus-3.13.6 → dulus-3.13.7}/setup.cfg +0 -0
  229. {dulus-3.13.6 → dulus-3.13.7}/skill/__init__.py +0 -0
  230. {dulus-3.13.6 → dulus-3.13.7}/skill/builtin.py +0 -0
  231. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/LICENSE-obsidian-skills +0 -0
  232. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/README.md +0 -0
  233. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/defuddle/SKILL.md +0 -0
  234. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/json-canvas/SKILL.md +0 -0
  235. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/json-canvas/references/EXAMPLES.md +0 -0
  236. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/obsidian-bases/SKILL.md +0 -0
  237. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/obsidian-bases/references/FUNCTIONS_REFERENCE.md +0 -0
  238. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/obsidian-cli/SKILL.md +0 -0
  239. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/obsidian-markdown/SKILL.md +0 -0
  240. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/obsidian-markdown/references/CALLOUTS.md +0 -0
  241. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/obsidian-markdown/references/EMBEDS.md +0 -0
  242. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/obsidian-markdown/references/PROPERTIES.md +0 -0
  243. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/README.md +0 -0
  244. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/SKILL.md +0 -0
  245. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/addresses.json +0 -0
  246. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/advancedbackgroundchecks.json +0 -0
  247. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/beenverified.json +0 -0
  248. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/clustal.json +0 -0
  249. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/clustrmaps.json +0 -0
  250. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/cyberbackgroundchecks.json +0 -0
  251. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/familytreenow.json +0 -0
  252. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/fastpeoplesearch.json +0 -0
  253. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/intelius.json +0 -0
  254. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/mylife.json +0 -0
  255. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/nuwber.json +0 -0
  256. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/peekyou.json +0 -0
  257. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/peoplefinders.json +0 -0
  258. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/radaris.json +0 -0
  259. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/rehold.json +0 -0
  260. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/searchpeoplefree.json +0 -0
  261. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/socialcatfish.json +0 -0
  262. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/spokeo.json +0 -0
  263. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/thatsthem.json +0 -0
  264. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/truepeoplesearch.json +0 -0
  265. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/usphonebook.json +0 -0
  266. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/whitepages.json +0 -0
  267. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/legal/ccpa.md +0 -0
  268. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/legal/drop.md +0 -0
  269. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/legal/gdpr.md +0 -0
  270. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/methods.md +0 -0
  271. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/site-playbooks.md +0 -0
  272. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/state-machine.md +0 -0
  273. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/autopilot.py +0 -0
  274. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/badbool.py +0 -0
  275. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/brokers.py +0 -0
  276. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/cdp.py +0 -0
  277. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/config.py +0 -0
  278. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/crypto.py +0 -0
  279. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/dossier.py +0 -0
  280. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/email_modes.py +0 -0
  281. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/emailer.py +0 -0
  282. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/ledger.py +0 -0
  283. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/legal.py +0 -0
  284. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/paths.py +0 -0
  285. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/pdd.py +0 -0
  286. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/registry.py +0 -0
  287. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/report.py +0 -0
  288. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/scan.py +0 -0
  289. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/storage.py +0 -0
  290. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/tiers.py +0 -0
  291. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/vectors.py +0 -0
  292. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/templates/consent/authorization.md +0 -0
  293. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/templates/emails/ccpa-authorized-agent.txt +0 -0
  294. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/templates/emails/ccpa-deletion.txt +0 -0
  295. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/templates/emails/ccpa-indirect-deletion.txt +0 -0
  296. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/templates/emails/gdpr-erasure.txt +0 -0
  297. {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/templates/emails/generic-optout.txt +0 -0
  298. {dulus-3.13.6 → dulus-3.13.7}/skill/clawhub.py +0 -0
  299. {dulus-3.13.6 → dulus-3.13.7}/skill/executor.py +0 -0
  300. {dulus-3.13.6 → dulus-3.13.7}/skill/loader.py +0 -0
  301. {dulus-3.13.6 → dulus-3.13.7}/skill/tools.py +0 -0
  302. {dulus-3.13.6 → dulus-3.13.7}/skills.py +0 -0
  303. {dulus-3.13.6 → dulus-3.13.7}/soul.py +0 -0
  304. {dulus-3.13.6 → dulus-3.13.7}/spinner.py +0 -0
  305. {dulus-3.13.6 → dulus-3.13.7}/steer_input.py +0 -0
  306. {dulus-3.13.6 → dulus-3.13.7}/subagent.py +0 -0
  307. {dulus-3.13.6 → dulus-3.13.7}/task/__init__.py +0 -0
  308. {dulus-3.13.6 → dulus-3.13.7}/task/store.py +0 -0
  309. {dulus-3.13.6 → dulus-3.13.7}/task/tools.py +0 -0
  310. {dulus-3.13.6 → dulus-3.13.7}/task/types.py +0 -0
  311. {dulus-3.13.6 → dulus-3.13.7}/tests/test_afk_yolo.py +0 -0
  312. {dulus-3.13.6 → dulus-3.13.7}/tests/test_approval_runtime.py +0 -0
  313. {dulus-3.13.6 → dulus-3.13.7}/tests/test_background_task_tools.py +0 -0
  314. {dulus-3.13.6 → dulus-3.13.7}/tests/test_background_tasks.py +0 -0
  315. {dulus-3.13.6 → dulus-3.13.7}/tests/test_chatgpt_oauth.py +0 -0
  316. {dulus-3.13.6 → dulus-3.13.7}/tests/test_checkpoint.py +0 -0
  317. {dulus-3.13.6 → dulus-3.13.7}/tests/test_cli_animations_integration.py +0 -0
  318. {dulus-3.13.6 → dulus-3.13.7}/tests/test_clipboard_utils.py +0 -0
  319. {dulus-3.13.6 → dulus-3.13.7}/tests/test_compaction.py +0 -0
  320. {dulus-3.13.6 → dulus-3.13.7}/tests/test_composio_tool_generator.py +0 -0
  321. {dulus-3.13.6 → dulus-3.13.7}/tests/test_diff_view.py +0 -0
  322. {dulus-3.13.6 → dulus-3.13.7}/tests/test_diff_visualization.py +0 -0
  323. {dulus-3.13.6 → dulus-3.13.7}/tests/test_display_blocks.py +0 -0
  324. {dulus-3.13.6 → dulus-3.13.7}/tests/test_dulus_account.py +0 -0
  325. {dulus-3.13.6 → dulus-3.13.7}/tests/test_export_import.py +0 -0
  326. {dulus-3.13.6 → dulus-3.13.7}/tests/test_gold_memory_preload.py +0 -0
  327. {dulus-3.13.6 → dulus-3.13.7}/tests/test_grok_usage.py +0 -0
  328. {dulus-3.13.6 → dulus-3.13.7}/tests/test_hook_engine.py +0 -0
  329. {dulus-3.13.6 → dulus-3.13.7}/tests/test_injection_fix.py +0 -0
  330. {dulus-3.13.6 → dulus-3.13.7}/tests/test_json_protocol.py +0 -0
  331. {dulus-3.13.6 → dulus-3.13.7}/tests/test_license.py +0 -0
  332. {dulus-3.13.6 → dulus-3.13.7}/tests/test_lookback_cache.py +0 -0
  333. {dulus-3.13.6 → dulus-3.13.7}/tests/test_lookback_compact.py +0 -0
  334. {dulus-3.13.6 → dulus-3.13.7}/tests/test_mcp.py +0 -0
  335. {dulus-3.13.6 → dulus-3.13.7}/tests/test_mcp_algolia.py +0 -0
  336. {dulus-3.13.6 → dulus-3.13.7}/tests/test_memory.py +0 -0
  337. {dulus-3.13.6 → dulus-3.13.7}/tests/test_memory_home_resolution.py +0 -0
  338. {dulus-3.13.6 → dulus-3.13.7}/tests/test_notification_manager.py +0 -0
  339. {dulus-3.13.6 → dulus-3.13.7}/tests/test_plugin.py +0 -0
  340. {dulus-3.13.6 → dulus-3.13.7}/tests/test_profile_isolation.py +0 -0
  341. {dulus-3.13.6 → dulus-3.13.7}/tests/test_profile_plugin_scope.py +0 -0
  342. {dulus-3.13.6 → dulus-3.13.7}/tests/test_provider_errors.py +0 -0
  343. {dulus-3.13.6 → dulus-3.13.7}/tests/test_session_fork.py +0 -0
  344. {dulus-3.13.6 → dulus-3.13.7}/tests/test_shell_detection.py +0 -0
  345. {dulus-3.13.6 → dulus-3.13.7}/tests/test_shell_mode.py +0 -0
  346. {dulus-3.13.6 → dulus-3.13.7}/tests/test_skills.py +0 -0
  347. {dulus-3.13.6 → dulus-3.13.7}/tests/test_skillssh_algolia.py +0 -0
  348. {dulus-3.13.6 → dulus-3.13.7}/tests/test_steer_input.py +0 -0
  349. {dulus-3.13.6 → dulus-3.13.7}/tests/test_subagent.py +0 -0
  350. {dulus-3.13.6 → dulus-3.13.7}/tests/test_task.py +0 -0
  351. {dulus-3.13.6 → dulus-3.13.7}/tests/test_telegram_buffer.py +0 -0
  352. {dulus-3.13.6 → dulus-3.13.7}/tests/test_think_tool.py +0 -0
  353. {dulus-3.13.6 → dulus-3.13.7}/tests/test_todo_tool.py +0 -0
  354. {dulus-3.13.6 → dulus-3.13.7}/tests/test_todo_visualization.py +0 -0
  355. {dulus-3.13.6 → dulus-3.13.7}/tests/test_tool_registry.py +0 -0
  356. {dulus-3.13.6 → dulus-3.13.7}/tests/test_tree_ls.py +0 -0
  357. {dulus-3.13.6 → dulus-3.13.7}/tests/test_voice.py +0 -0
  358. {dulus-3.13.6 → dulus-3.13.7}/tests/test_webbridge_tabs.py +0 -0
  359. {dulus-3.13.6 → dulus-3.13.7}/tests/test_wire_events.py +0 -0
  360. {dulus-3.13.6 → dulus-3.13.7}/tmux_tools.py +0 -0
  361. {dulus-3.13.6 → dulus-3.13.7}/token_budget.py +0 -0
  362. {dulus-3.13.6 → dulus-3.13.7}/tool_registry.py +0 -0
  363. {dulus-3.13.6 → dulus-3.13.7}/tools.py +0 -0
  364. {dulus-3.13.6 → dulus-3.13.7}/tools_background.py +0 -0
  365. {dulus-3.13.6 → dulus-3.13.7}/tools_think.py +0 -0
  366. {dulus-3.13.6 → dulus-3.13.7}/tools_todo.py +0 -0
  367. {dulus-3.13.6 → dulus-3.13.7}/ui/__init__.py +0 -0
  368. {dulus-3.13.6 → dulus-3.13.7}/ui/input.py +0 -0
  369. {dulus-3.13.6 → dulus-3.13.7}/ui/render.py +0 -0
  370. {dulus-3.13.6 → dulus-3.13.7}/updater.py +0 -0
  371. {dulus-3.13.6 → dulus-3.13.7}/voice/__init__.py +0 -0
  372. {dulus-3.13.6 → dulus-3.13.7}/voice/audio_utils.py +0 -0
  373. {dulus-3.13.6 → dulus-3.13.7}/voice/keyterms.py +0 -0
  374. {dulus-3.13.6 → dulus-3.13.7}/voice/recorder.py +0 -0
  375. {dulus-3.13.6 → dulus-3.13.7}/voice/stt.py +0 -0
  376. {dulus-3.13.6 → dulus-3.13.7}/voice/tts.py +0 -0
  377. {dulus-3.13.6 → dulus-3.13.7}/voice/wake_word.py +0 -0
  378. {dulus-3.13.6 → dulus-3.13.7}/webbridge/__init__.py +0 -0
  379. {dulus-3.13.6 → dulus-3.13.7}/webbridge/core.py +0 -0
  380. {dulus-3.13.6 → dulus-3.13.7}/webbridge/tools.py +0 -0
  381. {dulus-3.13.6 → dulus-3.13.7}/webchat.py +0 -0
  382. {dulus-3.13.6 → dulus-3.13.7}/webchat_server.py +0 -0
  383. {dulus-3.13.6 → dulus-3.13.7}/webchat_ui/__init__.py +0 -0
  384. {dulus-3.13.6 → dulus-3.13.7}/webchat_ui/app.js +0 -0
  385. {dulus-3.13.6 → dulus-3.13.7}/webchat_ui/index.html +0 -0
  386. {dulus-3.13.6 → dulus-3.13.7}/webchat_ui/style.css +0 -0
  387. {dulus-3.13.6 → dulus-3.13.7}/welcome.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: dulus
3
- Version: 3.13.6
3
+ Version: 3.13.7
4
4
  Summary: Provider-independent agent runtime for the terminal — cloud and local models, real tools, MCP, plugins, memory, sub-agents, and Dulus OS.
5
5
  Author-email: KevRojo <dev@dulus.ai>
6
6
  Maintainer-email: KevRojo <dev@dulus.ai>
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [3.13.7] - 2026-09-01
11
+
12
+ ### Added
13
+ - **EU/GDPR Gemini consent solver**: pre-seed Google `CONSENT` + `SOCS` cookies; multi-locale Accept clicks across main page and consent iframes so headless EEA boxes (OVH/Hetzner/…) reach the chat composer
14
+ - **`/news` Dulus Radio**: live broadcast console — latest 3 by default, `/news N` / `/news more` / `/news all` (archive opt-in); auto-shows on interactive boot
15
+ - **Boot Fuel nudge**: points at `/fuel`, OpenAI-compatible endpoint `https://control.dulus.ai/v1`, and **Qwen UNCENSORED** drop — no premium/binary/GUI pitch
16
+
17
+ ### Changed
18
+ - Public boot nag no longer pitches the install binary / Premium GUI / MCP-skills trial wall
19
+
10
20
  ## [3.13.6] - 2026-08-31
11
21
 
12
22
  ### Fixed
@@ -2,6 +2,10 @@
2
2
 
3
3
  ## 🔥🔥🔥 News (Pacific Time)
4
4
 
5
+ - September 1, 2026 (**v3.13.7 — 🔥 TODAY — Qwen UNCENSORED on the endpoint**): **Stock Fuel. Now.** Run `/login dulus`, then `/fuel` for balance + a console QR you can scan with Phantom/Solflare. Our router is **OpenAI-compatible** at `https://control.dulus.ai/v1` — drop it into anything that speaks `/v1/chat/completions`. **Today** we put **Qwen UNCENSORED** on that endpoint. Buy `$DULUS`, load Fuel, go. No premium pitch, no binary wall — the open runtime stays free; Fuel pays for hosted compute. `pip install -U dulus`. 🦅🇩🇴⛽
6
+
7
+ - August 31, 2026 (**Community — EU & the world**): **Something is happening — and we’re not slowing down.** **~25,000 pip installs** and climbing. Git clones just ripped: **2,058 clones / 326 unique cloners in 14 days**, with a vertical spike at the end of the month. To our people in **Europe**, this one’s for you. Google’s **GDPR / “Before you continue” consent walls**, cookie banners, and privacy popups used to brick keyless Gemini harvest on EEA and headless EU boxes (OVH, Hetzner, and friends) — no human to click **Accept**, composer never mounts, intercept times out. Dulus now ships a real **popup / consent solver**: pre-seeds Google `CONSENT` + `SOCS` cookies, dismisses EU privacy gates across the main page **and** consent iframes, multi-locale Accept clicks (`Accept all` / `Aceptar todo` / `Tout accepter` / `Alle akzeptieren` / …), so the agent can actually reach the chat. `/news` is now a live **broadcast** (latest 3 by default — no more dumping the whole archive into your console). Same posture as always — **no API key, no credit card, your machine**. Humans built this for humans. Europe, you’re in. Everybody else too. We’ll keep shipping like this. Love you. `pip install -U dulus`. 🦅🇩🇴
8
+
5
9
  - August 31, 2026 (**v3.13.6**): **CI quality locked green.** WebChat guards `CONFIG` before pop (pyright clean) and background workers launch Windows-safe without bash-quoted `python -c` — win-3.13 flake gone. Hotfix patch on top of 3.13.5. `pip install -U dulus`. 🦅
6
10
 
7
11
  - August 31, 2026 (**v3.13.5**): **🏆 short_memory stopped vanishing.** Gold memories + soul now live in the **system prompt** every turn (always-on — `/mem_palace off` no longer starves them). WebChat seeds gold for the transcript; the agent/lookback strip those display blobs before the provider call so first-turn consolidate can’t eat your context. Second `/compact` no longer wipes the Loopback archive. Engine + WebChat fix — not a desktop-GUI rewrite. `pip install -U dulus`. 🦅
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: dulus
3
- Version: 3.13.6
3
+ Version: 3.13.7
4
4
  Summary: Provider-independent agent runtime for the terminal — cloud and local models, real tools, MCP, plugins, memory, sub-agents, and Dulus OS.
5
5
  Author-email: KevRojo <dev@dulus.ai>
6
6
  Maintainer-email: KevRojo <dev@dulus.ai>
@@ -395,7 +395,7 @@ try:
395
395
  from importlib.metadata import version as _pkg_version
396
396
  VERSION = _pkg_version("dulus")
397
397
  except Exception:
398
- VERSION = "3.13.6" # dev fallback — keep in sync with pyproject.toml
398
+ VERSION = "3.13.7" # dev fallback — keep in sync with pyproject.toml
399
399
 
400
400
  # ── Machine-readable protocol output (`--output json`) ─────────────────────
401
401
  # Dulus is increasingly run as a child process by another agent runtime (an
@@ -1659,12 +1659,12 @@ def _theme_accent(config: dict):
1659
1659
 
1660
1660
 
1661
1661
  def _print_login_nag(config: dict) -> None:
1662
- """Freeware-style boot nudge shown when no Dulus account is signed in.
1662
+ """Boot nudge: /fuel + OpenAI-compatible endpoint + live drops.
1663
1663
 
1664
- Invites the user to `/login dulus` — which unlocks the Fuel-metered Dulus
1665
- router (12 models on $DULUS). This is NOT a wall: local and web-session
1666
- models keep working without it; the router is gated server-side regardless.
1667
- Best-effort — never breaks boot.
1664
+ No premium / binary / GUI pitch — public stays free. Points people at
1665
+ `/fuel`, the OpenAI-compatible control plane, and today's model drop.
1666
+ Best-effort — never breaks boot. Still skips when already signed in
1667
+ (they already know the path).
1668
1668
  """
1669
1669
  try:
1670
1670
  import dulus_account
@@ -1672,31 +1672,28 @@ def _print_login_nag(config: dict) -> None:
1672
1672
  if store.get("access_token") and not dulus_account._token_expired(store):
1673
1673
  return # signed in — no nag
1674
1674
  except Exception:
1675
- pass # can't tell → show it (nudge toward the account)
1675
+ pass # can't tell → show it
1676
1676
  try:
1677
1677
  accent = _theme_accent(config) or "cyan"
1678
1678
  except Exception:
1679
1679
  accent = "cyan"
1680
1680
  try:
1681
- # Import gradient effect for URL
1682
1681
  from cli_animations.ansi import gradient_text, ORANGE, CYAN
1683
- # Create gradient URL
1684
1682
  dx_url = "https://tinyurl.com/DulusDexScreener"
1685
1683
  styled_dx_url = gradient_text(dx_url, ORANGE, CYAN)
1686
-
1687
- print(clr(" Dulus free - one step left: claim your free account.", accent))
1688
- print(" Run " + clr("/login dulus", "cyan", "bold")
1689
- + " for your AI username + the Dulus router (12 models on "
1690
- + clr("$DULUS", accent) + ").")
1691
- print(" Fuel is LIVE - buy " + clr("$DULUS", accent)
1692
- + " now and run " + clr("dulus-a-9b", "cyan") + ", "
1693
- + clr("dulus-b-27b", "cyan") + ", " + clr("dulus-x-397b", "cyan")
1694
- + " + the uncensored " + clr("dulus-f", "cyan") + ".")
1695
- print(clr(" Local & web models keep working without it.", "cyan"))
1696
- print()
1697
- print(clr(" Trial time! Go to https://dulus.ai/ and download the install binary", "green"))
1698
- print(clr(" according to your OS. Log in and use the Dulus GUI which brings", "green"))
1699
- print(clr(" 2,000 MCPS + 60,000 skills!", "green"))
1684
+ api_url = gradient_text("https://control.dulus.ai/v1", ORANGE, CYAN)
1685
+
1686
+ # Fuel + OpenAI-compatible + today's uncensored drop. No premium pitch.
1687
+ print(clr(" ⛽ Fuel is LIVE — buy $DULUS, load Fuel, run the router.", accent))
1688
+ print(" " + clr("/login dulus", "cyan", "bold")
1689
+ + " → free account · "
1690
+ + clr("/fuel", "cyan", "bold")
1691
+ + " → balance + reload QR (Phantom/Solflare)")
1692
+ print(" OpenAI-compatible endpoint → " + api_url)
1693
+ print(" " + clr("TODAY", "yellow", "bold")
1694
+ + ": " + clr("Qwen UNCENSORED", "cyan", "bold")
1695
+ + " drops on our endpoint. Stock Fuel now.")
1696
+ print(clr(" Local & web models keep working without it.", "dim"))
1700
1697
  print()
1701
1698
  print(" Buy $DULUS now:")
1702
1699
  print(f" {styled_dx_url}")
@@ -3895,6 +3892,238 @@ def _launch_harvest_browser(p, pw_profile, headless):
3895
3892
  return p.chromium.launch_persistent_context(**common)
3896
3893
 
3897
3894
 
3895
+
3896
+ def _seed_google_consent_cookies(context) -> None:
3897
+ """Pre-seed Google CONSENT/SOCS cookies so EEA walls often never appear.
3898
+
3899
+ Must run on the browser context BEFORE the first navigation to
3900
+ gemini.google.com. Safe no-op if the context rejects the cookies.
3901
+ """
3902
+ try:
3903
+ seed = []
3904
+ for domain in (".google.com", ".gemini.google.com", ".youtube.com"):
3905
+ seed.append({
3906
+ "name": "CONSENT", "value": "YES+cb.20210328-17-p0.en+FX+710",
3907
+ "domain": domain, "path": "/",
3908
+ })
3909
+ seed.append({
3910
+ "name": "SOCS",
3911
+ "value": "CAESHAgBEhJnd3NfMjAyNTA4MzEtMF9SQzIaAmVuIAEaBgiA_LWmBg",
3912
+ "domain": domain, "path": "/",
3913
+ })
3914
+ context.add_cookies(seed)
3915
+ except Exception:
3916
+ pass
3917
+
3918
+
3919
+ def _dismiss_eu_privacy_gates(page, log=None) -> bool:
3920
+ """Click through EU/GDPR consent walls that block Gemini on headless EU IPs.
3921
+
3922
+ Google (and gemini.google.com) show a "Before you continue" / cookie consent
3923
+ dialog for EEA users. On a headless OVH/Hetzner/etc box there is no human to
3924
+ press Accept — the chat composer never mounts, and the intercept times out.
3925
+
3926
+ Strategy (best-effort, multi-locale, multi-frame):
3927
+ 1. Seed CONSENT cookies so some Google properties skip the wall.
3928
+ 2. Click known Accept selectors on the main page + every consent iframe.
3929
+ 3. JS sweep: any button whose label matches Accept/Aceptar/Tout accepter/…
3930
+ Returns True if something was clicked or cookies were seeded.
3931
+ """
3932
+ def _log(msg: str) -> None:
3933
+ if log:
3934
+ try:
3935
+ log(msg)
3936
+ except Exception:
3937
+ pass
3938
+
3939
+ clicked = False
3940
+
3941
+ # ── 1) Seed consent cookies (domain-wide) ──────────────────────────────
3942
+ # CONSENT=YES+… is the classic "already accepted" signal Google still
3943
+ # honours on many properties. SOCS is the newer companion cookie; a
3944
+ # permissive value stops the SOCS interstitial on recent Google UIs.
3945
+ try:
3946
+ ctx = page.context
3947
+ seed = []
3948
+ for domain in (".google.com", ".gemini.google.com", ".youtube.com"):
3949
+ seed.append({
3950
+ "name": "CONSENT", "value": "YES+cb.20210328-17-p0.en+FX+710",
3951
+ "domain": domain, "path": "/",
3952
+ })
3953
+ seed.append({
3954
+ "name": "SOCS",
3955
+ "value": "CAESHAgBEhJnd3NfMjAyNTA4MzEtMF9SQzIaAmVuIAEaBgiA_LWmBg",
3956
+ "domain": domain, "path": "/",
3957
+ })
3958
+ ctx.add_cookies(seed)
3959
+ clicked = True # cookies count as progress even if UI still shows
3960
+ except Exception:
3961
+ pass
3962
+
3963
+ # Labels Google actually paints on the Accept button across EEA locales.
3964
+ # Keep these short + case-insensitive; we match as substring on button text.
3965
+ _ACCEPT_LABELS = (
3966
+ "accept all", "i agree", "agree", "accept", "got it", "continue",
3967
+ "aceptar todo", "acepto", "aceptar", "de acuerdo", "continuar",
3968
+ "tout accepter", "j'accepte", "accepter", "tout autoriser",
3969
+ "alle akzeptieren", "ich stimme zu", "akzeptieren", "zustimmen",
3970
+ "accetta tutto", "accetto", "accetta",
3971
+ "aceitar tudo", "aceito", "aceitar",
3972
+ "alles accepteren", "akkoord",
3973
+ "zaakceptuj wszystko", "przyjmuję",
3974
+ "přijmout vše", "přijmout",
3975
+ "acceptați tot", "accept all cookies",
3976
+ "alle ablehnen", # never — we only click accept-side below
3977
+ )
3978
+ # Only the ACCEPT side — never "Reject all" / "Reject".
3979
+ _ACCEPT_ONLY = tuple(
3980
+ t for t in _ACCEPT_LABELS
3981
+ if not t.startswith("alle ablehnen") and "reject" not in t and "ablehnen" not in t
3982
+ and "rechazar" not in t and "refuser" not in t
3983
+ )
3984
+
3985
+ _CSS_SELECTORS = (
3986
+ "#L2AGLb", # classic Google "I agree"
3987
+ "button#L2AGLb",
3988
+ "button[aria-label*='Accept' i]",
3989
+ "button[aria-label*='Aceptar' i]",
3990
+ "button[aria-label*='Agree' i]",
3991
+ "button[aria-label*='Akzeptieren' i]",
3992
+ "button[aria-label*='Accetta' i]",
3993
+ "button[aria-label*='Accepter' i]",
3994
+ "form[action*='consent'] button",
3995
+ "div[role='dialog'] button",
3996
+ "[data-testid='accept-button']",
3997
+ "button.tHlp3d", # Google consent CSS class (varies)
3998
+ )
3999
+
4000
+ def _try_click_in(target) -> bool:
4001
+ """Click Accept inside a Page or Frame. Returns True on success."""
4002
+ # CSS selectors first (fast path)
4003
+ for sel in _CSS_SELECTORS:
4004
+ try:
4005
+ loc = target.locator(sel).first
4006
+ if loc.count() > 0 and loc.is_visible(timeout=400):
4007
+ txt = ""
4008
+ try:
4009
+ txt = (loc.inner_text(timeout=300) or "").strip().lower()
4010
+ except Exception:
4011
+ pass
4012
+ # Skip obvious Reject buttons if a selector was too broad
4013
+ if any(bad in txt for bad in ("reject", "rechazar", "refuser", "ablehnen", "rifiuta", "recusar")):
4014
+ continue
4015
+ loc.click(timeout=2000)
4016
+ return True
4017
+ except Exception:
4018
+ continue
4019
+
4020
+ # Text / aria-label sweep via JS (covers locale variants + shadow-ish UIs)
4021
+ try:
4022
+ labels = list(_ACCEPT_ONLY)
4023
+ hit = target.evaluate(
4024
+ r"""(labels) => {
4025
+ const bad = /reject|rechazar|refuser|ablehnen|rifiuta|recusar|nie\s+akcept/i;
4026
+ const nodes = [
4027
+ ...document.querySelectorAll('button, [role="button"], input[type="submit"], input[type="button"]')
4028
+ ];
4029
+ for (const el of nodes) {
4030
+ const t = (
4031
+ (el.innerText || el.textContent || el.value || '') + ' ' +
4032
+ (el.getAttribute('aria-label') || '') + ' ' +
4033
+ (el.getAttribute('data-mdc-dialog-action') || '')
4034
+ ).replace(/\s+/g, ' ').trim().toLowerCase();
4035
+ if (!t || bad.test(t)) continue;
4036
+ for (const lab of labels) {
4037
+ if (t === lab || t.includes(lab)) {
4038
+ el.click();
4039
+ return t.slice(0, 60);
4040
+ }
4041
+ }
4042
+ }
4043
+ const el = document.querySelector('#L2AGLb');
4044
+ if (el) { el.click(); return 'L2AGLb'; }
4045
+ return null;
4046
+ }""",
4047
+ labels,
4048
+ )
4049
+ if hit:
4050
+ return True
4051
+ except Exception:
4052
+ pass
4053
+ return False
4054
+
4055
+ # ── 2) Main page + consent iframes, a few rounds (dialog can re-paint) ──
4056
+ for round_i in range(4):
4057
+ try:
4058
+ page.wait_for_timeout(600 if round_i == 0 else 900)
4059
+ except Exception:
4060
+ pass
4061
+
4062
+ # Main document
4063
+ if _try_click_in(page):
4064
+ clicked = True
4065
+ _log(f" EU consent dismissed (page, round {round_i + 1})")
4066
+ try:
4067
+ page.wait_for_timeout(800)
4068
+ except Exception:
4069
+ pass
4070
+
4071
+ # Iframes (consent.google.com often lives here)
4072
+ try:
4073
+ frames = list(page.frames)
4074
+ except Exception:
4075
+ frames = []
4076
+ for fr in frames:
4077
+ try:
4078
+ fu = (fr.url or "").lower()
4079
+ except Exception:
4080
+ fu = ""
4081
+ # Always try consent frames; also try unnamed frames on first rounds
4082
+ if "consent" in fu or "accounts.google" in fu or round_i == 0:
4083
+ if _try_click_in(fr):
4084
+ clicked = True
4085
+ _log(f" EU consent dismissed (iframe, round {round_i + 1})")
4086
+ try:
4087
+ page.wait_for_timeout(800)
4088
+ except Exception:
4089
+ pass
4090
+
4091
+ # If we landed ON consent.google.com as top-level, Accept then wait for redirect back
4092
+ try:
4093
+ u = (page.url or "").lower()
4094
+ except Exception:
4095
+ u = ""
4096
+ if "consent.google" in u or "consent.youtube" in u:
4097
+ if _try_click_in(page):
4098
+ clicked = True
4099
+ _log(" EU consent page accepted — waiting for redirect...")
4100
+ try:
4101
+ page.wait_for_url("**gemini.google.com**", timeout=15000)
4102
+ except Exception:
4103
+ try:
4104
+ page.wait_for_timeout(2000)
4105
+ except Exception:
4106
+ pass
4107
+
4108
+ # Stop early once the Gemini composer is reachable (consent is gone)
4109
+ try:
4110
+ for sel in (
4111
+ "rich-textarea .ql-editor",
4112
+ "div.ql-editor[contenteditable='true']",
4113
+ "div[contenteditable='true']",
4114
+ "textarea",
4115
+ ):
4116
+ loc = page.locator(sel).first
4117
+ if loc.count() > 0 and loc.is_visible(timeout=300):
4118
+ return True
4119
+ except Exception:
4120
+ pass
4121
+
4122
+ if clicked:
4123
+ _log(" EU privacy gates handled (cookies and/or Accept click)")
4124
+ return clicked
4125
+
4126
+
3898
4127
  def cmd_harvest_gemini(_args: str, _state, config) -> "bool | None":
3899
4128
  """Harvest fresh session data from gemini.google.com using Playwright.
3900
4129
 
@@ -3934,6 +4163,10 @@ def cmd_harvest_gemini(_args: str, _state, config) -> "bool | None":
3934
4163
  browser = _launch_harvest_browser(p, pw_profile, headless)
3935
4164
 
3936
4165
  page = browser.pages[0] if browser.pages else browser.new_page()
4166
+ try:
4167
+ _seed_google_consent_cookies(page.context)
4168
+ except Exception:
4169
+ pass
3937
4170
 
3938
4171
  intercepted = []
3939
4172
 
@@ -3962,6 +4195,14 @@ def cmd_harvest_gemini(_args: str, _state, config) -> "bool | None":
3962
4195
  except Exception:
3963
4196
  pass
3964
4197
 
4198
+ # EU/EEA GDPR wall: without this, headless servers in Europe hang
4199
+ # forever because the Accept button never gets pressed and the
4200
+ # Gemini composer never mounts.
4201
+ try:
4202
+ _dismiss_eu_privacy_gates(page, log=info)
4203
+ except Exception as _cg:
4204
+ warn(f" Consent dismiss skipped: {_cg}")
4205
+
3965
4206
  # Auto-send "DULUS" so headless servers / VMs don't need a human in the browser.
3966
4207
  info("Sending 'DULUS' to Gemini chat (headless auto-send)...")
3967
4208
  auto_send_ok = False
@@ -10172,24 +10413,215 @@ def cmd_update(args: str, state, config) -> bool:
10172
10413
  return True
10173
10414
 
10174
10415
 
10175
- def cmd_news(args: str, state, config) -> bool:
10176
- """Show the latest news from docs/news.md."""
10416
+ def _load_news_entries() -> list[str]:
10417
+ """Parse docs/news.md into ordered dispatch strings (newest first)."""
10177
10418
  news_file = Path(__file__).parent / "docs" / "news.md"
10178
10419
  if not news_file.exists():
10179
- err("News file not found.")
10180
- return True
10181
-
10420
+ return []
10182
10421
  try:
10183
10422
  content = news_file.read_text(encoding="utf-8")
10423
+ except Exception:
10424
+ return []
10425
+ entries: list[str] = []
10426
+ buf: list[str] = []
10427
+ for line in content.splitlines():
10428
+ if line.startswith("- "):
10429
+ if buf:
10430
+ entries.append("\n".join(buf).strip())
10431
+ buf = [line]
10432
+ elif buf:
10433
+ if line.startswith("#"):
10434
+ entries.append("\n".join(buf).strip())
10435
+ buf = []
10436
+ else:
10437
+ buf.append(line)
10438
+ if buf:
10439
+ entries.append("\n".join(buf).strip())
10440
+ return entries
10441
+
10442
+
10443
+ def _news_strip_md(s: str) -> str:
10444
+ s = s.replace("**", "").replace("__", "")
10445
+ s = s.replace("`", "")
10446
+ s = re.sub(r"\[([^\]]+)\]\([^)]+\)", r"\1", s)
10447
+ return re.sub(r"\s+", " ", s).strip()
10448
+
10449
+
10450
+ def _news_parse_entry(entry: str) -> tuple[str, str, str, str]:
10451
+ """Return (date, tag, headline, body) from a news bullet."""
10452
+ text = entry[2:].strip() if entry.startswith("- ") else entry.strip()
10453
+ text = text.replace("\n", " ")
10454
+ date, tag, headline, body = "", "", "", text
10455
+ m = re.match(
10456
+ r"^(?P<date>.+?)\s*(?:\(\*\*(?P<tag>.*?)\*\*\)|\((?P<tag2>[^)]+)\))?\s*:\s*(?P<rest>.*)$",
10457
+ text,
10458
+ )
10459
+ if m:
10460
+ date = (m.group("date") or "").strip()
10461
+ tag = (m.group("tag") or m.group("tag2") or "").strip()
10462
+ rest = (m.group("rest") or "").strip()
10463
+ hm = re.match(r"^\*\*(?P<head>.*?)\*\*\s*(?P<body>.*)$", rest)
10464
+ if hm:
10465
+ headline = hm.group("head").strip().rstrip(".")
10466
+ body = hm.group("body").strip()
10467
+ else:
10468
+ parts = re.split(r"(?<=[.!?])\s+", rest, maxsplit=1)
10469
+ headline = parts[0].strip().rstrip(".")
10470
+ body = parts[1].strip() if len(parts) > 1 else ""
10471
+ else:
10472
+ headline = text[:80]
10473
+ body = text[80:]
10474
+ return (
10475
+ _news_strip_md(date),
10476
+ _news_strip_md(tag),
10477
+ _news_strip_md(headline),
10478
+ _news_strip_md(body),
10479
+ )
10480
+
10481
+
10482
+ def _news_wrap(text: str, width: int = 72, max_chars: int = 360) -> list[str]:
10483
+ text = (text or "").strip()
10484
+ if not text:
10485
+ return []
10486
+ if len(text) > max_chars:
10487
+ cut = text[: max_chars - 1]
10488
+ sp = cut.rfind(" ")
10489
+ if sp > max_chars // 2:
10490
+ cut = cut[:sp]
10491
+ text = cut.rstrip(".,;:") + "…"
10492
+ words = text.split()
10493
+ out: list[str] = []
10494
+ cur = ""
10495
+ for w in words:
10496
+ trial = (cur + " " + w).strip()
10497
+ if len(trial) > width and cur:
10498
+ out.append(cur)
10499
+ cur = w
10500
+ else:
10501
+ cur = trial
10502
+ if cur:
10503
+ out.append(cur)
10504
+ return out
10505
+
10506
+
10507
+ def _print_news_broadcast(
10508
+ limit: int = 3,
10509
+ *,
10510
+ boot: bool = False,
10511
+ quiet_footer: bool = False,
10512
+ ) -> bool:
10513
+ """Radio-format Dulus dispatch board. Used by /news and interactive boot.
10514
+
10515
+ boot=True → slightly tighter body (ON AIR teaser, still 3 tracks).
10516
+ """
10517
+ entries = _load_news_entries()
10518
+ if not entries:
10519
+ if not boot:
10520
+ err("News file not found or empty.")
10521
+ return False
10522
+
10523
+ limit = max(1, min(30, int(limit or 3)))
10524
+ picked = entries[:limit]
10525
+ total = len(entries)
10526
+ # Boot stays punchy; manual /news lets the body breathe a bit more.
10527
+ body_cap = 220 if boot else 420
10528
+ width = 70 if boot else 74
10529
+
10530
+ rule = "━" * 48
10531
+ print()
10532
+ print(clr(f" ◆{rule}◆", "cyan", "dim"))
10533
+ print(
10534
+ clr(" 📡 DULUS RADIO", "cyan", "bold")
10535
+ + clr(" · ON AIR", "green", "bold")
10536
+ + clr(f" · {len(picked)}/{total}", "dim")
10537
+ )
10538
+ print(clr(f" ◆{rule}◆", "cyan", "dim"))
10539
+ print()
10540
+
10541
+ for idx, entry in enumerate(picked, 1):
10542
+ date, tag, headline, body = _news_parse_entry(entry)
10543
+ label = "NOW" if idx == 1 else "WAS"
10544
+ tag_bit = tag if tag else "dispatch"
10545
+ # ♪ NOW · Aug 31 · Community — EU & the world
10546
+ meta = f" ♪ {label} · {date}"
10547
+ if tag_bit:
10548
+ meta += f" · {tag_bit}"
10549
+ print(clr(meta, "green", "bold"))
10550
+ if headline:
10551
+ print(clr(f" » {headline}", "white", "bold"))
10552
+ for bl in _news_wrap(body, width=width, max_chars=body_cap):
10553
+ print(clr(f" {bl}", "dim"))
10554
+ if idx < len(picked):
10555
+ print(clr(" · · · · · · · · · · · · · · · · · · · · · ·", "dim"))
10556
+
10557
+ print()
10558
+ print(clr(f" ◆{rule}◆", "cyan", "dim"))
10559
+ if not quiet_footer:
10560
+ if boot:
10561
+ print(
10562
+ clr(" stay tuned", "dim")
10563
+ + clr(" · ", "dim")
10564
+ + clr("/news", "cyan")
10565
+ + clr(" more · humans only · ", "dim")
10566
+ + clr("pip install -U dulus", "cyan")
10567
+ + clr(" 🦅", "dim")
10568
+ )
10569
+ else:
10570
+ print(
10571
+ clr(" /news", "cyan")
10572
+ + clr(" · ", "dim")
10573
+ + clr("/news 5", "cyan")
10574
+ + clr(" · ", "dim")
10575
+ + clr("/news more", "cyan")
10576
+ + clr(" · ", "dim")
10577
+ + clr("/news all", "yellow")
10578
+ + clr(" · humans only 🦅", "dim")
10579
+ )
10580
+ print()
10581
+ return True
10582
+
10583
+
10584
+ def cmd_news(args: str, state, config) -> bool:
10585
+ """Dulus Radio — latest dispatches from docs/news.md.
10586
+
10587
+ /news — latest 3 (radio console) [also auto on boot]
10588
+ /news 5 — latest N (1–30)
10589
+ /news all — full archive (opt-in; can be huge)
10590
+ /news more — latest 10
10591
+ """
10592
+ raw_arg = (args or "").strip().lower()
10593
+ show_all = raw_arg in ("all", "full", "archive", "everything", "*")
10594
+ if raw_arg in ("more", "long"):
10595
+ limit = 10
10596
+ elif raw_arg.isdigit():
10597
+ limit = max(1, min(30, int(raw_arg)))
10598
+ else:
10599
+ limit = 3 # default radio: top 3 :)
10600
+
10601
+ if show_all:
10602
+ news_file = Path(__file__).parent / "docs" / "news.md"
10603
+ if not news_file.exists():
10604
+ err("News file not found.")
10605
+ return True
10606
+ try:
10607
+ content = news_file.read_text(encoding="utf-8")
10608
+ except Exception as e:
10609
+ err(f"Failed to read news: {e}")
10610
+ return True
10184
10611
  if _RICH:
10185
- from rich.console import Console
10186
- from rich.markdown import Markdown
10187
- c = Console()
10188
- c.print(Markdown(content))
10612
+ try:
10613
+ from rich.console import Console
10614
+ from rich.markdown import Markdown
10615
+ Console().print(Markdown(content))
10616
+ except Exception:
10617
+ print(content)
10189
10618
  else:
10190
10619
  print(content)
10191
- except Exception as e:
10192
- err(f"Failed to read news: {e}")
10620
+ print()
10621
+ print(clr(" (full archive) · /news for Dulus Radio", "dim"))
10622
+ return True
10623
+
10624
+ _print_news_broadcast(limit, boot=False)
10193
10625
  return True
10194
10626
 
10195
10627
 
@@ -11799,7 +12231,7 @@ _CMD_META: dict[str, tuple[str, list[str]]] = {
11799
12231
  "quit": ("Exit (alias for /exit)", []),
11800
12232
  "resume": ("Resume last session", []),
11801
12233
  "update": ("Self-update Dulus from PyPI", ["now", "check", "status", "on", "off"]),
11802
- "news": ("Show latest project news", []),
12234
+ "news": ("Dulus Radio — latest 3 (also auto on boot)", ["all", "more", "5"]),
11803
12235
  "claude_chats": ("List Claude.ai conversations", ["all"]),
11804
12236
  "gemini_chats": ("Manage Gemini Web conversations", ["new"]),
11805
12237
  "gemini_harvest": ("Harvest Gemini Web cookies (alias)", []),
@@ -12340,7 +12772,7 @@ def repl(config: dict, initial_prompt: str | None = None):
12340
12772
  "⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠛⠻⢾⣯⣧⣬⣭⣤⡶⠖⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀",
12341
12773
  ]
12342
12774
  _DULUS_LOGO.append(" " + clr("v" + VERSION, "green", "bold"))
12343
- _DULUS_LOGO.append(" " + clr("New: /lang — speak any language or role. Type /news", "cyan", "dim"))
12775
+ _DULUS_LOGO.append(" " + clr("📡 Dulus Radio is ON AIR at boot — EU consent solver live", "cyan", "dim"))
12344
12776
  _DULUS_LOGO.append(" ")
12345
12777
 
12346
12778
  # Shared Dulus animation language for the short boot beat. Redirected
@@ -12424,13 +12856,21 @@ def repl(config: dict, initial_prompt: str | None = None):
12424
12856
  print(msg)
12425
12857
  print()
12426
12858
 
12427
- # Freeware-style login nudge (every interactive boot until signed in).
12428
- # Not a wall — it just funnels anonymous users toward a free account.
12859
+ # Fuel + OpenAI-compat + live drop nudge (until signed in).
12860
+ # Premium / binary / GUI pitch intentionally removed — public stays free.
12429
12861
  try:
12430
12862
  _print_login_nag(config)
12431
12863
  except Exception:
12432
12864
  pass
12433
12865
 
12866
+ # ── Dulus Radio ON AIR (default) ────────────────────────────────────
12867
+ # Don't make humans type /news to hear us. Latest 3 dispatches, radio
12868
+ # format, every interactive boot. /news still works for more/all.
12869
+ try:
12870
+ _print_news_broadcast(3, boot=True)
12871
+ except Exception:
12872
+ pass
12873
+
12434
12874
  # First-run /doctor — runs LAST, after every boot print so it's the
12435
12875
  # final thing the user sees before the prompt. Welcome wizard set
12436
12876
  # the flag; we cleared it into _first_run_doctor_pending earlier.
@@ -10,7 +10,7 @@ build-backend = "setuptools.build_meta"
10
10
 
11
11
  [project]
12
12
  name = "dulus"
13
- version = "3.13.6"
13
+ version = "3.13.7"
14
14
  description = "Provider-independent agent runtime for the terminal — cloud and local models, real tools, MCP, plugins, memory, sub-agents, and Dulus OS."
15
15
  readme = "README.md"
16
16
  requires-python = ">=3.10"
File without changes