dulus 3.12.0__tar.gz → 3.13.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 (398) hide show
  1. {dulus-3.12.0/dulus.egg-info → dulus-3.13.0}/PKG-INFO +28 -3
  2. {dulus-3.12.0 → dulus-3.13.0}/README.md +27 -2
  3. {dulus-3.12.0 → dulus-3.13.0}/agent.py +27 -1
  4. {dulus-3.12.0 → dulus-3.13.0}/common.py +23 -0
  5. {dulus-3.12.0 → dulus-3.13.0}/docs/CHANGELOG.md +33 -0
  6. {dulus-3.12.0 → dulus-3.13.0}/docs/news.md +2 -0
  7. {dulus-3.12.0 → dulus-3.13.0/dulus.egg-info}/PKG-INFO +28 -3
  8. {dulus-3.12.0 → dulus-3.13.0}/dulus.egg-info/SOURCES.txt +0 -11
  9. {dulus-3.12.0 → dulus-3.13.0}/dulus.egg-info/top_level.txt +0 -1
  10. {dulus-3.12.0 → dulus-3.13.0}/dulus.py +100 -24
  11. {dulus-3.12.0 → dulus-3.13.0}/pyproject.toml +1 -1
  12. dulus-3.13.0/tests/test_json_protocol.py +456 -0
  13. {dulus-3.12.0 → dulus-3.13.0}/webchat_ui/index.html +6 -0
  14. dulus-3.12.0/sandbox/components.json +0 -22
  15. dulus-3.12.0/sandbox/eslint.config.js +0 -23
  16. dulus-3.12.0/sandbox/index.html +0 -16
  17. dulus-3.12.0/sandbox/package-lock.json +0 -8274
  18. dulus-3.12.0/sandbox/package.json +0 -80
  19. dulus-3.12.0/sandbox/postcss.config.js +0 -6
  20. dulus-3.12.0/sandbox/tailwind.config.js +0 -84
  21. dulus-3.12.0/sandbox/tsconfig.app.json +0 -34
  22. dulus-3.12.0/sandbox/tsconfig.json +0 -17
  23. dulus-3.12.0/sandbox/tsconfig.node.json +0 -26
  24. dulus-3.12.0/sandbox/vite.config.ts +0 -28
  25. dulus-3.12.0/tests/test_json_protocol.py +0 -203
  26. {dulus-3.12.0 → dulus-3.13.0}/LICENSE +0 -0
  27. {dulus-3.12.0 → dulus-3.13.0}/MANIFEST.in +0 -0
  28. {dulus-3.12.0 → dulus-3.13.0}/algolia_search.py +0 -0
  29. {dulus-3.12.0 → dulus-3.13.0}/analytics.py +0 -0
  30. {dulus-3.12.0 → dulus-3.13.0}/backend/__init__.py +0 -0
  31. {dulus-3.12.0 → dulus-3.13.0}/backend/agents_bridge.py +0 -0
  32. {dulus-3.12.0 → dulus-3.13.0}/backend/compressor.py +0 -0
  33. {dulus-3.12.0 → dulus-3.13.0}/backend/context.py +0 -0
  34. {dulus-3.12.0 → dulus-3.13.0}/backend/githook.py +0 -0
  35. {dulus-3.12.0 → dulus-3.13.0}/backend/marketplace.py +0 -0
  36. {dulus-3.12.0 → dulus-3.13.0}/backend/mempalace_bridge.py +0 -0
  37. {dulus-3.12.0 → dulus-3.13.0}/backend/paths.py +0 -0
  38. {dulus-3.12.0 → dulus-3.13.0}/backend/personas.py +0 -0
  39. {dulus-3.12.0 → dulus-3.13.0}/backend/plugins.py +0 -0
  40. {dulus-3.12.0 → dulus-3.13.0}/backend/server.py +0 -0
  41. {dulus-3.12.0 → dulus-3.13.0}/backend/tasks.py +0 -0
  42. {dulus-3.12.0 → dulus-3.13.0}/banners/__init__.py +0 -0
  43. {dulus-3.12.0 → dulus-3.13.0}/banners/banner_rotator.py +0 -0
  44. {dulus-3.12.0 → dulus-3.13.0}/banners/banners_dulus_ai.txt +0 -0
  45. {dulus-3.12.0 → dulus-3.13.0}/batch_api.py +0 -0
  46. {dulus-3.12.0 → dulus-3.13.0}/checkpoint/__init__.py +0 -0
  47. {dulus-3.12.0 → dulus-3.13.0}/checkpoint/hooks.py +0 -0
  48. {dulus-3.12.0 → dulus-3.13.0}/checkpoint/store.py +0 -0
  49. {dulus-3.12.0 → dulus-3.13.0}/checkpoint/types.py +0 -0
  50. {dulus-3.12.0 → dulus-3.13.0}/cli_animations/__init__.py +0 -0
  51. {dulus-3.12.0 → dulus-3.13.0}/cli_animations/__main__.py +0 -0
  52. {dulus-3.12.0 → dulus-3.13.0}/cli_animations/ansi.py +0 -0
  53. {dulus-3.12.0 → dulus-3.13.0}/cli_animations/assets/ascii/dulus_logo.txt +0 -0
  54. {dulus-3.12.0 → dulus-3.13.0}/cli_animations/assets/ascii/eagle.txt +0 -0
  55. {dulus-3.12.0 → dulus-3.13.0}/cli_animations/assets/ascii/terminal_art.txt +0 -0
  56. {dulus-3.12.0 → dulus-3.13.0}/cli_animations/banners.py +0 -0
  57. {dulus-3.12.0 → dulus-3.13.0}/cli_animations/demo.py +0 -0
  58. {dulus-3.12.0 → dulus-3.13.0}/cli_animations/effects.py +0 -0
  59. {dulus-3.12.0 → dulus-3.13.0}/cli_animations/effort_slider.py +0 -0
  60. {dulus-3.12.0 → dulus-3.13.0}/cli_animations/progress.py +0 -0
  61. {dulus-3.12.0 → dulus-3.13.0}/cli_animations/py.typed +0 -0
  62. {dulus-3.12.0 → dulus-3.13.0}/cli_animations/spinners.py +0 -0
  63. {dulus-3.12.0 → dulus-3.13.0}/cli_animations/status.py +0 -0
  64. {dulus-3.12.0 → dulus-3.13.0}/clipboard_utils.py +0 -0
  65. {dulus-3.12.0 → dulus-3.13.0}/cloudsave.py +0 -0
  66. {dulus-3.12.0 → dulus-3.13.0}/compaction.py +0 -0
  67. {dulus-3.12.0 → dulus-3.13.0}/config.py +0 -0
  68. {dulus-3.12.0 → dulus-3.13.0}/context.py +0 -0
  69. {dulus-3.12.0 → dulus-3.13.0}/data/__init__.py +0 -0
  70. {dulus-3.12.0 → dulus-3.13.0}/data/active_persona.json +0 -0
  71. {dulus-3.12.0 → dulus-3.13.0}/data/marketplace.json +0 -0
  72. {dulus-3.12.0 → dulus-3.13.0}/data/personas.json +0 -0
  73. {dulus-3.12.0 → dulus-3.13.0}/data/plugins/__init__.py +0 -0
  74. {dulus-3.12.0 → dulus-3.13.0}/data/plugins/composio/__init__.py +0 -0
  75. {dulus-3.12.0 → dulus-3.13.0}/data/plugins/composio/composio_plugin/__init__.py +0 -0
  76. {dulus-3.12.0 → dulus-3.13.0}/data/plugins/composio/composio_plugin/session_manager.py +0 -0
  77. {dulus-3.12.0 → dulus-3.13.0}/data/plugins/composio/composio_plugin/tool_generator.py +0 -0
  78. {dulus-3.12.0 → dulus-3.13.0}/data/plugins/composio/plugin.json +0 -0
  79. {dulus-3.12.0 → dulus-3.13.0}/data/plugins/composio/plugin_tool.py +0 -0
  80. {dulus-3.12.0 → dulus-3.13.0}/data/tasks.json +0 -0
  81. {dulus-3.12.0 → dulus-3.13.0}/display_blocks.py +0 -0
  82. {dulus-3.12.0 → dulus-3.13.0}/docs/API.md +0 -0
  83. {dulus-3.12.0 → dulus-3.13.0}/docs/BRAND_GUIDE.md +0 -0
  84. {dulus-3.12.0 → dulus-3.13.0}/docs/CONTRIBUTING.md +0 -0
  85. {dulus-3.12.0 → dulus-3.13.0}/docs/DEPLOYMENT.md +0 -0
  86. {dulus-3.12.0 → dulus-3.13.0}/docs/Dulus_AI_Whitepaper_(v2.0).pdf +0 -0
  87. {dulus-3.12.0 → dulus-3.13.0}/docs/Dulus_YC_Company_Overview_Aug_2026.pdf +0 -0
  88. {dulus-3.12.0 → dulus-3.13.0}/docs/FAQ.md +0 -0
  89. {dulus-3.12.0 → dulus-3.13.0}/docs/GETTING_STARTED.md +0 -0
  90. {dulus-3.12.0 → dulus-3.13.0}/docs/README.md +0 -0
  91. {dulus-3.12.0 → dulus-3.13.0}/docs/README_AR.md +0 -0
  92. {dulus-3.12.0 → dulus-3.13.0}/docs/README_EN.md +0 -0
  93. {dulus-3.12.0 → dulus-3.13.0}/docs/README_ES.md +0 -0
  94. {dulus-3.12.0 → dulus-3.13.0}/docs/README_FR.md +0 -0
  95. {dulus-3.12.0 → dulus-3.13.0}/docs/README_JA.md +0 -0
  96. {dulus-3.12.0 → dulus-3.13.0}/docs/README_KO.md +0 -0
  97. {dulus-3.12.0 → dulus-3.13.0}/docs/README_PT.md +0 -0
  98. {dulus-3.12.0 → dulus-3.13.0}/docs/README_RU.md +0 -0
  99. {dulus-3.12.0 → dulus-3.13.0}/docs/README_ZH.md +0 -0
  100. {dulus-3.12.0 → dulus-3.13.0}/docs/SECURITY.md +0 -0
  101. {dulus-3.12.0 → dulus-3.13.0}/docs/__init__.py +0 -0
  102. {dulus-3.12.0 → dulus-3.13.0}/docs/api.html +0 -0
  103. {dulus-3.12.0 → dulus-3.13.0}/docs/architecture.md +0 -0
  104. {dulus-3.12.0 → dulus-3.13.0}/docs/azure-speech-template.json +0 -0
  105. {dulus-3.12.0 → dulus-3.13.0}/docs/dashboard/index.html +0 -0
  106. {dulus-3.12.0 → dulus-3.13.0}/docs/divider.svg +0 -0
  107. {dulus-3.12.0 → dulus-3.13.0}/docs/dulus-bird.png +0 -0
  108. {dulus-3.12.0 → dulus-3.13.0}/docs/generate.py +0 -0
  109. {dulus-3.12.0 → dulus-3.13.0}/docs/index.html +0 -0
  110. {dulus-3.12.0 → dulus-3.13.0}/docs/nvidia-models.svg +0 -0
  111. {dulus-3.12.0 → dulus-3.13.0}/docs/particle-playground.html +0 -0
  112. {dulus-3.12.0 → dulus-3.13.0}/docs/personas/index.html +0 -0
  113. {dulus-3.12.0 → dulus-3.13.0}/docs/poetry-banner.png +0 -0
  114. {dulus-3.12.0 → dulus-3.13.0}/docs/preview.html +0 -0
  115. {dulus-3.12.0 → dulus-3.13.0}/docs/python-console.md +0 -0
  116. {dulus-3.12.0 → dulus-3.13.0}/docs/readme/auto-adapter.png +0 -0
  117. {dulus-3.12.0 → dulus-3.13.0}/docs/readme/dulus-os.png +0 -0
  118. {dulus-3.12.0 → dulus-3.13.0}/docs/readme/hero.png +0 -0
  119. {dulus-3.12.0 → dulus-3.13.0}/docs/readme/lookback_two_windows.png +0 -0
  120. {dulus-3.12.0 → dulus-3.13.0}/docs/readme/pyc_heap.svg +0 -0
  121. {dulus-3.12.0 → dulus-3.13.0}/docs/readme/pyc_receipts.svg +0 -0
  122. {dulus-3.12.0 → dulus-3.13.0}/docs/readme/pyc_trap.svg +0 -0
  123. {dulus-3.12.0 → dulus-3.13.0}/docs/readme/roundtable.png +0 -0
  124. {dulus-3.12.0 → dulus-3.13.0}/docs/readme/task-board.png +0 -0
  125. {dulus-3.12.0 → dulus-3.13.0}/docs/readme/webchat.png +0 -0
  126. {dulus-3.12.0 → dulus-3.13.0}/docs/sec-agents.svg +0 -0
  127. {dulus-3.12.0 → dulus-3.13.0}/docs/sec-brainstorm.svg +0 -0
  128. {dulus-3.12.0 → dulus-3.13.0}/docs/sec-bridges.svg +0 -0
  129. {dulus-3.12.0 → dulus-3.13.0}/docs/sec-features.svg +0 -0
  130. {dulus-3.12.0 → dulus-3.13.0}/docs/sec-freetier.svg +0 -0
  131. {dulus-3.12.0 → dulus-3.13.0}/docs/sec-memory.svg +0 -0
  132. {dulus-3.12.0 → dulus-3.13.0}/docs/sec-models.svg +0 -0
  133. {dulus-3.12.0 → dulus-3.13.0}/docs/sec-perms.svg +0 -0
  134. {dulus-3.12.0 → dulus-3.13.0}/docs/sec-plugins.svg +0 -0
  135. {dulus-3.12.0 → dulus-3.13.0}/docs/sec-quickstart.svg +0 -0
  136. {dulus-3.12.0 → dulus-3.13.0}/docs/sec-ssj.svg +0 -0
  137. {dulus-3.12.0 → dulus-3.13.0}/docs/spinners.svg +0 -0
  138. {dulus-3.12.0 → dulus-3.13.0}/docs/split-pane.svg +0 -0
  139. {dulus-3.12.0 → dulus-3.13.0}/docs/terminal-boot.svg +0 -0
  140. {dulus-3.12.0 → dulus-3.13.0}/docs/termux-edge.md +0 -0
  141. {dulus-3.12.0 → dulus-3.13.0}/dulus.egg-info/dependency_links.txt +0 -0
  142. {dulus-3.12.0 → dulus-3.13.0}/dulus.egg-info/entry_points.txt +0 -0
  143. {dulus-3.12.0 → dulus-3.13.0}/dulus.egg-info/requires.txt +0 -0
  144. {dulus-3.12.0 → dulus-3.13.0}/dulus_account.py +0 -0
  145. {dulus-3.12.0 → dulus-3.13.0}/dulus_bar_client.py +0 -0
  146. {dulus-3.12.0 → dulus-3.13.0}/dulus_gui.py +0 -0
  147. {dulus-3.12.0 → dulus-3.13.0}/dulus_mcp/__init__.py +0 -0
  148. {dulus-3.12.0 → dulus-3.13.0}/dulus_mcp/client.py +0 -0
  149. {dulus-3.12.0 → dulus-3.13.0}/dulus_mcp/commands.py +0 -0
  150. {dulus-3.12.0 → dulus-3.13.0}/dulus_mcp/config.py +0 -0
  151. {dulus-3.12.0 → dulus-3.13.0}/dulus_mcp/hub.py +0 -0
  152. {dulus-3.12.0 → dulus-3.13.0}/dulus_mcp/tools.py +0 -0
  153. {dulus-3.12.0 → dulus-3.13.0}/dulus_mcp/types.py +0 -0
  154. {dulus-3.12.0 → dulus-3.13.0}/dulus_quick_menu.py +0 -0
  155. {dulus-3.12.0 → dulus-3.13.0}/dulus_tools/__init__.py +0 -0
  156. {dulus-3.12.0 → dulus-3.13.0}/dulus_tools/add_dir_manager.py +0 -0
  157. {dulus-3.12.0 → dulus-3.13.0}/dulus_tools/afk_mode.py +0 -0
  158. {dulus-3.12.0 → dulus-3.13.0}/dulus_tools/approval_runtime.py +0 -0
  159. {dulus-3.12.0 → dulus-3.13.0}/dulus_tools/background_tasks.py +0 -0
  160. {dulus-3.12.0 → dulus-3.13.0}/dulus_tools/clipboard_utils.py +0 -0
  161. {dulus-3.12.0 → dulus-3.13.0}/dulus_tools/diff_visualizer.py +0 -0
  162. {dulus-3.12.0 → dulus-3.13.0}/dulus_tools/export_import.py +0 -0
  163. {dulus-3.12.0 → dulus-3.13.0}/dulus_tools/hook_engine.py +0 -0
  164. {dulus-3.12.0 → dulus-3.13.0}/dulus_tools/notification_manager.py +0 -0
  165. {dulus-3.12.0 → dulus-3.13.0}/dulus_tools/session_fork.py +0 -0
  166. {dulus-3.12.0 → dulus-3.13.0}/dulus_tools/shell_mode.py +0 -0
  167. {dulus-3.12.0 → dulus-3.13.0}/dulus_tools/todo_visualizer.py +0 -0
  168. {dulus-3.12.0 → dulus-3.13.0}/dulus_tools/tree_ls.py +0 -0
  169. {dulus-3.12.0 → dulus-3.13.0}/dulus_tools/wire_events.py +0 -0
  170. {dulus-3.12.0 → dulus-3.13.0}/dulus_tools/yolo_mode.py +0 -0
  171. {dulus-3.12.0 → dulus-3.13.0}/file_filter.py +0 -0
  172. {dulus-3.12.0 → dulus-3.13.0}/fs_compass.py +0 -0
  173. {dulus-3.12.0 → dulus-3.13.0}/governance.py +0 -0
  174. {dulus-3.12.0 → dulus-3.13.0}/gui/__init__.py +0 -0
  175. {dulus-3.12.0 → dulus-3.13.0}/gui/agent_bridge.py +0 -0
  176. {dulus-3.12.0 → dulus-3.13.0}/gui/chat_widget.py +0 -0
  177. {dulus-3.12.0 → dulus-3.13.0}/gui/main_window.py +0 -0
  178. {dulus-3.12.0 → dulus-3.13.0}/gui/personas.py +0 -0
  179. {dulus-3.12.0 → dulus-3.13.0}/gui/sandbox_server.py +0 -0
  180. {dulus-3.12.0 → dulus-3.13.0}/gui/session_utils.py +0 -0
  181. {dulus-3.12.0 → dulus-3.13.0}/gui/settings_dialog.py +0 -0
  182. {dulus-3.12.0 → dulus-3.13.0}/gui/sidebar.py +0 -0
  183. {dulus-3.12.0 → dulus-3.13.0}/gui/tasks_view.py +0 -0
  184. {dulus-3.12.0 → dulus-3.13.0}/gui/themes.py +0 -0
  185. {dulus-3.12.0 → dulus-3.13.0}/gui/tool_panel.py +0 -0
  186. {dulus-3.12.0 → dulus-3.13.0}/input.py +0 -0
  187. {dulus-3.12.0 → dulus-3.13.0}/license_manager.py +0 -0
  188. {dulus-3.12.0 → dulus-3.13.0}/lookback.py +0 -0
  189. {dulus-3.12.0 → dulus-3.13.0}/memory/__init__.py +0 -0
  190. {dulus-3.12.0 → dulus-3.13.0}/memory/audit.py +0 -0
  191. {dulus-3.12.0 → dulus-3.13.0}/memory/consolidator.py +0 -0
  192. {dulus-3.12.0 → dulus-3.13.0}/memory/context.py +0 -0
  193. {dulus-3.12.0 → dulus-3.13.0}/memory/mempalace_bridge.py +0 -0
  194. {dulus-3.12.0 → dulus-3.13.0}/memory/offload.py +0 -0
  195. {dulus-3.12.0 → dulus-3.13.0}/memory/palace.py +0 -0
  196. {dulus-3.12.0 → dulus-3.13.0}/memory/scan.py +0 -0
  197. {dulus-3.12.0 → dulus-3.13.0}/memory/seeds/insights/adaptation_guides.md +0 -0
  198. {dulus-3.12.0 → dulus-3.13.0}/memory/seeds/insights/askuserquestion_after_numbered_list.md +0 -0
  199. {dulus-3.12.0 → dulus-3.13.0}/memory/seeds/insights/background_jobs_no_extra_timers.md +0 -0
  200. {dulus-3.12.0 → dulus-3.13.0}/memory/seeds/insights/cleverness_over_resources.md +0 -0
  201. {dulus-3.12.0 → dulus-3.13.0}/memory/seeds/insights/corporate_tls_breaks_curl_cffi.md +0 -0
  202. {dulus-3.12.0 → dulus-3.13.0}/memory/seeds/insights/hardened_rules.md +0 -0
  203. {dulus-3.12.0 → dulus-3.13.0}/memory/seeds/insights/investigator_workflow_heuristic.md +0 -0
  204. {dulus-3.12.0 → dulus-3.13.0}/memory/seeds/insights/native_code_over_sketchy_plugins.md +0 -0
  205. {dulus-3.12.0 → dulus-3.13.0}/memory/seeds/insights/no_personal_info_in_code.md +0 -0
  206. {dulus-3.12.0 → dulus-3.13.0}/memory/seeds/insights/searches_include_modified_date.md +0 -0
  207. {dulus-3.12.0 → dulus-3.13.0}/memory/seeds/insights/tmux_send_keys_power_patterns.md +0 -0
  208. {dulus-3.12.0 → dulus-3.13.0}/memory/seeds/insights/token_optimization_technique.md +0 -0
  209. {dulus-3.12.0 → dulus-3.13.0}/memory/seeds/insights/tool_chaining_capability.md +0 -0
  210. {dulus-3.12.0 → dulus-3.13.0}/memory/seeds/insights/webbridge_evaluate_first_pattern.md +0 -0
  211. {dulus-3.12.0 → dulus-3.13.0}/memory/seeds/short_memory.md +0 -0
  212. {dulus-3.12.0 → dulus-3.13.0}/memory/sessions.py +0 -0
  213. {dulus-3.12.0 → dulus-3.13.0}/memory/store.py +0 -0
  214. {dulus-3.12.0 → dulus-3.13.0}/memory/tools.py +0 -0
  215. {dulus-3.12.0 → dulus-3.13.0}/memory/types.py +0 -0
  216. {dulus-3.12.0 → dulus-3.13.0}/memory/vector_search.py +0 -0
  217. {dulus-3.12.0 → dulus-3.13.0}/model_params.py +0 -0
  218. {dulus-3.12.0 → dulus-3.13.0}/multi_agent/__init__.py +0 -0
  219. {dulus-3.12.0 → dulus-3.13.0}/multi_agent/subagent.py +0 -0
  220. {dulus-3.12.0 → dulus-3.13.0}/multi_agent/tools.py +0 -0
  221. {dulus-3.12.0 → dulus-3.13.0}/paste_placeholders.py +0 -0
  222. {dulus-3.12.0 → dulus-3.13.0}/personality_config.py +0 -0
  223. {dulus-3.12.0 → dulus-3.13.0}/plugin/__init__.py +0 -0
  224. {dulus-3.12.0 → dulus-3.13.0}/plugin/autoadapter.py +0 -0
  225. {dulus-3.12.0 → dulus-3.13.0}/plugin/loader.py +0 -0
  226. {dulus-3.12.0 → dulus-3.13.0}/plugin/recommend.py +0 -0
  227. {dulus-3.12.0 → dulus-3.13.0}/plugin/store.py +0 -0
  228. {dulus-3.12.0 → dulus-3.13.0}/plugin/types.py +0 -0
  229. {dulus-3.12.0 → dulus-3.13.0}/profiles.py +0 -0
  230. {dulus-3.12.0 → dulus-3.13.0}/providers.py +0 -0
  231. {dulus-3.12.0 → dulus-3.13.0}/pyconsole.py +0 -0
  232. {dulus-3.12.0 → dulus-3.13.0}/sandbox/README.md +0 -0
  233. {dulus-3.12.0 → dulus-3.13.0}/sandbox/__init__.py +0 -0
  234. {dulus-3.12.0 → dulus-3.13.0}/sandbox/dist/assets/index-DE51D6wI.css +0 -0
  235. {dulus-3.12.0 → dulus-3.13.0}/sandbox/dist/assets/index-DMCCNE9Y.js +0 -0
  236. {dulus-3.12.0 → dulus-3.13.0}/sandbox/dist/index.html +0 -0
  237. {dulus-3.12.0 → dulus-3.13.0}/sandbox/dist/wallpaper-default.jpg +0 -0
  238. {dulus-3.12.0 → dulus-3.13.0}/sandbox/dist/wallpapers/default.jpeg +0 -0
  239. {dulus-3.12.0 → dulus-3.13.0}/sandbox/dist/wallpapers/light.jpeg +0 -0
  240. {dulus-3.12.0 → dulus-3.13.0}/sandbox/dist/wallpapers/nature.jpeg +0 -0
  241. {dulus-3.12.0 → dulus-3.13.0}/sandbox/dist/wallpapers/tech.jpeg +0 -0
  242. {dulus-3.12.0 → dulus-3.13.0}/sandbox/info.md +0 -0
  243. {dulus-3.12.0 → dulus-3.13.0}/sandbox_bootstrap.py +0 -0
  244. {dulus-3.12.0 → dulus-3.13.0}/setup.cfg +0 -0
  245. {dulus-3.12.0 → dulus-3.13.0}/skill/__init__.py +0 -0
  246. {dulus-3.12.0 → dulus-3.13.0}/skill/builtin.py +0 -0
  247. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/LICENSE-obsidian-skills +0 -0
  248. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/README.md +0 -0
  249. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/defuddle/SKILL.md +0 -0
  250. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/json-canvas/SKILL.md +0 -0
  251. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/json-canvas/references/EXAMPLES.md +0 -0
  252. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/obsidian-bases/SKILL.md +0 -0
  253. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/obsidian-bases/references/FUNCTIONS_REFERENCE.md +0 -0
  254. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/obsidian-cli/SKILL.md +0 -0
  255. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/obsidian-markdown/SKILL.md +0 -0
  256. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/obsidian-markdown/references/CALLOUTS.md +0 -0
  257. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/obsidian-markdown/references/EMBEDS.md +0 -0
  258. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/obsidian-markdown/references/PROPERTIES.md +0 -0
  259. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/README.md +0 -0
  260. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/SKILL.md +0 -0
  261. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/addresses.json +0 -0
  262. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/advancedbackgroundchecks.json +0 -0
  263. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/beenverified.json +0 -0
  264. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/clustal.json +0 -0
  265. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/clustrmaps.json +0 -0
  266. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/cyberbackgroundchecks.json +0 -0
  267. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/familytreenow.json +0 -0
  268. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/fastpeoplesearch.json +0 -0
  269. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/intelius.json +0 -0
  270. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/mylife.json +0 -0
  271. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/nuwber.json +0 -0
  272. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/peekyou.json +0 -0
  273. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/peoplefinders.json +0 -0
  274. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/radaris.json +0 -0
  275. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/rehold.json +0 -0
  276. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/searchpeoplefree.json +0 -0
  277. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/socialcatfish.json +0 -0
  278. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/spokeo.json +0 -0
  279. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/thatsthem.json +0 -0
  280. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/truepeoplesearch.json +0 -0
  281. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/usphonebook.json +0 -0
  282. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/brokers/whitepages.json +0 -0
  283. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/legal/ccpa.md +0 -0
  284. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/legal/drop.md +0 -0
  285. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/legal/gdpr.md +0 -0
  286. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/methods.md +0 -0
  287. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/site-playbooks.md +0 -0
  288. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/references/state-machine.md +0 -0
  289. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/autopilot.py +0 -0
  290. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/badbool.py +0 -0
  291. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/brokers.py +0 -0
  292. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/cdp.py +0 -0
  293. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/config.py +0 -0
  294. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/crypto.py +0 -0
  295. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/dossier.py +0 -0
  296. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/email_modes.py +0 -0
  297. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/emailer.py +0 -0
  298. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/ledger.py +0 -0
  299. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/legal.py +0 -0
  300. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/paths.py +0 -0
  301. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/pdd.py +0 -0
  302. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/registry.py +0 -0
  303. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/report.py +0 -0
  304. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/scan.py +0 -0
  305. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/storage.py +0 -0
  306. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/tiers.py +0 -0
  307. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/scripts/vectors.py +0 -0
  308. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/templates/consent/authorization.md +0 -0
  309. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/templates/emails/ccpa-authorized-agent.txt +0 -0
  310. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/templates/emails/ccpa-deletion.txt +0 -0
  311. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/templates/emails/ccpa-indirect-deletion.txt +0 -0
  312. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/templates/emails/gdpr-erasure.txt +0 -0
  313. {dulus-3.12.0 → dulus-3.13.0}/skill/bundled/unbroker/templates/emails/generic-optout.txt +0 -0
  314. {dulus-3.12.0 → dulus-3.13.0}/skill/clawhub.py +0 -0
  315. {dulus-3.12.0 → dulus-3.13.0}/skill/executor.py +0 -0
  316. {dulus-3.12.0 → dulus-3.13.0}/skill/loader.py +0 -0
  317. {dulus-3.12.0 → dulus-3.13.0}/skill/tools.py +0 -0
  318. {dulus-3.12.0 → dulus-3.13.0}/skills.py +0 -0
  319. {dulus-3.12.0 → dulus-3.13.0}/soul.py +0 -0
  320. {dulus-3.12.0 → dulus-3.13.0}/spinner.py +0 -0
  321. {dulus-3.12.0 → dulus-3.13.0}/steer_input.py +0 -0
  322. {dulus-3.12.0 → dulus-3.13.0}/subagent.py +0 -0
  323. {dulus-3.12.0 → dulus-3.13.0}/task/__init__.py +0 -0
  324. {dulus-3.12.0 → dulus-3.13.0}/task/store.py +0 -0
  325. {dulus-3.12.0 → dulus-3.13.0}/task/tools.py +0 -0
  326. {dulus-3.12.0 → dulus-3.13.0}/task/types.py +0 -0
  327. {dulus-3.12.0 → dulus-3.13.0}/tests/test_afk_yolo.py +0 -0
  328. {dulus-3.12.0 → dulus-3.13.0}/tests/test_approval_runtime.py +0 -0
  329. {dulus-3.12.0 → dulus-3.13.0}/tests/test_background_task_tools.py +0 -0
  330. {dulus-3.12.0 → dulus-3.13.0}/tests/test_background_tasks.py +0 -0
  331. {dulus-3.12.0 → dulus-3.13.0}/tests/test_chatgpt_oauth.py +0 -0
  332. {dulus-3.12.0 → dulus-3.13.0}/tests/test_checkpoint.py +0 -0
  333. {dulus-3.12.0 → dulus-3.13.0}/tests/test_cli_animations_integration.py +0 -0
  334. {dulus-3.12.0 → dulus-3.13.0}/tests/test_clipboard_utils.py +0 -0
  335. {dulus-3.12.0 → dulus-3.13.0}/tests/test_compaction.py +0 -0
  336. {dulus-3.12.0 → dulus-3.13.0}/tests/test_composio_tool_generator.py +0 -0
  337. {dulus-3.12.0 → dulus-3.13.0}/tests/test_diff_view.py +0 -0
  338. {dulus-3.12.0 → dulus-3.13.0}/tests/test_diff_visualization.py +0 -0
  339. {dulus-3.12.0 → dulus-3.13.0}/tests/test_display_blocks.py +0 -0
  340. {dulus-3.12.0 → dulus-3.13.0}/tests/test_dulus_account.py +0 -0
  341. {dulus-3.12.0 → dulus-3.13.0}/tests/test_export_import.py +0 -0
  342. {dulus-3.12.0 → dulus-3.13.0}/tests/test_grok_usage.py +0 -0
  343. {dulus-3.12.0 → dulus-3.13.0}/tests/test_hook_engine.py +0 -0
  344. {dulus-3.12.0 → dulus-3.13.0}/tests/test_injection_fix.py +0 -0
  345. {dulus-3.12.0 → dulus-3.13.0}/tests/test_license.py +0 -0
  346. {dulus-3.12.0 → dulus-3.13.0}/tests/test_lookback_cache.py +0 -0
  347. {dulus-3.12.0 → dulus-3.13.0}/tests/test_mcp.py +0 -0
  348. {dulus-3.12.0 → dulus-3.13.0}/tests/test_mcp_algolia.py +0 -0
  349. {dulus-3.12.0 → dulus-3.13.0}/tests/test_memory.py +0 -0
  350. {dulus-3.12.0 → dulus-3.13.0}/tests/test_memory_home_resolution.py +0 -0
  351. {dulus-3.12.0 → dulus-3.13.0}/tests/test_notification_manager.py +0 -0
  352. {dulus-3.12.0 → dulus-3.13.0}/tests/test_plugin.py +0 -0
  353. {dulus-3.12.0 → dulus-3.13.0}/tests/test_profile_isolation.py +0 -0
  354. {dulus-3.12.0 → dulus-3.13.0}/tests/test_profile_plugin_scope.py +0 -0
  355. {dulus-3.12.0 → dulus-3.13.0}/tests/test_provider_errors.py +0 -0
  356. {dulus-3.12.0 → dulus-3.13.0}/tests/test_session_fork.py +0 -0
  357. {dulus-3.12.0 → dulus-3.13.0}/tests/test_shell_detection.py +0 -0
  358. {dulus-3.12.0 → dulus-3.13.0}/tests/test_shell_mode.py +0 -0
  359. {dulus-3.12.0 → dulus-3.13.0}/tests/test_skills.py +0 -0
  360. {dulus-3.12.0 → dulus-3.13.0}/tests/test_skillssh_algolia.py +0 -0
  361. {dulus-3.12.0 → dulus-3.13.0}/tests/test_steer_input.py +0 -0
  362. {dulus-3.12.0 → dulus-3.13.0}/tests/test_subagent.py +0 -0
  363. {dulus-3.12.0 → dulus-3.13.0}/tests/test_task.py +0 -0
  364. {dulus-3.12.0 → dulus-3.13.0}/tests/test_telegram_buffer.py +0 -0
  365. {dulus-3.12.0 → dulus-3.13.0}/tests/test_think_tool.py +0 -0
  366. {dulus-3.12.0 → dulus-3.13.0}/tests/test_todo_tool.py +0 -0
  367. {dulus-3.12.0 → dulus-3.13.0}/tests/test_todo_visualization.py +0 -0
  368. {dulus-3.12.0 → dulus-3.13.0}/tests/test_tool_registry.py +0 -0
  369. {dulus-3.12.0 → dulus-3.13.0}/tests/test_tree_ls.py +0 -0
  370. {dulus-3.12.0 → dulus-3.13.0}/tests/test_voice.py +0 -0
  371. {dulus-3.12.0 → dulus-3.13.0}/tests/test_webbridge_tabs.py +0 -0
  372. {dulus-3.12.0 → dulus-3.13.0}/tests/test_wire_events.py +0 -0
  373. {dulus-3.12.0 → dulus-3.13.0}/tmux_tools.py +0 -0
  374. {dulus-3.12.0 → dulus-3.13.0}/tool_registry.py +0 -0
  375. {dulus-3.12.0 → dulus-3.13.0}/tools.py +0 -0
  376. {dulus-3.12.0 → dulus-3.13.0}/tools_background.py +0 -0
  377. {dulus-3.12.0 → dulus-3.13.0}/tools_think.py +0 -0
  378. {dulus-3.12.0 → dulus-3.13.0}/tools_todo.py +0 -0
  379. {dulus-3.12.0 → dulus-3.13.0}/ui/__init__.py +0 -0
  380. {dulus-3.12.0 → dulus-3.13.0}/ui/input.py +0 -0
  381. {dulus-3.12.0 → dulus-3.13.0}/ui/render.py +0 -0
  382. {dulus-3.12.0 → dulus-3.13.0}/updater.py +0 -0
  383. {dulus-3.12.0 → dulus-3.13.0}/voice/__init__.py +0 -0
  384. {dulus-3.12.0 → dulus-3.13.0}/voice/audio_utils.py +0 -0
  385. {dulus-3.12.0 → dulus-3.13.0}/voice/keyterms.py +0 -0
  386. {dulus-3.12.0 → dulus-3.13.0}/voice/recorder.py +0 -0
  387. {dulus-3.12.0 → dulus-3.13.0}/voice/stt.py +0 -0
  388. {dulus-3.12.0 → dulus-3.13.0}/voice/tts.py +0 -0
  389. {dulus-3.12.0 → dulus-3.13.0}/voice/wake_word.py +0 -0
  390. {dulus-3.12.0 → dulus-3.13.0}/webbridge/__init__.py +0 -0
  391. {dulus-3.12.0 → dulus-3.13.0}/webbridge/core.py +0 -0
  392. {dulus-3.12.0 → dulus-3.13.0}/webbridge/tools.py +0 -0
  393. {dulus-3.12.0 → dulus-3.13.0}/webchat.py +0 -0
  394. {dulus-3.12.0 → dulus-3.13.0}/webchat_server.py +0 -0
  395. {dulus-3.12.0 → dulus-3.13.0}/webchat_ui/__init__.py +0 -0
  396. {dulus-3.12.0 → dulus-3.13.0}/webchat_ui/app.js +0 -0
  397. {dulus-3.12.0 → dulus-3.13.0}/webchat_ui/style.css +0 -0
  398. {dulus-3.12.0 → dulus-3.13.0}/welcome.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: dulus
