dulus 0.2.21__tar.gz → 0.2.23__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.21/dulus.egg-info → dulus-0.2.23}/PKG-INFO +7 -2
- {dulus-0.2.21 → dulus-0.2.23}/README.md +6 -1
- {dulus-0.2.21 → dulus-0.2.23}/context.py +1 -0
- {dulus-0.2.21 → dulus-0.2.23}/docs/news.md +10 -0
- {dulus-0.2.21 → dulus-0.2.23/dulus.egg-info}/PKG-INFO +7 -2
- {dulus-0.2.21 → dulus-0.2.23}/dulus.py +260 -2
- {dulus-0.2.21 → dulus-0.2.23}/plugin/autoadapter.py +7 -0
- {dulus-0.2.21 → dulus-0.2.23}/pyproject.toml +1 -1
- {dulus-0.2.21 → dulus-0.2.23}/webchat_server.py +5 -1
- {dulus-0.2.21 → dulus-0.2.23}/LICENSE +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/MANIFEST.in +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/agent.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/backend/__init__.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/backend/compressor.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/backend/context.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/backend/githook.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/backend/marketplace.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/backend/mempalace_bridge.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/backend/personas.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/backend/plugins.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/backend/server.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/backend/tasks.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/batch_api.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/checkpoint/__init__.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/checkpoint/hooks.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/checkpoint/store.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/checkpoint/types.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/claude_code_watcher.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/clipboard_utils.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/cloudsave.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/common.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/compaction.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/config.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/data/__init__.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/data/active_persona.json +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/data/context.json +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/data/marketplace.json +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/data/personas.json +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/data/plugins/__init__.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/data/plugins/composio/__init__.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/data/plugins/composio/composio_plugin/__init__.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/data/plugins/composio/composio_plugin/session_manager.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/data/plugins/composio/composio_plugin/tool_generator.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/data/plugins/composio/plugin.json +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/data/plugins/composio/plugin_tool.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/data/tasks.json +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/docs/README.md +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/docs/__init__.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/docs/api.html +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/docs/architecture.md +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/docs/azure-speech-template.json +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/docs/dashboard/index.html +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/docs/divider.svg +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/docs/generate.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/docs/hero.svg +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/docs/index.html +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/docs/nvidia-models.svg +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/docs/particle-playground.html +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/docs/personas/index.html +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/docs/poetry-banner.png +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/docs/preview.html +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/docs/sec-agents.svg +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/docs/sec-brainstorm.svg +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/docs/sec-bridges.svg +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/docs/sec-features.svg +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/docs/sec-freetier.svg +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/docs/sec-memory.svg +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/docs/sec-models.svg +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/docs/sec-perms.svg +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/docs/sec-plugins.svg +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/docs/sec-quickstart.svg +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/docs/sec-ssj.svg +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/docs/spinners.svg +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/docs/split-pane.svg +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/docs/terminal-boot.svg +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/docs/uploads/particle-playground.html +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/dulus.egg-info/SOURCES.txt +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/dulus.egg-info/dependency_links.txt +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/dulus.egg-info/entry_points.txt +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/dulus.egg-info/requires.txt +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/dulus.egg-info/top_level.txt +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/dulus_gui.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/dulus_mcp/__init__.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/dulus_mcp/client.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/dulus_mcp/config.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/dulus_mcp/tools.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/dulus_mcp/types.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/gui/__init__.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/gui/agent_bridge.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/gui/chat_widget.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/gui/main_window.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/gui/personas.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/gui/session_utils.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/gui/settings_dialog.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/gui/sidebar.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/gui/tasks_view.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/gui/themes.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/gui/tool_panel.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/input.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/license_manager.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/memory/__init__.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/memory/audit.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/memory/consolidator.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/memory/context.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/memory/offload.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/memory/palace.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/memory/scan.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/memory/sessions.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/memory/store.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/memory/tools.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/memory/types.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/memory/vector_search.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/multi_agent/__init__.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/multi_agent/subagent.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/multi_agent/tools.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/offload_helper.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/plugin/__init__.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/plugin/loader.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/plugin/recommend.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/plugin/store.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/plugin/types.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/providers.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/setup.cfg +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/skill/__init__.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/skill/builtin.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/skill/clawhub.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/skill/executor.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/skill/loader.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/skill/tools.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/skills.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/spinner.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/string_utils.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/subagent.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/task/__init__.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/task/store.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/task/tools.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/task/types.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/tests/test_checkpoint.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/tests/test_compaction.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/tests/test_diff_view.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/tests/test_injection_fix.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/tests/test_license.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/tests/test_mcp.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/tests/test_memory.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/tests/test_plugin.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/tests/test_skills.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/tests/test_subagent.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/tests/test_task.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/tests/test_telegram_buffer.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/tests/test_tool_registry.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/tests/test_voice.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/tmux_offloader.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/tmux_tools.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/tool_registry.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/tools.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/ui/__init__.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/ui/input.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/ui/render.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/voice/__init__.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/voice/keyterms.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/voice/recorder.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/voice/stt.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/voice/tts.py +0 -0
- {dulus-0.2.21 → dulus-0.2.23}/webchat.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.23
|
|
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.23-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"/>
|
|
@@ -105,6 +105,11 @@ Use claude-code as an API without the new 'extra-usage' wall <3
|
|
|
105
105
|
|
|
106
106
|
Dulus is a **lightweight Python reimplementation of Claude Code** that isn't locked to Claude. It ships the whole loop — REPL, tool dispatch, streaming, context compaction, checkpoints, sub-agents, voice, Telegram bridge, MCP, plugins — in roughly **12K lines you can actually read**. Fork it. Bend it. Run it offline against Qwen on your M2.
|
|
107
107
|
|
|
108
|
+
> **v0.2.22 — May 9, 2026** — `/bg start` spawns one detached Dulus daemon that serves CLI (IPC), Web (browser at `127.0.0.1:5000`), and Telegram simultaneously — all sharing the same session. WebChat now defaults to **loopback-only** (opt-in to LAN exposure with `/webchat lan on`).
|
|
109
|
+
> **v0.2.21 — May 9, 2026** — Console-freeze fix: system prompt now tells the model SleepTimer is for user reminders only — pauses inside command pipelines must use `sleep N` inside the Bash command itself.
|
|
110
|
+
> **v0.2.20 — May 9, 2026** — Windows IPC port-collision fix: switched to `SO_EXCLUSIVEADDRUSE` so a second Dulus instance correctly cedes the port and acts as client. End-to-end tested.
|
|
111
|
+
> **v0.2.19 — May 9, 2026** — Shared sessions via tiny TCP socket on `127.0.0.1:5151`. `dulus "do X"` from any shell forwards into the running REPL/daemon — same history, memory, plugins. 80 lines of plain socket code instead of a daemon manager + IPC framework.
|
|
112
|
+
> **v0.2.18 — May 9, 2026** — `beautifulsoup4` added as default dep for web scraping flows.
|
|
108
113
|
> **v0.2.17 — May 9, 2026** — Mega-release: Composio plugin bundled (1000+ apps, no MCP), `/skill list` interactive picker (awesome / composio / local), awesome skills live from GitHub (no Claude Code needed), lite mode finally functional, system prompt rewritten in English, `VERSION` auto-syncs from pyproject.
|
|
109
114
|
> **v0.2.16 — May 9, 2026** — MemPalace per-session dedup. No more re-injecting the same memory every turn — content-hash cache saves ~8K tokens in a 20-turn conversation. `/mem_palace reset` clears it on demand.
|
|
110
115
|
> **v0.2.15 — May 9, 2026** — Banner image hosted locally so PyPI renders it correctly.
|
|
@@ -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.23-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"/>
|
|
@@ -58,6 +58,11 @@ Use claude-code as an API without the new 'extra-usage' wall <3
|
|
|
58
58
|
|
|
59
59
|
Dulus is a **lightweight Python reimplementation of Claude Code** that isn't locked to Claude. It ships the whole loop — REPL, tool dispatch, streaming, context compaction, checkpoints, sub-agents, voice, Telegram bridge, MCP, plugins — in roughly **12K lines you can actually read**. Fork it. Bend it. Run it offline against Qwen on your M2.
|
|
60
60
|
|
|
61
|
+
> **v0.2.22 — May 9, 2026** — `/bg start` spawns one detached Dulus daemon that serves CLI (IPC), Web (browser at `127.0.0.1:5000`), and Telegram simultaneously — all sharing the same session. WebChat now defaults to **loopback-only** (opt-in to LAN exposure with `/webchat lan on`).
|
|
62
|
+
> **v0.2.21 — May 9, 2026** — Console-freeze fix: system prompt now tells the model SleepTimer is for user reminders only — pauses inside command pipelines must use `sleep N` inside the Bash command itself.
|
|
63
|
+
> **v0.2.20 — May 9, 2026** — Windows IPC port-collision fix: switched to `SO_EXCLUSIVEADDRUSE` so a second Dulus instance correctly cedes the port and acts as client. End-to-end tested.
|
|
64
|
+
> **v0.2.19 — May 9, 2026** — Shared sessions via tiny TCP socket on `127.0.0.1:5151`. `dulus "do X"` from any shell forwards into the running REPL/daemon — same history, memory, plugins. 80 lines of plain socket code instead of a daemon manager + IPC framework.
|
|
65
|
+
> **v0.2.18 — May 9, 2026** — `beautifulsoup4` added as default dep for web scraping flows.
|
|
61
66
|
> **v0.2.17 — May 9, 2026** — Mega-release: Composio plugin bundled (1000+ apps, no MCP), `/skill list` interactive picker (awesome / composio / local), awesome skills live from GitHub (no Claude Code needed), lite mode finally functional, system prompt rewritten in English, `VERSION` auto-syncs from pyproject.
|
|
62
67
|
> **v0.2.16 — May 9, 2026** — MemPalace per-session dedup. No more re-injecting the same memory every turn — content-hash cache saves ~8K tokens in a 20-turn conversation. `/mem_palace reset` clears it on demand.
|
|
63
68
|
> **v0.2.15 — May 9, 2026** — Banner image hosted locally so PyPI renders it correctly.
|
|
@@ -20,6 +20,7 @@ You are Dulus, an AI coding agent. Think in English; reply to KevRojo in Dominic
|
|
|
20
20
|
# Autonomy: Background scripts (nohup/&) allowed | Never refuse monitoring/long tasks | Always wait for tool results before replying
|
|
21
21
|
# Tools: SearchLastOutput → for [TRUNCATED] | WebFetch/WebSearch → web | TmuxOffload → tasks > 5s | ReadJob → background results
|
|
22
22
|
# SleepTimer: ONLY for user-facing reminders/notifications (e.g. "remind me in 10min"). NEVER use it to wait between your own tool calls — it freezes the console. To pause inside a command sequence, put `sleep N` INSIDE the Bash command itself (e.g. Bash('cmd1 && sleep 2 && cmd2')).
|
|
23
|
+
# Long-running tools: any tool whose `description` ends in `[long-running — wrap in TmuxOffload]` MUST be invoked via TmuxOffload (not directly), so the REPL stays responsive while it runs.
|
|
23
24
|
# Multi-agent: Agent(subagent_type=...) | isolation="worktree" runs parallel | wait=false + name=... for fire-and-forget
|
|
24
25
|
# Rules: Edit > Write | Use absolute paths + line numbers | Surface errors immediately, do not retry blindly
|
|
25
26
|
# Input: "🎙 Transcribed:" prefix = voice input — tolerate typos/misspellings
|
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
## 🔥🔥🔥 News (Pacific Time)
|
|
4
4
|
|
|
5
5
|
|
|
6
|
+
- May 09, 2026 (**v0.2.23**): **Auto-adapter teaches new plugins to declare TmuxOffload-worthy tools**
|
|
7
|
+
- **The adapter prompt now requires** the model to estimate per-tool runtime. Any tool that typically takes more than ~15 seconds (sherlock, holehe, OSINT crawls, video downloads, full-repo analysis, etc.) must end its `description` with the literal marker `[long-running — wrap in TmuxOffload]`.
|
|
8
|
+
- **System prompt now honors that marker** at runtime. When the agent sees a tool with that suffix, it wraps the call in TmuxOffload automatically instead of blocking the REPL. No more 90-second sherlock freezes pretending to be productive.
|
|
9
|
+
- **Why this matters.** New plugins adopted via `/plugin adapt` now self-declare their UX hints. The agent picks them up the moment they install — zero manual config, zero "oh I should have offloaded that" regrets.
|
|
10
|
+
|
|
11
|
+
- May 09, 2026 (**v0.2.22**): **`/bg start` — one detached daemon for CLI + Web + Telegram**
|
|
12
|
+
- **One command, three doors.** `/bg start` spawns a detached Dulus daemon that simultaneously listens on `127.0.0.1:5151` (IPC for `dulus "..."` from any shell), `127.0.0.1:5000` (WebChat in your browser), and the Telegram bridge if configured. All three entry points hit the SAME live session — same history, memory, plugins, tool state.
|
|
13
|
+
- **`/bg status` / `stop` / `attach`** — small surface, big convenience. `attach` prints how to reach the running daemon (CLI, browser, tail the log).
|
|
14
|
+
- **WebChat is loopback-only by default.** Previously the webchat server bound to `0.0.0.0` and was visible on the LAN out of the box. Now it binds to `127.0.0.1` unless you opt in with `/webchat lan on` (or `webchat_lan: true` in config). Anyone on the wifi can no longer poke your agent by accident.
|
|
15
|
+
|
|
6
16
|
- May 09, 2026 (**v0.2.21**): **System prompt clarifies SleepTimer scope** — added an explicit hint that SleepTimer is ONLY for user-facing reminders/notifications, NEVER for inter-tool waits (those freeze the console). When a pause is needed mid-pipeline, models should use `sleep N` inside the Bash command itself. Fixes a recurring console-freeze when models reflexively reached for SleepTimer between commands.
|
|
7
17
|
|
|
8
18
|
- May 09, 2026 (**v0.2.20**): **IPC port-collision fix on Windows** — `SO_REUSEADDR` on Windows lets two sockets share the same port, which would let a second Dulus instance silently "steal" the IPC listener. Switched to `SO_EXCLUSIVEADDRUSE` so the second instance correctly backs off and acts as a client. Verified end-to-end with the new test harness.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dulus
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.23
|
|
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.23-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"/>
|
|
@@ -105,6 +105,11 @@ Use claude-code as an API without the new 'extra-usage' wall <3
|
|
|
105
105
|
|
|
106
106
|
Dulus is a **lightweight Python reimplementation of Claude Code** that isn't locked to Claude. It ships the whole loop — REPL, tool dispatch, streaming, context compaction, checkpoints, sub-agents, voice, Telegram bridge, MCP, plugins — in roughly **12K lines you can actually read**. Fork it. Bend it. Run it offline against Qwen on your M2.
|
|
107
107
|
|
|
108
|
+
> **v0.2.22 — May 9, 2026** — `/bg start` spawns one detached Dulus daemon that serves CLI (IPC), Web (browser at `127.0.0.1:5000`), and Telegram simultaneously — all sharing the same session. WebChat now defaults to **loopback-only** (opt-in to LAN exposure with `/webchat lan on`).
|
|
109
|
+
> **v0.2.21 — May 9, 2026** — Console-freeze fix: system prompt now tells the model SleepTimer is for user reminders only — pauses inside command pipelines must use `sleep N` inside the Bash command itself.
|
|
110
|
+
> **v0.2.20 — May 9, 2026** — Windows IPC port-collision fix: switched to `SO_EXCLUSIVEADDRUSE` so a second Dulus instance correctly cedes the port and acts as client. End-to-end tested.
|
|
111
|
+
> **v0.2.19 — May 9, 2026** — Shared sessions via tiny TCP socket on `127.0.0.1:5151`. `dulus "do X"` from any shell forwards into the running REPL/daemon — same history, memory, plugins. 80 lines of plain socket code instead of a daemon manager + IPC framework.
|
|
112
|
+
> **v0.2.18 — May 9, 2026** — `beautifulsoup4` added as default dep for web scraping flows.
|
|
108
113
|
> **v0.2.17 — May 9, 2026** — Mega-release: Composio plugin bundled (1000+ apps, no MCP), `/skill list` interactive picker (awesome / composio / local), awesome skills live from GitHub (no Claude Code needed), lite mode finally functional, system prompt rewritten in English, `VERSION` auto-syncs from pyproject.
|
|
109
114
|
> **v0.2.16 — May 9, 2026** — MemPalace per-session dedup. No more re-injecting the same memory every turn — content-hash cache saves ~8K tokens in a 20-turn conversation. `/mem_palace reset` clears it on demand.
|
|
110
115
|
> **v0.2.15 — May 9, 2026** — Banner image hosted locally so PyPI renders it correctly.
|
|
@@ -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.23" # 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
|
|
@@ -1509,6 +1509,214 @@ def cmd_daemon(args: str, _state, config) -> bool:
|
|
|
1509
1509
|
save_config(config)
|
|
1510
1510
|
return True
|
|
1511
1511
|
|
|
1512
|
+
def cmd_bg(args: str, _state, config) -> bool:
|
|
1513
|
+
"""Background Dulus — one detached daemon serving CLI (IPC), Web (browser),
|
|
1514
|
+
and Telegram simultaneously.
|
|
1515
|
+
|
|
1516
|
+
/bg start [--web-port PORT] — spawn detached daemon + webchat
|
|
1517
|
+
/bg stop — kill the background daemon
|
|
1518
|
+
/bg status — is it alive? on which ports?
|
|
1519
|
+
/bg attach — print how to attach (tmux on unix, URL on win)
|
|
1520
|
+
|
|
1521
|
+
The detached process listens on:
|
|
1522
|
+
• 127.0.0.1:5151 → IPC socket (`dulus "..."` from any shell joins this)
|
|
1523
|
+
• 127.0.0.1:5000 → WebChat (open http://localhost:5000/ in browser)
|
|
1524
|
+
• Telegram bridge if configured
|
|
1525
|
+
|
|
1526
|
+
All three entry points share the SAME live session. No session manager,
|
|
1527
|
+
no service installer, no XML config — just a detached process and three
|
|
1528
|
+
listeners. Workaround supremo.
|
|
1529
|
+
"""
|
|
1530
|
+
import os as _os, sys as _sys, json as _json, subprocess as _sp, socket as _socket, time as _time
|
|
1531
|
+
import signal as _signal
|
|
1532
|
+
from pathlib import Path as _Path
|
|
1533
|
+
|
|
1534
|
+
BG_DIR = _Path.home() / ".dulus"
|
|
1535
|
+
BG_DIR.mkdir(parents=True, exist_ok=True)
|
|
1536
|
+
BG_PID = BG_DIR / "bg.pid"
|
|
1537
|
+
BG_LOG = BG_DIR / "bg.log"
|
|
1538
|
+
|
|
1539
|
+
parts = (args or "").strip().split()
|
|
1540
|
+
sub = parts[0].lower() if parts else "status"
|
|
1541
|
+
|
|
1542
|
+
def _is_alive(pid: int) -> bool:
|
|
1543
|
+
if pid <= 0:
|
|
1544
|
+
return False
|
|
1545
|
+
try:
|
|
1546
|
+
if _sys.platform == "win32":
|
|
1547
|
+
# On Windows, os.kill(pid, 0) raises if the process doesn't exist.
|
|
1548
|
+
_os.kill(pid, 0)
|
|
1549
|
+
return True
|
|
1550
|
+
else:
|
|
1551
|
+
_os.kill(pid, 0)
|
|
1552
|
+
return True
|
|
1553
|
+
except (ProcessLookupError, OSError, PermissionError):
|
|
1554
|
+
return False
|
|
1555
|
+
|
|
1556
|
+
def _read_pid() -> int:
|
|
1557
|
+
try:
|
|
1558
|
+
return int(BG_PID.read_text().strip())
|
|
1559
|
+
except Exception:
|
|
1560
|
+
return 0
|
|
1561
|
+
|
|
1562
|
+
def _ipc_alive() -> bool:
|
|
1563
|
+
try:
|
|
1564
|
+
s = _socket.create_connection(("127.0.0.1", DULUS_IPC_PORT), timeout=0.5)
|
|
1565
|
+
s.close()
|
|
1566
|
+
return True
|
|
1567
|
+
except Exception:
|
|
1568
|
+
return False
|
|
1569
|
+
|
|
1570
|
+
# ── /bg status ────────────────────────────────────────────────────────
|
|
1571
|
+
if sub == "status":
|
|
1572
|
+
pid = _read_pid()
|
|
1573
|
+
alive = _is_alive(pid)
|
|
1574
|
+
ipc = _ipc_alive()
|
|
1575
|
+
if alive and ipc:
|
|
1576
|
+
ok(f"Dulus background: RUNNING")
|
|
1577
|
+
info(f" PID: {pid}")
|
|
1578
|
+
info(f" IPC: 127.0.0.1:{DULUS_IPC_PORT} (responding)")
|
|
1579
|
+
info(f" Web: http://127.0.0.1:{config.get('_webchat_port', 5000)}/")
|
|
1580
|
+
info(f" Log: {BG_LOG}")
|
|
1581
|
+
elif alive and not ipc:
|
|
1582
|
+
warn(f"PID {pid} alive but IPC port not responding (still booting?)")
|
|
1583
|
+
elif ipc:
|
|
1584
|
+
warn("Some Dulus is listening on IPC, but our PID file is stale.")
|
|
1585
|
+
else:
|
|
1586
|
+
info("Dulus background: NOT RUNNING")
|
|
1587
|
+
return True
|
|
1588
|
+
|
|
1589
|
+
# ── /bg stop ──────────────────────────────────────────────────────────
|
|
1590
|
+
if sub == "stop":
|
|
1591
|
+
pid = _read_pid()
|
|
1592
|
+
if not _is_alive(pid):
|
|
1593
|
+
info("Dulus background not running.")
|
|
1594
|
+
try:
|
|
1595
|
+
BG_PID.unlink()
|
|
1596
|
+
except FileNotFoundError:
|
|
1597
|
+
pass
|
|
1598
|
+
return True
|
|
1599
|
+
try:
|
|
1600
|
+
if _sys.platform == "win32":
|
|
1601
|
+
_os.kill(pid, _signal.SIGTERM)
|
|
1602
|
+
else:
|
|
1603
|
+
_os.kill(pid, _signal.SIGTERM)
|
|
1604
|
+
ok(f"Sent SIGTERM to PID {pid}.")
|
|
1605
|
+
except Exception as e:
|
|
1606
|
+
err(f"Failed to kill {pid}: {e}")
|
|
1607
|
+
return True
|
|
1608
|
+
for _ in range(20):
|
|
1609
|
+
if not _is_alive(pid):
|
|
1610
|
+
break
|
|
1611
|
+
_time.sleep(0.25)
|
|
1612
|
+
try:
|
|
1613
|
+
BG_PID.unlink()
|
|
1614
|
+
except FileNotFoundError:
|
|
1615
|
+
pass
|
|
1616
|
+
ok("Dulus background stopped.")
|
|
1617
|
+
return True
|
|
1618
|
+
|
|
1619
|
+
# ── /bg attach ────────────────────────────────────────────────────────
|
|
1620
|
+
if sub == "attach":
|
|
1621
|
+
pid = _read_pid()
|
|
1622
|
+
if not _is_alive(pid):
|
|
1623
|
+
warn("Dulus background not running. Use `/bg start` first.")
|
|
1624
|
+
return True
|
|
1625
|
+
port = config.get("_webchat_port", 5000)
|
|
1626
|
+
info("Attach options:")
|
|
1627
|
+
info(f" • From any shell: dulus \"your prompt\" (joins via IPC)")
|
|
1628
|
+
info(f" • Browser: http://127.0.0.1:{port}/")
|
|
1629
|
+
if _sys.platform != "win32":
|
|
1630
|
+
info(f" • Tmux (if used): tmux attach -t dulus-bg")
|
|
1631
|
+
info(f" • Tail log: tail -f {BG_LOG}")
|
|
1632
|
+
return True
|
|
1633
|
+
|
|
1634
|
+
# ── /bg start ─────────────────────────────────────────────────────────
|
|
1635
|
+
if sub == "start":
|
|
1636
|
+
# Already running?
|
|
1637
|
+
existing_pid = _read_pid()
|
|
1638
|
+
if _is_alive(existing_pid) and _ipc_alive():
|
|
1639
|
+
info(f"Already running (PID {existing_pid}). Use `/bg status` for details.")
|
|
1640
|
+
return True
|
|
1641
|
+
|
|
1642
|
+
# Parse --web-port
|
|
1643
|
+
web_port = config.get("_webchat_port", 5000)
|
|
1644
|
+
if "--web-port" in parts:
|
|
1645
|
+
try:
|
|
1646
|
+
web_port = int(parts[parts.index("--web-port") + 1])
|
|
1647
|
+
except (IndexError, ValueError):
|
|
1648
|
+
err("--web-port needs a number")
|
|
1649
|
+
return True
|
|
1650
|
+
config["_webchat_port"] = web_port
|
|
1651
|
+
from config import save_config
|
|
1652
|
+
save_config(config)
|
|
1653
|
+
|
|
1654
|
+
# Build the spawn command. Prefer the installed `dulus` shim; fall
|
|
1655
|
+
# back to `python dulus.py` when running from source.
|
|
1656
|
+
dulus_bin = None
|
|
1657
|
+
for cand in ["dulus", "dulus.exe"]:
|
|
1658
|
+
from shutil import which
|
|
1659
|
+
p = which(cand)
|
|
1660
|
+
if p:
|
|
1661
|
+
dulus_bin = p
|
|
1662
|
+
break
|
|
1663
|
+
if dulus_bin:
|
|
1664
|
+
cmd = [dulus_bin, "--daemon"]
|
|
1665
|
+
else:
|
|
1666
|
+
dulus_script = _os.path.abspath(__file__)
|
|
1667
|
+
cmd = [_sys.executable, dulus_script, "--daemon"]
|
|
1668
|
+
|
|
1669
|
+
# Pass the auto-webchat hint via env so the daemon picks it up.
|
|
1670
|
+
env = _os.environ.copy()
|
|
1671
|
+
env["DULUS_BG_AUTO_WEBCHAT"] = "1"
|
|
1672
|
+
env["DULUS_BG_WEBCHAT_PORT"] = str(web_port)
|
|
1673
|
+
|
|
1674
|
+
# Detach properly per platform.
|
|
1675
|
+
log_fp = open(BG_LOG, "ab")
|
|
1676
|
+
try:
|
|
1677
|
+
if _sys.platform == "win32":
|
|
1678
|
+
# CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS
|
|
1679
|
+
DETACHED = 0x00000008
|
|
1680
|
+
NEW_GROUP = 0x00000200
|
|
1681
|
+
proc = _sp.Popen(
|
|
1682
|
+
cmd,
|
|
1683
|
+
stdout=log_fp, stderr=log_fp, stdin=_sp.DEVNULL,
|
|
1684
|
+
creationflags=DETACHED | NEW_GROUP,
|
|
1685
|
+
close_fds=True,
|
|
1686
|
+
env=env,
|
|
1687
|
+
)
|
|
1688
|
+
else:
|
|
1689
|
+
proc = _sp.Popen(
|
|
1690
|
+
cmd,
|
|
1691
|
+
stdout=log_fp, stderr=log_fp, stdin=_sp.DEVNULL,
|
|
1692
|
+
start_new_session=True,
|
|
1693
|
+
close_fds=True,
|
|
1694
|
+
env=env,
|
|
1695
|
+
)
|
|
1696
|
+
except Exception as e:
|
|
1697
|
+
err(f"Failed to spawn daemon: {e}")
|
|
1698
|
+
log_fp.close()
|
|
1699
|
+
return True
|
|
1700
|
+
|
|
1701
|
+
BG_PID.write_text(str(proc.pid))
|
|
1702
|
+
|
|
1703
|
+
# Wait briefly for the IPC port to come up
|
|
1704
|
+
for _ in range(40): # up to 10 seconds
|
|
1705
|
+
if _ipc_alive():
|
|
1706
|
+
break
|
|
1707
|
+
_time.sleep(0.25)
|
|
1708
|
+
|
|
1709
|
+
ok(f"Dulus background started. PID {proc.pid}")
|
|
1710
|
+
info(f" IPC: 127.0.0.1:{DULUS_IPC_PORT}")
|
|
1711
|
+
info(f" Web: http://127.0.0.1:{web_port}/")
|
|
1712
|
+
info(f" Log: {BG_LOG}")
|
|
1713
|
+
info(" Stop with `/bg stop`.")
|
|
1714
|
+
return True
|
|
1715
|
+
|
|
1716
|
+
err(f"Unknown subcommand: {sub}. Use start | stop | status | attach")
|
|
1717
|
+
return True
|
|
1718
|
+
|
|
1719
|
+
|
|
1512
1720
|
def cmd_webchat(args: str, state, config) -> bool:
|
|
1513
1721
|
"""Start the in-process webchat mirror. /webchat stop kills it."""
|
|
1514
1722
|
import time, urllib.request, socket
|
|
@@ -1535,6 +1743,25 @@ def cmd_webchat(args: str, state, config) -> bool:
|
|
|
1535
1743
|
config.pop("_webchat_proc", None)
|
|
1536
1744
|
return True
|
|
1537
1745
|
|
|
1746
|
+
# /webchat lan on|off — toggle LAN exposure (default: loopback only)
|
|
1747
|
+
if arg.startswith("lan"):
|
|
1748
|
+
from config import save_config
|
|
1749
|
+
sub = arg.replace("lan", "", 1).strip()
|
|
1750
|
+
if sub in ("on", "1", "true", "yes"):
|
|
1751
|
+
config["webchat_lan"] = True
|
|
1752
|
+
elif sub in ("off", "0", "false", "no"):
|
|
1753
|
+
config["webchat_lan"] = False
|
|
1754
|
+
else:
|
|
1755
|
+
info(f"WebChat LAN exposure: {'ON' if config.get('webchat_lan') else 'OFF (loopback only)'}")
|
|
1756
|
+
info("Use `/webchat lan on` to expose to the local network.")
|
|
1757
|
+
return True
|
|
1758
|
+
save_config(config)
|
|
1759
|
+
state_str = "ON — visible on the LAN" if config["webchat_lan"] else "OFF — loopback only (safe)"
|
|
1760
|
+
ok(f"WebChat LAN exposure: {state_str}")
|
|
1761
|
+
if webchat_server.is_running():
|
|
1762
|
+
warn("Restart with `/webchat stop` then `/webchat` to apply the new bind.")
|
|
1763
|
+
return True
|
|
1764
|
+
|
|
1538
1765
|
active_model = config.get("model", "")
|
|
1539
1766
|
|
|
1540
1767
|
if webchat_server.is_running():
|
|
@@ -5335,7 +5562,36 @@ def _run_daemon(config: dict) -> None:
|
|
|
5335
5562
|
config["_last_interaction_time"] = time.time()
|
|
5336
5563
|
|
|
5337
5564
|
# Same callback used by the REPL so Telegram can trigger runs
|
|
5338
|
-
|
|
5565
|
+
# NB: the run_query referenced here lives on the global namespace once the
|
|
5566
|
+
# daemon is running with --daemon (we provide a thin wrapper below).
|
|
5567
|
+
def _daemon_run_query(msg, is_background=True):
|
|
5568
|
+
try:
|
|
5569
|
+
from agent import run as agent_run
|
|
5570
|
+
for _ in agent_run(state, msg, config, is_background=is_background):
|
|
5571
|
+
pass
|
|
5572
|
+
except Exception as _e:
|
|
5573
|
+
err(f"daemon run_query error: {type(_e).__name__}: {_e}")
|
|
5574
|
+
config["_run_query_callback"] = lambda msg: _daemon_run_query(msg, is_background=True)
|
|
5575
|
+
|
|
5576
|
+
# Auto-start the webchat server alongside the daemon when /bg start asked
|
|
5577
|
+
# for it — same session, same state. One Dulus, three entry points (CLI
|
|
5578
|
+
# via IPC, browser via webchat, Telegram via bridge).
|
|
5579
|
+
import os as _os_d
|
|
5580
|
+
if _os_d.environ.get("DULUS_BG_AUTO_WEBCHAT") == "1":
|
|
5581
|
+
config["_bg_start_webchat"] = True
|
|
5582
|
+
try:
|
|
5583
|
+
config["_webchat_port"] = int(_os_d.environ.get("DULUS_BG_WEBCHAT_PORT", 5000))
|
|
5584
|
+
except ValueError:
|
|
5585
|
+
pass
|
|
5586
|
+
if config.get("_bg_start_webchat"):
|
|
5587
|
+
try:
|
|
5588
|
+
import webchat_server as _wc
|
|
5589
|
+
_wc_port = int(config.get("_webchat_port", 5000))
|
|
5590
|
+
if not _wc.is_running():
|
|
5591
|
+
_wc.start(state, config, port=_wc_port)
|
|
5592
|
+
ok(f"WebChat started → http://127.0.0.1:{_wc_port}/")
|
|
5593
|
+
except Exception as _wce:
|
|
5594
|
+
warn(f"WebChat auto-start failed: {_wce}")
|
|
5339
5595
|
|
|
5340
5596
|
# IPC server — same socket the REPL uses, so external `dulus "..."` calls
|
|
5341
5597
|
# land in this daemon's session.
|
|
@@ -6725,6 +6981,7 @@ COMMANDS = {
|
|
|
6725
6981
|
"task": cmd_tasks,
|
|
6726
6982
|
"proactive": cmd_proactive,
|
|
6727
6983
|
"daemon": cmd_daemon,
|
|
6984
|
+
"bg": cmd_bg,
|
|
6728
6985
|
"lite": cmd_lite,
|
|
6729
6986
|
"cloudsave": cmd_cloudsave,
|
|
6730
6987
|
"voice": cmd_voice,
|
|
@@ -6825,6 +7082,7 @@ _CMD_META: dict[str, tuple[str, list[str]]] = {
|
|
|
6825
7082
|
"todo", "in-progress", "done", "blocked"]),
|
|
6826
7083
|
"proactive": ("Manage proactive background watcher", ["off"]),
|
|
6827
7084
|
"daemon": ("Toggle daemon — allow external triggers (Telegram) to spawn Dulus", ["on", "off"]),
|
|
7085
|
+
"bg": ("Background Dulus — one detached daemon for CLI + Web + Telegram", ["start", "stop", "status", "attach"]),
|
|
6828
7086
|
"lite": ("Toggle lite mode (reduce system prompt)", ["on", "off"]),
|
|
6829
7087
|
"rtk": ("Toggle RTK token-optimized shell rewriting", ["on", "off"]),
|
|
6830
7088
|
"cloudsave": ("Cloud-sync sessions to GitHub Gist", ["setup", "auto", "list", "load", "push"]),
|
|
@@ -352,6 +352,13 @@ CRITICAL:
|
|
|
352
352
|
- plugin.json "dependencies" MUST be a simple LIST of strings
|
|
353
353
|
- Include "ADAPTATION_GUIDE.md" in plugin.json "skills" list
|
|
354
354
|
|
|
355
|
+
TMUX-OFFLOAD HINT (important for UX):
|
|
356
|
+
- For each tool you generate, ESTIMATE its typical runtime.
|
|
357
|
+
- If a tool typically runs > 15 seconds (network scans, sherlock, full holehe sweeps, large file ingestion, OSINT crawls, video downloads, full-repo analysis, etc.), APPEND the literal marker `[long-running — wrap in TmuxOffload]` at the END of the tool's `description` field in the JSON schema.
|
|
358
|
+
- Tools that are fast (< 5s) do NOT need the marker. Don't be over-cautious — only mark tools where users will visibly wait.
|
|
359
|
+
- The marker is read by Dulus's agent at runtime: when it sees a tool description ending in `[long-running — wrap in TmuxOffload]`, it knows to wrap that call in TmuxOffload instead of blocking the REPL.
|
|
360
|
+
- Example: `"description": "Search for a username across hundreds of social networks. [long-running — wrap in TmuxOffload]"`
|
|
361
|
+
|
|
355
362
|
Respond with the delimited format:
|
|
356
363
|
---FILE: ADAPTATION_GUIDE.md---
|
|
357
364
|
(Overview, tool design decisions, error patterns, validation)
|
|
@@ -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.23"
|
|
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"
|
|
@@ -1759,7 +1759,11 @@ def start(state: AgentState, config: dict, port: int = 5000, open_browser: bool
|
|
|
1759
1759
|
|
|
1760
1760
|
from werkzeug.serving import make_server
|
|
1761
1761
|
|
|
1762
|
-
|
|
1762
|
+
# Default to loopback-only — exposing to the LAN by accident is a real
|
|
1763
|
+
# safety footgun (anyone on the wifi can poke the agent). Opt-in via
|
|
1764
|
+
# config["webchat_lan"] = true (or /webchat lan on).
|
|
1765
|
+
bind_host = "0.0.0.0" if config.get("webchat_lan") else "127.0.0.1"
|
|
1766
|
+
_WERKZEUG_SERVER = make_server(bind_host, port, app, threaded=True)
|
|
1763
1767
|
_SERVER_THREAD = threading.Thread(target=_WERKZEUG_SERVER.serve_forever, daemon=True)
|
|
1764
1768
|
_SERVER_THREAD.start()
|
|
1765
1769
|
return True
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|