dulus 0.2.24__tar.gz → 0.2.25__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.24/dulus.egg-info → dulus-0.2.25}/PKG-INFO +39 -2
- {dulus-0.2.24 → dulus-0.2.25}/README.md +38 -1
- {dulus-0.2.24 → dulus-0.2.25}/docs/news.md +2 -0
- {dulus-0.2.24 → dulus-0.2.25/dulus.egg-info}/PKG-INFO +39 -2
- {dulus-0.2.24 → dulus-0.2.25}/dulus.py +15 -5
- {dulus-0.2.24 → dulus-0.2.25}/pyproject.toml +1 -1
- {dulus-0.2.24 → dulus-0.2.25}/skill/clawhub.py +44 -16
- {dulus-0.2.24 → dulus-0.2.25}/LICENSE +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/MANIFEST.in +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/agent.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/backend/__init__.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/backend/compressor.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/backend/context.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/backend/githook.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/backend/marketplace.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/backend/mempalace_bridge.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/backend/personas.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/backend/plugins.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/backend/server.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/backend/tasks.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/batch_api.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/checkpoint/__init__.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/checkpoint/hooks.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/checkpoint/store.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/checkpoint/types.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/claude_code_watcher.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/clipboard_utils.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/cloudsave.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/common.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/compaction.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/config.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/context.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/data/__init__.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/data/active_persona.json +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/data/context.json +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/data/marketplace.json +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/data/personas.json +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/data/plugins/__init__.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/data/plugins/composio/__init__.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/data/plugins/composio/composio_plugin/__init__.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/data/plugins/composio/composio_plugin/session_manager.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/data/plugins/composio/composio_plugin/tool_generator.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/data/plugins/composio/plugin.json +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/data/plugins/composio/plugin_tool.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/data/tasks.json +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/docs/README.md +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/docs/__init__.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/docs/api.html +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/docs/architecture.md +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/docs/azure-speech-template.json +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/docs/dashboard/index.html +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/docs/divider.svg +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/docs/generate.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/docs/hero.svg +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/docs/index.html +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/docs/nvidia-models.svg +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/docs/particle-playground.html +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/docs/personas/index.html +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/docs/poetry-banner.png +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/docs/preview.html +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/docs/sec-agents.svg +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/docs/sec-brainstorm.svg +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/docs/sec-bridges.svg +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/docs/sec-features.svg +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/docs/sec-freetier.svg +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/docs/sec-memory.svg +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/docs/sec-models.svg +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/docs/sec-perms.svg +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/docs/sec-plugins.svg +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/docs/sec-quickstart.svg +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/docs/sec-ssj.svg +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/docs/spinners.svg +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/docs/split-pane.svg +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/docs/terminal-boot.svg +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/docs/uploads/particle-playground.html +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/dulus.egg-info/SOURCES.txt +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/dulus.egg-info/dependency_links.txt +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/dulus.egg-info/entry_points.txt +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/dulus.egg-info/requires.txt +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/dulus.egg-info/top_level.txt +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/dulus_gui.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/dulus_mcp/__init__.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/dulus_mcp/client.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/dulus_mcp/config.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/dulus_mcp/tools.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/dulus_mcp/types.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/gui/__init__.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/gui/agent_bridge.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/gui/chat_widget.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/gui/main_window.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/gui/personas.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/gui/session_utils.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/gui/settings_dialog.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/gui/sidebar.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/gui/tasks_view.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/gui/themes.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/gui/tool_panel.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/input.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/license_manager.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/memory/__init__.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/memory/audit.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/memory/consolidator.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/memory/context.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/memory/offload.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/memory/palace.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/memory/scan.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/memory/sessions.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/memory/store.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/memory/tools.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/memory/types.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/memory/vector_search.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/multi_agent/__init__.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/multi_agent/subagent.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/multi_agent/tools.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/offload_helper.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/plugin/__init__.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/plugin/autoadapter.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/plugin/loader.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/plugin/recommend.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/plugin/store.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/plugin/types.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/providers.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/setup.cfg +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/skill/__init__.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/skill/builtin.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/skill/executor.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/skill/loader.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/skill/tools.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/skills.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/spinner.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/string_utils.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/subagent.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/task/__init__.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/task/store.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/task/tools.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/task/types.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/tests/test_checkpoint.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/tests/test_compaction.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/tests/test_diff_view.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/tests/test_injection_fix.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/tests/test_license.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/tests/test_mcp.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/tests/test_memory.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/tests/test_plugin.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/tests/test_skills.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/tests/test_subagent.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/tests/test_task.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/tests/test_telegram_buffer.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/tests/test_tool_registry.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/tests/test_voice.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/tmux_offloader.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/tmux_tools.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/tool_registry.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/tools.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/ui/__init__.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/ui/input.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/ui/render.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/voice/__init__.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/voice/keyterms.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/voice/recorder.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/voice/stt.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/voice/tts.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/webchat.py +0 -0
- {dulus-0.2.24 → dulus-0.2.25}/webchat_server.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.25
|
|
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
|
|
@@ -69,7 +69,7 @@ SET /sticky_input ON since the first run for the best experience!
|
|
|
69
69
|
<a href="https://pypi.org/project/dulus/"><img src="https://static.pepy.tech/badge/dulus?style=flat-square" alt="downloads"/></a>
|
|
70
70
|
<img src="https://img.shields.io/badge/python-3.11+-ff6b1f?style=flat-square&labelColor=07070a" alt="python"/>
|
|
71
71
|
<img src="https://img.shields.io/badge/license-GPLv3-ff6b1f?style=flat-square&labelColor=07070a" alt="license"/>
|
|
72
|
-
<img src="https://img.shields.io/badge/version-v0.2.
|
|
72
|
+
<img src="https://img.shields.io/badge/version-v0.2.25-ff6b1f?style=flat-square&labelColor=07070a" alt="version"/>
|
|
73
73
|
<img src="https://img.shields.io/badge/providers-11-ff6b1f?style=flat-square&labelColor=07070a" alt="providers"/>
|
|
74
74
|
<img src="https://img.shields.io/badge/tools-27-ff6b1f?style=flat-square&labelColor=07070a" alt="tools"/>
|
|
75
75
|
<img src="https://img.shields.io/badge/tests-263+-ff6b1f?style=flat-square&labelColor=07070a" alt="tests"/>
|
|
@@ -103,6 +103,26 @@ Use claude-code as an API without the new 'extra-usage' wall <3
|
|
|
103
103
|
<sub>⚡ <b>Saves you Claude tokens?</b> Throw a sat — BTC: <code>1JzatQDn9fMLnKTd3KYgztsLHC95bJEzSN</code></sub>
|
|
104
104
|
</p>
|
|
105
105
|
|
|
106
|
+
|
|
107
|
+
Another reminder of a Dulus magic spell:
|
|
108
|
+
Wanna get stock prices, history , etc?
|
|
109
|
+
|
|
110
|
+
/plugin install yfinance@https://github.com/ranaroussi/yfinance
|
|
111
|
+
|
|
112
|
+
them:
|
|
113
|
+
/plugin reload
|
|
114
|
+
|
|
115
|
+
dulus get the prices of NVDA, TSLA, SP500:
|
|
116
|
+
|
|
117
|
+
<img width="2094" height="1365" alt="image" src="https://github.com/user-attachments/assets/1551d651-9d69-4607-bac0-4adbde645783" />
|
|
118
|
+
|
|
119
|
+
Be creative!!!
|
|
120
|
+
|
|
121
|
+
Dulus adapt any python repository <3
|
|
122
|
+
|
|
123
|
+
<p align="center"><img src="https://raw.githubusercontent.com/KevRojo/Dulus/main/docs/divider.svg" alt="" width="100%"></p>
|
|
124
|
+
|
|
125
|
+
|
|
106
126
|
Dulus is a **lightweight Python reimplementation of Claude Code** that isn't locked to Claude. It ships the whole loop — REPL, tool dispatch, streaming, context compaction, checkpoints, sub-agents, voice, Telegram bridge, MCP, plugins — in roughly **12K lines you can actually read**. Fork it. Bend it. Run it offline against Qwen on your M2.
|
|
107
127
|
|
|
108
128
|
> **v0.2.22 — May 9, 2026** — `/bg start` spawns one detached Dulus daemon that serves CLI (IPC), Web (browser at `127.0.0.1:5000`), and Telegram simultaneously — all sharing the same session. WebChat now defaults to **loopback-only** (opt-in to LAN exposure with `/webchat lan on`).
|
|
@@ -341,6 +361,23 @@ Dulus's **Auto-Adapter** reads a random Python repo and figures out its tools on
|
|
|
341
361
|
|
|
342
362
|
Adapt-and-install runs in under a second. New tools register **live**, no restart.
|
|
343
363
|
|
|
364
|
+
Example adapting Sherlock repo:
|
|
365
|
+
|
|
366
|
+
<img width="1765" height="166" alt="image" src="https://github.com/user-attachments/assets/c67dc15e-a2e3-4575-be34-8c9b54045510" />
|
|
367
|
+
|
|
368
|
+
-----
|
|
369
|
+
|
|
370
|
+
<img width="1327" height="751" alt="image" src="https://github.com/user-attachments/assets/676a0ef5-3699-4960-98a4-14a55fbef081" />
|
|
371
|
+
|
|
372
|
+
-----
|
|
373
|
+
|
|
374
|
+
<img width="885" height="301" alt="image" src="https://github.com/user-attachments/assets/52c02444-2606-41dc-bc33-ebe26ac41e5e" />
|
|
375
|
+
|
|
376
|
+
----
|
|
377
|
+
|
|
378
|
+
<img width="1006" height="271" alt="image" src="https://github.com/user-attachments/assets/d823428e-6344-4414-bf42-14ed3128f763" />
|
|
379
|
+
|
|
380
|
+
|
|
344
381
|
## MCP
|
|
345
382
|
|
|
346
383
|
Drop a `.mcp.json` in your project root (or `~/.dulus/mcp.json` for user-wide):
|
|
@@ -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.25-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"/>
|
|
@@ -56,6 +56,26 @@ Use claude-code as an API without the new 'extra-usage' wall <3
|
|
|
56
56
|
<sub>⚡ <b>Saves you Claude tokens?</b> Throw a sat — BTC: <code>1JzatQDn9fMLnKTd3KYgztsLHC95bJEzSN</code></sub>
|
|
57
57
|
</p>
|
|
58
58
|
|
|
59
|
+
|
|
60
|
+
Another reminder of a Dulus magic spell:
|
|
61
|
+
Wanna get stock prices, history , etc?
|
|
62
|
+
|
|
63
|
+
/plugin install yfinance@https://github.com/ranaroussi/yfinance
|
|
64
|
+
|
|
65
|
+
them:
|
|
66
|
+
/plugin reload
|
|
67
|
+
|
|
68
|
+
dulus get the prices of NVDA, TSLA, SP500:
|
|
69
|
+
|
|
70
|
+
<img width="2094" height="1365" alt="image" src="https://github.com/user-attachments/assets/1551d651-9d69-4607-bac0-4adbde645783" />
|
|
71
|
+
|
|
72
|
+
Be creative!!!
|
|
73
|
+
|
|
74
|
+
Dulus adapt any python repository <3
|
|
75
|
+
|
|
76
|
+
<p align="center"><img src="https://raw.githubusercontent.com/KevRojo/Dulus/main/docs/divider.svg" alt="" width="100%"></p>
|
|
77
|
+
|
|
78
|
+
|
|
59
79
|
Dulus is a **lightweight Python reimplementation of Claude Code** that isn't locked to Claude. It ships the whole loop — REPL, tool dispatch, streaming, context compaction, checkpoints, sub-agents, voice, Telegram bridge, MCP, plugins — in roughly **12K lines you can actually read**. Fork it. Bend it. Run it offline against Qwen on your M2.
|
|
60
80
|
|
|
61
81
|
> **v0.2.22 — May 9, 2026** — `/bg start` spawns one detached Dulus daemon that serves CLI (IPC), Web (browser at `127.0.0.1:5000`), and Telegram simultaneously — all sharing the same session. WebChat now defaults to **loopback-only** (opt-in to LAN exposure with `/webchat lan on`).
|
|
@@ -294,6 +314,23 @@ Dulus's **Auto-Adapter** reads a random Python repo and figures out its tools on
|
|
|
294
314
|
|
|
295
315
|
Adapt-and-install runs in under a second. New tools register **live**, no restart.
|
|
296
316
|
|
|
317
|
+
Example adapting Sherlock repo:
|
|
318
|
+
|
|
319
|
+
<img width="1765" height="166" alt="image" src="https://github.com/user-attachments/assets/c67dc15e-a2e3-4575-be34-8c9b54045510" />
|
|
320
|
+
|
|
321
|
+
-----
|
|
322
|
+
|
|
323
|
+
<img width="1327" height="751" alt="image" src="https://github.com/user-attachments/assets/676a0ef5-3699-4960-98a4-14a55fbef081" />
|
|
324
|
+
|
|
325
|
+
-----
|
|
326
|
+
|
|
327
|
+
<img width="885" height="301" alt="image" src="https://github.com/user-attachments/assets/52c02444-2606-41dc-bc33-ebe26ac41e5e" />
|
|
328
|
+
|
|
329
|
+
----
|
|
330
|
+
|
|
331
|
+
<img width="1006" height="271" alt="image" src="https://github.com/user-attachments/assets/d823428e-6344-4414-bf42-14ed3128f763" />
|
|
332
|
+
|
|
333
|
+
|
|
297
334
|
## MCP
|
|
298
335
|
|
|
299
336
|
Drop a `.mcp.json` in your project root (or `~/.dulus/mcp.json` for user-wide):
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
## 🔥🔥🔥 News (Pacific Time)
|
|
4
4
|
|
|
5
5
|
|
|
6
|
+
- May 09, 2026 (**v0.2.25**): **`/skill list awesome` no longer hangs** — was fetching 235 SKILL.md files sequentially (50-120 seconds, looked frozen). Now uses one GitHub tree API call (instant, <1s, names only) by default; pass `--full` to also pull per-skill descriptions in parallel via a 12-worker thread pool (~5s instead of 120s). Cache stores the with_descriptions flag so future calls reuse the right data.
|
|
7
|
+
|
|
6
8
|
- May 09, 2026 (**v0.2.24**): **Auto-adapter prompt — 5 fixes from a sherlock postmortem**
|
|
7
9
|
- **Reconciled `limit` default** — the prompt had two contradictory rules ("default: 50, max: 200" vs "default: 10, NOT 50"). Models burned tokens reasoning about which to follow. Unified on `default: 10, hard max: 200` everywhere.
|
|
8
10
|
- **"READ the source first" rule** at the top of the wrapper guidelines. Adapters were inferring upstream function signatures from class names and shipping plugins that compile/import/export cleanly but crash at runtime due to type-shape mismatches. Now the prompt explicitly tells the model to read the consumer code (`param.get(...)` / `for x in param`) before guessing shapes.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dulus
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.25
|
|
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
|
|
@@ -69,7 +69,7 @@ SET /sticky_input ON since the first run for the best experience!
|
|
|
69
69
|
<a href="https://pypi.org/project/dulus/"><img src="https://static.pepy.tech/badge/dulus?style=flat-square" alt="downloads"/></a>
|
|
70
70
|
<img src="https://img.shields.io/badge/python-3.11+-ff6b1f?style=flat-square&labelColor=07070a" alt="python"/>
|
|
71
71
|
<img src="https://img.shields.io/badge/license-GPLv3-ff6b1f?style=flat-square&labelColor=07070a" alt="license"/>
|
|
72
|
-
<img src="https://img.shields.io/badge/version-v0.2.
|
|
72
|
+
<img src="https://img.shields.io/badge/version-v0.2.25-ff6b1f?style=flat-square&labelColor=07070a" alt="version"/>
|
|
73
73
|
<img src="https://img.shields.io/badge/providers-11-ff6b1f?style=flat-square&labelColor=07070a" alt="providers"/>
|
|
74
74
|
<img src="https://img.shields.io/badge/tools-27-ff6b1f?style=flat-square&labelColor=07070a" alt="tools"/>
|
|
75
75
|
<img src="https://img.shields.io/badge/tests-263+-ff6b1f?style=flat-square&labelColor=07070a" alt="tests"/>
|
|
@@ -103,6 +103,26 @@ Use claude-code as an API without the new 'extra-usage' wall <3
|
|
|
103
103
|
<sub>⚡ <b>Saves you Claude tokens?</b> Throw a sat — BTC: <code>1JzatQDn9fMLnKTd3KYgztsLHC95bJEzSN</code></sub>
|
|
104
104
|
</p>
|
|
105
105
|
|
|
106
|
+
|
|
107
|
+
Another reminder of a Dulus magic spell:
|
|
108
|
+
Wanna get stock prices, history , etc?
|
|
109
|
+
|
|
110
|
+
/plugin install yfinance@https://github.com/ranaroussi/yfinance
|
|
111
|
+
|
|
112
|
+
them:
|
|
113
|
+
/plugin reload
|
|
114
|
+
|
|
115
|
+
dulus get the prices of NVDA, TSLA, SP500:
|
|
116
|
+
|
|
117
|
+
<img width="2094" height="1365" alt="image" src="https://github.com/user-attachments/assets/1551d651-9d69-4607-bac0-4adbde645783" />
|
|
118
|
+
|
|
119
|
+
Be creative!!!
|
|
120
|
+
|
|
121
|
+
Dulus adapt any python repository <3
|
|
122
|
+
|
|
123
|
+
<p align="center"><img src="https://raw.githubusercontent.com/KevRojo/Dulus/main/docs/divider.svg" alt="" width="100%"></p>
|
|
124
|
+
|
|
125
|
+
|
|
106
126
|
Dulus is a **lightweight Python reimplementation of Claude Code** that isn't locked to Claude. It ships the whole loop — REPL, tool dispatch, streaming, context compaction, checkpoints, sub-agents, voice, Telegram bridge, MCP, plugins — in roughly **12K lines you can actually read**. Fork it. Bend it. Run it offline against Qwen on your M2.
|
|
107
127
|
|
|
108
128
|
> **v0.2.22 — May 9, 2026** — `/bg start` spawns one detached Dulus daemon that serves CLI (IPC), Web (browser at `127.0.0.1:5000`), and Telegram simultaneously — all sharing the same session. WebChat now defaults to **loopback-only** (opt-in to LAN exposure with `/webchat lan on`).
|
|
@@ -341,6 +361,23 @@ Dulus's **Auto-Adapter** reads a random Python repo and figures out its tools on
|
|
|
341
361
|
|
|
342
362
|
Adapt-and-install runs in under a second. New tools register **live**, no restart.
|
|
343
363
|
|
|
364
|
+
Example adapting Sherlock repo:
|
|
365
|
+
|
|
366
|
+
<img width="1765" height="166" alt="image" src="https://github.com/user-attachments/assets/c67dc15e-a2e3-4575-be34-8c9b54045510" />
|
|
367
|
+
|
|
368
|
+
-----
|
|
369
|
+
|
|
370
|
+
<img width="1327" height="751" alt="image" src="https://github.com/user-attachments/assets/676a0ef5-3699-4960-98a4-14a55fbef081" />
|
|
371
|
+
|
|
372
|
+
-----
|
|
373
|
+
|
|
374
|
+
<img width="885" height="301" alt="image" src="https://github.com/user-attachments/assets/52c02444-2606-41dc-bc33-ebe26ac41e5e" />
|
|
375
|
+
|
|
376
|
+
----
|
|
377
|
+
|
|
378
|
+
<img width="1006" height="271" alt="image" src="https://github.com/user-attachments/assets/d823428e-6344-4414-bf42-14ed3128f763" />
|
|
379
|
+
|
|
380
|
+
|
|
344
381
|
## MCP
|
|
345
382
|
|
|
346
383
|
Drop a `.mcp.json` in your project root (or `~/.dulus/mcp.json` for user-wide):
|
|
@@ -218,7 +218,7 @@ try:
|
|
|
218
218
|
from importlib.metadata import version as _pkg_version
|
|
219
219
|
VERSION = _pkg_version("dulus")
|
|
220
220
|
except Exception:
|
|
221
|
-
VERSION = "0.2.
|
|
221
|
+
VERSION = "0.2.25" # dev fallback — keep in sync with pyproject.toml
|
|
222
222
|
|
|
223
223
|
# ── ANSI helpers (used even with rich for non-markdown output) ─────────────
|
|
224
224
|
from common import C, clr, info, ok, warn, err, stream_thinking, print_tool_start, print_tool_end, sanitize_text
|
|
@@ -4251,17 +4251,27 @@ def cmd_skill(args: str, state, config) -> bool:
|
|
|
4251
4251
|
|
|
4252
4252
|
if rest.startswith("awesome"):
|
|
4253
4253
|
query = rest[7:].strip()
|
|
4254
|
-
|
|
4255
|
-
|
|
4254
|
+
# `--full` flag pulls per-skill descriptions in parallel (slower but
|
|
4255
|
+
# informative). Default lists names only — instant.
|
|
4256
|
+
full = False
|
|
4257
|
+
if "--full" in query.split():
|
|
4258
|
+
full = True
|
|
4259
|
+
query = " ".join(t for t in query.split() if t != "--full").strip()
|
|
4260
|
+
if full:
|
|
4261
|
+
info("Fetching awesome skills + descriptions from GitHub (parallel, ~5s)...")
|
|
4262
|
+
else:
|
|
4263
|
+
info("Fetching awesome skill list from GitHub (instant)...")
|
|
4264
|
+
skills = list_awesome_remote(query, with_descriptions=full)
|
|
4256
4265
|
if not skills:
|
|
4257
4266
|
err("Could not fetch awesome skills (network or rate-limit).")
|
|
4258
4267
|
return True
|
|
4259
4268
|
lines = [
|
|
4260
|
-
f" {clr(s['id'], 'cyan'):55s} {s
|
|
4269
|
+
f" {clr(s['id'], 'cyan'):55s} {s.get('description', '')[:80]}"
|
|
4261
4270
|
for s in skills
|
|
4262
4271
|
]
|
|
4263
4272
|
header = f"Awesome skills ({len(skills)})" + (f" matching '{query}'" if query else "")
|
|
4264
|
-
|
|
4273
|
+
hint = "" if full else " — add `--full` for descriptions"
|
|
4274
|
+
_pager(f"{header}{hint} — n=next q=quit", lines)
|
|
4265
4275
|
return True
|
|
4266
4276
|
|
|
4267
4277
|
if rest.startswith("composio"):
|
|
@@ -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.25"
|
|
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"
|
|
@@ -172,9 +172,14 @@ _AWESOME_EXCLUDE_REMOTE = {
|
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
|
|
175
|
-
def _fetch_awesome_remote() -> list[dict]:
|
|
176
|
-
"""Hit the GitHub tree API
|
|
177
|
-
|
|
175
|
+
def _fetch_awesome_remote(with_descriptions: bool = False) -> list[dict]:
|
|
176
|
+
"""Hit the GitHub tree API to list awesome skills.
|
|
177
|
+
|
|
178
|
+
Default (with_descriptions=False): ONE API call, instant, no descriptions.
|
|
179
|
+
Returns 235 entries with name + url ready in <1s.
|
|
180
|
+
|
|
181
|
+
with_descriptions=True: also pulls each SKILL.md's frontmatter via
|
|
182
|
+
raw.githubusercontent.com — done with a thread pool so it stays under ~5s.
|
|
178
183
|
"""
|
|
179
184
|
import time
|
|
180
185
|
tree_url = (
|
|
@@ -199,6 +204,7 @@ def _fetch_awesome_remote() -> list[dict]:
|
|
|
199
204
|
continue
|
|
200
205
|
skill_paths.append(path)
|
|
201
206
|
|
|
207
|
+
# Build the skill list from paths alone — instant, no per-file fetch.
|
|
202
208
|
skills = []
|
|
203
209
|
for path in skill_paths:
|
|
204
210
|
rel_dir = "/".join(path.split("/")[:-1])
|
|
@@ -207,26 +213,43 @@ def _fetch_awesome_remote() -> list[dict]:
|
|
|
207
213
|
f"https://raw.githubusercontent.com/{_AWESOME_REPO}/"
|
|
208
214
|
f"{_AWESOME_BRANCH}/{path}"
|
|
209
215
|
)
|
|
210
|
-
try:
|
|
211
|
-
with urllib.request.urlopen(raw_url, timeout=10) as r:
|
|
212
|
-
raw = r.read().decode("utf-8", errors="ignore")
|
|
213
|
-
except Exception:
|
|
214
|
-
continue
|
|
215
|
-
meta = _parse_frontmatter(raw)
|
|
216
216
|
skills.append({
|
|
217
217
|
"id": f"awesome/{rel_dir}",
|
|
218
218
|
"plugin": "awesome",
|
|
219
219
|
"skill": skill_name,
|
|
220
|
-
"description":
|
|
220
|
+
"description": "", # filled in below if with_descriptions
|
|
221
221
|
"path": raw_url,
|
|
222
222
|
"source": "awesome-remote",
|
|
223
223
|
"_remote_dir": rel_dir,
|
|
224
224
|
})
|
|
225
225
|
|
|
226
|
+
if with_descriptions and skills:
|
|
227
|
+
# Pull frontmatter in parallel via raw.githubusercontent.com (no
|
|
228
|
+
# rate limit). 12 workers keeps GitHub happy and 235 fetches done
|
|
229
|
+
# in 3-5 seconds instead of the original 50-120 seconds.
|
|
230
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
231
|
+
|
|
232
|
+
def _fetch_one(s):
|
|
233
|
+
try:
|
|
234
|
+
with urllib.request.urlopen(s["path"], timeout=8) as r:
|
|
235
|
+
raw = r.read().decode("utf-8", errors="ignore")
|
|
236
|
+
meta = _parse_frontmatter(raw)
|
|
237
|
+
s["description"] = meta.get("description", "")
|
|
238
|
+
except Exception:
|
|
239
|
+
pass
|
|
240
|
+
return s
|
|
241
|
+
|
|
242
|
+
with ThreadPoolExecutor(max_workers=12) as pool:
|
|
243
|
+
list(pool.map(_fetch_one, skills))
|
|
244
|
+
|
|
226
245
|
_AWESOME_CACHE.parent.mkdir(parents=True, exist_ok=True)
|
|
227
246
|
try:
|
|
228
247
|
_AWESOME_CACHE.write_text(
|
|
229
|
-
json.dumps({
|
|
248
|
+
json.dumps({
|
|
249
|
+
"fetched_at": time.time(),
|
|
250
|
+
"with_descriptions": with_descriptions,
|
|
251
|
+
"skills": skills,
|
|
252
|
+
}, indent=2),
|
|
230
253
|
encoding="utf-8",
|
|
231
254
|
)
|
|
232
255
|
except Exception:
|
|
@@ -234,21 +257,26 @@ def _fetch_awesome_remote() -> list[dict]:
|
|
|
234
257
|
return skills
|
|
235
258
|
|
|
236
259
|
|
|
237
|
-
def list_awesome_remote(query: Optional[str] = None, force_refresh: bool = False) -> list[dict]:
|
|
238
|
-
"""Return the awesome-skills catalog (cached).
|
|
239
|
-
|
|
260
|
+
def list_awesome_remote(query: Optional[str] = None, force_refresh: bool = False, with_descriptions: bool = False) -> list[dict]:
|
|
261
|
+
"""Return the awesome-skills catalog (cached).
|
|
262
|
+
|
|
263
|
+
Default: one GitHub tree call (~1s, no descriptions), cached 24h.
|
|
264
|
+
with_descriptions=True: also fetches each SKILL.md frontmatter in parallel.
|
|
240
265
|
"""
|
|
241
266
|
import time
|
|
242
267
|
skills: list[dict] = []
|
|
268
|
+
cache_has_descriptions = False
|
|
243
269
|
if not force_refresh and _AWESOME_CACHE.exists():
|
|
244
270
|
try:
|
|
245
271
|
data = json.loads(_AWESOME_CACHE.read_text(encoding="utf-8"))
|
|
246
272
|
if time.time() - float(data.get("fetched_at", 0)) < _AWESOME_TTL_SEC:
|
|
247
273
|
skills = data.get("skills", [])
|
|
274
|
+
cache_has_descriptions = bool(data.get("with_descriptions"))
|
|
248
275
|
except Exception:
|
|
249
276
|
skills = []
|
|
250
|
-
if
|
|
251
|
-
|
|
277
|
+
# Refetch if no cache, or if user wants descriptions but cache doesn't have them.
|
|
278
|
+
if not skills or (with_descriptions and not cache_has_descriptions):
|
|
279
|
+
skills = _fetch_awesome_remote(with_descriptions=with_descriptions)
|
|
252
280
|
|
|
253
281
|
if query:
|
|
254
282
|
q = query.lower()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|