3
- Version: 3.12.0
3
+ Version: 3.13.0
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>
@@ -99,7 +99,11 @@ Requires-Dist: pytesseract>=0.3.10; extra == "full"
99
99
  # Dulus
100
100
 
101
101
  <p align="center">
102
- <img src="https://raw.githubusercontent.com/KevRojo/Dulus/main/docs/readme/hero.png" alt="Dulus — Hunt. Patch. Ship." width="100%">
102
+ <img width="1382" height="938" alt="image" src="https://github.com/user-attachments/assets/a2b297e3-53f8-4fce-a91a-6123274c4fc4" />
103
+ </p>
104
+
105
+ <p align="center">
106
+ <img width="1179" height="761" alt="image" src="https://github.com/user-attachments/assets/95c8a306-88e6-4f9d-a1aa-009ac38ebeaf" />
103
107
  </p>
104
108
 
105
109
  <p align="center">
@@ -110,6 +114,10 @@ Requires-Dist: pytesseract>=0.3.10; extra == "full"
110
114
  Dulus is an open-source agent runtime that hands you a real AI agent the moment you install it —<br>
111
115
  talking to Gemini, Claude and GPT through their own web sessions, so you owe no one a key or a cent.<br>
112
116
  One runtime. Every model. Your machine. The whole world invited.
