dulus 0.2.25__tar.gz → 0.2.26__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.25/dulus.egg-info → dulus-0.2.26}/PKG-INFO +2 -4
  2. {dulus-0.2.25 → dulus-0.2.26}/README.md +1 -3
  3. {dulus-0.2.25 → dulus-0.2.26}/common.py +5 -1
  4. {dulus-0.2.25 → dulus-0.2.26}/docs/news.md +5 -0
  5. {dulus-0.2.25 → dulus-0.2.26/dulus.egg-info}/PKG-INFO +2 -4
  6. {dulus-0.2.25 → dulus-0.2.26}/dulus.py +8 -2
  7. {dulus-0.2.25 → dulus-0.2.26}/pyproject.toml +1 -1
  8. {dulus-0.2.25 → dulus-0.2.26}/skill/clawhub.py +101 -18
  9. {dulus-0.2.25 → dulus-0.2.26}/LICENSE +0 -0
  10. {dulus-0.2.25 → dulus-0.2.26}/MANIFEST.in +0 -0
  11. {dulus-0.2.25 → dulus-0.2.26}/agent.py +0 -0
  12. {dulus-0.2.25 → dulus-0.2.26}/backend/__init__.py +0 -0
  13. {dulus-0.2.25 → dulus-0.2.26}/backend/compressor.py +0 -0
  14. {dulus-0.2.25 → dulus-0.2.26}/backend/context.py +0 -0
  15. {dulus-0.2.25 → dulus-0.2.26}/backend/githook.py +0 -0
  16. {dulus-0.2.25 → dulus-0.2.26}/backend/marketplace.py +0 -0
  17. {dulus-0.2.25 → dulus-0.2.26}/backend/mempalace_bridge.py +0 -0
  18. {dulus-0.2.25 → dulus-0.2.26}/backend/personas.py +0 -0
  19. {dulus-0.2.25 → dulus-0.2.26}/backend/plugins.py +0 -0
  20. {dulus-0.2.25 → dulus-0.2.26}/backend/server.py +0 -0
  21. {dulus-0.2.25 → dulus-0.2.26}/backend/tasks.py +0 -0
  22. {dulus-0.2.25 → dulus-0.2.26}/batch_api.py +0 -0
  23. {dulus-0.2.25 → dulus-0.2.26}/checkpoint/__init__.py +0 -0
  24. {dulus-0.2.25 → dulus-0.2.26}/checkpoint/hooks.py +0 -0
  25. {dulus-0.2.25 → dulus-0.2.26}/checkpoint/store.py +0 -0
  26. {dulus-0.2.25 → dulus-0.2.26}/checkpoint/types.py +0 -0
  27. {dulus-0.2.25 → dulus-0.2.26}/claude_code_watcher.py +0 -0
  28. {dulus-0.2.25 → dulus-0.2.26}/clipboard_utils.py +0 -0
  29. {dulus-0.2.25 → dulus-0.2.26}/cloudsave.py +0 -0
  30. {dulus-0.2.25 → dulus-0.2.26}/compaction.py +0 -0
  31. {dulus-0.2.25 → dulus-0.2.26}/config.py +0 -0
  32. {dulus-0.2.25 → dulus-0.2.26}/context.py +0 -0
  33. {dulus-0.2.25 → dulus-0.2.26}/data/__init__.py +0 -0
  34. {dulus-0.2.25 → dulus-0.2.26}/data/active_persona.json +0 -0
  35. {dulus-0.2.25 → dulus-0.2.26}/data/context.json +0 -0
  36. {dulus-0.2.25 → dulus-0.2.26}/data/marketplace.json +0 -0
  37. {dulus-0.2.25 → dulus-0.2.26}/data/personas.json +0 -0
  38. {dulus-0.2.25 → dulus-0.2.26}/data/plugins/__init__.py +0 -0
  39. {dulus-0.2.25 → dulus-0.2.26}/data/plugins/composio/__init__.py +0 -0
  40. {dulus-0.2.25 → dulus-0.2.26}/data/plugins/composio/composio_plugin/__init__.py +0 -0
  41. {dulus-0.2.25 → dulus-0.2.26}/data/plugins/composio/composio_plugin/session_manager.py +0 -0
  42. {dulus-0.2.25 → dulus-0.2.26}/data/plugins/composio/composio_plugin/tool_generator.py +0 -0
  43. {dulus-0.2.25 → dulus-0.2.26}/data/plugins/composio/plugin.json +0 -0
  44. {dulus-0.2.25 → dulus-0.2.26}/data/plugins/composio/plugin_tool.py +0 -0
  45. {dulus-0.2.25 → dulus-0.2.26}/data/tasks.json +0 -0
  46. {dulus-0.2.25 → dulus-0.2.26}/docs/README.md +0 -0
  47. {dulus-0.2.25 → dulus-0.2.26}/docs/__init__.py +0 -0
  48. {dulus-0.2.25 → dulus-0.2.26}/docs/api.html +0 -0
  49. {dulus-0.2.25 → dulus-0.2.26}/docs/architecture.md +0 -0
  50. {dulus-0.2.25 → dulus-0.2.26}/docs/azure-speech-template.json +0 -0
  51. {dulus-0.2.25 → dulus-0.2.26}/docs/dashboard/index.html +0 -0
  52. {dulus-0.2.25 → dulus-0.2.26}/docs/divider.svg +0 -0
  53. {dulus-0.2.25 → dulus-0.2.26}/docs/generate.py +0 -0
  54. {dulus-0.2.25 → dulus-0.2.26}/docs/hero.svg +0 -0
  55. {dulus-0.2.25 → dulus-0.2.26}/docs/index.html +0 -0
  56. {dulus-0.2.25 → dulus-0.2.26}/docs/nvidia-models.svg +0 -0
  57. {dulus-0.2.25 → dulus-0.2.26}/docs/particle-playground.html +0 -0
  58. {dulus-0.2.25 → dulus-0.2.26}/docs/personas/index.html +0 -0
  59. {dulus-0.2.25 → dulus-0.2.26}/docs/poetry-banner.png +0 -0
  60. {dulus-0.2.25 → dulus-0.2.26}/docs/preview.html +0 -0
  61. {dulus-0.2.25 → dulus-0.2.26}/docs/sec-agents.svg +0 -0
  62. {dulus-0.2.25 → dulus-0.2.26}/docs/sec-brainstorm.svg +0 -0
  63. {dulus-0.2.25 → dulus-0.2.26}/docs/sec-bridges.svg +0 -0
  64. {dulus-0.2.25 → dulus-0.2.26}/docs/sec-features.svg +0 -0
  65. {dulus-0.2.25 → dulus-0.2.26}/docs/sec-freetier.svg +0 -0
  66. {dulus-0.2.25 → dulus-0.2.26}/docs/sec-memory.svg +0 -0
  67. {dulus-0.2.25 → dulus-0.2.26}/docs/sec-models.svg +0 -0
  68. {dulus-0.2.25 → dulus-0.2.26}/docs/sec-perms.svg +0 -0
  69. {dulus-0.2.25 → dulus-0.2.26}/docs/sec-plugins.svg +0 -0
  70. {dulus-0.2.25 → dulus-0.2.26}/docs/sec-quickstart.svg +0 -0
  71. {dulus-0.2.25 → dulus-0.2.26}/docs/sec-ssj.svg +0 -0
  72. {dulus-0.2.25 → dulus-0.2.26}/docs/spinners.svg +0 -0
  73. {dulus-0.2.25 → dulus-0.2.26}/docs/split-pane.svg +0 -0
  74. {dulus-0.2.25 → dulus-0.2.26}/docs/terminal-boot.svg +0 -0
  75. {dulus-0.2.25 → dulus-0.2.26}/docs/uploads/particle-playground.html +0 -0
  76. {dulus-0.2.25 → dulus-0.2.26}/dulus.egg-info/SOURCES.txt +0 -0
  77. {dulus-0.2.25 → dulus-0.2.26}/dulus.egg-info/dependency_links.txt +0 -0
  78. {dulus-0.2.25 → dulus-0.2.26}/dulus.egg-info/entry_points.txt +0 -0
  79. {dulus-0.2.25 → dulus-0.2.26}/dulus.egg-info/requires.txt +0 -0
  80. {dulus-0.2.25 → dulus-0.2.26}/dulus.egg-info/top_level.txt +0 -0
  81. {dulus-0.2.25 → dulus-0.2.26}/dulus_gui.py +0 -0
  82. {dulus-0.2.25 → dulus-0.2.26}/dulus_mcp/__init__.py +0 -0
  83. {dulus-0.2.25 → dulus-0.2.26}/dulus_mcp/client.py +0 -0
  84. {dulus-0.2.25 → dulus-0.2.26}/dulus_mcp/config.py +0 -0
  85. {dulus-0.2.25 → dulus-0.2.26}/dulus_mcp/tools.py +0 -0
  86. {dulus-0.2.25 → dulus-0.2.26}/dulus_mcp/types.py +0 -0
  87. {dulus-0.2.25 → dulus-0.2.26}/gui/__init__.py +0 -0
  88. {dulus-0.2.25 → dulus-0.2.26}/gui/agent_bridge.py +0 -0
  89. {dulus-0.2.25 → dulus-0.2.26}/gui/chat_widget.py +0 -0
  90. {dulus-0.2.25 → dulus-0.2.26}/gui/main_window.py +0 -0
  91. {dulus-0.2.25 → dulus-0.2.26}/gui/personas.py +0 -0
  92. {dulus-0.2.25 → dulus-0.2.26}/gui/session_utils.py +0 -0
  93. {dulus-0.2.25 → dulus-0.2.26}/gui/settings_dialog.py +0 -0
  94. {dulus-0.2.25 → dulus-0.2.26}/gui/sidebar.py +0 -0
  95. {dulus-0.2.25 → dulus-0.2.26}/gui/tasks_view.py +0 -0
  96. {dulus-0.2.25 → dulus-0.2.26}/gui/themes.py +0 -0
  97. {dulus-0.2.25 → dulus-0.2.26}/gui/tool_panel.py +0 -0
  98. {dulus-0.2.25 → dulus-0.2.26}/input.py +0 -0
  99. {dulus-0.2.25 → dulus-0.2.26}/license_manager.py +0 -0
  100. {dulus-0.2.25 → dulus-0.2.26}/memory/__init__.py +0 -0
  101. {dulus-0.2.25 → dulus-0.2.26}/memory/audit.py +0 -0
  102. {dulus-0.2.25 → dulus-0.2.26}/memory/consolidator.py +0 -0
  103. {dulus-0.2.25 → dulus-0.2.26}/memory/context.py +0 -0
  104. {dulus-0.2.25 → dulus-0.2.26}/memory/offload.py +0 -0
  105. {dulus-0.2.25 → dulus-0.2.26}/memory/palace.py +0 -0
  106. {dulus-0.2.25 → dulus-0.2.26}/memory/scan.py +0 -0
  107. {dulus-0.2.25 → dulus-0.2.26}/memory/sessions.py +0 -0
  108. {dulus-0.2.25 → dulus-0.2.26}/memory/store.py +0 -0
  109. {dulus-0.2.25 → dulus-0.2.26}/memory/tools.py +0 -0
  110. {dulus-0.2.25 → dulus-0.2.26}/memory/types.py +0 -0
  111. {dulus-0.2.25 → dulus-0.2.26}/memory/vector_search.py +0 -0
  112. {dulus-0.2.25 → dulus-0.2.26}/multi_agent/__init__.py +0 -0
  113. {dulus-0.2.25 → dulus-0.2.26}/multi_agent/subagent.py +0 -0
  114. {dulus-0.2.25 → dulus-0.2.26}/multi_agent/tools.py +0 -0
  115. {dulus-0.2.25 → dulus-0.2.26}/offload_helper.py +0 -0
  116. {dulus-0.2.25 → dulus-0.2.26}/plugin/__init__.py +0 -0
  117. {dulus-0.2.25 → dulus-0.2.26}/plugin/autoadapter.py +0 -0
  118. {dulus-0.2.25 → dulus-0.2.26}/plugin/loader.py +0 -0
  119. {dulus-0.2.25 → dulus-0.2.26}/plugin/recommend.py +0 -0
  120. {dulus-0.2.25 → dulus-0.2.26}/plugin/store.py +0 -0
  121. {dulus-0.2.25 → dulus-0.2.26}/plugin/types.py +0 -0
  122. {dulus-0.2.25 → dulus-0.2.26}/providers.py +0 -0
  123. {dulus-0.2.25 → dulus-0.2.26}/setup.cfg +0 -0
  124. {dulus-0.2.25 → dulus-0.2.26}/skill/__init__.py +0 -0
  125. {dulus-0.2.25 → dulus-0.2.26}/skill/builtin.py +0 -0
  126. {dulus-0.2.25 → dulus-0.2.26}/skill/executor.py +0 -0
  127. {dulus-0.2.25 → dulus-0.2.26}/skill/loader.py +0 -0
  128. {dulus-0.2.25 → dulus-0.2.26}/skill/tools.py +0 -0
  129. {dulus-0.2.25 → dulus-0.2.26}/skills.py +0 -0
  130. {dulus-0.2.25 → dulus-0.2.26}/spinner.py +0 -0
  131. {dulus-0.2.25 → dulus-0.2.26}/string_utils.py +0 -0
  132. {dulus-0.2.25 → dulus-0.2.26}/subagent.py +0 -0
  133. {dulus-0.2.25 → dulus-0.2.26}/task/__init__.py +0 -0
  134. {dulus-0.2.25 → dulus-0.2.26}/task/store.py +0 -0
  135. {dulus-0.2.25 → dulus-0.2.26}/task/tools.py +0 -0
  136. {dulus-0.2.25 → dulus-0.2.26}/task/types.py +0 -0
  137. {dulus-0.2.25 → dulus-0.2.26}/tests/test_checkpoint.py +0 -0
  138. {dulus-0.2.25 → dulus-0.2.26}/tests/test_compaction.py +0 -0
  139. {dulus-0.2.25 → dulus-0.2.26}/tests/test_diff_view.py +0 -0
  140. {dulus-0.2.25 → dulus-0.2.26}/tests/test_injection_fix.py +0 -0
  141. {dulus-0.2.25 → dulus-0.2.26}/tests/test_license.py +0 -0
  142. {dulus-0.2.25 → dulus-0.2.26}/tests/test_mcp.py +0 -0
  143. {dulus-0.2.25 → dulus-0.2.26}/tests/test_memory.py +0 -0
  144. {dulus-0.2.25 → dulus-0.2.26}/tests/test_plugin.py +0 -0
  145. {dulus-0.2.25 → dulus-0.2.26}/tests/test_skills.py +0 -0
  146. {dulus-0.2.25 → dulus-0.2.26}/tests/test_subagent.py +0 -0
  147. {dulus-0.2.25 → dulus-0.2.26}/tests/test_task.py +0 -0
  148. {dulus-0.2.25 → dulus-0.2.26}/tests/test_telegram_buffer.py +0 -0
  149. {dulus-0.2.25 → dulus-0.2.26}/tests/test_tool_registry.py +0 -0
  150. {dulus-0.2.25 → dulus-0.2.26}/tests/test_voice.py +0 -0
  151. {dulus-0.2.25 → dulus-0.2.26}/tmux_offloader.py +0 -0
  152. {dulus-0.2.25 → dulus-0.2.26}/tmux_tools.py +0 -0
  153. {dulus-0.2.25 → dulus-0.2.26}/tool_registry.py +0 -0
  154. {dulus-0.2.25 → dulus-0.2.26}/tools.py +0 -0
  155. {dulus-0.2.25 → dulus-0.2.26}/ui/__init__.py +0 -0
  156. {dulus-0.2.25 → dulus-0.2.26}/ui/input.py +0 -0
  157. {dulus-0.2.25 → dulus-0.2.26}/ui/render.py +0 -0
  158. {dulus-0.2.25 → dulus-0.2.26}/voice/__init__.py +0 -0
  159. {dulus-0.2.25 → dulus-0.2.26}/voice/keyterms.py +0 -0
  160. {dulus-0.2.25 → dulus-0.2.26}/voice/recorder.py +0 -0
  161. {dulus-0.2.25 → dulus-0.2.26}/voice/stt.py +0 -0
  162. {dulus-0.2.25 → dulus-0.2.26}/voice/tts.py +0 -0
  163. {dulus-0.2.25 → dulus-0.2.26}/webchat.py +0 -0
  164. {dulus-0.2.25 → dulus-0.2.26}/webchat_server.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dulus
