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.
- {dulus-0.2.40/dulus.egg-info → dulus-0.2.42}/PKG-INFO +10 -2
- {dulus-0.2.40 → dulus-0.2.42}/README.md +8 -1
- {dulus-0.2.40 → dulus-0.2.42}/common.py +1 -1
- {dulus-0.2.40 → dulus-0.2.42}/data/context.json +42 -12
- {dulus-0.2.40 → dulus-0.2.42/dulus.egg-info}/PKG-INFO +10 -2
- {dulus-0.2.40 → dulus-0.2.42}/dulus.egg-info/SOURCES.txt +1 -0
- {dulus-0.2.40 → dulus-0.2.42}/dulus.py +45 -1
- dulus-0.2.42/license_manager.py +187 -0
- {dulus-0.2.40 → dulus-0.2.42}/pyproject.toml +1 -1
- {dulus-0.2.40 → dulus-0.2.42}/task/store.py +10 -0
- {dulus-0.2.40 → dulus-0.2.42}/task/tools.py +17 -6
- {dulus-0.2.40 → dulus-0.2.42}/tools.py +39 -0
- {dulus-0.2.40 → dulus-0.2.42}/webchat_server.py +223 -44
- {dulus-0.2.40 → dulus-0.2.42}/LICENSE +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/MANIFEST.in +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/agent.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/backend/__init__.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/backend/compressor.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/backend/context.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/backend/githook.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/backend/marketplace.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/backend/mempalace_bridge.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/backend/personas.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/backend/plugins.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/backend/server.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/backend/tasks.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/batch_api.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/checkpoint/__init__.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/checkpoint/hooks.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/checkpoint/store.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/checkpoint/types.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/claude_code_watcher.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/clipboard_utils.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/cloudsave.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/compaction.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/config.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/context.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/data/__init__.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/data/active_persona.json +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/data/marketplace.json +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/data/personas.json +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/data/plugins/__init__.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/data/plugins/composio/__init__.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/data/plugins/composio/composio_plugin/__init__.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/data/plugins/composio/composio_plugin/session_manager.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/data/plugins/composio/composio_plugin/tool_generator.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/data/plugins/composio/plugin.json +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/data/plugins/composio/plugin_tool.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/data/tasks.json +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/docs/README.md +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/docs/__init__.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/docs/api.html +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/docs/architecture.md +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/docs/azure-speech-template.json +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/docs/dashboard/index.html +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/docs/divider.svg +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/docs/generate.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/docs/hero.svg +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/docs/index.html +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/docs/news.md +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/docs/nvidia-models.svg +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/docs/particle-playground.html +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/docs/personas/index.html +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/docs/poetry-banner.png +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/docs/preview.html +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/docs/sec-agents.svg +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/docs/sec-brainstorm.svg +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/docs/sec-bridges.svg +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/docs/sec-features.svg +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/docs/sec-freetier.svg +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/docs/sec-memory.svg +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/docs/sec-models.svg +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/docs/sec-perms.svg +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/docs/sec-plugins.svg +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/docs/sec-quickstart.svg +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/docs/sec-ssj.svg +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/docs/spinners.svg +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/docs/split-pane.svg +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/docs/terminal-boot.svg +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/docs/uploads/particle-playground.html +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/dulus.egg-info/dependency_links.txt +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/dulus.egg-info/entry_points.txt +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/dulus.egg-info/requires.txt +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/dulus.egg-info/top_level.txt +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/dulus_gui.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/dulus_mcp/__init__.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/dulus_mcp/client.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/dulus_mcp/config.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/dulus_mcp/tools.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/dulus_mcp/types.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/gui/__init__.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/gui/agent_bridge.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/gui/chat_widget.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/gui/main_window.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/gui/personas.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/gui/session_utils.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/gui/settings_dialog.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/gui/sidebar.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/gui/tasks_view.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/gui/themes.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/gui/tool_panel.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/input.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/memory/__init__.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/memory/audit.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/memory/consolidator.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/memory/context.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/memory/offload.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/memory/palace.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/memory/scan.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/memory/sessions.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/memory/store.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/memory/tools.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/memory/types.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/memory/vector_search.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/multi_agent/__init__.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/multi_agent/subagent.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/multi_agent/tools.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/offload_helper.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/plugin/__init__.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/plugin/autoadapter.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/plugin/loader.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/plugin/recommend.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/plugin/store.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/plugin/types.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/providers.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/setup.cfg +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/skill/__init__.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/skill/builtin.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/skill/clawhub.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/skill/executor.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/skill/loader.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/skill/tools.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/skills.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/spinner.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/string_utils.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/subagent.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/task/__init__.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/task/types.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tests/test_afk_yolo.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tests/test_approval_runtime.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tests/test_background_task_tools.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tests/test_background_tasks.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tests/test_checkpoint.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tests/test_clipboard_utils.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tests/test_compaction.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tests/test_diff_view.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tests/test_diff_visualization.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tests/test_display_blocks.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tests/test_export_import.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tests/test_hook_engine.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tests/test_injection_fix.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tests/test_license.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tests/test_mcp.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tests/test_memory.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tests/test_notification_manager.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tests/test_plugin.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tests/test_session_fork.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tests/test_shell_mode.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tests/test_skills.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tests/test_steer_input.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tests/test_subagent.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tests/test_task.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tests/test_telegram_buffer.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tests/test_think_tool.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tests/test_todo_tool.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tests/test_todo_visualization.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tests/test_tool_registry.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tests/test_voice.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tests/test_wire_events.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tmux_offloader.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tmux_tools.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/tool_registry.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/ui/__init__.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/ui/input.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/ui/render.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/voice/__init__.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/voice/keyterms.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/voice/recorder.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/voice/stt.py +0 -0
- {dulus-0.2.40 → dulus-0.2.42}/voice/tts.py +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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>
|
|
@@ -9,22 +9,52 @@
|
|
|
9
9
|
"project": {
|
|
10
10
|
"name": "Dulus Command Center",
|
|
11
11
|
"repo_stats": {
|
|
12
|
-
"files":
|
|
13
|
-
"lines":
|
|
12
|
+
"files": 26697,
|
|
13
|
+
"lines": 3602498,
|
|
14
14
|
"languages": {
|
|
15
15
|
".example": 5,
|
|
16
|
-
"
|
|
17
|
-
".
|
|
18
|
-
"
|
|
16
|
+
"no_ext": 16410,
|
|
17
|
+
".py": 60171,
|
|
18
|
+
".html": 15960,
|
|
19
19
|
".in": 2,
|
|
20
20
|
".toml": 123,
|
|
21
|
-
".md":
|
|
22
|
-
".txt":
|
|
23
|
-
".
|
|
24
|
-
".
|
|
25
|
-
".
|
|
26
|
-
".
|
|
27
|
-
".
|
|
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.
|
|
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.
|
|
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>
|
|
@@ -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.
|
|
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("
|
|
224
|
-
p.get("
|
|
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
|
+
|