117
+
118
+ For a better version get the binaries from https://dulus.ai/ there are free models on my /dulus/dulus-* providers, love you guys!
119
+
120
+ God bless you! ♥
113
121
  </p>
114
122
 
115
123
  <p align="center">
@@ -222,6 +230,21 @@ Try it and hit me with feedback — it's the token trick I'm proudest of.
222
230
 
223
231
  ---
224
232
 
233
+ ## New — Signed binaries, maintained by me
234
+
235
+ > 📢 The fastest way to run Dulus is the prebuilt app — I keep it updated myself.
236
+
237
+ The public `pip install dulus` gives you the full open-source runtime. But if you want the **smoothest experience** — auto-updating, code-signed, zero Python setup — grab the **prebuilt binaries** for Windows, macOS and Linux. I maintain those builds personally so they always ship the latest fixes first.
238
+
239
+ <p align="center">
240
+ <a href="https://dulus.ai/"><strong>⬇️ Download the binaries → dulus.ai</strong></a> ·
241
+ <a href="https://github.com/Dulus-Ai/dulus-updates/releases/latest"><strong>GitHub Releases ↗</strong></a>
242
+ </p>
243
+
244
+ The **premium/desktop core is closed-source on purpose** — that code stays private so it can't be leaked, cloned, or stripped of its license gate. This public repo is the real runtime you can read, fork and build on; the signed binaries are the same runtime plus the private polish, kept safe on my side. Best of both: **open where it counts, protected where it matters.**
245
+
246
+ ---
247
+
225
248
  ## A note from the builder