3
- Version: 0.2.25
3
+ Version: 0.2.26
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.25-ff6b1f?style=flat-square&labelColor=07070a" alt="version"/>
72
+ <img src="https://img.shields.io/badge/version-v0.2.26-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"/>
@@ -159,8 +159,6 @@ Dulus is a **lightweight Python reimplementation of Claude Code** that isn't loc
159
159
 
160
160
  ROUND TABLE (DULUS UNIQUE FEATURE)
161
161
 
162
- <img alt="image" src="https://github.com/user-attachments/assets/648ffe5e-28e2-49e0-bc27-362a585edd4f" />
163
-
164
162
  <img alt="image" src="https://github.com/user-attachments/assets/9e8f17ed-6ca2-4ae0-b8c3-146ae5fef491" />
165
163
 
166
164
  Dulus is the first one meeting multiple models at the same time working for the same objective and sharing their ideas.
@@ -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-ff6b1f?style=flat-square&labelColor=07070a" alt="version"/>
25
+ <img src="https://img.shields.io/badge/version-v0.2.26-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"/>
@@ -112,8 +112,6 @@ Dulus is a **lightweight Python reimplementation of Claude Code** that isn't loc
112
112
 
113
113
  ROUND TABLE (DULUS UNIQUE FEATURE)
114
114
 
