dulus 0.2.40__tar.gz → 0.2.42__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 (181) hide show
  1. {dulus-0.2.40/dulus.egg-info → dulus-0.2.42}/PKG-INFO +10 -2
  2. {dulus-0.2.40 → dulus-0.2.42}/README.md +8 -1
  3. {dulus-0.2.40 → dulus-0.2.42}/common.py +1 -1
  4. {dulus-0.2.40 → dulus-0.2.42}/data/context.json +42 -12
  5. {dulus-0.2.40 → dulus-0.2.42/dulus.egg-info}/PKG-INFO +10 -2
  6. {dulus-0.2.40 → dulus-0.2.42}/dulus.egg-info/SOURCES.txt +1 -0
  7. {dulus-0.2.40 → dulus-0.2.42}/dulus.py +45 -1
  8. dulus-0.2.42/license_manager.py +187 -0
  9. {dulus-0.2.40 → dulus-0.2.42}/pyproject.toml +1 -1
  10. {dulus-0.2.40 → dulus-0.2.42}/task/store.py +10 -0
  11. {dulus-0.2.40 → dulus-0.2.42}/task/tools.py +17 -6
  12. {dulus-0.2.40 → dulus-0.2.42}/tools.py +39 -0
  13. {dulus-0.2.40 → dulus-0.2.42}/webchat_server.py +223 -44
  14. {dulus-0.2.40 → dulus-0.2.42}/LICENSE +0 -0
  15. {dulus-0.2.40 → dulus-0.2.42}/MANIFEST.in +0 -0
  16. {dulus-0.2.40 → dulus-0.2.42}/agent.py +0 -0
  17. {dulus-0.2.40 → dulus-0.2.42}/backend/__init__.py +0 -0
  18. {dulus-0.2.40 → dulus-0.2.42}/backend/compressor.py +0 -0
  19. {dulus-0.2.40 → dulus-0.2.42}/backend/context.py +0 -0
  20. {dulus-0.2.40 → dulus-0.2.42}/backend/githook.py +0 -0
  21. {dulus-0.2.40 → dulus-0.2.42}/backend/marketplace.py +0 -0
  22. {dulus-0.2.40 → dulus-0.2.42}/backend/mempalace_bridge.py +0 -0
  23. {dulus-0.2.40 → dulus-0.2.42}/backend/personas.py +0 -0
  24. {dulus-0.2.40 → dulus-0.2.42}/backend/plugins.py +0 -0
  25. {dulus-0.2.40 → dulus-0.2.42}/backend/server.py +0 -0
  26. {dulus-0.2.40 → dulus-0.2.42}/backend/tasks.py +0 -0
  27. {dulus-0.2.40 → dulus-0.2.42}/batch_api.py +0 -0
  28. {dulus-0.2.40 → dulus-0.2.42}/checkpoint/__init__.py +0 -0
  29. {dulus-0.2.40 → dulus-0.2.42}/checkpoint/hooks.py +0 -0
  30. {dulus-0.2.40 → dulus-0.2.42}/checkpoint/store.py +0 -0
  31. {dulus-0.2.40 → dulus-0.2.42}/checkpoint/types.py +0 -0
  32. {dulus-0.2.40 → dulus-0.2.42}/claude_code_watcher.py +0 -0
  33. {dulus-0.2.40 → dulus-0.2.42}/clipboard_utils.py +0 -0
  34. {dulus-0.2.40 → dulus-0.2.42}/cloudsave.py +0 -0
  35. {dulus-0.2.40 → dulus-0.2.42}/compaction.py +0 -0
  36. {dulus-0.2.40 → dulus-0.2.42}/config.py +0 -0
  37. {dulus-0.2.40 → dulus-0.2.42}/context.py +0 -0
  38. {dulus-0.2.40 → dulus-0.2.42}/data/__init__.py +0 -0
  39. {dulus-0.2.40 → dulus-0.2.42}/data/active_persona.json +0 -0
  40. {dulus-0.2.40 → dulus-0.2.42}/data/marketplace.json +0 -0
  41. {dulus-0.2.40 → dulus-0.2.42}/data/personas.json +0 -0
  42. {dulus-0.2.40 → dulus-0.2.42}/data/plugins/__init__.py +0 -0
  43. {dulus-0.2.40 → dulus-0.2.42}/data/plugins/composio/__init__.py +0 -0
  44. {dulus-0.2.40 → dulus-0.2.42}/data/plugins/composio/composio_plugin/__init__.py +0 -0
  45. {dulus-0.2.40 → dulus-0.2.42}/data/plugins/composio/composio_plugin/session_manager.py +0 -0
  46. {dulus-0.2.40 → dulus-0.2.42}/data/plugins/composio/composio_plugin/tool_generator.py +0 -0
  47. {dulus-0.2.40 → dulus-0.2.42}/data/plugins/composio/plugin.json +0 -0
  48. {dulus-0.2.40 → dulus-0.2.42}/data/plugins/composio/plugin_tool.py +0 -0
  49. {dulus-0.2.40 → dulus-0.2.42}/data/tasks.json +0 -0
  50. {dulus-0.2.40 → dulus-0.2.42}/docs/README.md +0 -0
  51. {dulus-0.2.40 → dulus-0.2.42}/docs/__init__.py +0 -0
  52. {dulus-0.2.40 → dulus-0.2.42}/docs/api.html +0 -0
  53. {dulus-0.2.40 → dulus-0.2.42}/docs/architecture.md +0 -0
  54. {dulus-0.2.40 → dulus-0.2.42}/docs/azure-speech-template.json +0 -0
  55. {dulus-0.2.40 → dulus-0.2.42}/docs/dashboard/index.html +0 -0
  56. {dulus-0.2.40 → dulus-0.2.42}/docs/divider.svg +0 -0
  57. {dulus-0.2.40 → dulus-0.2.42}/docs/generate.py +0 -0
  58. {dulus-0.2.40 → dulus-0.2.42}/docs/hero.svg +0 -0
  59. {dulus-0.2.40 → dulus-0.2.42}/docs/index.html +0 -0
  60. {dulus-0.2.40 → dulus-0.2.42}/docs/news.md +0 -0
  61. {dulus-0.2.40 → dulus-0.2.42}/docs/nvidia-models.svg +0 -0
  62. {dulus-0.2.40 → dulus-0.2.42}/docs/particle-playground.html +0 -0
  63. {dulus-0.2.40 → dulus-0.2.42}/docs/personas/index.html +0 -0
  64. {dulus-0.2.40 → dulus-0.2.42}/docs/poetry-banner.png +0 -0
  65. {dulus-0.2.40 → dulus-0.2.42}/docs/preview.html +0 -0
  66. {dulus-0.2.40 → dulus-0.2.42}/docs/sec-agents.svg +0 -0
  67. {dulus-0.2.40 → dulus-0.2.42}/docs/sec-brainstorm.svg +0 -0
  68. {dulus-0.2.40 → dulus-0.2.42}/docs/sec-bridges.svg +0 -0
  69. {dulus-0.2.40 → dulus-0.2.42}/docs/sec-features.svg +0 -0
  70. {dulus-0.2.40 → dulus-0.2.42}/docs/sec-freetier.svg +0 -0
  71. {dulus-0.2.40 → dulus-0.2.42}/docs/sec-memory.svg +0 -0
  72. {dulus-0.2.40 → dulus-0.2.42}/docs/sec-models.svg +0 -0
  73. {dulus-0.2.40 → dulus-0.2.42}/docs/sec-perms.svg +0 -0
  74. {dulus-0.2.40 → dulus-0.2.42}/docs/sec-plugins.svg +0 -0
  75. {dulus-0.2.40 → dulus-0.2.42}/docs/sec-quickstart.svg +0 -0
  76. {dulus-0.2.40 → dulus-0.2.42}/docs/sec-ssj.svg +0 -0
  77. {dulus-0.2.40 → dulus-0.2.42}/docs/spinners.svg +0 -0
  78. {dulus-0.2.40 → dulus-0.2.42}/docs/split-pane.svg +0 -0
  79. {dulus-0.2.40 → dulus-0.2.42}/docs/terminal-boot.svg +0 -0
  80. {dulus-0.2.40 → dulus-0.2.42}/docs/uploads/particle-playground.html +0 -0
  81. {dulus-0.2.40 → dulus-0.2.42}/dulus.egg-info/dependency_links.txt +0 -0
  82. {dulus-0.2.40 → dulus-0.2.42}/dulus.egg-info/entry_points.txt +0 -0
  83. {dulus-0.2.40 → dulus-0.2.42}/dulus.egg-info/requires.txt +0 -0
  84. {dulus-0.2.40 → dulus-0.2.42}/dulus.egg-info/top_level.txt +0 -0
  85. {dulus-0.2.40 → dulus-0.2.42}/dulus_gui.py +0 -0
  86. {dulus-0.2.40 → dulus-0.2.42}/dulus_mcp/__init__.py +0 -0
  87. {dulus-0.2.40 → dulus-0.2.42}/dulus_mcp/client.py +0 -0
  88. {dulus-0.2.40 → dulus-0.2.42}/dulus_mcp/config.py +0 -0
  89. {dulus-0.2.40 → dulus-0.2.42}/dulus_mcp/tools.py +0 -0
  90. {dulus-0.2.40 → dulus-0.2.42}/dulus_mcp/types.py +0 -0
  91. {dulus-0.2.40 → dulus-0.2.42}/gui/__init__.py +0 -0
  92. {dulus-0.2.40 → dulus-0.2.42}/gui/agent_bridge.py +0 -0
  93. {dulus-0.2.40 → dulus-0.2.42}/gui/chat_widget.py +0 -0
  94. {dulus-0.2.40 → dulus-0.2.42}/gui/main_window.py +0 -0
  95. {dulus-0.2.40 → dulus-0.2.42}/gui/personas.py +0 -0
  96. {dulus-0.2.40 → dulus-0.2.42}/gui/session_utils.py +0 -0
  97. {dulus-0.2.40 → dulus-0.2.42}/gui/settings_dialog.py +0 -0
  98. {dulus-0.2.40 → dulus-0.2.42}/gui/sidebar.py +0 -0
  99. {dulus-0.2.40 → dulus-0.2.42}/gui/tasks_view.py +0 -0
  100. {dulus-0.2.40 → dulus-0.2.42}/gui/themes.py +0 -0
  101. {dulus-0.2.40 → dulus-0.2.42}/gui/tool_panel.py +0 -0
  102. {dulus-0.2.40 → dulus-0.2.42}/input.py +0 -0
  103. {dulus-0.2.40 → dulus-0.2.42}/memory/__init__.py +0 -0
  104. {dulus-0.2.40 → dulus-0.2.42}/memory/audit.py +0 -0
  105. {dulus-0.2.40 → dulus-0.2.42}/memory/consolidator.py +0 -0
  106. {dulus-0.2.40 → dulus-0.2.42}/memory/context.py +0 -0
  107. {dulus-0.2.40 → dulus-0.2.42}/memory/offload.py +0 -0
  108. {dulus-0.2.40 → dulus-0.2.42}/memory/palace.py +0 -0
  109. {dulus-0.2.40 → dulus-0.2.42}/memory/scan.py +0 -0
  110. {dulus-0.2.40 → dulus-0.2.42}/memory/sessions.py +0 -0
  111. {dulus-0.2.40 → dulus-0.2.42}/memory/store.py +0 -0
  112. {dulus-0.2.40 → dulus-0.2.42}/memory/tools.py +0 -0
  113. {dulus-0.2.40 → dulus-0.2.42}/memory/types.py +0 -0
  114. {dulus-0.2.40 → dulus-0.2.42}/memory/vector_search.py +0 -0
  115. {dulus-0.2.40 → dulus-0.2.42}/multi_agent/__init__.py +0 -0
  116. {dulus-0.2.40 → dulus-0.2.42}/multi_agent/subagent.py +0 -0
  117. {dulus-0.2.40 → dulus-0.2.42}/multi_agent/tools.py +0 -0
  118. {dulus-0.2.40 → dulus-0.2.42}/offload_helper.py +0 -0
  119. {dulus-0.2.40 → dulus-0.2.42}/plugin/__init__.py +0 -0
  120. {dulus-0.2.40 → dulus-0.2.42}/plugin/autoadapter.py +0 -0
  121. {dulus-0.2.40 → dulus-0.2.42}/plugin/loader.py +0 -0
  122. {dulus-0.2.40 → dulus-0.2.42}/plugin/recommend.py +0 -0
  123. {dulus-0.2.40 → dulus-0.2.42}/plugin/store.py +0 -0
  124. {dulus-0.2.40 → dulus-0.2.42}/plugin/types.py +0 -0
  125. {dulus-0.2.40 → dulus-0.2.42}/providers.py +0 -0
  126. {dulus-0.2.40 → dulus-0.2.42}/setup.cfg +0 -0
  127. {dulus-0.2.40 → dulus-0.2.42}/skill/__init__.py +0 -0
  128. {dulus-0.2.40 → dulus-0.2.42}/skill/builtin.py +0 -0
  129. {dulus-0.2.40 → dulus-0.2.42}/skill/clawhub.py +0 -0
  130. {dulus-0.2.40 → dulus-0.2.42}/skill/executor.py +0 -0
  131. {dulus-0.2.40 → dulus-0.2.42}/skill/loader.py +0 -0
  132. {dulus-0.2.40 → dulus-0.2.42}/skill/tools.py +0 -0
  133. {dulus-0.2.40 → dulus-0.2.42}/skills.py +0 -0
  134. {dulus-0.2.40 → dulus-0.2.42}/spinner.py +0 -0
  135. {dulus-0.2.40 → dulus-0.2.42}/string_utils.py +0 -0
  136. {dulus-0.2.40 → dulus-0.2.42}/subagent.py +0 -0
  137. {dulus-0.2.40 → dulus-0.2.42}/task/__init__.py +0 -0
  138. {dulus-0.2.40 → dulus-0.2.42}/task/types.py +0 -0
  139. {dulus-0.2.40 → dulus-0.2.42}/tests/test_afk_yolo.py +0 -0
  140. {dulus-0.2.40 → dulus-0.2.42}/tests/test_approval_runtime.py +0 -0
  141. {dulus-0.2.40 → dulus-0.2.42}/tests/test_background_task_tools.py +0 -0
  142. {dulus-0.2.40 → dulus-0.2.42}/tests/test_background_tasks.py +0 -0
  143. {dulus-0.2.40 → dulus-0.2.42}/tests/test_checkpoint.py +0 -0
  144. {dulus-0.2.40 → dulus-0.2.42}/tests/test_clipboard_utils.py +0 -0
  145. {dulus-0.2.40 → dulus-0.2.42}/tests/test_compaction.py +0 -0
  146. {dulus-0.2.40 → dulus-0.2.42}/tests/test_diff_view.py +0 -0
  147. {dulus-0.2.40 → dulus-0.2.42}/tests/test_diff_visualization.py +0 -0
  148. {dulus-0.2.40 → dulus-0.2.42}/tests/test_display_blocks.py +0 -0
  149. {dulus-0.2.40 → dulus-0.2.42}/tests/test_export_import.py +0 -0
  150. {dulus-0.2.40 → dulus-0.2.42}/tests/test_hook_engine.py +0 -0
  151. {dulus-0.2.40 → dulus-0.2.42}/tests/test_injection_fix.py +0 -0
  152. {dulus-0.2.40 → dulus-0.2.42}/tests/test_license.py +0 -0
  153. {dulus-0.2.40 → dulus-0.2.42}/tests/test_mcp.py +0 -0
  154. {dulus-0.2.40 → dulus-0.2.42}/tests/test_memory.py +0 -0
  155. {dulus-0.2.40 → dulus-0.2.42}/tests/test_notification_manager.py +0 -0
  156. {dulus-0.2.40 → dulus-0.2.42}/tests/test_plugin.py +0 -0
  157. {dulus-0.2.40 → dulus-0.2.42}/tests/test_session_fork.py +0 -0
  158. {dulus-0.2.40 → dulus-0.2.42}/tests/test_shell_mode.py +0 -0
  159. {dulus-0.2.40 → dulus-0.2.42}/tests/test_skills.py +0 -0
  160. {dulus-0.2.40 → dulus-0.2.42}/tests/test_steer_input.py +0 -0
  161. {dulus-0.2.40 → dulus-0.2.42}/tests/test_subagent.py +0 -0
  162. {dulus-0.2.40 → dulus-0.2.42}/tests/test_task.py +0 -0
  163. {dulus-0.2.40 → dulus-0.2.42}/tests/test_telegram_buffer.py +0 -0
  164. {dulus-0.2.40 → dulus-0.2.42}/tests/test_think_tool.py +0 -0
  165. {dulus-0.2.40 → dulus-0.2.42}/tests/test_todo_tool.py +0 -0
  166. {dulus-0.2.40 → dulus-0.2.42}/tests/test_todo_visualization.py +0 -0
  167. {dulus-0.2.40 → dulus-0.2.42}/tests/test_tool_registry.py +0 -0
  168. {dulus-0.2.40 → dulus-0.2.42}/tests/test_voice.py +0 -0
  169. {dulus-0.2.40 → dulus-0.2.42}/tests/test_wire_events.py +0 -0
  170. {dulus-0.2.40 → dulus-0.2.42}/tmux_offloader.py +0 -0
  171. {dulus-0.2.40 → dulus-0.2.42}/tmux_tools.py +0 -0
  172. {dulus-0.2.40 → dulus-0.2.42}/tool_registry.py +0 -0
  173. {dulus-0.2.40 → dulus-0.2.42}/ui/__init__.py +0 -0
  174. {dulus-0.2.40 → dulus-0.2.42}/ui/input.py +0 -0
  175. {dulus-0.2.40 → dulus-0.2.42}/ui/render.py +0 -0
  176. {dulus-0.2.40 → dulus-0.2.42}/voice/__init__.py +0 -0
  177. {dulus-0.2.40 → dulus-0.2.42}/voice/keyterms.py +0 -0
  178. {dulus-0.2.40 → dulus-0.2.42}/voice/recorder.py +0 -0
  179. {dulus-0.2.40 → dulus-0.2.42}/voice/stt.py +0 -0
  180. {dulus-0.2.40 → dulus-0.2.42}/voice/tts.py +0 -0
  181. {dulus-0.2.40 → dulus-0.2.42}/webchat.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dulus
