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.
Files changed (164) hide show
  1. {dulus-0.2.21/dulus.egg-info → dulus-0.2.23}/PKG-INFO +7 -2
  2. {dulus-0.2.21 → dulus-0.2.23}/README.md +6 -1
  3. {dulus-0.2.21 → dulus-0.2.23}/context.py +1 -0
  4. {dulus-0.2.21 → dulus-0.2.23}/docs/news.md +10 -0
  5. {dulus-0.2.21 → dulus-0.2.23/dulus.egg-info}/PKG-INFO +7 -2
  6. {dulus-0.2.21 → dulus-0.2.23}/dulus.py +260 -2
  7. {dulus-0.2.21 → dulus-0.2.23}/plugin/autoadapter.py +7 -0
  8. {dulus-0.2.21 → dulus-0.2.23}/pyproject.toml +1 -1
  9. {dulus-0.2.21 → dulus-0.2.23}/webchat_server.py +5 -1
  10. {dulus-0.2.21 → dulus-0.2.23}/LICENSE +0 -0
  11. {dulus-0.2.21 → dulus-0.2.23}/MANIFEST.in +0 -0
  12. {dulus-0.2.21 → dulus-0.2.23}/agent.py +0 -0
  13. {dulus-0.2.21 → dulus-0.2.23}/backend/__init__.py +0 -0
  14. {dulus-0.2.21 → dulus-0.2.23}/backend/compressor.py +0 -0
  15. {dulus-0.2.21 → dulus-0.2.23}/backend/context.py +0 -0
  16. {dulus-0.2.21 → dulus-0.2.23}/backend/githook.py +0 -0
  17. {dulus-0.2.21 → dulus-0.2.23}/backend/marketplace.py +0 -0
  18. {dulus-0.2.21 → dulus-0.2.23}/backend/mempalace_bridge.py +0 -0
  19. {dulus-0.2.21 → dulus-0.2.23}/backend/personas.py +0 -0
  20. {dulus-0.2.21 → dulus-0.2.23}/backend/plugins.py +0 -0
  21. {dulus-0.2.21 → dulus-0.2.23}/backend/server.py +0 -0
  22. {dulus-0.2.21 → dulus-0.2.23}/backend/tasks.py +0 -0
  23. {dulus-0.2.21 → dulus-0.2.23}/batch_api.py +0 -0
  24. {dulus-0.2.21 → dulus-0.2.23}/checkpoint/__init__.py +0 -0
  25. {dulus-0.2.21 → dulus-0.2.23}/checkpoint/hooks.py +0 -0
  26. {dulus-0.2.21 → dulus-0.2.23}/checkpoint/store.py +0 -0
  27. {dulus-0.2.21 → dulus-0.2.23}/checkpoint/types.py +0 -0
  28. {dulus-0.2.21 → dulus-0.2.23}/claude_code_watcher.py +0 -0
  29. {dulus-0.2.21 → dulus-0.2.23}/clipboard_utils.py +0 -0
  30. {dulus-0.2.21 → dulus-0.2.23}/cloudsave.py +0 -0
  31. {dulus-0.2.21 → dulus-0.2.23}/common.py +0 -0
  32. {dulus-0.2.21 → dulus-0.2.23}/compaction.py +0 -0
  33. {dulus-0.2.21 → dulus-0.2.23}/config.py +0 -0
  34. {dulus-0.2.21 → dulus-0.2.23}/data/__init__.py +0 -0
  35. {dulus-0.2.21 → dulus-0.2.23}/data/active_persona.json +0 -0
  36. {dulus-0.2.21 → dulus-0.2.23}/data/context.json +0 -0
  37. {dulus-0.2.21 → dulus-0.2.23}/data/marketplace.json +0 -0
  38. {dulus-0.2.21 → dulus-0.2.23}/data/personas.json +0 -0
  39. {dulus-0.2.21 → dulus-0.2.23}/data/plugins/__init__.py +0 -0
  40. {dulus-0.2.21 → dulus-0.2.23}/data/plugins/composio/__init__.py +0 -0
  41. {dulus-0.2.21 → dulus-0.2.23}/data/plugins/composio/composio_plugin/__init__.py +0 -0
  42. {dulus-0.2.21 → dulus-0.2.23}/data/plugins/composio/composio_plugin/session_manager.py +0 -0
  43. {dulus-0.2.21 → dulus-0.2.23}/data/plugins/composio/composio_plugin/tool_generator.py +0 -0
  44. {dulus-0.2.21 → dulus-0.2.23}/data/plugins/composio/plugin.json +0 -0
  45. {dulus-0.2.21 → dulus-0.2.23}/data/plugins/composio/plugin_tool.py +0 -0
  46. {dulus-0.2.21 → dulus-0.2.23}/data/tasks.json +0 -0
  47. {dulus-0.2.21 → dulus-0.2.23}/docs/README.md +0 -0
  48. {dulus-0.2.21 → dulus-0.2.23}/docs/__init__.py +0 -0
  49. {dulus-0.2.21 → dulus-0.2.23}/docs/api.html +0 -0
  50. {dulus-0.2.21 → dulus-0.2.23}/docs/architecture.md +0 -0
  51. {dulus-0.2.21 → dulus-0.2.23}/docs/azure-speech-template.json +0 -0
  52. {dulus-0.2.21 → dulus-0.2.23}/docs/dashboard/index.html +0 -0
  53. {dulus-0.2.21 → dulus-0.2.23}/docs/divider.svg +0 -0
  54. {dulus-0.2.21 → dulus-0.2.23}/docs/generate.py +0 -0
  55. {dulus-0.2.21 → dulus-0.2.23}/docs/hero.svg +0 -0
  56. {dulus-0.2.21 → dulus-0.2.23}/docs/index.html +0 -0
  57. {dulus-0.2.21 → dulus-0.2.23}/docs/nvidia-models.svg +0 -0
  58. {dulus-0.2.21 → dulus-0.2.23}/docs/particle-playground.html +0 -0
  59. {dulus-0.2.21 → dulus-0.2.23}/docs/personas/index.html +0 -0
  60. {dulus-0.2.21 → dulus-0.2.23}/docs/poetry-banner.png +0 -0
  61. {dulus-0.2.21 → dulus-0.2.23}/docs/preview.html +0 -0
  62. {dulus-0.2.21 → dulus-0.2.23}/docs/sec-agents.svg +0 -0
  63. {dulus-0.2.21 → dulus-0.2.23}/docs/sec-brainstorm.svg +0 -0
  64. {dulus-0.2.21 → dulus-0.2.23}/docs/sec-bridges.svg +0 -0
  65. {dulus-0.2.21 → dulus-0.2.23}/docs/sec-features.svg +0 -0
  66. {dulus-0.2.21 → dulus-0.2.23}/docs/sec-freetier.svg +0 -0
  67. {dulus-0.2.21 → dulus-0.2.23}/docs/sec-memory.svg +0 -0
  68. {dulus-0.2.21 → dulus-0.2.23}/docs/sec-models.svg +0 -0
  69. {dulus-0.2.21 → dulus-0.2.23}/docs/sec-perms.svg +0 -0
  70. {dulus-0.2.21 → dulus-0.2.23}/docs/sec-plugins.svg +0 -0
  71. {dulus-0.2.21 → dulus-0.2.23}/docs/sec-quickstart.svg +0 -0
  72. {dulus-0.2.21 → dulus-0.2.23}/docs/sec-ssj.svg +0 -0
  73. {dulus-0.2.21 → dulus-0.2.23}/docs/spinners.svg +0 -0
  74. {dulus-0.2.21 → dulus-0.2.23}/docs/split-pane.svg +0 -0
  75. {dulus-0.2.21 → dulus-0.2.23}/docs/terminal-boot.svg +0 -0
  76. {dulus-0.2.21 → dulus-0.2.23}/docs/uploads/particle-playground.html +0 -0
  77. {dulus-0.2.21 → dulus-0.2.23}/dulus.egg-info/SOURCES.txt +0 -0
  78. {dulus-0.2.21 → dulus-0.2.23}/dulus.egg-info/dependency_links.txt +0 -0
  79. {dulus-0.2.21 → dulus-0.2.23}/dulus.egg-info/entry_points.txt +0 -0
  80. {dulus-0.2.21 → dulus-0.2.23}/dulus.egg-info/requires.txt +0 -0
  81. {dulus-0.2.21 → dulus-0.2.23}/dulus.egg-info/top_level.txt +0 -0
  82. {dulus-0.2.21 → dulus-0.2.23}/dulus_gui.py +0 -0
  83. {dulus-0.2.21 → dulus-0.2.23}/dulus_mcp/__init__.py +0 -0
  84. {dulus-0.2.21 → dulus-0.2.23}/dulus_mcp/client.py +0 -0
  85. {dulus-0.2.21 → dulus-0.2.23}/dulus_mcp/config.py +0 -0
  86. {dulus-0.2.21 → dulus-0.2.23}/dulus_mcp/tools.py +0 -0
  87. {dulus-0.2.21 → dulus-0.2.23}/dulus_mcp/types.py +0 -0
  88. {dulus-0.2.21 → dulus-0.2.23}/gui/__init__.py +0 -0
  89. {dulus-0.2.21 → dulus-0.2.23}/gui/agent_bridge.py +0 -0
  90. {dulus-0.2.21 → dulus-0.2.23}/gui/chat_widget.py +0 -0
  91. {dulus-0.2.21 → dulus-0.2.23}/gui/main_window.py +0 -0
  92. {dulus-0.2.21 → dulus-0.2.23}/gui/personas.py +0 -0
  93. {dulus-0.2.21 → dulus-0.2.23}/gui/session_utils.py +0 -0
  94. {dulus-0.2.21 → dulus-0.2.23}/gui/settings_dialog.py +0 -0
  95. {dulus-0.2.21 → dulus-0.2.23}/gui/sidebar.py +0 -0
  96. {dulus-0.2.21 → dulus-0.2.23}/gui/tasks_view.py +0 -0
  97. {dulus-0.2.21 → dulus-0.2.23}/gui/themes.py +0 -0
  98. {dulus-0.2.21 → dulus-0.2.23}/gui/tool_panel.py +0 -0
  99. {dulus-0.2.21 → dulus-0.2.23}/input.py +0 -0
  100. {dulus-0.2.21 → dulus-0.2.23}/license_manager.py +0 -0
  101. {dulus-0.2.21 → dulus-0.2.23}/memory/__init__.py +0 -0
  102. {dulus-0.2.21 → dulus-0.2.23}/memory/audit.py +0 -0
  103. {dulus-0.2.21 → dulus-0.2.23}/memory/consolidator.py +0 -0
  104. {dulus-0.2.21 → dulus-0.2.23}/memory/context.py +0 -0
  105. {dulus-0.2.21 → dulus-0.2.23}/memory/offload.py +0 -0
  106. {dulus-0.2.21 → dulus-0.2.23}/memory/palace.py +0 -0
  107. {dulus-0.2.21 → dulus-0.2.23}/memory/scan.py +0 -0
  108. {dulus-0.2.21 → dulus-0.2.23}/memory/sessions.py +0 -0
  109. {dulus-0.2.21 → dulus-0.2.23}/memory/store.py +0 -0
  110. {dulus-0.2.21 → dulus-0.2.23}/memory/tools.py +0 -0
  111. {dulus-0.2.21 → dulus-0.2.23}/memory/types.py +0 -0
  112. {dulus-0.2.21 → dulus-0.2.23}/memory/vector_search.py +0 -0
  113. {dulus-0.2.21 → dulus-0.2.23}/multi_agent/__init__.py +0 -0
  114. {dulus-0.2.21 → dulus-0.2.23}/multi_agent/subagent.py +0 -0
  115. {dulus-0.2.21 → dulus-0.2.23}/multi_agent/tools.py +0 -0
  116. {dulus-0.2.21 → dulus-0.2.23}/offload_helper.py +0 -0
  117. {dulus-0.2.21 → dulus-0.2.23}/plugin/__init__.py +0 -0
  118. {dulus-0.2.21 → dulus-0.2.23}/plugin/loader.py +0 -0
  119. {dulus-0.2.21 → dulus-0.2.23}/plugin/recommend.py +0 -0
  120. {dulus-0.2.21 → dulus-0.2.23}/plugin/store.py +0 -0
  121. {dulus-0.2.21 → dulus-0.2.23}/plugin/types.py +0 -0
  122. {dulus-0.2.21 → dulus-0.2.23}/providers.py +0 -0
  123. {dulus-0.2.21 → dulus-0.2.23}/setup.cfg +0 -0
  124. {dulus-0.2.21 → dulus-0.2.23}/skill/__init__.py +0 -0
  125. {dulus-0.2.21 → dulus-0.2.23}/skill/builtin.py +0 -0
  126. {dulus-0.2.21 → dulus-0.2.23}/skill/clawhub.py +0 -0
  127. {dulus-0.2.21 → dulus-0.2.23}/skill/executor.py +0 -0
  128. {dulus-0.2.21 → dulus-0.2.23}/skill/loader.py +0 -0
  129. {dulus-0.2.21 → dulus-0.2.23}/skill/tools.py +0 -0
  130. {dulus-0.2.21 → dulus-0.2.23}/skills.py +0 -0
  131. {dulus-0.2.21 → dulus-0.2.23}/spinner.py +0 -0
  132. {dulus-0.2.21 → dulus-0.2.23}/string_utils.py +0 -0
  133. {dulus-0.2.21 → dulus-0.2.23}/subagent.py +0 -0
  134. {dulus-0.2.21 → dulus-0.2.23}/task/__init__.py +0 -0
  135. {dulus-0.2.21 → dulus-0.2.23}/task/store.py +0 -0
  136. {dulus-0.2.21 → dulus-0.2.23}/task/tools.py +0 -0
  137. {dulus-0.2.21 → dulus-0.2.23}/task/types.py +0 -0
  138. {dulus-0.2.21 → dulus-0.2.23}/tests/test_checkpoint.py +0 -0
  139. {dulus-0.2.21 → dulus-0.2.23}/tests/test_compaction.py +0 -0
  140. {dulus-0.2.21 → dulus-0.2.23}/tests/test_diff_view.py +0 -0
  141. {dulus-0.2.21 → dulus-0.2.23}/tests/test_injection_fix.py +0 -0
  142. {dulus-0.2.21 → dulus-0.2.23}/tests/test_license.py +0 -0
  143. {dulus-0.2.21 → dulus-0.2.23}/tests/test_mcp.py +0 -0
  144. {dulus-0.2.21 → dulus-0.2.23}/tests/test_memory.py +0 -0
  145. {dulus-0.2.21 → dulus-0.2.23}/tests/test_plugin.py +0 -0
  146. {dulus-0.2.21 → dulus-0.2.23}/tests/test_skills.py +0 -0
  147. {dulus-0.2.21 → dulus-0.2.23}/tests/test_subagent.py +0 -0
  148. {dulus-0.2.21 → dulus-0.2.23}/tests/test_task.py +0 -0
  149. {dulus-0.2.21 → dulus-0.2.23}/tests/test_telegram_buffer.py +0 -0
  150. {dulus-0.2.21 → dulus-0.2.23}/tests/test_tool_registry.py +0 -0
  151. {dulus-0.2.21 → dulus-0.2.23}/tests/test_voice.py +0 -0
  152. {dulus-0.2.21 → dulus-0.2.23}/tmux_offloader.py +0 -0
  153. {dulus-0.2.21 → dulus-0.2.23}/tmux_tools.py +0 -0
  154. {dulus-0.2.21 → dulus-0.2.23}/tool_registry.py +0 -0
  155. {dulus-0.2.21 → dulus-0.2.23}/tools.py +0 -0
  156. {dulus-0.2.21 → dulus-0.2.23}/ui/__init__.py +0 -0
  157. {dulus-0.2.21 → dulus-0.2.23}/ui/input.py +0 -0
  158. {dulus-0.2.21 → dulus-0.2.23}/ui/render.py +0 -0
  159. {dulus-0.2.21 → dulus-0.2.23}/voice/__init__.py +0 -0
  160. {dulus-0.2.21 → dulus-0.2.23}/voice/keyterms.py +0 -0
  161. {dulus-0.2.21 → dulus-0.2.23}/voice/recorder.py +0 -0
  162. {dulus-0.2.21 → dulus-0.2.23}/voice/stt.py +0 -0
  163. {dulus-0.2.21 → dulus-0.2.23}/voice/tts.py +0 -0
  164. {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.21
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.21-ff6b1f?style=flat-square&labelColor=07070a" alt="version"/>
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.21-ff6b1f?style=flat-square&labelColor=07070a" alt="version"/>
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.21
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.21-ff6b1f?style=flat-square&labelColor=07070a" alt="version"/>
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.21" # dev fallback — keep in sync with pyproject.toml
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
- config["_run_query_callback"] = lambda msg: run_query(msg, is_background=True)
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.21"
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
- _WERKZEUG_SERVER = make_server("0.0.0.0", port, app, threaded=True)
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