115
- <img alt="image" src="https://github.com/user-attachments/assets/648ffe5e-28e2-49e0-bc27-362a585edd4f" />
116
-
117
115
  <img alt="image" src="https://github.com/user-attachments/assets/9e8f17ed-6ca2-4ae0-b8c3-146ae5fef491" />
118
116
 
119
117
  Dulus is the first one meeting multiple models at the same time working for the same objective and sharing their ideas.
@@ -120,7 +120,11 @@ def apply_theme(name: str) -> bool:
120
120
  apply_theme("dulus")
121
121
 
122
122
  def clr(text: str, *keys: str) -> str:
123
- return "".join(C[k] for k in keys) + str(text) + C["reset"]
123
+ # Defensive: a missing color key (theme-specific names like "accent" or
124
+ # "orange" in palettes that don't define them) used to raise KeyError and
125
+ # could crash callers. Skip unknown keys instead so a stale theme name
126
+ # never takes down the daemon or REPL.
127
+ return "".join(C.get(k, "") for k in keys) + str(text) + C.get("reset", "")
124
128
 
125
129
  def info(msg: str): print(clr(msg, "cyan"))
126
130
  def ok(msg: str): print(clr(msg, "green"))
@@ -3,6 +3,11 @@
3
3
  ## 🔥🔥🔥 News (Pacific Time)