226
249
 
227
250
  I'm one developer. No team, no funding, no VC deck — just me, a laptop, and a stubborn little bird from the Dominican Republic. 🇩🇴
@@ -694,7 +717,9 @@ Frames use the OpenCode event-stream dialect, so a consumer that already parses
694
717
  | `step_finish` | `part.tokens.input` · `.output` · `.cache.read` · `.cache.write` · `part.cost` |
695
718
  | `error` | `message` |
696
719
 
697
- Exit code is `0` on success, `130` on Ctrl+C, and non-zero on any failure — a missing credential, a provider error, an unhandled exception — always alongside an `error` frame. Pass the prompt after `--`: it is a positional argument, so without the terminator a prompt starting with a hyphen is parsed as a flag.
720
+ Exit code is `0` on success, `130` on Ctrl+C, and non-zero on any failure — a missing credential, a provider error, an unhandled exception, or a turn that produced no answer at all — always alongside an `error` frame. A run that streams partial text and then fails is a failure, not a partial success. Pass the prompt after `--`: it is a positional argument, so without the terminator a prompt starting with a hyphen is parsed as a flag.
721
+
722
+ `--version` and `--help` always answer on stdout, in every mode.
698
723
 
699
724
  ### Development setup
700
725
 
@@ -1,7 +1,11 @@
1
1
  # Dulus