3
- Version: 0.2.40
3
+ Version: 0.2.42
4
4
  Summary: Spanish-first multi-provider AI CLI — 14 NVIDIA models free, Mesa Redonda, voice, TTS, RTK token reducer, MemPalace
5
5
  Author: KevRojo
6
6
  License: GPL-3.0
@@ -22,6 +22,7 @@ Classifier: Topic :: Terminals
22
22
  Requires-Python: >=3.11
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
+ License-File: license_manager.py
25
26
  Requires-Dist: anthropic>=0.40.0
26
27
  Requires-Dist: openai>=1.30.0
27
28
  Requires-Dist: httpx>=0.27.0
@@ -68,7 +69,7 @@ SET /sticky_input ON since the first run for the best experience!
68
69
  <a href="https://pypi.org/project/dulus/"><img src="https://static.pepy.tech/badge/dulus?style=flat-square" alt="downloads"/></a>
69
70
  <img src="https://img.shields.io/badge/python-3.11+-ff6b1f?style=flat-square&labelColor=07070a" alt="python"/>
70
71
  <img src="https://img.shields.io/badge/license-GPLv3-ff6b1f?style=flat-square&labelColor=07070a" alt="license"/>
71
- <img src="https://img.shields.io/badge/version-v0.2.30-ff6b1f?style=flat-square&labelColor=07070a" alt="version"/>
72
+ <img src="https://img.shields.io/badge/version-v0.2.42-ff6b1f?style=flat-square&labelColor=07070a" alt="version"/>
72
73
  <img src="https://img.shields.io/badge/providers-11-ff6b1f?style=flat-square&labelColor=07070a" alt="providers"/>