4
4
 
5
5
 
6
+ - May 09, 2026 (**v0.2.26**): **`/bg start` daemon crash fix + defensive `clr()` + composio fallback**
7
+ - **Daemon was silently crashing on launch.** `_run_daemon` printed its banner with `clr("...", "accent", "bold")`, but the default theme palette only ships {blue, cyan, gray, green, magenta, red, white, yellow} — `"accent"` raised `KeyError` and killed the process before the prompt loop started. WebChat + IPC threads, being daemon=True, died with it. Switched the banner to `"yellow"` and wrapped in try/except so a stale theme color name never takes the daemon down.
8
+ - **`clr()` is now defensive.** Missing color keys are silently dropped instead of raising. One typo in a theme name no longer crashes the REPL.
9
+ - **`/skill list composio` no longer errors out.** The public `/api/v3/toolkits` endpoint requires elevated auth (returns 401/403 even with a valid API key for free tiers). Added a curated 32-toolkit fallback (Gmail, Slack, GitHub, Notion, Linear, Asana, ClickUp, Jira, Discord, Stripe, etc.) so the menu always shows useful targets. Authenticated path is still attempted first when an API key is configured.
10
+
6
11
  - 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
12
 
8
13
  - May 09, 2026 (**v0.2.24**): **Auto-adapter prompt — 5 fixes from a sherlock postmortem**
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dulus
3
- Version: 0.2.25
3
+ Version: 0.2.26
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.25-ff6b1f?style=flat-square&labelColor=07070a" alt="version"/>
72
+ <img src="https://img.shields.io/badge/version-v0.2.26-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"/>
@@ -159,8 +159,6 @@ Dulus is a **lightweight Python reimplementation of Claude Code** that isn't loc
159
159
 
