dulus 0.2.15__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.
Files changed (164) hide show
  1. {dulus-0.2.15/dulus.egg-info → dulus-0.2.17}/PKG-INFO +14 -2
  2. {dulus-0.2.15 → dulus-0.2.17}/README.md +12 -1
  3. {dulus-0.2.15 → dulus-0.2.17}/context.py +36 -18
  4. dulus-0.2.17/data/plugins/__init__.py +1 -0
  5. dulus-0.2.17/data/plugins/composio/__init__.py +1 -0
  6. dulus-0.2.17/data/plugins/composio/composio_plugin/__init__.py +5 -0
  7. dulus-0.2.17/data/plugins/composio/composio_plugin/session_manager.py +71 -0
  8. dulus-0.2.17/data/plugins/composio/composio_plugin/tool_generator.py +156 -0
  9. dulus-0.2.17/data/plugins/composio/plugin.json +11 -0
  10. dulus-0.2.17/data/plugins/composio/plugin_tool.py +434 -0
  11. {dulus-0.2.15 → dulus-0.2.17}/docs/news.md +17 -0
  12. {dulus-0.2.15 → dulus-0.2.17/dulus.egg-info}/PKG-INFO +14 -2
  13. {dulus-0.2.15 → dulus-0.2.17}/dulus.egg-info/SOURCES.txt +7 -0
  14. {dulus-0.2.15 → dulus-0.2.17}/dulus.egg-info/requires.txt +1 -0
  15. {dulus-0.2.15 → dulus-0.2.17}/dulus.py +115 -1
  16. {dulus-0.2.15 → dulus-0.2.17}/pyproject.toml +5 -2
  17. {dulus-0.2.15 → dulus-0.2.17}/skill/clawhub.py +161 -0
  18. {dulus-0.2.15 → dulus-0.2.17}/tools.py +8 -0
  19. {dulus-0.2.15 → dulus-0.2.17}/LICENSE +0 -0
  20. {dulus-0.2.15 → dulus-0.2.17}/MANIFEST.in +0 -0
  21. {dulus-0.2.15 → dulus-0.2.17}/agent.py +0 -0
  22. {dulus-0.2.15 → dulus-0.2.17}/backend/__init__.py +0 -0
  23. {dulus-0.2.15 → dulus-0.2.17}/backend/compressor.py +0 -0
  24. {dulus-0.2.15 → dulus-0.2.17}/backend/context.py +0 -0
  25. {dulus-0.2.15 → dulus-0.2.17}/backend/githook.py +0 -0
  26. {dulus-0.2.15 → dulus-0.2.17}/backend/marketplace.py +0 -0
  27. {dulus-0.2.15 → dulus-0.2.17}/backend/mempalace_bridge.py +0 -0
  28. {dulus-0.2.15 → dulus-0.2.17}/backend/personas.py +0 -0
  29. {dulus-0.2.15 → dulus-0.2.17}/backend/plugins.py +0 -0
  30. {dulus-0.2.15 → dulus-0.2.17}/backend/server.py +0 -0
  31. {dulus-0.2.15 → dulus-0.2.17}/backend/tasks.py +0 -0
  32. {dulus-0.2.15 → dulus-0.2.17}/batch_api.py +0 -0
  33. {dulus-0.2.15 → dulus-0.2.17}/checkpoint/__init__.py +0 -0
  34. {dulus-0.2.15 → dulus-0.2.17}/checkpoint/hooks.py +0 -0
  35. {dulus-0.2.15 → dulus-0.2.17}/checkpoint/store.py +0 -0
  36. {dulus-0.2.15 → dulus-0.2.17}/checkpoint/types.py +0 -0
  37. {dulus-0.2.15 → dulus-0.2.17}/claude_code_watcher.py +0 -0
  38. {dulus-0.2.15 → dulus-0.2.17}/clipboard_utils.py +0 -0
  39. {dulus-0.2.15 → dulus-0.2.17}/cloudsave.py +0 -0
  40. {dulus-0.2.15 → dulus-0.2.17}/common.py +0 -0
  41. {dulus-0.2.15 → dulus-0.2.17}/compaction.py +0 -0
  42. {dulus-0.2.15 → dulus-0.2.17}/config.py +0 -0
  43. {dulus-0.2.15 → dulus-0.2.17}/data/__init__.py +0 -0
  44. {dulus-0.2.15 → dulus-0.2.17}/data/active_persona.json +0 -0
  45. {dulus-0.2.15 → dulus-0.2.17}/data/context.json +0 -0
  46. {dulus-0.2.15 → dulus-0.2.17}/data/marketplace.json +0 -0
  47. {dulus-0.2.15 → dulus-0.2.17}/data/personas.json +0 -0
  48. {dulus-0.2.15 → dulus-0.2.17}/data/tasks.json +0 -0
  49. {dulus-0.2.15 → dulus-0.2.17}/docs/README.md +0 -0
  50. {dulus-0.2.15 → dulus-0.2.17}/docs/__init__.py +0 -0
  51. {dulus-0.2.15 → dulus-0.2.17}/docs/api.html +0 -0
  52. {dulus-0.2.15 → dulus-0.2.17}/docs/architecture.md +0 -0
  53. {dulus-0.2.15 → dulus-0.2.17}/docs/azure-speech-template.json +0 -0
  54. {dulus-0.2.15 → dulus-0.2.17}/docs/dashboard/index.html +0 -0
  55. {dulus-0.2.15 → dulus-0.2.17}/docs/divider.svg +0 -0
  56. {dulus-0.2.15 → dulus-0.2.17}/docs/generate.py +0 -0
  57. {dulus-0.2.15 → dulus-0.2.17}/docs/hero.svg +0 -0
  58. {dulus-0.2.15 → dulus-0.2.17}/docs/index.html +0 -0
  59. {dulus-0.2.15 → dulus-0.2.17}/docs/nvidia-models.svg +0 -0
  60. {dulus-0.2.15 → dulus-0.2.17}/docs/particle-playground.html +0 -0
  61. {dulus-0.2.15 → dulus-0.2.17}/docs/personas/index.html +0 -0
  62. {dulus-0.2.15 → dulus-0.2.17}/docs/poetry-banner.png +0 -0
  63. {dulus-0.2.15 → dulus-0.2.17}/docs/preview.html +0 -0
  64. {dulus-0.2.15 → dulus-0.2.17}/docs/sec-agents.svg +0 -0
  65. {dulus-0.2.15 → dulus-0.2.17}/docs/sec-brainstorm.svg +0 -0
  66. {dulus-0.2.15 → dulus-0.2.17}/docs/sec-bridges.svg +0 -0
  67. {dulus-0.2.15 → dulus-0.2.17}/docs/sec-features.svg +0 -0
  68. {dulus-0.2.15 → dulus-0.2.17}/docs/sec-freetier.svg +0 -0
  69. {dulus-0.2.15 → dulus-0.2.17}/docs/sec-memory.svg +0 -0
  70. {dulus-0.2.15 → dulus-0.2.17}/docs/sec-models.svg +0 -0
  71. {dulus-0.2.15 → dulus-0.2.17}/docs/sec-perms.svg +0 -0
  72. {dulus-0.2.15 → dulus-0.2.17}/docs/sec-plugins.svg +0 -0
  73. {dulus-0.2.15 → dulus-0.2.17}/docs/sec-quickstart.svg +0 -0
  74. {dulus-0.2.15 → dulus-0.2.17}/docs/sec-ssj.svg +0 -0
  75. {dulus-0.2.15 → dulus-0.2.17}/docs/spinners.svg +0 -0
  76. {dulus-0.2.15 → dulus-0.2.17}/docs/split-pane.svg +0 -0
  77. {dulus-0.2.15 → dulus-0.2.17}/docs/terminal-boot.svg +0 -0
  78. {dulus-0.2.15 → dulus-0.2.17}/docs/uploads/particle-playground.html +0 -0
  79. {dulus-0.2.15 → dulus-0.2.17}/dulus.egg-info/dependency_links.txt +0 -0
  80. {dulus-0.2.15 → dulus-0.2.17}/dulus.egg-info/entry_points.txt +0 -0
  81. {dulus-0.2.15 → dulus-0.2.17}/dulus.egg-info/top_level.txt +0 -0
  82. {dulus-0.2.15 → dulus-0.2.17}/dulus_gui.py +0 -0
  83. {dulus-0.2.15 → dulus-0.2.17}/dulus_mcp/__init__.py +0 -0
  84. {dulus-0.2.15 → dulus-0.2.17}/dulus_mcp/client.py +0 -0
  85. {dulus-0.2.15 → dulus-0.2.17}/dulus_mcp/config.py +0 -0
  86. {dulus-0.2.15 → dulus-0.2.17}/dulus_mcp/tools.py +0 -0
  87. {dulus-0.2.15 → dulus-0.2.17}/dulus_mcp/types.py +0 -0
  88. {dulus-0.2.15 → dulus-0.2.17}/gui/__init__.py +0 -0
  89. {dulus-0.2.15 → dulus-0.2.17}/gui/agent_bridge.py +0 -0
  90. {dulus-0.2.15 → dulus-0.2.17}/gui/chat_widget.py +0 -0
  91. {dulus-0.2.15 → dulus-0.2.17}/gui/main_window.py +0 -0
  92. {dulus-0.2.15 → dulus-0.2.17}/gui/personas.py +0 -0
  93. {dulus-0.2.15 → dulus-0.2.17}/gui/session_utils.py +0 -0
  94. {dulus-0.2.15 → dulus-0.2.17}/gui/settings_dialog.py +0 -0
  95. {dulus-0.2.15 → dulus-0.2.17}/gui/sidebar.py +0 -0
  96. {dulus-0.2.15 → dulus-0.2.17}/gui/tasks_view.py +0 -0
  97. {dulus-0.2.15 → dulus-0.2.17}/gui/themes.py +0 -0
  98. {dulus-0.2.15 → dulus-0.2.17}/gui/tool_panel.py +0 -0
  99. {dulus-0.2.15 → dulus-0.2.17}/input.py +0 -0
  100. {dulus-0.2.15 → dulus-0.2.17}/license_manager.py +0 -0
  101. {dulus-0.2.15 → dulus-0.2.17}/memory/__init__.py +0 -0
  102. {dulus-0.2.15 → dulus-0.2.17}/memory/audit.py +0 -0
  103. {dulus-0.2.15 → dulus-0.2.17}/memory/consolidator.py +0 -0
  104. {dulus-0.2.15 → dulus-0.2.17}/memory/context.py +0 -0
  105. {dulus-0.2.15 → dulus-0.2.17}/memory/offload.py +0 -0
  106. {dulus-0.2.15 → dulus-0.2.17}/memory/palace.py +0 -0
  107. {dulus-0.2.15 → dulus-0.2.17}/memory/scan.py +0 -0
  108. {dulus-0.2.15 → dulus-0.2.17}/memory/sessions.py +0 -0
  109. {dulus-0.2.15 → dulus-0.2.17}/memory/store.py +0 -0
  110. {dulus-0.2.15 → dulus-0.2.17}/memory/tools.py +0 -0
  111. {dulus-0.2.15 → dulus-0.2.17}/memory/types.py +0 -0
  112. {dulus-0.2.15 → dulus-0.2.17}/memory/vector_search.py +0 -0
  113. {dulus-0.2.15 → dulus-0.2.17}/multi_agent/__init__.py +0 -0
  114. {dulus-0.2.15 → dulus-0.2.17}/multi_agent/subagent.py +0 -0
  115. {dulus-0.2.15 → dulus-0.2.17}/multi_agent/tools.py +0 -0
  116. {dulus-0.2.15 → dulus-0.2.17}/offload_helper.py +0 -0
  117. {dulus-0.2.15 → dulus-0.2.17}/plugin/__init__.py +0 -0
  118. {dulus-0.2.15 → dulus-0.2.17}/plugin/autoadapter.py +0 -0
  119. {dulus-0.2.15 → dulus-0.2.17}/plugin/loader.py +0 -0
  120. {dulus-0.2.15 → dulus-0.2.17}/plugin/recommend.py +0 -0
  121. {dulus-0.2.15 → dulus-0.2.17}/plugin/store.py +0 -0
  122. {dulus-0.2.15 → dulus-0.2.17}/plugin/types.py +0 -0
  123. {dulus-0.2.15 → dulus-0.2.17}/providers.py +0 -0
  124. {dulus-0.2.15 → dulus-0.2.17}/setup.cfg +0 -0
  125. {dulus-0.2.15 → dulus-0.2.17}/skill/__init__.py +0 -0
  126. {dulus-0.2.15 → dulus-0.2.17}/skill/builtin.py +0 -0
  127. {dulus-0.2.15 → dulus-0.2.17}/skill/executor.py +0 -0
  128. {dulus-0.2.15 → dulus-0.2.17}/skill/loader.py +0 -0
  129. {dulus-0.2.15 → dulus-0.2.17}/skill/tools.py +0 -0
  130. {dulus-0.2.15 → dulus-0.2.17}/skills.py +0 -0
  131. {dulus-0.2.15 → dulus-0.2.17}/spinner.py +0 -0
  132. {dulus-0.2.15 → dulus-0.2.17}/string_utils.py +0 -0
  133. {dulus-0.2.15 → dulus-0.2.17}/subagent.py +0 -0
  134. {dulus-0.2.15 → dulus-0.2.17}/task/__init__.py +0 -0
  135. {dulus-0.2.15 → dulus-0.2.17}/task/store.py +0 -0
  136. {dulus-0.2.15 → dulus-0.2.17}/task/tools.py +0 -0
  137. {dulus-0.2.15 → dulus-0.2.17}/task/types.py +0 -0
  138. {dulus-0.2.15 → dulus-0.2.17}/tests/test_checkpoint.py +0 -0
  139. {dulus-0.2.15 → dulus-0.2.17}/tests/test_compaction.py +0 -0
  140. {dulus-0.2.15 → dulus-0.2.17}/tests/test_diff_view.py +0 -0
  141. {dulus-0.2.15 → dulus-0.2.17}/tests/test_injection_fix.py +0 -0
  142. {dulus-0.2.15 → dulus-0.2.17}/tests/test_license.py +0 -0
  143. {dulus-0.2.15 → dulus-0.2.17}/tests/test_mcp.py +0 -0
  144. {dulus-0.2.15 → dulus-0.2.17}/tests/test_memory.py +0 -0
  145. {dulus-0.2.15 → dulus-0.2.17}/tests/test_plugin.py +0 -0
  146. {dulus-0.2.15 → dulus-0.2.17}/tests/test_skills.py +0 -0
  147. {dulus-0.2.15 → dulus-0.2.17}/tests/test_subagent.py +0 -0
  148. {dulus-0.2.15 → dulus-0.2.17}/tests/test_task.py +0 -0
  149. {dulus-0.2.15 → dulus-0.2.17}/tests/test_telegram_buffer.py +0 -0
  150. {dulus-0.2.15 → dulus-0.2.17}/tests/test_tool_registry.py +0 -0
  151. {dulus-0.2.15 → dulus-0.2.17}/tests/test_voice.py +0 -0
  152. {dulus-0.2.15 → dulus-0.2.17}/tmux_offloader.py +0 -0
  153. {dulus-0.2.15 → dulus-0.2.17}/tmux_tools.py +0 -0
  154. {dulus-0.2.15 → dulus-0.2.17}/tool_registry.py +0 -0
  155. {dulus-0.2.15 → dulus-0.2.17}/ui/__init__.py +0 -0
  156. {dulus-0.2.15 → dulus-0.2.17}/ui/input.py +0 -0
  157. {dulus-0.2.15 → dulus-0.2.17}/ui/render.py +0 -0
  158. {dulus-0.2.15 → dulus-0.2.17}/voice/__init__.py +0 -0
  159. {dulus-0.2.15 → dulus-0.2.17}/voice/keyterms.py +0 -0
  160. {dulus-0.2.15 → dulus-0.2.17}/voice/recorder.py +0 -0
  161. {dulus-0.2.15 → dulus-0.2.17}/voice/stt.py +0 -0
  162. {dulus-0.2.15 → dulus-0.2.17}/voice/tts.py +0 -0
  163. {dulus-0.2.15 → dulus-0.2.17}/webchat.py +0 -0
  164. {dulus-0.2.15 → 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.15
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.15-ff6b1f?style=flat-square&labelColor=07070a" alt="version"/>
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"/>
@@ -90,9 +91,13 @@ SET /sticky_input ON since the first run for the best experience!
90
91
  Talent cant be copied.
91
92
 
92
93
  Dulus Reduce your IA costs by 60% parsing webchats and claude-code directly. Write poetry while Anthropic only see text.
94
+ Use claude-code as an API without the new 'extra-usage' wall <3
93
95
 
94
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>
95
97
 
98
+ <p align="center">
99
+ <sub>⚡ <b>Saves you Claude tokens?</b> Throw a sat — BTC: <code>1JzatQDn9fMLnKTd3KYgztsLHC95bJEzSN</code></sub>
100
+ </p>
96
101
 
97
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.
98
103
 
@@ -622,6 +627,13 @@ git diff | dulus -p "write a commit message"
622
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.
623
628
 
624
629
  ---
630
+ ## Donations
631
+
632
+ If Dulus saved you tokens, time, or sanity — throw some sats:
633
+
634
+ ```
635
+ BTC: 1JzatQDn9fMLnKTd3KYgztsLHC95bJEzSN
636
+ ```
625
637
 
626
638
  <p align="center"><img src="https://raw.githubusercontent.com/KevRojo/Dulus/main/docs/divider.svg" alt="" width="100%"></p>
627
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.15-ff6b1f?style=flat-square&labelColor=07070a" alt="version"/>
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"/>
@@ -45,9 +45,13 @@ SET /sticky_input ON since the first run for the best experience!
45
45
  Talent cant be copied.
46
46
 
47
47
  Dulus Reduce your IA costs by 60% parsing webchats and claude-code directly. Write poetry while Anthropic only see text.
48
+ Use claude-code as an API without the new 'extra-usage' wall <3
48
49
 
49
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>
50
51
 
52
+ <p align="center">
53
+ <sub>⚡ <b>Saves you Claude tokens?</b> Throw a sat — BTC: <code>1JzatQDn9fMLnKTd3KYgztsLHC95bJEzSN</code></sub>
54
+ </p>
51
55
 
52
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.
53
57
 
@@ -577,6 +581,13 @@ git diff | dulus -p "write a commit message"
577
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.
578
582
 
579
583
  ---
584
+ ## Donations
585
+
586
+ If Dulus saved you tokens, time, or sanity — throw some sats:
587
+
588
+ ```
589
+ BTC: 1JzatQDn9fMLnKTd3KYgztsLHC95bJEzSN
590
+ ```
580
591
 
581
592
  <p align="center"><img src="https://raw.githubusercontent.com/KevRojo/Dulus/main/docs/divider.svg" alt="" width="100%"></p>
582
593
 
@@ -13,19 +13,19 @@ import subprocess
13
13
  from pathlib import Path
14
14
 
15
15
  SYSTEM_PROMPT_TEMPLATE = """\
16
- 你是DulusAI编程助手。思考用英语,回复用西班牙语(多米尼加克里奥尔风格)与KevRojo交流。
17
- # 身份: 你的名字是 Dulus。不需要主动向用户声明这一点,除非用户直接问你 "quién eres" "qué modelo eres"
18
- # 禁止: 不要声称自己是 QwenLlamaGPTClaudeGeminiDeepSeek 或任何其他底层模型。不要提及 Ollama 或运行你的技术栈。
19
- # 环境: {cwd} | {platform} | auto_show={auto_show}
20
- # 自主性: 后台脚本(nohup/&)可用 | 不拒绝监控/长任务 | 工具结果后再响应
21
- # 工具: SearchLastOutput→[TRUNCATED] | WebFetch/WebSearch→网页 | TmuxOffload→>5s | ReadJob→后台
22
- # 多代理: Agent(subagent_type=...) | isolation="worktree"并行 | wait=false+name=...
23
- # 准则: 编辑>新建 | 绝对路径+行号 | 报错立即告知不重试
24
- # 输入: 🎙 Transcribed: = 语音(容忍拼写错误)
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: "最小化", 2: "适度", 3: "深度"}
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# 批处理: /batch list|status|fetch (3+同类任务建议) | "
226
- 'Agent内: Bash(\'python dulus.py -c "batch status|fetch ID"\')'
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# 推理: {thk_label}"
243
+ prompt += f"\n# Thinking: {thk_label}"
232
244
 
233
245
  if config and config.get("_plan_mode"):
234
- prompt += f"\n# 计划模式: 只读 ( {config.get('_plan_file', 'PLAN.md')})"
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,5 @@
1
+ """Composio plugin helpers for Falcon."""
2
+ from .session_manager import get_client, get_or_create_session, list_accounts
3
+ from .tool_generator import generate_tool_py
4
+
5
+ __all__ = ["get_client", "get_or_create_session", "list_accounts", "generate_tool_py"]
@@ -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
+ }