73
74
  <img src="https://img.shields.io/badge/tools-27-ff6b1f?style=flat-square&labelColor=07070a" alt="tools"/>
74
75
  <img src="https://img.shields.io/badge/tests-263+-ff6b1f?style=flat-square&labelColor=07070a" alt="tests"/>
@@ -226,6 +227,13 @@ echo "explain this diff" | git diff | dulus -p --accept-all
226
227
 
227
228
  <p align="center"><sub>↑ session boot. soul loaded, gold memory warm, shell sniffed. the little circles are real buttons on your Mac.</sub></p>
228
229
 
230
+ ### 💻 Dulus OS (Sandbox)
231
+ > [!NOTE]
232
+ > **Experimental features:** The folder `sandbox/` contains the early implementation of **Dulus OS** — a mini-operating system that runs entirely in your browser. It is currently in heavy development and not 100% functional yet. It will serve as a secure, isolated environment for browser-based tool execution and visualizations.
233
+
234
+ ---
235
+
236
+
229
237
  ---
230
238
 
231
239
  <p align="center"><img src="https://raw.githubusercontent.com/KevRojo/Dulus/main/docs/sec-features.svg" alt="Features" width="100%"></p>
@@ -22,7 +22,7 @@ SET /sticky_input ON since the first run for the best experience!
22
22
  <a href="https://pypi.org/project/dulus/"><img src="https://static.pepy.tech/badge/dulus?style=flat-square" alt="downloads"/></a>