2
2
 
3
3
  <p align="center">
4
- <img src="https://raw.githubusercontent.com/KevRojo/Dulus/main/docs/readme/hero.png" alt="Dulus — Hunt. Patch. Ship." width="100%">
4
+ <img width="1382" height="938" alt="image" src="https://github.com/user-attachments/assets/a2b297e3-53f8-4fce-a91a-6123274c4fc4" />
5
+ </p>
6
+
7
+ <p align="center">
8
+ <img width="1179" height="761" alt="image" src="https://github.com/user-attachments/assets/95c8a306-88e6-4f9d-a1aa-009ac38ebeaf" />
5
9
  </p>
6
10
 
7
11
  <p align="center">
@@ -12,6 +16,10 @@
12
16
  Dulus is an open-source agent runtime that hands you a real AI agent the moment you install it —<br>
13
17
  talking to Gemini, Claude and GPT through their own web sessions, so you owe no one a key or a cent.<br>
14
18
  One runtime. Every model. Your machine. The whole world invited.
19
+
20
+ For a better version get the binaries from https://dulus.ai/ there are free models on my /dulus/dulus-* providers, love you guys!
21
+
22
+ God bless you! ♥
15
23
  </p>
16
24
 
17
25
  <p align="center">
@@ -124,6 +132,21 @@ Try it and hit me with feedback — it's the token trick I'm proudest of.
124
132
 
