dulus 0.2.16__tar.gz → 0.2.17__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.16/dulus.egg-info → dulus-0.2.17}/PKG-INFO +12 -4
- {dulus-0.2.16 → dulus-0.2.17}/README.md +10 -3
- {dulus-0.2.16 → dulus-0.2.17}/context.py +36 -18
- dulus-0.2.17/data/plugins/__init__.py +1 -0
- dulus-0.2.17/data/plugins/composio/__init__.py +1 -0
- dulus-0.2.17/data/plugins/composio/composio_plugin/__init__.py +5 -0
- dulus-0.2.17/data/plugins/composio/composio_plugin/session_manager.py +71 -0
- dulus-0.2.17/data/plugins/composio/composio_plugin/tool_generator.py +156 -0
- dulus-0.2.17/data/plugins/composio/plugin.json +11 -0
- dulus-0.2.17/data/plugins/composio/plugin_tool.py +434 -0
- {dulus-0.2.16 → dulus-0.2.17}/docs/news.md +9 -0
- {dulus-0.2.16 → dulus-0.2.17/dulus.egg-info}/PKG-INFO +12 -4
- {dulus-0.2.16 → dulus-0.2.17}/dulus.egg-info/SOURCES.txt +7 -0
- {dulus-0.2.16 → dulus-0.2.17}/dulus.egg-info/requires.txt +1 -0
- {dulus-0.2.16 → dulus-0.2.17}/dulus.py +76 -1
- {dulus-0.2.16 → dulus-0.2.17}/pyproject.toml +5 -2
- {dulus-0.2.16 → dulus-0.2.17}/skill/clawhub.py +161 -0
- {dulus-0.2.16 → dulus-0.2.17}/tools.py +8 -0
- {dulus-0.2.16 → dulus-0.2.17}/LICENSE +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/MANIFEST.in +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/agent.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/backend/__init__.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/backend/compressor.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/backend/context.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/backend/githook.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/backend/marketplace.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/backend/mempalace_bridge.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/backend/personas.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/backend/plugins.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/backend/server.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/backend/tasks.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/batch_api.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/checkpoint/__init__.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/checkpoint/hooks.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/checkpoint/store.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/checkpoint/types.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/claude_code_watcher.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/clipboard_utils.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/cloudsave.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/common.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/compaction.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/config.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/data/__init__.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/data/active_persona.json +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/data/context.json +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/data/marketplace.json +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/data/personas.json +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/data/tasks.json +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/docs/README.md +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/docs/__init__.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/docs/api.html +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/docs/architecture.md +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/docs/azure-speech-template.json +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/docs/dashboard/index.html +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/docs/divider.svg +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/docs/generate.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/docs/hero.svg +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/docs/index.html +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/docs/nvidia-models.svg +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/docs/particle-playground.html +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/docs/personas/index.html +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/docs/poetry-banner.png +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/docs/preview.html +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/docs/sec-agents.svg +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/docs/sec-brainstorm.svg +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/docs/sec-bridges.svg +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/docs/sec-features.svg +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/docs/sec-freetier.svg +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/docs/sec-memory.svg +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/docs/sec-models.svg +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/docs/sec-perms.svg +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/docs/sec-plugins.svg +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/docs/sec-quickstart.svg +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/docs/sec-ssj.svg +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/docs/spinners.svg +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/docs/split-pane.svg +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/docs/terminal-boot.svg +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/docs/uploads/particle-playground.html +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/dulus.egg-info/dependency_links.txt +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/dulus.egg-info/entry_points.txt +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/dulus.egg-info/top_level.txt +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/dulus_gui.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/dulus_mcp/__init__.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/dulus_mcp/client.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/dulus_mcp/config.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/dulus_mcp/tools.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/dulus_mcp/types.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/gui/__init__.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/gui/agent_bridge.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/gui/chat_widget.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/gui/main_window.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/gui/personas.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/gui/session_utils.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/gui/settings_dialog.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/gui/sidebar.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/gui/tasks_view.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/gui/themes.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/gui/tool_panel.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/input.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/license_manager.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/memory/__init__.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/memory/audit.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/memory/consolidator.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/memory/context.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/memory/offload.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/memory/palace.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/memory/scan.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/memory/sessions.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/memory/store.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/memory/tools.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/memory/types.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/memory/vector_search.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/multi_agent/__init__.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/multi_agent/subagent.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/multi_agent/tools.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/offload_helper.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/plugin/__init__.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/plugin/autoadapter.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/plugin/loader.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/plugin/recommend.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/plugin/store.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/plugin/types.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/providers.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/setup.cfg +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/skill/__init__.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/skill/builtin.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/skill/executor.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/skill/loader.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/skill/tools.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/skills.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/spinner.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/string_utils.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/subagent.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/task/__init__.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/task/store.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/task/tools.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/task/types.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/tests/test_checkpoint.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/tests/test_compaction.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/tests/test_diff_view.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/tests/test_injection_fix.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/tests/test_license.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/tests/test_mcp.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/tests/test_memory.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/tests/test_plugin.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/tests/test_skills.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/tests/test_subagent.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/tests/test_task.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/tests/test_telegram_buffer.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/tests/test_tool_registry.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/tests/test_voice.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/tmux_offloader.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/tmux_tools.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/tool_registry.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/ui/__init__.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/ui/input.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/ui/render.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/voice/__init__.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/voice/keyterms.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/voice/recorder.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/voice/stt.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/voice/tts.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/webchat.py +0 -0
- {dulus-0.2.16 → dulus-0.2.17}/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.17
|
|
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
|
|
@@ -34,6 +34,7 @@ Requires-Dist: bubblewrap-cli>=1.0.0
|
|
|
34
34
|
Requires-Dist: customtkinter>=5.2.0
|
|
35
35
|
Requires-Dist: Pillow>=10.0.0
|
|
36
36
|
Requires-Dist: typing-extensions>=4.10.0
|
|
37
|
+
Requires-Dist: composio>=1.0.0rc2
|
|
37
38
|
Provides-Extra: memory
|
|
38
39
|
Requires-Dist: mempalace>=3.3.4; extra == "memory"
|
|
39
40
|
Provides-Extra: voice
|
|
@@ -67,7 +68,7 @@ SET /sticky_input ON since the first run for the best experience!
|
|
|
67
68
|
<a href="https://pypi.org/project/dulus/"><img src="https://static.pepy.tech/badge/dulus?style=flat-square" alt="downloads"/></a>
|
|
68
69
|
<img src="https://img.shields.io/badge/python-3.11+-ff6b1f?style=flat-square&labelColor=07070a" alt="python"/>
|
|
69
70
|
<img src="https://img.shields.io/badge/license-GPLv3-ff6b1f?style=flat-square&labelColor=07070a" alt="license"/>
|
|
70
|
-
<img src="https://img.shields.io/badge/version-v0.2.
|
|
71
|
+
<img src="https://img.shields.io/badge/version-v0.2.17-ff6b1f?style=flat-square&labelColor=07070a" alt="version"/>
|
|
71
72
|
<img src="https://img.shields.io/badge/providers-11-ff6b1f?style=flat-square&labelColor=07070a" alt="providers"/>
|
|
72
73
|
<img src="https://img.shields.io/badge/tools-27-ff6b1f?style=flat-square&labelColor=07070a" alt="tools"/>
|
|
73
74
|
<img src="https://img.shields.io/badge/tests-263+-ff6b1f?style=flat-square&labelColor=07070a" alt="tests"/>
|
|
@@ -94,6 +95,9 @@ Use claude-code as an API without the new 'extra-usage' wall <3
|
|
|
94
95
|
|
|
95
96
|
<p align="center"><img src="https://raw.githubusercontent.com/KevRojo/Dulus/main/docs/poetry-banner.png" alt="Anthropic only sees text while you and Claude are writing poetry" width="100%"></p>
|
|
96
97
|
|
|
98
|
+
<p align="center">
|
|
99
|
+
<sub>⚡ <b>Saves you Claude tokens?</b> Throw a sat — BTC: <code>1JzatQDn9fMLnKTd3KYgztsLHC95bJEzSN</code></sub>
|
|
100
|
+
</p>
|
|
97
101
|
|
|
98
102
|
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.
|
|
99
103
|
|
|
@@ -623,9 +627,13 @@ git diff | dulus -p "write a commit message"
|
|
|
623
627
|
GPLv3. Fork it, modify it, redistribute it — but keep it open. Derivative works must stay under GPLv3. Just don't ship `--accept-all` as the default.
|
|
624
628
|
|
|
625
629
|
---
|
|
626
|
-
Donations
|
|
630
|
+
## Donations
|
|
631
|
+
|
|
632
|
+
If Dulus saved you tokens, time, or sanity — throw some sats:
|
|
627
633
|
|
|
628
|
-
|
|
634
|
+
```
|
|
635
|
+
BTC: 1JzatQDn9fMLnKTd3KYgztsLHC95bJEzSN
|
|
636
|
+
```
|
|
629
637
|
|
|
630
638
|
<p align="center"><img src="https://raw.githubusercontent.com/KevRojo/Dulus/main/docs/divider.svg" alt="" width="100%"></p>
|
|
631
639
|
|
|
@@ -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.17-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"/>
|
|
@@ -49,6 +49,9 @@ Use claude-code as an API without the new 'extra-usage' wall <3
|
|
|
49
49
|
|
|
50
50
|
<p align="center"><img src="https://raw.githubusercontent.com/KevRojo/Dulus/main/docs/poetry-banner.png" alt="Anthropic only sees text while you and Claude are writing poetry" width="100%"></p>
|
|
51
51
|
|
|
52
|
+
<p align="center">
|
|
53
|
+
<sub>⚡ <b>Saves you Claude tokens?</b> Throw a sat — BTC: <code>1JzatQDn9fMLnKTd3KYgztsLHC95bJEzSN</code></sub>
|
|
54
|
+
</p>
|
|
52
55
|
|
|
53
56
|
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.
|
|
54
57
|
|
|
@@ -578,9 +581,13 @@ git diff | dulus -p "write a commit message"
|
|
|
578
581
|
GPLv3. Fork it, modify it, redistribute it — but keep it open. Derivative works must stay under GPLv3. Just don't ship `--accept-all` as the default.
|
|
579
582
|
|
|
580
583
|
---
|
|
581
|
-
Donations
|
|
584
|
+
## Donations
|
|
585
|
+
|
|
586
|
+
If Dulus saved you tokens, time, or sanity — throw some sats:
|
|
582
587
|
|
|
583
|
-
|
|
588
|
+
```
|
|
589
|
+
BTC: 1JzatQDn9fMLnKTd3KYgztsLHC95bJEzSN
|
|
590
|
+
```
|
|
584
591
|
|
|
585
592
|
<p align="center"><img src="https://raw.githubusercontent.com/KevRojo/Dulus/main/docs/divider.svg" alt="" width="100%"></p>
|
|
586
593
|
|
|
@@ -13,19 +13,19 @@ import subprocess
|
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
|
|
15
15
|
SYSTEM_PROMPT_TEMPLATE = """\
|
|
16
|
-
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
16
|
+
You are Dulus, an AI coding agent. Think in English; reply to KevRojo in Dominican Spanish.
|
|
17
|
+
# Identity: Your name is Dulus. Do NOT proactively declare this — only if the user asks "quién eres" or "qué modelo eres".
|
|
18
|
+
# Forbidden: Do NOT claim to be Qwen, Llama, GPT, Claude, Gemini, DeepSeek, or any underlying model. Do NOT mention Ollama or your runtime stack.
|
|
19
|
+
# Env: {cwd} | {platform} | auto_show={auto_show}
|
|
20
|
+
# Autonomy: Background scripts (nohup/&) allowed | Never refuse monitoring/long tasks | Always wait for tool results before replying
|
|
21
|
+
# Tools: SearchLastOutput → for [TRUNCATED] | WebFetch/WebSearch → web | TmuxOffload → tasks > 5s | ReadJob → background results
|
|
22
|
+
# Multi-agent: Agent(subagent_type=...) | isolation="worktree" runs parallel | wait=false + name=... for fire-and-forget
|
|
23
|
+
# Rules: Edit > Write | Use absolute paths + line numbers | Surface errors immediately, do not retry blindly
|
|
24
|
+
# Input: "🎙 Transcribed:" prefix = voice input — tolerate typos/misspellings
|
|
25
25
|
# REPL: /help /batch /auto_show /verbose /soul /memory /schema /thinking /config
|
|
26
26
|
{platform_hints}{git_info}{dulus_md}"""
|
|
27
27
|
|
|
28
|
-
_THINKING_LABELS = {1: "
|
|
28
|
+
_THINKING_LABELS = {1: "minimal", 2: "moderate", 3: "deep"}
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def get_git_info(config: dict | None = None) -> str:
|
|
@@ -204,34 +204,52 @@ def build_system_prompt(config: dict | None = None) -> str:
|
|
|
204
204
|
return _build_ollama_system_prompt(config)
|
|
205
205
|
|
|
206
206
|
auto_show = "ON" if (not config or config.get("auto_show", True)) else "OFF"
|
|
207
|
+
lite = bool(config and config.get("lite_mode"))
|
|
207
208
|
|
|
209
|
+
# In LITE mode: drop the optional context blocks (platform hints, git info,
|
|
210
|
+
# DULUS.md, project memory index, batch/thinking/plan/tmux hints). The
|
|
211
|
+
# core identity + tool rules stay. This is what the /lite toggle was
|
|
212
|
+
# supposed to do all along — previously the flag flipped a config bit
|
|
213
|
+
# that nothing actually consumed.
|
|
208
214
|
prompt = SYSTEM_PROMPT_TEMPLATE.format(
|
|
209
215
|
cwd=str(Path.cwd()),
|
|
210
216
|
platform=platform.system(),
|
|
211
217
|
auto_show=auto_show,
|
|
212
|
-
platform_hints=get_platform_hints(config),
|
|
213
|
-
git_info=get_git_info(config),
|
|
214
|
-
dulus_md=get_dulus_md(),
|
|
218
|
+
platform_hints="" if lite else get_platform_hints(config),
|
|
219
|
+
git_info="" if lite else get_git_info(config),
|
|
220
|
+
dulus_md="" if lite else get_dulus_md(),
|
|
215
221
|
)
|
|
216
222
|
|
|
223
|
+
if lite:
|
|
224
|
+
# Bail early — minimal prompt only.
|
|
225
|
+
return prompt
|
|
226
|
+
|
|
217
227
|
try:
|
|
218
228
|
from tmux_tools import tmux_available
|
|
219
229
|
if tmux_available():
|
|
220
|
-
prompt += "\n# Tmux:
|
|
230
|
+
prompt += "\n# Tmux: available"
|
|
221
231
|
except Exception:
|
|
222
232
|
pass
|
|
223
233
|
|
|
224
234
|
prompt += (
|
|
225
|
-
"\n#
|
|
226
|
-
|
|
235
|
+
"\n# Batch: /batch list|status|fetch (suggest when 3+ similar tasks) | "
|
|
236
|
+
# Both `dulus` (when pip-installed) and `python dulus.py` work — the
|
|
237
|
+
# entry-point shim is registered in pyproject.toml [project.scripts].
|
|
238
|
+
'In agents: Bash(\'dulus -c "batch status|fetch ID"\')'
|
|
227
239
|
)
|
|
228
240
|
|
|
229
241
|
thk_label = _THINKING_LABELS.get(_normalize_thinking_level(config))
|
|
230
242
|
if thk_label:
|
|
231
|
-
prompt += f"\n#
|
|
243
|
+
prompt += f"\n# Thinking: {thk_label}"
|
|
232
244
|
|
|
233
245
|
if config and config.get("_plan_mode"):
|
|
234
|
-
prompt += f"\n#
|
|
246
|
+
prompt += f"\n# Plan mode: read-only (except {config.get('_plan_file', 'PLAN.md')})"
|
|
247
|
+
|
|
248
|
+
# Hint: pip-installed users can run `dulus` directly (no .py path).
|
|
249
|
+
prompt += (
|
|
250
|
+
"\n# CLI: 'dulus' command works after `pip install dulus` — "
|
|
251
|
+
"no need for `python dulus.py`. Same flags: --print, --accept-all, -c, etc."
|
|
252
|
+
)
|
|
235
253
|
|
|
236
254
|
project_mem = get_project_memory_index()
|
|
237
255
|
if project_mem:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Session manager for Composio integration."""
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
_composio_client = None
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _load_api_key() -> str:
|
|
11
|
+
"""Load Composio API key from Dulus config (with Falcon fallback) or env."""
|
|
12
|
+
api_key = os.environ.get("COMPOSIO_API_KEY", "")
|
|
13
|
+
if not api_key:
|
|
14
|
+
for cfg_path in (Path.home() / ".dulus" / "config.json",
|
|
15
|
+
Path.home() / ".falcon" / "config.json"):
|
|
16
|
+
if cfg_path.exists():
|
|
17
|
+
try:
|
|
18
|
+
with open(cfg_path, "r", encoding="utf-8") as f:
|
|
19
|
+
config = json.load(f)
|
|
20
|
+
api_key = config.get("composio_api_key", "")
|
|
21
|
+
if api_key:
|
|
22
|
+
break
|
|
23
|
+
except Exception:
|
|
24
|
+
pass
|
|
25
|
+
return api_key
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_client():
|
|
29
|
+
"""Get or create Composio client."""
|
|
30
|
+
global _composio_client
|
|
31
|
+
if _composio_client is not None:
|
|
32
|
+
return _composio_client
|
|
33
|
+
|
|
34
|
+
api_key = _load_api_key()
|
|
35
|
+
if not api_key:
|
|
36
|
+
raise RuntimeError("COMPOSIO_API_KEY not found. Set it in ~/.falcon/config.json or env.")
|
|
37
|
+
|
|
38
|
+
os.environ["COMPOSIO_API_KEY"] = api_key
|
|
39
|
+
|
|
40
|
+
from composio import Composio
|
|
41
|
+
_composio_client = Composio()
|
|
42
|
+
return _composio_client
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_or_create_session(user_id: str, toolkits: List[str], connected_accounts: Optional[Dict[str, str]] = None):
|
|
46
|
+
"""Create a Composio session with given toolkits."""
|
|
47
|
+
client = get_client()
|
|
48
|
+
kwargs = {
|
|
49
|
+
"user_id": user_id,
|
|
50
|
+
"toolkits": toolkits,
|
|
51
|
+
"manage_connections": {"wait_for_connections": True},
|
|
52
|
+
}
|
|
53
|
+
if connected_accounts:
|
|
54
|
+
kwargs["connected_accounts"] = connected_accounts
|
|
55
|
+
return client.create(**kwargs)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def list_accounts() -> List[Dict[str, Any]]:
|
|
59
|
+
"""List all connected accounts."""
|
|
60
|
+
client = get_client()
|
|
61
|
+
accounts = client.connected_accounts.list()
|
|
62
|
+
result = []
|
|
63
|
+
for acc in accounts.items:
|
|
64
|
+
result.append({
|
|
65
|
+
"id": getattr(acc, "id", "N/A"),
|
|
66
|
+
"app": getattr(acc, "appName", getattr(acc, "app_name", "N/A")),
|
|
67
|
+
"status": getattr(acc, "status", "N/A"),
|
|
68
|
+
"toolkit": acc.dict().get("toolkit", {}).get("slug", "N/A") if hasattr(acc, "dict") else "N/A",
|
|
69
|
+
"auth_scheme": getattr(acc, "authScheme", "N/A"),
|
|
70
|
+
})
|
|
71
|
+
return result
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""Tool generator - creates native Falcon .py files from Composio tool schemas."""
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Dict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _slug_to_func_name(slug: str) -> str:
|
|
9
|
+
"""Convert a Composio tool slug to a valid Python function name."""
|
|
10
|
+
return re.sub(r"[^a-zA-Z0-9_]", "_", slug.lower()).strip("_")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _build_param_signature(params: Dict[str, Any]) -> str:
|
|
14
|
+
"""Build Python function parameter signature from JSON schema."""
|
|
15
|
+
if not params:
|
|
16
|
+
return ""
|
|
17
|
+
parts = []
|
|
18
|
+
for name, spec in params.items():
|
|
19
|
+
ptype = spec.get("type", "Any")
|
|
20
|
+
default = spec.get("default")
|
|
21
|
+
desc = spec.get("description", "")
|
|
22
|
+
py_type = {"string": "str", "integer": "int", "number": "float", "boolean": "bool", "array": "list", "object": "dict"}.get(ptype, "Any")
|
|
23
|
+
if default is not None:
|
|
24
|
+
parts.append(f"{name}: {py_type} = {repr(default)}")
|
|
25
|
+
else:
|
|
26
|
+
parts.append(f"{name}: {py_type}")
|
|
27
|
+
return ", ".join(parts)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def generate_tool_py(
|
|
31
|
+
tool_slug: str,
|
|
32
|
+
schema: Dict[str, Any],
|
|
33
|
+
output_dir: Path,
|
|
34
|
+
session_id: Optional[str] = None,
|
|
35
|
+
user_id: Optional[str] = None,
|
|
36
|
+
) -> Path:
|
|
37
|
+
"""Generate a standalone Falcon tool .py file for a Composio tool."""
|
|
38
|
+
func_name = _slug_to_func_name(tool_slug)
|
|
39
|
+
file_name = f"{func_name}.py"
|
|
40
|
+
output_path = output_dir / file_name
|
|
41
|
+
|
|
42
|
+
description = schema.get("description", f"Execute {tool_slug} via Composio")
|
|
43
|
+
params = schema.get("parameters", schema.get("input_schema", {})).get("properties", {})
|
|
44
|
+
required = schema.get("parameters", schema.get("input_schema", {})).get("required", [])
|
|
45
|
+
|
|
46
|
+
param_sig = _build_param_signature(params)
|
|
47
|
+
param_unpack = ", ".join([f'{name}=params.get("{name}")' for name in params.keys()])
|
|
48
|
+
|
|
49
|
+
code = f'''"""Auto-generated Falcon tool for Composio: {tool_slug}
|
|
50
|
+
Generated by Falcon Composio Plugin
|
|
51
|
+
"""
|
|
52
|
+
import sys
|
|
53
|
+
import json
|
|
54
|
+
from pathlib import Path
|
|
55
|
+
|
|
56
|
+
# Ensure composio is available
|
|
57
|
+
sys.path.insert(0, str(Path.home() / ".falcon" / "plugins" / "composio"))
|
|
58
|
+
from composio_plugin.session_manager import get_or_create_session
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def {func_name}(params: dict, config: dict) -> str:
|
|
62
|
+
"""
|
|
63
|
+
{description.replace(chr(10), chr(10) + " ")}
|
|
64
|
+
"""
|
|
65
|
+
session = get_or_create_session(
|
|
66
|
+
user_id={repr(user_id or "kevrojo_falcon")},
|
|
67
|
+
toolkits=["gmail"], # TODO: infer from tool slug
|
|
68
|
+
)
|
|
69
|
+
result = session.execute(
|
|
70
|
+
tool_slug={repr(tool_slug)},
|
|
71
|
+
arguments= {{{param_unpack}}}
|
|
72
|
+
)
|
|
73
|
+
data = result.data if hasattr(result, "data") else result
|
|
74
|
+
return json.dumps(data, indent=2, default=str)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# Standalone test
|
|
78
|
+
if __name__ == "__main__":
|
|
79
|
+
test_params = {{
|
|
80
|
+
# Fill with required params
|
|
81
|
+
}}
|
|
82
|
+
print({func_name}(test_params, {{}}))
|
|
83
|
+
'''
|
|
84
|
+
output_path.write_text(code, encoding="utf-8")
|
|
85
|
+
return output_path
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def generate_plugin_tool_py(
|
|
89
|
+
tool_defs: list,
|
|
90
|
+
output_path: Path,
|
|
91
|
+
) -> Path:
|
|
92
|
+
"""Generate a plugin_tool.py exporting multiple ToolDefs."""
|
|
93
|
+
header = '''"""Auto-generated Composio plugin_tool.py
|
|
94
|
+
Generated by Falcon Composio Plugin
|
|
95
|
+
"""
|
|
96
|
+
import sys
|
|
97
|
+
import json
|
|
98
|
+
from pathlib import Path
|
|
99
|
+
|
|
100
|
+
PLUGIN_DIR = Path(__file__).parent.absolute()
|
|
101
|
+
if str(PLUGIN_DIR) not in sys.path:
|
|
102
|
+
sys.path.insert(0, str(PLUGIN_DIR))
|
|
103
|
+
|
|
104
|
+
from tool_registry import ToolDef
|
|
105
|
+
from composio_plugin.session_manager import get_or_create_session, get_client
|
|
106
|
+
|
|
107
|
+
'''
|
|
108
|
+
|
|
109
|
+
functions = []
|
|
110
|
+
tooldefs = []
|
|
111
|
+
|
|
112
|
+
for td in tool_defs:
|
|
113
|
+
slug = td["slug"]
|
|
114
|
+
func_name = _slug_to_func_name(slug)
|
|
115
|
+
desc = td.get("description", f"Execute {slug}")
|
|
116
|
+
params = td.get("schema", {}).get("properties", {})
|
|
117
|
+
required = td.get("schema", {}).get("required", [])
|
|
118
|
+
|
|
119
|
+
param_sig = _build_param_signature(params)
|
|
120
|
+
param_unpack = ", ".join([f'{name}=params.get("{name}")' for name in params.keys()])
|
|
121
|
+
|
|
122
|
+
func = f'''
|
|
123
|
+
def {func_name}(params: dict, config: dict) -> str:
|
|
124
|
+
"""{desc.replace(chr(10), " ")}"""
|
|
125
|
+
session = get_or_create_session(user_id="kevrojo_falcon", toolkits=["gmail"])
|
|
126
|
+
result = session.execute(tool_slug={repr(slug)}, arguments={{{param_unpack}}})
|
|
127
|
+
data = result.data if hasattr(result, "data") else result
|
|
128
|
+
return json.dumps(data, indent=2, default=str)
|
|
129
|
+
'''
|
|
130
|
+
functions.append(func)
|
|
131
|
+
|
|
132
|
+
schema = {
|
|
133
|
+
"name": slug,
|
|
134
|
+
"description": desc,
|
|
135
|
+
"input_schema": {
|
|
136
|
+
"type": "object",
|
|
137
|
+
"properties": params,
|
|
138
|
+
"required": required
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
tooldefs.append(f''' ToolDef(
|
|
142
|
+
name={repr(slug)},
|
|
143
|
+
schema={schema},
|
|
144
|
+
func={func_name}
|
|
145
|
+
)''')
|
|
146
|
+
|
|
147
|
+
footer = f'''
|
|
148
|
+
|
|
149
|
+
TOOL_DEFS = [
|
|
150
|
+
{",".join(tooldefs)}
|
|
151
|
+
]
|
|
152
|
+
TOOL_SCHEMAS = [t.schema for t in TOOL_DEFS]
|
|
153
|
+
'''
|
|
154
|
+
|
|
155
|
+
output_path.write_text(header + "".join(functions) + footer, encoding="utf-8")
|
|
156
|
+
return output_path
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "composio",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Composio integration for Dulus. Connect to 1000+ apps via Composio Tool Router (no MCP needed).",
|
|
5
|
+
"author": "KevRojo",
|
|
6
|
+
"tags": ["automation", "integration", "composio", "mcp"],
|
|
7
|
+
"tools": ["plugin_tool"],
|
|
8
|
+
"skills": [],
|
|
9
|
+
"dependencies": ["composio"],
|
|
10
|
+
"homepage": "https://composio.dev"
|
|
11
|
+
}
|