23
23
  <img src="https://img.shields.io/badge/python-3.11+-ff6b1f?style=flat-square&labelColor=07070a" alt="python"/>
24
24
  <img src="https://img.shields.io/badge/license-GPLv3-ff6b1f?style=flat-square&labelColor=07070a" alt="license"/>
25
- <img src="https://img.shields.io/badge/version-v0.2.30-ff6b1f?style=flat-square&labelColor=07070a" alt="version"/>
25
+ <img src="https://img.shields.io/badge/version-v0.2.42-ff6b1f?style=flat-square&labelColor=07070a" alt="version"/>
26
26
  <img src="https://img.shields.io/badge/providers-11-ff6b1f?style=flat-square&labelColor=07070a" alt="providers"/>
27
27
  <img src="https://img.shields.io/badge/tools-27-ff6b1f?style=flat-square&labelColor=07070a" alt="tools"/>
28
28
  <img src="https://img.shields.io/badge/tests-263+-ff6b1f?style=flat-square&labelColor=07070a" alt="tests"/>
@@ -180,6 +180,13 @@ echo "explain this diff" | git diff | dulus -p --accept-all
180
180
 
181
181
  <p align="center"><sub>↑ session boot. soul loaded, gold memory warm, shell sniffed. the little circles are real buttons on your Mac.</sub></p>
182
182
 
183
+ ### 💻 Dulus OS (Sandbox)
184
+ > [!NOTE]
185
+ > **Experimental features:** The folder `sandbox/` contains the early implementation of **Dulus OS** — a mini-operating system that runs entirely in your browser. It is currently in heavy development and not 100% functional yet. It will serve as a secure, isolated environment for browser-based tool execution and visualizations.
186
+
187
+ ---
188
+
189
+
183
190
  ---
184
191
 
