dulus 0.2.25__tar.gz → 0.2.27__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.25/dulus.egg-info → dulus-0.2.27}/PKG-INFO +2 -4
- {dulus-0.2.25 → dulus-0.2.27}/README.md +1 -3
- {dulus-0.2.25 → dulus-0.2.27}/common.py +5 -1
- {dulus-0.2.25 → dulus-0.2.27}/docs/news.md +7 -0
- {dulus-0.2.25 → dulus-0.2.27/dulus.egg-info}/PKG-INFO +2 -4
- {dulus-0.2.25 → dulus-0.2.27}/dulus.py +37 -3
- {dulus-0.2.25 → dulus-0.2.27}/pyproject.toml +1 -1
- {dulus-0.2.25 → dulus-0.2.27}/skill/clawhub.py +101 -18
- {dulus-0.2.25 → dulus-0.2.27}/LICENSE +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/MANIFEST.in +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/agent.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/backend/__init__.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/backend/compressor.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/backend/context.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/backend/githook.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/backend/marketplace.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/backend/mempalace_bridge.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/backend/personas.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/backend/plugins.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/backend/server.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/backend/tasks.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/batch_api.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/checkpoint/__init__.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/checkpoint/hooks.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/checkpoint/store.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/checkpoint/types.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/claude_code_watcher.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/clipboard_utils.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/cloudsave.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/compaction.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/config.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/context.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/data/__init__.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/data/active_persona.json +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/data/context.json +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/data/marketplace.json +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/data/personas.json +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/data/plugins/__init__.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/data/plugins/composio/__init__.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/data/plugins/composio/composio_plugin/__init__.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/data/plugins/composio/composio_plugin/session_manager.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/data/plugins/composio/composio_plugin/tool_generator.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/data/plugins/composio/plugin.json +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/data/plugins/composio/plugin_tool.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/data/tasks.json +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/docs/README.md +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/docs/__init__.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/docs/api.html +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/docs/architecture.md +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/docs/azure-speech-template.json +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/docs/dashboard/index.html +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/docs/divider.svg +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/docs/generate.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/docs/hero.svg +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/docs/index.html +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/docs/nvidia-models.svg +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/docs/particle-playground.html +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/docs/personas/index.html +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/docs/poetry-banner.png +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/docs/preview.html +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/docs/sec-agents.svg +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/docs/sec-brainstorm.svg +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/docs/sec-bridges.svg +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/docs/sec-features.svg +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/docs/sec-freetier.svg +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/docs/sec-memory.svg +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/docs/sec-models.svg +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/docs/sec-perms.svg +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/docs/sec-plugins.svg +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/docs/sec-quickstart.svg +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/docs/sec-ssj.svg +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/docs/spinners.svg +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/docs/split-pane.svg +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/docs/terminal-boot.svg +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/docs/uploads/particle-playground.html +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/dulus.egg-info/SOURCES.txt +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/dulus.egg-info/dependency_links.txt +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/dulus.egg-info/entry_points.txt +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/dulus.egg-info/requires.txt +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/dulus.egg-info/top_level.txt +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/dulus_gui.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/dulus_mcp/__init__.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/dulus_mcp/client.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/dulus_mcp/config.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/dulus_mcp/tools.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/dulus_mcp/types.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/gui/__init__.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/gui/agent_bridge.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/gui/chat_widget.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/gui/main_window.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/gui/personas.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/gui/session_utils.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/gui/settings_dialog.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/gui/sidebar.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/gui/tasks_view.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/gui/themes.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/gui/tool_panel.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/input.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/license_manager.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/memory/__init__.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/memory/audit.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/memory/consolidator.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/memory/context.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/memory/offload.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/memory/palace.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/memory/scan.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/memory/sessions.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/memory/store.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/memory/tools.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/memory/types.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/memory/vector_search.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/multi_agent/__init__.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/multi_agent/subagent.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/multi_agent/tools.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/offload_helper.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/plugin/__init__.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/plugin/autoadapter.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/plugin/loader.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/plugin/recommend.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/plugin/store.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/plugin/types.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/providers.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/setup.cfg +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/skill/__init__.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/skill/builtin.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/skill/executor.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/skill/loader.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/skill/tools.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/skills.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/spinner.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/string_utils.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/subagent.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/task/__init__.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/task/store.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/task/tools.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/task/types.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/tests/test_checkpoint.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/tests/test_compaction.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/tests/test_diff_view.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/tests/test_injection_fix.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/tests/test_license.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/tests/test_mcp.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/tests/test_memory.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/tests/test_plugin.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/tests/test_skills.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/tests/test_subagent.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/tests/test_task.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/tests/test_telegram_buffer.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/tests/test_tool_registry.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/tests/test_voice.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/tmux_offloader.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/tmux_tools.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/tool_registry.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/tools.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/ui/__init__.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/ui/input.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/ui/render.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/voice/__init__.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/voice/keyterms.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/voice/recorder.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/voice/stt.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/voice/tts.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/webchat.py +0 -0
- {dulus-0.2.25 → dulus-0.2.27}/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.27
|
|
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.27-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
|
+
<img src="https://img.shields.io/badge/version-v0.2.27-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
|
-
|
|
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,13 @@
|
|
|
3
3
|
## 🔥🔥🔥 News (Pacific Time)
|
|
4
4
|
|
|
5
5
|
|
|
6
|
+
- May 09, 2026 (**v0.2.27**): **`/bg` no longer leaves stale state** — when something else (a REPL, an old daemon) is already on `127.0.0.1:5151`, `/bg start` now refuses to spawn a duplicate that would just fail to bind, explains what's happening, and clears the stale PID file. `/bg status` self-heals: when it sees a stale PID it auto-removes it instead of complaining on every call. The previous "Some Dulus is listening on IPC, but our PID file is stale" warning is gone for good.
|
|
7
|
+
|
|
8
|
+
- May 09, 2026 (**v0.2.26**): **`/bg start` daemon crash fix + defensive `clr()` + composio fallback**
|
|
9
|
+
- **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.
|
|
10
|
+
- **`clr()` is now defensive.** Missing color keys are silently dropped instead of raising. One typo in a theme name no longer crashes the REPL.
|
|
11
|
+
- **`/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.
|
|
12
|
+
|
|
6
13
|
- 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
14
|
|
|
8
15
|
- 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.
|
|
3
|
+
Version: 0.2.27
|
|
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.27-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.
|
|
221
|
+
VERSION = "0.2.27" # 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
|
|
@@ -1581,9 +1581,22 @@ def cmd_bg(args: str, _state, config) -> bool:
|
|
|
1581
1581
|
elif alive and not ipc:
|
|
1582
1582
|
warn(f"PID {pid} alive but IPC port not responding (still booting?)")
|
|
1583
1583
|
elif ipc:
|
|
1584
|
-
warn("
|
|
1584
|
+
warn("Another Dulus (REPL or older daemon) is on the IPC port.")
|
|
1585
|
+
info(f" IPC: 127.0.0.1:{DULUS_IPC_PORT} (responding, but not our daemon)")
|
|
1586
|
+
info(" You can still reach it via `dulus \"...\"` from any shell.")
|
|
1587
|
+
info(" /bg start won't spawn a duplicate — kill the other one first if needed.")
|
|
1588
|
+
# Clear the stale PID file so this warning self-heals next time.
|
|
1589
|
+
try:
|
|
1590
|
+
BG_PID.unlink()
|
|
1591
|
+
except FileNotFoundError:
|
|
1592
|
+
pass
|
|
1585
1593
|
else:
|
|
1586
1594
|
info("Dulus background: NOT RUNNING")
|
|
1595
|
+
# Clean up any stale PID file just in case.
|
|
1596
|
+
try:
|
|
1597
|
+
BG_PID.unlink()
|
|
1598
|
+
except FileNotFoundError:
|
|
1599
|
+
pass
|
|
1587
1600
|
return True
|
|
1588
1601
|
|
|
1589
1602
|
# ── /bg stop ──────────────────────────────────────────────────────────
|
|
@@ -1639,6 +1652,21 @@ def cmd_bg(args: str, _state, config) -> bool:
|
|
|
1639
1652
|
info(f"Already running (PID {existing_pid}). Use `/bg status` for details.")
|
|
1640
1653
|
return True
|
|
1641
1654
|
|
|
1655
|
+
# Some other Dulus (a REPL on this machine, an old daemon, etc.) is
|
|
1656
|
+
# holding the IPC port. Spawning a new daemon would just fail to bind
|
|
1657
|
+
# and leave a stale PID. Better: tell the user what's up.
|
|
1658
|
+
if _ipc_alive():
|
|
1659
|
+
warn(f"Port {DULUS_IPC_PORT} is already in use by another Dulus process.")
|
|
1660
|
+
info("If that's a REPL you're running, this Dulus is already reachable")
|
|
1661
|
+
info(f" via `dulus \"...\"` from any shell — no /bg start needed.")
|
|
1662
|
+
info("If it's a stale daemon, find and kill it manually, then retry.")
|
|
1663
|
+
# Clean up the stale PID file so /bg status stops complaining.
|
|
1664
|
+
try:
|
|
1665
|
+
BG_PID.unlink()
|
|
1666
|
+
except FileNotFoundError:
|
|
1667
|
+
pass
|
|
1668
|
+
return True
|
|
1669
|
+
|
|
1642
1670
|
# Parse --web-port
|
|
1643
1671
|
web_port = config.get("_webchat_port", 5000)
|
|
1644
1672
|
if "--web-port" in parts:
|
|
@@ -5612,7 +5640,13 @@ def _run_daemon(config: dict) -> None:
|
|
|
5612
5640
|
config["_ipc_thread"] = ti
|
|
5613
5641
|
ti.start()
|
|
5614
5642
|
|
|
5615
|
-
|
|
5643
|
+
# 'accent' / 'orange' are only present in some custom themes; default
|
|
5644
|
+
# palette is {blue, cyan, gray, green, magenta, red, white, yellow}.
|
|
5645
|
+
# KeyError here would crash the daemon before the user ever sees a prompt.
|
|
5646
|
+
try:
|
|
5647
|
+
print(clr("\n ▲ DULUS DAEMON", "yellow", "bold"))
|
|
5648
|
+
except KeyError:
|
|
5649
|
+
print("\n ▲ DULUS DAEMON")
|
|
5616
5650
|
print(clr(" " + "─" * 40, "dim"))
|
|
5617
5651
|
info(f"Session: {session_id}")
|
|
5618
5652
|
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.
|
|
7
|
+
version = "0.2.27"
|
|
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
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|