160
160
  ROUND TABLE (DULUS UNIQUE FEATURE)
161
161
 
162
- <img alt="image" src="https://github.com/user-attachments/assets/648ffe5e-28e2-49e0-bc27-362a585edd4f" />
163
-
164
162
  <img alt="image" src="https://github.com/user-attachments/assets/9e8f17ed-6ca2-4ae0-b8c3-146ae5fef491" />
165
163
 
166
164
  Dulus is the first one meeting multiple models at the same time working for the same objective and sharing their ideas.
@@ -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.25" # dev fallback — keep in sync with pyproject.toml
221
+ VERSION = "0.2.26" # 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
@@ -5612,7 +5612,13 @@ def _run_daemon(config: dict) -> None:
5612
5612
  config["_ipc_thread"] = ti
5613
5613
  ti.start()
5614
5614
 
5615
- print(clr("\n ▲ DULUS DAEMON", "accent", "bold"))
5615
+ # 'accent' / 'orange' are only present in some custom themes; default
5616
+ # palette is {blue, cyan, gray, green, magenta, red, white, yellow}.
5617
+ # KeyError here would crash the daemon before the user ever sees a prompt.
5618
+ try:
5619
+ print(clr("\n ▲ DULUS DAEMON", "yellow", "bold"))
5620
+ except KeyError:
5621
+ print("\n ▲ DULUS DAEMON")
5616
5622
  print(clr(" " + "─" * 40, "dim"))