185
192
  <p align="center"><img src="https://raw.githubusercontent.com/KevRojo/Dulus/main/docs/sec-features.svg" alt="Features" width="100%"></p>
@@ -3,7 +3,7 @@ import json
3
3
 
4
4
  # ── Import slash completer helpers ──
5
5
  try:
6
- from backend.ui.input import (
6
+ from ui.input import (
7
7
  setup as _setup_slash_complete,
8
8
  read_line as _read_line_pt,
9
9
  reset_session as _reset_pt_session,
@@ -9,22 +9,52 @@
9
9
  "project": {
10
10
  "name": "Dulus Command Center",
11
11
  "repo_stats": {
12
- "files": 241,
13
- "lines": 110197,
12
+ "files": 26697,
13
+ "lines": 3602498,
14
14
  "languages": {
15
15
  ".example": 5,
16
- ".py": 58599,
17
- ".html": 14582,
18
- "no_ext": 1158,
16
+ "no_ext": 16410,
17
+ ".py": 60171,
18
+ ".html": 15960,
19
19
  ".in": 2,
20
20
  ".toml": 123,
21
- ".md": 2050,
22
- ".txt": 40,
23
- ".TAG": 4,
24
- ".json": 728,
25
- ".svg": 1123,
26
- ".png": 31639,
27
- ".sh": 144
21
+ ".md": 79646,
22
+ ".txt": 1018,
23
+ ".json": 133634,
24
+ ".whl": 17908,
25
+ ".gz": 18064,
26
+ ".svg": 1168,
27
+ ".png": 157513,
28
+ ".sh": 144,
29
+ ".js": 1992568,
30
+ ".ts": 570599,
31
+ ".log": 1,
32
+ ".jpg": 1076,
33
+ ".tsx": 34508,
34
+ ".css": 2848,
35
+ ".cmd": 476,
36
+ ".ps1": 784,
37
+ ".tsbuildinfo": 2,
38
+ ".mts": 29541,
39
+ ".yml": 250,
40
+ ".markdown": 763,
41
+ ".flow": 21999,
42
+ ".cjs": 150052,
43
+ ".cts": 44618,
44
+ ".map": 5059,
45
+ ".mjs": 102525,
46
+ ".BSD": 59,
47
+ ".lock": 2598,
48
+ ".nix": 20,
49
+ ".bnf": 32,
50
+ ".coffee": 1,
51
+ ".1": 164,
52
+ ".php": 156,
53
+ ".jst": 1840,
54
+ ".def": 534,
55
+ ".node": 34828,
56
+ ".snap": 4138,
57
+ ".exe": 98693
28
58
  }
29
59
  },
30
60
  "recent_commits": [],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dulus
3
- Version: 0.2.40
3
+ Version: 0.2.42
4
4
  Summary: Spanish-first multi-provider AI CLI — 14 NVIDIA models free, Mesa Redonda, voice, TTS, RTK token reducer, MemPalace
5
5
  Author: KevRojo
6
6
  License: GPL-3.0
@@ -22,6 +22,7 @@ Classifier: Topic :: Terminals
22
22
  Requires-Python: >=3.11
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
+ License-File: license_manager.py
25
26
  Requires-Dist: anthropic>=0.40.0
26
27
  Requires-Dist: openai>=1.30.0
27
28
  Requires-Dist: httpx>=0.27.0
@@ -68,7 +69,7 @@ SET /sticky_input ON since the first run for the best experience!
68
69
  <a href="https://pypi.org/project/dulus/"><img src="https://static.pepy.tech/badge/dulus?style=flat-square" alt="downloads"/></a>
69
70
  <img src="https://img.shields.io/badge/python-3.11+-ff6b1f?style=flat-square&labelColor=07070a" alt="python"/>
70
71
  <img src="https://img.shields.io/badge/license-GPLv3-ff6b1f?style=flat-square&labelColor=07070a" alt="license"/>
71
- <img src="https://img.shields.io/badge/version-v0.2.30-ff6b1f?style=flat-square&labelColor=07070a" alt="version"/>
72
+ <img src="https://img.shields.io/badge/version-v0.2.42-ff6b1f?style=flat-square&labelColor=07070a" alt="version"/>
72
73
  <img src="https://img.shields.io/badge/providers-11-ff6b1f?style=flat-square&labelColor=07070a" alt="providers"/>
73
74
  <img src="https://img.shields.io/badge/tools-27-ff6b1f?style=flat-square&labelColor=07070a" alt="tools"/>
74
75
  <img src="https://img.shields.io/badge/tests-263+-ff6b1f?style=flat-square&labelColor=07070a" alt="tests"/>
@@ -226,6 +227,13 @@ echo "explain this diff" | git diff | dulus -p --accept-all
226
227
 
227
228
  <p align="center"><sub>↑ session boot. soul loaded, gold memory warm, shell sniffed. the little circles are real buttons on your Mac.</sub></p>
228
229
 
230
+ ### 💻 Dulus OS (Sandbox)
231
+ > [!NOTE]
232
+ > **Experimental features:** The folder `sandbox/` contains the early implementation of **Dulus OS** — a mini-operating system that runs entirely in your browser. It is currently in heavy development and not 100% functional yet. It will serve as a secure, isolated environment for browser-based tool execution and visualizations.
233
+
234
+ ---
235
+
236
+
229
237
  ---
230
238
 
231
239
  <p align="center"><img src="https://raw.githubusercontent.com/KevRojo/Dulus/main/docs/sec-features.svg" alt="Features" width="100%"></p>
@@ -13,6 +13,7 @@ context.py
13
13
  dulus.py
14
14
  dulus_gui.py
15
15
  input.py
16
+ license_manager.py
16
17
  offload_helper.py
17
18
  providers.py
18
19
  pyproject.toml
@@ -115,6 +115,8 @@ Slash commands in REPL:
115
115
  /kimi_chats List recent Kimi conversations
116
116
  /webchat [port] Spawn web chat UI (background Flask server)
117
117
  /webchat stop Kill the webchat server
118
+ /sandbox Open Dulus Sandbox OS in browser (starts webchat if needed)
119
+ /sandbox stop Stop the webchat server
118
120
  /rtk [on|off] Toggle RTK token-optimized shell command rewriting
119
121
  /exit /quit Exit
120
122
  """
@@ -1828,6 +1830,46 @@ def cmd_webchat(args: str, state, config) -> bool:
1828
1830
  info(f"WebChat spawn timed out -- try opening {local_url} manually or check :{port}")
1829
1831
  return True
1830
1832
 
1833
+
1834
+ def cmd_sandbox(args: str, state, config) -> bool:
1835
+ """Open the Dulus Sandbox OS in the browser.
1836
+
1837
+ /sandbox — Ensure webchat is running, open /sandbox in browser
1838
+ /sandbox stop — Alias for /webchat stop
1839
+ """
1840
+ import webbrowser, time, urllib.request
1841
+
1842
+ arg = (args or "").strip().lower()
1843
+
1844
+ if arg in ("stop", "kill", "off"):
1845
+ return cmd_webchat("stop", state, config)
1846
+
1847
+ # Make sure webchat is running first
1848
+ import webchat_server
1849
+ port = config.get("_webchat_port", 5000)
1850
+
1851
+ def _wc_alive(p):
1852
+ try:
1853
+ urllib.request.urlopen(f"http://127.0.0.1:{p}/api/health", timeout=0.5).read(1)
1854
+ return True
1855
+ except Exception:
1856
+ return False
1857
+
1858
+ if not _wc_alive(port):
1859
+ ok("Starting WebChat first...")
1860
+ cmd_webchat("", state, config)
1861
+ # Wait up to 5s for it to be ready
1862
+ for _ in range(20):
1863
+ if _wc_alive(port):
1864
+ break
1865
+ time.sleep(0.25)
1866
+
1867
+ sandbox_url = f"http://127.0.0.1:{port}/sandbox"
1868
+ ok(f"Opening Sandbox OS -> {sandbox_url}")
1869
+ webbrowser.open(sandbox_url)
1870
+ info("Mini OS running in your browser. Use /sandbox stop to shut down the server.")
1871
+ return True
1872
+
1831
1873
  def cmd_gui(_args: str, _state, config) -> bool:
1832
1874
  """Launch the desktop GUI from the REPL."""
1833
1875
  try:
@@ -7440,6 +7482,7 @@ COMMANDS = {
7440
7482
  "voice": cmd_voice,
7441
7483
  "git": cmd_git,
7442
7484
  "webchat": cmd_webchat,
7485
+ "sandbox": cmd_sandbox,
7443
7486
  "gui": cmd_gui,
7444
7487
  "brave": cmd_brave,
7445
7488
  "rtk": cmd_rtk,
@@ -7579,7 +7622,8 @@ _CMD_META: dict[str, tuple[str, list[str]]] = {
7579
7622
  "gemini_chats": ("Manage Gemini Web conversations", ["new"]),
7580
7623
  "gemini_harvest": ("Harvest Gemini Web cookies (alias)", []),
7581
7624
  "harvest-claude": ("Harvest Claude.ai cookies (alias)", []),
7582
- "webchat": ("Spawn web chat UI", ["stop"]),
7625
+ "webchat": ("Spawn web chat UI", ["stop", "lan"]),
7626
+ "sandbox": ("Open Dulus Sandbox OS in browser", ["stop"]),
7583
7627
  "gui": ("Launch desktop GUI", []),
7584
7628
  }
7585
7629
 
@@ -0,0 +1,187 @@
1
+ """Dulus License Manager — Offline-first key validation + feature gating.
2
+
3
+ Tiers:
4
+ FREE No key required. Limited tool calls, local providers only.
5
+ PRO $15/mo. Full features, BYOK, priority support.
6
+ ENTERPRISE $50/mo. Team features + admin dashboard + SSO (future).
7
+
8
+ Key format (offline):
9
+ DULUS-<base64(json_payload + ":" + hmac_signature)>
10
+
11
+ The secret lives in ~/.dulus/.license_secret (never commit this file).
12
+ If the secret file is missing we fall back to a hardcoded dev-key so
13
+ Kev can develop without friction, but distribution builds MUST bundle
14
+ a real secret via CI env var or PyInstaller --add-data.
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import base64
19
+ import hashlib
20
+ import hmac
21
+ import json
22
+ import os
23
+ import sys
24
+ import time
25
+ from pathlib import Path
26
+ from typing import Optional
27
+
28
+ # ── Secret resolution ───────────────────────────────────────────────────────
29
+ # 1. CI / build-time env var (safest for releases)
30
+ # 2. ~/.dulus/.license_secret (Kev's local dev key)
31
+ # 3. Fallback dev secret (NEVER use in production builds)
32
+ _LICENSE_SECRET = os.environ.get("DULUS_LICENSE_SECRET", "")
33
+ if not _LICENSE_SECRET:
34
+ _secret_path = Path.home() / ".dulus" / ".license_secret"
35
+ if _secret_path.exists():
36
+ _LICENSE_SECRET = _secret_path.read_text().strip()
37
+ else:
38
+ _LICENSE_SECRET = "dulus-dev-secret-do-not-distribute"
39
+ import warnings
40
+ warnings.warn(
41
+ "DULUS_LICENSE_SECRET not set — using hardcoded DEV secret. "
42
+ "Generated keys will be trivially forgeable in production!",
43
+ RuntimeWarning,
44
+ stacklevel=2,
45
+ )
46
+
47
+
48
+ class LicenseTier:
49
+ FREE = "free"
50
+ PRO = "pro"
51
+ ENTERPRISE = "enterprise"
52
+
53
+
54
+ class LicenseManager:
55
+ """Parse and validate a Dulus license key."""
56
+
57
+ def __init__(self, key: Optional[str] = None):
58
+ self.raw_key = key or ""
59
+ self.tier = LicenseTier.FREE
60
+ self.expiry: float = 0.0
61
+ self.features: list[str] = []
62
+ self.valid = False
63
+ self.error: Optional[str] = None
64
+
65
+ if self.raw_key:
66
+ self._validate()
67
+
68
+ # ── validation core ─────────────────────────────────────────────────────
69
+
70
+ def _validate(self) -> None:
71
+ if not self.raw_key.startswith("DULUS-"):
72
+ self.error = "Invalid key prefix"
73
+ return
74
+
75
+ try:
76
+ b64 = self.raw_key.split("-", 1)[1]
77
+ payload_sig = base64.urlsafe_b64decode(b64 + "==")
78
+ payload_json, sig_hex = payload_sig.rsplit(b":", 1)
79
+ data = json.loads(payload_json)
80
+ except Exception as exc:
81
+ self.error = f"Malformed key: {exc}"
82
+ return
83
+
84
+ # Verify HMAC-SHA256 signature
85
+ expected_sig = hmac.new(
86
+ _LICENSE_SECRET.encode(),
87
+ payload_json,
88
+ hashlib.sha256,
89
+ ).hexdigest()[:24]
90
+
91
+ if not hmac.compare_digest(sig_hex.decode(), expected_sig):
92
+ self.error = "Invalid signature (tampered or wrong secret)"
93
+ return
94
+
95
+ self.tier = data.get("tier", LicenseTier.FREE)
96
+ self.expiry = data.get("exp", 0)
97
+ self.features = data.get("features", [])
98
+
99
+ if time.time() > self.expiry:
100
+ self.error = "License expired"
101
+ return
102
+
103
+ self.valid = True
104
+
105
+ # ── feature gates ───────────────────────────────────────────────────────
106
+
107
+ def can_use(self, feature: str) -> bool:
108
+ """Check if a feature is allowed by current tier."""
109
+ if self.tier == LicenseTier.ENTERPRISE:
110
+ return True
111
+ if self.tier == LicenseTier.PRO:
112
+ return feature not in {"sso", "audit_logs", "admin_dashboard"}
113
+ # FREE
114
+ free_features = {"chat", "tools_basic", "local_providers"}
115
+ return feature in free_features
116
+
117
+ def max_tool_calls(self) -> int:
118
+ if self.tier == LicenseTier.ENTERPRISE:
119
+ return 999_999
120
+ if self.tier == LicenseTier.PRO:
121
+ return 10_000
122
+ return 25 # FREE daily limit
123
+
124
+ def max_providers(self) -> int:
125
+ if self.tier in (LicenseTier.PRO, LicenseTier.ENTERPRISE):
126
+ return 99
127
+ return 2 # FREE: e.g. ollama + 1 cloud
128
+
129
+ def max_subagents(self) -> int:
130
+ if self.tier == LicenseTier.ENTERPRISE:
131
+ return 50
132
+ if self.tier == LicenseTier.PRO:
133
+ return 10
134
+ return 0 # FREE: no subagents
135
+
136
+ def max_plugins(self) -> int:
137
+ if self.tier == LicenseTier.ENTERPRISE:
138
+ return 999
139
+ if self.tier == LicenseTier.PRO:
140
+ return 50
141
+ return 3 # FREE
142
+
143
+ def allow_cloudsave(self) -> bool:
144
+ return self.tier in (LicenseTier.PRO, LicenseTier.ENTERPRISE)
145
+
146
+ def allow_voice(self) -> bool:
147
+ return self.tier in (LicenseTier.PRO, LicenseTier.ENTERPRISE)
148
+
149
+ def allow_telegram(self) -> bool:
150
+ return self.tier in (LicenseTier.PRO, LicenseTier.ENTERPRISE)
151
+
152
+ def allow_mcp(self) -> bool:
153
+ return self.tier in (LicenseTier.PRO, LicenseTier.ENTERPRISE)
154
+
155
+ # ── UI helpers ──────────────────────────────────────────────────────────
156
+
157
+ def status_banner(self) -> str:
158
+ if self.error:
159
+ return f"[LICENSE EXPIRED / INVALID] {self.error} — running in FREE mode"
160
+ if self.tier == LicenseTier.FREE:
161
+ return "[FREE] Limited features. Upgrade: https://getdulus.dev/pro"
162
+ return f"[{self.tier.upper()}] Valid until {time.strftime('%Y-%m-%d', time.localtime(self.expiry))}"
163
+
164
+
165
+ # ── CLI helper for Kev ─────────────────────────────────────────────────────
166
+
167
+ def _generate_key(tier: str, days: int, secret: str) -> str:
168
+ """Generate a signed license key (Kev-only tool)."""
169
+ payload = json.dumps({
170
+ "tier": tier,
171
+ "exp": int(time.time() + days * 86400),
172
+ "features": [],
173
+ "iat": int(time.time()),
174
+ }, separators=(",", ":")).encode()
175
+ sig = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()[:24]
176
+ token = base64.urlsafe_b64encode(payload + b":" + sig.encode()).decode().rstrip("=")
177
+ return f"DULUS-{token}"
178
+
179
+
180
+ if __name__ == "__main__":
181
+ import argparse
182
+ ap = argparse.ArgumentParser(description="Dulus License Key Generator (Kev only)")
183
+ ap.add_argument("tier", choices=["free", "pro", "enterprise"])
184
+ ap.add_argument("--days", type=int, default=30)
185
+ ap.add_argument("--secret", default=_LICENSE_SECRET)
186
+ args = ap.parse_args()
187
+ print(_generate_key(args.tier, args.days, args.secret))
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dulus"
7
- version = "0.2.40"
7
+ version = "0.2.42"
8
8
  description = "Spanish-first multi-provider AI CLI — 14 NVIDIA models free, Mesa Redonda, voice, TTS, RTK token reducer, MemPalace"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -61,15 +61,25 @@ def _next_id() -> str:
61
61
  def create_task(
62
62
  subject: str,
63
63
  description: str,
64
+ status: str = "pending",
65
+ owner: str = "",
64
66
  active_form: str = "",
65
67
  metadata: dict[str, Any] | None = None,
66
68
  ) -> Task:
67
69
  with _lock:
68
70
  _load()
71
+ # Validate status enum
72
+ try:
73
+ task_status = TaskStatus(status)
74
+ except ValueError:
75
+ task_status = TaskStatus.PENDING
76
+
69
77
  task = Task(
70
78
  id=_next_id(),
71
79
  subject=subject,
72
80
  description=description,
81
+ status=task_status,
82
+ owner=owner,
73
83
  active_form=active_form,
74
84
  metadata=metadata or {},
75
85
  )
@@ -26,6 +26,15 @@ _TASK_CREATE_SCHEMA = {
26
26
  "type": "string",
27
27
  "description": "What needs to be done",
28
28
  },
29
+ "status": {
30
+ "type": "string",
31
+ "enum": ["pending", "in_progress", "completed", "cancelled"],
32
+ "description": "Current state of the task (default is 'pending')",
33
+ },
34
+ "owner": {
35
+ "type": "string",
36
+ "description": "The agent or user responsible (e.g. 'A', 'B', 'User')",
37
+ },
29
38
  "active_form": {
30
39
  "type": "string",
31
40
  "description": (
@@ -38,7 +47,7 @@ _TASK_CREATE_SCHEMA = {
38
47
  "description": "Arbitrary key-value metadata to attach to the task",
39
48
  },
40
49
  },
41
- "required": ["subject", "description"],
50
+ "required": ["subject", "description", "status", "owner"],
42
51
  },
43
52
  }
44
53
 
@@ -128,9 +137,9 @@ _TASK_LIST_SCHEMA = {
128
137
 
129
138
  # ── Implementations ────────────────────────────────────────────────────────────
130
139
 
131
- def _task_create(subject: str, description: str, active_form: str = "", metadata: dict = None) -> str:
132
- task = create_task(subject, description, active_form=active_form, metadata=metadata)
133
- return f"Task #{task.id} created: {task.subject}"
140
+ def _task_create(subject: str, description: str, status: str = "pending", owner: str = "", active_form: str = "", metadata: dict = None) -> str:
141
+ task = create_task(subject, description, status=status, owner=owner, active_form=active_form, metadata=metadata)
142
+ return f"Task #{task.id} created: {task.subject} (Owner: {task.owner}, Status: {task.status.value})"
134
143
 
135
144
 
136
145
  def _task_update(
@@ -220,8 +229,10 @@ def _register() -> None:
220
229
  func=lambda p, c: _task_create(
221
230
  p["subject"],
222
231
  p["description"],
223
- p.get("active_form", ""),
224
- p.get("metadata"),
232
+ status=p.get("status", "pending"),
233
+ owner=p.get("owner", ""),
234
+ active_form=p.get("active_form", ""),
235
+ metadata=p.get("metadata"),
225
236
  ),
226
237
  read_only=False,
227
238
  concurrent_safe=True,
@@ -59,7 +59,20 @@ def _is_in_tg_turn(config: dict) -> bool:
59
59
 
60
60
  # ── Tool JSON schemas (sent to Claude API) ─────────────────────────────────
61
61
 
62
+ _LAUNCH_SANDBOX_SCHEMA = {
63
+ "name": "LaunchSandbox",
64
+ "description": "Start the Dulus Sandbox (mini-OS) web interface in the browser. "
65
+ "Provides a visual desktop experience with integrated Dulus Terminal.",
66
+ "input_schema": {
67
+ "type": "object",
68
+ "properties": {
69
+ "stop": {"type": "boolean", "description": "If true, stop the server instead of starting it."}
70
+ },
71
+ },
72
+ }
73
+
62
74
  TOOL_SCHEMAS = [
75
+ _LAUNCH_SANDBOX_SCHEMA,
63
76
  {
64
77
  "name": "Read",
65
78
  "description": (
@@ -2683,6 +2696,30 @@ register_tool(ToolDef(name="GitStatus", schema=_GIT_STATUS_SCHEMA, func=_git_sta
2683
2696
  register_tool(ToolDef(name="GitLog", schema=_GIT_LOG_SCHEMA, func=_git_log, read_only=True, concurrent_safe=True))
2684
2697
 
2685
2698
 
2699
+ def _launch_sandbox(params: dict, config: dict) -> str:
2700
+ # Use the existing command handler from dulus.py
2701
+ try:
2702
+ from dulus import COMMANDS
2703
+ handler = COMMANDS.get("sandbox")
2704
+ if not handler:
2705
+ return "Error: /sandbox command not found in Dulus."
2706
+
2707
+ stop = params.get("stop", False)
2708
+ args = "stop" if stop else ""
2709
+
2710
+ state = config.get("_state")
2711
+ if not state:
2712
+ return "Error: Dulus session state not available to tool."
2713
+
2714
+ handler(args, state, config)
2715
+ return "Dulus Sandbox OS opened in browser." if not stop else "Sandbox stopped."
2716
+ except Exception as e:
2717
+ return f"Error launching sandbox: {e}"
2718
+
2719
+
2720
+ register_tool(ToolDef(name="LaunchSandbox", schema=_LAUNCH_SANDBOX_SCHEMA, func=_launch_sandbox))
2721
+
2722
+
2686
2723
  # Plugins are loaded once when Dulus starts (not on every reload to avoid overhead)
2687
2724
  try:
2688
2725
  from plugin.loader import register_plugin_tools
@@ -2700,3 +2737,5 @@ except Exception:
2700
2737
  # If plugin system fails, continue with core tools only
2701
2738
  _plugin_count = 0
2702
2739
 
2740
+
2741
+