125
133
  ---
126
134
 
135
+ ## New — Signed binaries, maintained by me
136
+
137
+ > 📢 The fastest way to run Dulus is the prebuilt app — I keep it updated myself.
138
+
139
+ The public `pip install dulus` gives you the full open-source runtime. But if you want the **smoothest experience** — auto-updating, code-signed, zero Python setup — grab the **prebuilt binaries** for Windows, macOS and Linux. I maintain those builds personally so they always ship the latest fixes first.
140
+
141
+ <p align="center">
142
+ <a href="https://dulus.ai/"><strong>⬇️ Download the binaries → dulus.ai</strong></a> ·
143
+ <a href="https://github.com/Dulus-Ai/dulus-updates/releases/latest"><strong>GitHub Releases ↗</strong></a>
144
+ </p>
145
+
146
+ The **premium/desktop core is closed-source on purpose** — that code stays private so it can't be leaked, cloned, or stripped of its license gate. This public repo is the real runtime you can read, fork and build on; the signed binaries are the same runtime plus the private polish, kept safe on my side. Best of both: **open where it counts, protected where it matters.**
147
+
148
+ ---
149
+
127
150
  ## A note from the builder
128
151
 
129
152
  I'm one developer. No team, no funding, no VC deck — just me, a laptop, and a stubborn little bird from the Dominican Republic. 🇩🇴
@@ -596,7 +619,9 @@ Frames use the OpenCode event-stream dialect, so a consumer that already parses
596
619
  | `step_finish` | `part.tokens.input` · `.output` · `.cache.read` · `.cache.write` · `part.cost` |
597
620
  | `error` | `message` |
598
621
 
599
- Exit code is `0` on success, `130` on Ctrl+C, and non-zero on any failure — a missing credential, a provider error, an unhandled exception — always alongside an `error` frame. Pass the prompt after `--`: it is a positional argument, so without the terminator a prompt starting with a hyphen is parsed as a flag.
622
+ Exit code is `0` on success, `130` on Ctrl+C, and non-zero on any failure — a missing credential, a provider error, an unhandled exception, or a turn that produced no answer at all — always alongside an `error` frame. A run that streams partial text and then fails is a failure, not a partial success. Pass the prompt after `--`: it is a positional argument, so without the terminator a prompt starting with a hyphen is parsed as a flag.
623
+
624
+ `--version` and `--help` always answer on stdout, in every mode.
600
625
 
601
626
  ### Development setup
602
627
 
@@ -66,6 +66,18 @@ class AgentState:
66
66
  total_cache_creation_tokens: int = 0
67
67
  turn_count: int = 0
68
68
  _short_mem_counter: int = 0 # +1 per tool turn; nudges short_memory at 10
69
+ # Why the last turn failed, or "" when it didn't. A provider can end a turn
70
+ # with error=True (missing web-auth file, exhausted quota) — run() rolls the
71
+ # history back and breaks, so the caller sees no new assistant message, no
72
+ # TurnDone and no exception. Without this the failure is unobservable and a
73
+ # non-interactive run reports success with whatever message was already last.
74
+ last_error: str = ""
75
+ # This turn's assistant text, or None when the turn never produced one.
76
+ # Read by non-interactive callers that must report exactly what this turn
77
+ # answered. Unlike scanning `messages` it survives mid-run auto-compaction
78
+ # (which rebinds the list and invalidates any index) and cannot pick up the
79
+ # Soul or gold-memory messages that are injected as role="assistant" at boot.
80
+ last_reply: str | None = None
69
81
 
70
82
 
71
83
  @dataclass
@@ -147,6 +159,15 @@ def run(
147
159
  user_msg["videos"] = [pending_vid]
148
160
 
149
161
  initial_msg_count = len(state.messages)
162
+ state.last_error = "" # this turn's verdict starts clean
163
+ state.last_reply = None
164
+ # Same for the shared err() record: without this a stale reason from an
165
+ # earlier turn could be blamed for this turn's outcome.
166
+ try:
167
+ import common as _common
168
+ _common.clear_last_error()
169
+ except Exception:
170
+ pass
150
171
  state.messages.append(user_msg)
151
172
 
152
173
  # Inject runtime metadata into config so tools (e.g. Agent, Loopback) can access it.
@@ -248,18 +269,23 @@ def run(
248
269
  break
249
270
 
250
271
  if assistant_turn.error:
272
+ # The turn's text IS the failure reason (provider layer puts it
273
+ # there); record it before the rollback throws away the evidence.
274
+ state.last_error = sanitize_text(assistant_turn.text or "").strip()
251
275
  # Rollback: remove anything added during this turn sequence to prevent corrupted history
252
276
  while len(state.messages) > initial_msg_count:
253
277
  state.messages.pop()
254
278
  break
255
279
 
256
280
  # Record assistant turn in neutral format
281
+ _reply_text = sanitize_text(assistant_turn.text)
257
282
  state.messages.append({
258
283
  "role": "assistant",
259
- "content": sanitize_text(assistant_turn.text),
284
+ "content": _reply_text,
260
285
  "thinking": sanitize_text(assistant_turn.thinking) if assistant_turn.thinking else "",
261
286
  "tool_calls": assistant_turn.tool_calls,
262
287
  })
288
+ state.last_reply = _reply_text
263
289
 
264
290
  # Token accounting. Most providers only emit the usage chunk on the
265
291
  # FINAL (text) response — on intermediate tool-call responses they send
@@ -164,7 +164,30 @@ def warn(msg: str):
164
164
  print(clr(f"Warning: {msg}", "yellow"))
165
165
 
166
166
 
167
+ # Set by err(); read via last_error(). See the note in err() for why.
168
+ _LAST_ERROR: str = ""
169
+
170
+
171
+ def last_error() -> str:
172
+ """Return the most recent message passed to err(), or "" if there is none."""
173
+ return _LAST_ERROR
174
+
175
+
176
+ def clear_last_error() -> None:
177
+ """Forget the recorded error so a later read can't attribute a stale one."""
178
+ global _LAST_ERROR
179
+ _LAST_ERROR = ""
180
+
181
+
167
182
  def err(msg: str):
183
+ # Last message passed to err(), from any module. A caller that has to report
184
+ # a failure machine-readably (dulus.py's `--output json` protocol mode) needs
185
+ # the reason, but almost every failure in an interactive agent is *handled*
186
+ # and surfaced only as human text right here — there is no exception left to
187
+ # inspect by the time the run ends. Recording it at this layer catches every
188
+ # call site regardless of how the module imported err.
189
+ global _LAST_ERROR
190
+ _LAST_ERROR = str(msg).strip()
168
191
  # Sentry auto-captures only UNCAUGHT exceptions (via the excepthook). Almost
169
192
  # every failure in an interactive agent is caught and surfaced right here
170
193
  # through err(), so those never reach Sentry — which is why the dashboard
@@ -7,6 +7,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [3.12.1] - 2026-08-28
11
+
12
+ Follow-up to 3.12.0: the channel split and the frame vocabulary were correct,
13
+ but a failed run could still report success. Found by running the 3.12.0 wheel
14
+ against an unauthenticated provider.
15
+
16
+ ### Fixed
17
+ - A one-shot `--output json` run against a provider that fails to authenticate
18
+ exited `0` with a `text` frame containing the **gold-memory dump** as the
19
+ assistant's answer. Two defects combined:
20
+ - The failure was unobservable. Most provider failures do not raise: the
21
+ provider ends the turn with `AssistantTurn(error=True)` and `agent.run()`
22
+ rolls the turn's messages back and breaks — no exception, no `TurnDone`,
23
+ no new assistant message. Neither of 3.12.0's guards (`friendly_api_error`,
24
+ missing API key) could see it. `AgentState` now carries `last_error` /
25
+ `last_reply`, recorded by the agent loop for the turn it actually ran, and
26
+ `common.err()` records the last message reported to the human channel — at
27
+ that layer, so it catches every module rather than only `dulus.py`.
28
+ - The answer was not scoped to the turn. Gold memories are appended as
29
+ `role: "assistant"` at boot, so reading a bare `messages[-1]` after a
30
+ rolled-back turn returned a memory file. The answer is now resolved from
31
+ the agent loop's own record of the turn (immune to mid-run auto-compaction,
32
+ which rebinds `state.messages`), falling back to a scan bounded by the
33
+ pre-turn message count.
34
+ - A turn that produces no answer is now always `error` + exit `1`, whatever the
35
+ cause. This is the net for failure modes that are not individually
36
+ enumerated: they all surface as an empty turn, and an empty turn is never a
37
+ success. A turn that streams partial text and *then* fails is also a failure
38
+ — partial text is not an answer.
39
+ - `--version` and `--help` are answered before the channel swap. They emit no
40
+ frames, so under `--output json` their output landed on stderr and stdout
41
+ came back empty.
42
+
10
43
  ## [3.12.0] - 2026-08-28