5617
5623
  info(f"Session: {session_id}")
5618
5624
  info("Daemon active — waiting for triggers…")
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dulus"
7
- version = "0.2.25"
7
+ version = "0.2.26"
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"
@@ -296,8 +296,71 @@ _COMPOSIO_TOOLKITS_URL = "https://backend.composio.dev/api/v3/toolkits?cursor=&l
296
296
  _COMPOSIO_CACHE = Path.home() / ".dulus" / "cache" / "composio-toolkits.json"
297
297
 
298
298
 
299
+ # Curated fallback list — used when no Composio API key is available so the
300
+ # /skill list composio command still shows something useful instead of an
301
+ # empty result. ~30 of the most-requested toolkits.
302
+ _COMPOSIO_FALLBACK = [
303
+ ("gmail", "Gmail email — read, send, label, search messages."),
304
+ ("googlecalendar", "Google Calendar — events, attendees, schedules."),
305
+ ("googledrive", "Google Drive — files, folders, sharing."),
306
+ ("googlesheets", "Google Sheets — read/write spreadsheets."),
307
+ ("googledocs", "Google Docs — create and edit documents."),
308
+ ("slack", "Slack — messages, channels, files, search."),
309
+ ("github", "GitHub — repos, issues, PRs, releases, branches."),
310
+ ("gitlab", "GitLab — projects, issues, merge requests."),
311
+ ("notion", "Notion — pages, databases, blocks."),
312
+ ("linear", "Linear — issues, projects, cycles, teams."),
313
+ ("asana", "Asana — tasks, projects, sections."),
314
+ ("trello", "Trello — boards, cards, lists."),
315
+ ("clickup", "ClickUp — tasks, lists, spaces."),
316
+ ("jira", "Jira — issues, sprints, projects."),
317
+ ("confluence", "Confluence — pages, spaces, content."),
318
+ ("discord", "Discord — guilds, channels, messages."),
319
+ ("telegram", "Telegram bot API — messages, files."),
320
+ ("twitter", "Twitter/X — tweets, search, profiles."),
321
+ ("reddit", "Reddit — posts, comments, subreddits."),
322
+ ("hackernews", "Hacker News — stories, comments, search."),
323
+ ("youtube", "YouTube — videos, channels, comments, captions."),
324
+ ("spotify", "Spotify — playlists, search, playback."),
325
+ ("hubspot", "HubSpot — contacts, deals, companies."),
326
+ ("salesforce", "Salesforce — leads, accounts, opportunities."),
327
+ ("shopify", "Shopify — products, orders, customers."),
328
+ ("stripe", "Stripe — payments, customers, subscriptions."),
329
+ ("airtable", "Airtable — bases, tables, records."),
330
+ ("firebase", "Firebase — Firestore, Realtime DB, Auth."),
331
+ ("supabase", "Supabase — Postgres, auth, storage."),
332
+ ("perplexity", "Perplexity — AI-powered web search."),
333
+ ("firecrawl", "Firecrawl — scrape & crawl websites to markdown."),
334
+ ("exa", "Exa — semantic web search."),
335
+ ]
336
+
337
+
338
+ def _load_composio_api_key() -> str:
339
+ """Load API key from env, ~/.dulus/config.json, or ~/.falcon/config.json."""
340
+ import os as _os
341
+ key = _os.environ.get("COMPOSIO_API_KEY", "").strip()
342
+ if key:
343
+ return key
344
+ for cfg_path in (Path.home() / ".dulus" / "config.json",
345
+ Path.home() / ".falcon" / "config.json"):
346
+ if cfg_path.exists():
347
+ try:
348
+ cfg = json.loads(cfg_path.read_text(encoding="utf-8"))
349
+ k = cfg.get("composio_api_key", "")
350
+ if k:
351
+ return k
352
+ except Exception:
353
+ continue
354
+ return ""
355
+
356
+
299
357
  def list_composio_toolkits(query: Optional[str] = None, force_refresh: bool = False) -> list[dict]:
300
- """Return Composio toolkits as skill-like dicts. Cached 24h."""
358
+ """Return Composio toolkits as skill-like dicts. Cached 24h.
359
+
360
+ Authenticated path (API key set): hit the live `/api/v3/toolkits` endpoint.
361
+ Unauthenticated path: return the curated _COMPOSIO_FALLBACK list so the
362
+ /skill list composio UI still shows something useful.
363
+ """
301
364
  import time
302
365
  items: list[dict] = []
303
366
  if not force_refresh and _COMPOSIO_CACHE.exists():
@@ -308,23 +371,43 @@ def list_composio_toolkits(query: Optional[str] = None, force_refresh: bool = Fa
308
371
  except Exception:
309
372
  items = []
310
373
  if not items:
311
- try:
312
- with urllib.request.urlopen(_COMPOSIO_TOOLKITS_URL, timeout=15) as resp:
313
- payload = json.loads(resp.read())
314
- except Exception:
315
- return []
316
- for tk in payload.get("items", payload.get("data", [])):
317
- slug = tk.get("slug") or tk.get("name", "")
318
- if not slug:
319
- continue
320
- items.append({
321
- "id": f"composio/{slug}",
322
- "plugin": "composio",
323
- "skill": slug,
324
- "description": tk.get("description") or tk.get("meta", {}).get("description", ""),
325
- "path": f"https://composio.dev/apps/{slug}",
326
- "source": "composio",
327
- })
374
+ api_key = _load_composio_api_key()
375
+ if api_key:
376
+ req = urllib.request.Request(
377
+ _COMPOSIO_TOOLKITS_URL,
378
+ headers={"x-api-key": api_key, "Accept": "application/json"},
379
+ )
380
+ try:
381
+ with urllib.request.urlopen(req, timeout=15) as resp:
382
+ payload = json.loads(resp.read())
383
+ for tk in payload.get("items", payload.get("data", [])):
384
+ slug = tk.get("slug") or tk.get("name", "")
385
+ if not slug:
386
+ continue
387
+ items.append({
388
+ "id": f"composio/{slug}",
389
+ "plugin": "composio",
390
+ "skill": slug,
391
+ "description": tk.get("description") or tk.get("meta", {}).get("description", ""),
392
+ "path": f"https://composio.dev/apps/{slug}",
393
+ "source": "composio",
394
+ })
395
+ except Exception:
396
+ pass # fall through to fallback list below
397
+
398
+ # Fallback: no key, or auth call failed — show the curated list so the
399
+ # user still has something to browse / use as session toolkits.
400
+ if not items:
401
+ for slug, desc in _COMPOSIO_FALLBACK:
402
+ items.append({
403
+ "id": f"composio/{slug}",
404
+ "plugin": "composio",
405
+ "skill": slug,
406
+ "description": desc + ("" if api_key else " [curated fallback — set COMPOSIO_API_KEY for the full live catalog]"),
407
+ "path": f"https://composio.dev/apps/{slug}",
408
+ "source": "composio-fallback" if not api_key else "composio",
409
+ })
410
+
328
411
  _COMPOSIO_CACHE.parent.mkdir(parents=True, exist_ok=True)
329
412
  try:
330
413
  _COMPOSIO_CACHE.write_text(
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