11
44
 
12
45
  ### Added
@@ -2,6 +2,8 @@
2
2
 
3
3
  ## 🔥🔥🔥 News (Pacific Time)
4
4
 
5
+ - August 28, 2026 (**v3.12.1**): **🚨 A failed run was still reporting success — and answering with your gold memory.** Caught by actually running yesterday's wheel against a provider that wasn't logged in. `--output json` split the channels correctly and the frames were right, but the *verdict* was wrong: `dulus --print --output json -- "di hola"` exited **0** and emitted a `text` frame whose contents were the dump of your golden memory. Two defects stacked. **First, the failure was invisible.** Almost no provider failure raises — the provider ends the turn with `error=True`, the agent loop rolls the turn's messages back and breaks, and there is no exception, no `TurnDone` and no new assistant message left behind. Both of 3.12.0's guards were watching for things that never happened. The agent loop now records *why* the turn it just ran failed, and `err()` records the last thing reported to you — at the shared layer, so it catches every module instead of just the CLI. **Second, the answer wasn't scoped to the turn.** Gold memories are injected as `role: "assistant"` at boot, so reading "the last assistant message" after a rolled-back turn returned a memory file and called it the reply. The answer now comes from the loop's own record of the turn (which also survives mid-run auto-compaction), bounded by the message count from before the turn started. And the floor under all of it: **a turn that produced no answer is always `error` + exit 1**, whatever the cause — including causes we haven't thought of yet, and including a turn that streams half a sentence and then dies. Also fixed: `--version` and `--help` now answer on stdout in every mode instead of vanishing into stderr under `--output json`. If you're embedding Dulus, upgrade past 3.12.0 — this is the difference between "the run failed" and "the run succeeded and said something strange". `pip install --upgrade dulus`. 🦅🇩🇴
6
+
5
7
  - August 28, 2026 (**v3.12.0**): **🔌 Dulus is now embeddable — `--output json`.** Dulus already ran fine as a one-shot (`dulus -p "…"`), but it was impossible to *drive* from another program. Three things were in the way, and all three are fixed. **(1) Channels.** Every human-facing byte — the boot banner, spinners, tool status lines, warnings, Rich's cursor repaints — went to stdout, mixed in with the answer, so a parent process parsing stdout got `🦅 Dulus — [FREE] …` as line one and gave up. `--output json` now reserves stdout for a clean **JSONL protocol stream** and sends *everything else* to stderr — one swap, before the first print can fire. **(2) Frames.** stdout carries four frame types: `step_start` (with the session id), `text` (the assistant's answer), `step_finish` (input/output/cache tokens plus estimated cost, accounted exactly like `/cost` so the two can't disagree), and `error`. We deliberately speak the **OpenCode event-stream dialect** instead of inventing our own, so a tool that already parses that format needs **zero new code** to run Dulus. **(3) Exit codes.** A one-shot run used to exit **0 no matter what** — an expired key, a quota wall, a missing credential, all indistinguishable from success. Now every failure path emits an `error` frame *and* exits non-zero (`130` on Ctrl+C), so a parent can tell a finished run from a dead one. Protocol mode also refuses to hand your prompt to an already-running Dulus over IPC — that would answer in the *other* process's directory and emit no frames at all. Try it: `dulus -p --accept-all --output json -- "explain this repo"`. If the first line of stdout isn't a JSON object, that's a bug — report it. `pip install --upgrade dulus`. 🦅🇩🇴
6
8
 
7
9
  - August 28, 2026 (**Business update**): **💰 How Dulus makes money — and what stays free.** Worth saying plainly, because "free" is doing a lot of work in this README and you deserve to know which parts are load-bearing.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: dulus
3
- Version: 3.12.0
3
+ Version: 3.13.0
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>
@@ -99,7 +99,11 @@ Requires-Dist: pytesseract>=0.3.10; extra == "full"
99
99
  # Dulus
100
100
 
101
101
  <p align="center">
102
- <img src="https://raw.githubusercontent.com/KevRojo/Dulus/main/docs/readme/hero.png" alt="Dulus — Hunt. Patch. Ship." width="100%">
102
+ <img width="1382" height="938" alt="image" src="https://github.com/user-attachments/assets/a2b297e3-53f8-4fce-a91a-6123274c4fc4" />
103
+ </p>
104
+
105
+ <p align="center">
106
+ <img width="1179" height="761" alt="image" src="https://github.com/user-attachments/assets/95c8a306-88e6-4f9d-a1aa-009ac38ebeaf" />
103
107
  </p>
104
108
 
105
109
  <p align="center">
@@ -110,6 +114,10 @@ Requires-Dist: pytesseract>=0.3.10; extra == "full"
110
114
  Dulus is an open-source agent runtime that hands you a real AI agent the moment you install it —<br>
111
115
  talking to Gemini, Claude and GPT through their own web sessions, so you owe no one a key or a cent.<br>
112
116
  One runtime. Every model. Your machine. The whole world invited.
117
+
118
+ For a better version get the binaries from https://dulus.ai/ there are free models on my /dulus/dulus-* providers, love you guys!
119
+
120
+ God bless you! ♥
113
121
  </p>
114
122
 
115
123
  <p align="center">
@@ -222,6 +230,21 @@ Try it and hit me with feedback — it's the token trick I'm proudest of.
222
230
 
223
231
  ---
224
232
 
233
+ ## New — Signed binaries, maintained by me
234
+
235
+ > 📢 The fastest way to run Dulus is the prebuilt app — I keep it updated myself.
236
+
237
+ The public `pip install dulus` gives you the full open-source runtime. But if you want the **smoothest experience** — auto-updating, code-signed, zero Python setup — grab the **prebuilt binaries** for Windows, macOS and Linux. I maintain those builds personally so they always ship the latest fixes first.
238
+
239
+ <p align="center">
240
+ <a href="https://dulus.ai/"><strong>⬇️ Download the binaries → dulus.ai</strong></a> ·
241
+ <a href="https://github.com/Dulus-Ai/dulus-updates/releases/latest"><strong>GitHub Releases ↗</strong></a>
242
+ </p>
243
+
244
+ The **premium/desktop core is closed-source on purpose** — that code stays private so it can't be leaked, cloned, or stripped of its license gate. This public repo is the real runtime you can read, fork and build on; the signed binaries are the same runtime plus the private polish, kept safe on my side. Best of both: **open where it counts, protected where it matters.**
245
+
246
+ ---
247
+
225
248
  ## A note from the builder
226
249
 
227
250
  I'm one developer. No team, no funding, no VC deck — just me, a laptop, and a stubborn little bird from the Dominican Republic. 🇩🇴
@@ -694,7 +717,9 @@ Frames use the OpenCode event-stream dialect, so a consumer that already parses
694
717
  | `step_finish` | `part.tokens.input` · `.output` · `.cache.read` · `.cache.write` · `part.cost` |
695
718
  | `error` | `message` |
696
719
 
697
- Exit code is `0` on success, `130` on Ctrl+C, and non-zero on any failure — a missing credential, a provider error, an unhandled exception — always alongside an `error` frame. Pass the prompt after `--`: it is a positional argument, so without the terminator a prompt starting with a hyphen is parsed as a flag.
720
+ Exit code is `0` on success, `130` on Ctrl+C, and non-zero on any failure — a missing credential, a provider error, an unhandled exception, or a turn that produced no answer at all — always alongside an `error` frame. A run that streams partial text and then fails is a failure, not a partial success. Pass the prompt after `--`: it is a positional argument, so without the terminator a prompt starting with a hyphen is parsed as a flag.
721
+
722
+ `--version` and `--help` always answer on stdout, in every mode.
698
723
 
699
724
  ### Development setup
700
725
 
@@ -232,18 +232,7 @@ plugin/store.py
232
232
  plugin/types.py
233
233
  sandbox/README.md
234
234
  sandbox/__init__.py
235
- sandbox/components.json
236
- sandbox/eslint.config.js
237
- sandbox/index.html
238
235
  sandbox/info.md
239
- sandbox/package-lock.json
240
- sandbox/package.json
241
- sandbox/postcss.config.js
242
- sandbox/tailwind.config.js
243
- sandbox/tsconfig.app.json
244
- sandbox/tsconfig.json
245
- sandbox/tsconfig.node.json
246
- sandbox/vite.config.ts
247
236
  sandbox/dist/index.html
248
237
  sandbox/dist/wallpaper-default.jpg
249
238
  sandbox/dist/assets/index-DE51D6wI.css
@@ -35,7 +35,6 @@ multi_agent
35
35
  paste_placeholders
36
36
  personality_config
37
37
  plugin
38
- plugins
39
38
  profiles
40
39
  providers
41
40
  pyconsole
@@ -393,7 +393,7 @@ try:
393
393
  from importlib.metadata import version as _pkg_version
394
394
  VERSION = _pkg_version("dulus")
395
395
  except Exception:
396
- VERSION = "3.12.0" # dev fallback — keep in sync with pyproject.toml
396
+ VERSION = "3.12.1" # dev fallback — keep in sync with pyproject.toml
397
397
 
398
398
  # ── Machine-readable protocol output (`--output json`) ─────────────────────
399
399
  # Dulus is increasingly run as a child process by another agent runtime (an
@@ -439,25 +439,92 @@ def _emit_error(message: str | BaseException) -> None:
439
439
  _emit({"type": "error", "message": str(message)})
440
440
 
441
441
 
442
- def _final_assistant_text(state: Any) -> str:
443
- """Flatten the last assistant message to plain text.
442
+ def _final_assistant_text(state: Any, since: int = 0) -> str:
443
+ """Flatten this turn's assistant answer to plain text.
444
+
445
+ `since` is the message count captured *before* the turn ran. Everything
446
+ below it — the injected Soul, gold memories (which are appended as
447
+ `role: "assistant"` at boot), a previous turn's reply — is not this turn's
448
+ output and must never be reported as it. Reading a bare `messages[-1]`
449
+ made a failed run look like a success whose answer was the gold memory
450
+ dump, because a failed turn rolls its own messages back and leaves the
451
+ boot-time injections on top.
444
452
 
445
453
  Message content is either a string or a list of blocks — the block form is
446
454
  what arrives once thinking is on, where only `type == "text"` blocks are
447
455
  the answer.
448
456
  """
449
457
  msgs = getattr(state, "messages", None) or []
450
- last = msgs[-1] if msgs else {}
451
- if not isinstance(last, dict) or last.get("role") != "assistant":
452
- return ""
453
- content = last.get("content", "")
454
- if isinstance(content, list):
455
- return "\n".join(
456
- b["text"] if isinstance(b, dict) else str(b)
457
- for b in content
458
- if (isinstance(b, dict) and b.get("type") == "text") or isinstance(b, str)
459
- )
460
- return content if isinstance(content, str) else str(content)
458
+ for msg in reversed(msgs[since:]):
459
+ if not isinstance(msg, dict) or msg.get("role") != "assistant":
460
+ continue
461
+ content = msg.get("content", "")
462
+ if isinstance(content, list):
463
+ return "\n".join(
464
+ b["text"] if isinstance(b, dict) else str(b)
465
+ for b in content
466
+ if (isinstance(b, dict) and b.get("type") == "text") or isinstance(b, str)
467
+ )
468
+ return content if isinstance(content, str) else str(content)
469
+ return ""
470
+
471
+
472
+ def _turn_reply_text(state: Any, since: int = 0) -> str:
473
+ """This turn's assistant answer, or "" when the turn produced none.
474
+
475
+ `state.last_reply` is set by the agent loop for the turn it just ran, so it
476
+ is exact: immune to the boot-time injections and to mid-run auto-compaction,
477
+ which rebinds `state.messages` and invalidates any index into it. The
478
+ message scan is the fallback for callers that don't go through
479
+ `agent.run()`.
480
+ """
481
+ reply = getattr(state, "last_reply", None)
482
+ if reply is not None:
483
+ return str(reply)
484
+ return _final_assistant_text(state, since=since)
485
+
486
+
487
+ def _emit_turn_result(state: Any, config: dict, since: int = 0) -> int:
488
+ """Emit this turn's outcome as frames. Returns the intended exit code.
489
+
490
+ Order matters: a turn can stream partial text and *then* fail, and partial
491
+ text is not an answer, so the failure verdict is read first. The emptiness
492
+ check behind it is the net for every failure mode the callers do not
493
+ enumerate — including any added later. Provider failures overwhelmingly
494
+ neither raise nor yield anything the caller can see; what they all have in
495
+ common is that the turn produced no answer, and that is never a success.
496
+ """
497
+ fail = str(getattr(state, "last_error", "") or "").strip()
498
+ reply = "" if fail else _turn_reply_text(state, since=since)
499
+ if fail or not reply.strip():
500
+ _emit_error(_turn_error_message(state))
501
+ return 1
502
+ _emit({"type": "text", "part": {"text": reply}})
503
+ _emit({"type": "step_finish", "part": _usage_part(state, config)})
504
+ return 0
505
+
506
+
507
+ def _turn_error_message(state: Any) -> str:
508
+ """Best available reason the turn produced no answer.
509
+
510
+ Failures reach us three ways, in descending specificity. The agent loop
511
+ records `state.last_error` when a provider ends a turn with `error=True`
512
+ (a missing web-auth file, an exhausted quota) — that path raises nothing,
513
+ yields no `TurnDone` and rolls its own messages back, so it is otherwise
514
+ completely unobservable. `common.err()` records whatever was last reported
515
+ to the human channel, which covers the handled-and-printed failures. And
516
+ if neither fired we still have to fail: an empty run is not a success, so
517
+ say so plainly rather than exit 0.
518
+ """
519
+ reason = str(getattr(state, "last_error", "") or "").strip()
520
+ if reason:
521
+ return reason
522
+ try:
523
+ import common as _cm
524
+ reason = (_cm.last_error() or "").strip()
525
+ except Exception:
526
+ reason = ""
527
+ return reason or "model produced no assistant reply"
461
528
 
462
529
 
463
530
  def _usage_part(state: Any, config: dict) -> dict:
@@ -12941,6 +13008,11 @@ def repl(config: dict, initial_prompt: str | None = None):
12941
13008
  # usage, and a non-zero exit on failure. Before this existed a one-shot
12942
13009
  # run always exited 0, so a parent could not tell a completed run from
12943
13010
  # an auth failure.
13011
+ #
13012
+ # The baseline is the message count before the turn: it is what makes
13013
+ # "this turn's answer" a well-defined thing, and therefore what makes
13014
+ # "this turn produced nothing" detectable. See _emit_turn_result.
13015
+ _baseline = len(getattr(state, "messages", []) or [])
12944
13016
  _emit({"type": "step_start", "sessionID": session_id})
12945
13017
  try:
12946
13018
  run_query(initial_prompt)
@@ -12957,8 +13029,9 @@ def repl(config: dict, initial_prompt: str | None = None):
12957
13029
  sys.exit(1)
12958
13030
  raise
12959
13031
  if _protocol_enabled():
12960
- _emit({"type": "text", "part": {"text": _final_assistant_text(state)}})
12961
- _emit({"type": "step_finish", "part": _usage_part(state, config)})
13032
+ _code = _emit_turn_result(state, config, since=_baseline)
13033
+ if _code:
13034
+ sys.exit(_code)
12962
13035
  return
12963
13036
 
12964
13037
  # ── Bracketed paste mode ──────────────────────────────────────────────
@@ -14025,6 +14098,17 @@ def main():
14025
14098
 
14026
14099
  args = parser.parse_args()
14027
14100
 
14101
+ # `--version` and `--help` answer on stdout in every mode. They are
14102
+ # queries, not runs: they emit no frames, so swapping the channel first
14103
+ # would leave stdout empty and hide the answer on stderr.
14104
+ if args.version:
14105
+ print(f"dulus v{VERSION}")
14106
+ sys.exit(0)
14107
+
14108
+ if args.help:
14109
+ print(__doc__)
14110
+ sys.exit(0)
14111
+
14028
14112
  # ── Protocol mode: split the channels before anything can print ────────
14029
14113
  # This must happen before the first print in main() (the license gate is
14030
14114
  # ~90 lines below) or the parent's first stdout line is our banner, not a
@@ -14037,14 +14121,6 @@ def main():
14037
14121
  # human channel is a pipe — without this stderr fills with \033[2K\r.
14038
14122
  os.environ["DULUS_NO_ANIMATIONS"] = "1"
14039
14123
 
14040
- if args.version:
14041
- print(f"dulus v{VERSION}")
14042
- sys.exit(0)
14043
-
14044
- if args.help:
14045
- print(__doc__)
14046
- sys.exit(0)
14047
-
14048
14124
  from config import load_config, save_config, has_api_key
14049
14125
  from providers import detect_provider, PROVIDERS
14050
14126
 
@@ -10,7 +10,7 @@ build-backend = "setuptools.build_meta"
10
10
 
11
11
  [project]
12
12
  name = "dulus"
13
- version = "3.12.0"
13
+ version = "3.13.0"
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"