dulus 3.5.0__tar.gz → 3.6.2__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-3.5.0/dulus.egg-info → dulus-3.6.2}/PKG-INFO +2 -2
- {dulus-3.5.0 → dulus-3.6.2}/README.md +1 -1
- {dulus-3.5.0 → dulus-3.6.2}/analytics.py +21 -1
- {dulus-3.5.0 → dulus-3.6.2}/config.py +42 -2
- {dulus-3.5.0 → dulus-3.6.2}/docs/CHANGELOG.md +15 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/news.md +8 -0
- {dulus-3.5.0 → dulus-3.6.2/dulus.egg-info}/PKG-INFO +2 -2
- {dulus-3.5.0 → dulus-3.6.2}/dulus.py +116 -1
- {dulus-3.5.0 → dulus-3.6.2}/memory/sessions.py +44 -12
- {dulus-3.5.0 → dulus-3.6.2}/memory/tools.py +22 -8
- {dulus-3.5.0 → dulus-3.6.2}/pyproject.toml +1 -1
- {dulus-3.5.0 → dulus-3.6.2}/LICENSE +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/MANIFEST.in +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/agent.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/backend/__init__.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/backend/agents_bridge.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/backend/compressor.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/backend/context.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/backend/githook.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/backend/marketplace.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/backend/mempalace_bridge.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/backend/personas.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/backend/plugins.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/backend/server.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/backend/tasks.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/batch_api.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/checkpoint/__init__.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/checkpoint/hooks.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/checkpoint/store.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/checkpoint/types.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/claude_code_watcher.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/clipboard_utils.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/cloudsave.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/common.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/compaction.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/context.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/data/__init__.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/data/active_persona.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/data/marketplace.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/data/personas.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/data/plugins/__init__.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/data/plugins/composio/__init__.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/data/plugins/composio/composio_plugin/__init__.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/data/plugins/composio/composio_plugin/session_manager.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/data/plugins/composio/composio_plugin/tool_generator.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/data/plugins/composio/plugin.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/data/plugins/composio/plugin_tool.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/data/tasks.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/API.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/BRAND_GUIDE.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/CONTRIBUTING.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/DEPLOYMENT.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/Dulus_AI_Whitepaper_(v2.0).pdf +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/FAQ.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/GETTING_STARTED.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/INVESTOR_BRIEF.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/README.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/README_AR.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/README_EN.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/README_ES.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/README_FR.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/README_JA.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/README_KO.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/README_PT.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/README_RU.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/README_ZH.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/SECURITY.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/__init__.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/api.html +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/architecture.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/azure-speech-template.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/dashboard/index.html +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/divider.svg +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/dulus-bird.png +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/generate.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/hero.svg +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/index.html +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/nvidia-models.svg +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/particle-playground.html +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/personas/index.html +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/poetry-banner.png +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/preview.html +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/sec-agents.svg +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/sec-brainstorm.svg +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/sec-bridges.svg +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/sec-features.svg +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/sec-freetier.svg +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/sec-memory.svg +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/sec-models.svg +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/sec-perms.svg +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/sec-plugins.svg +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/sec-quickstart.svg +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/sec-ssj.svg +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/spinners.svg +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/split-pane.svg +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/docs/terminal-boot.svg +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/dulus.egg-info/SOURCES.txt +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/dulus.egg-info/dependency_links.txt +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/dulus.egg-info/entry_points.txt +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/dulus.egg-info/requires.txt +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/dulus.egg-info/top_level.txt +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/dulus_gui.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/dulus_gui_web.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/dulus_mcp/__init__.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/dulus_mcp/client.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/dulus_mcp/config.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/dulus_mcp/tools.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/dulus_mcp/types.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/dulus_tools/__init__.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/dulus_tools/add_dir_manager.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/dulus_tools/afk_mode.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/dulus_tools/approval_runtime.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/dulus_tools/background_tasks.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/dulus_tools/clipboard_utils.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/dulus_tools/diff_visualizer.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/dulus_tools/export_import.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/dulus_tools/hook_engine.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/dulus_tools/notification_manager.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/dulus_tools/session_fork.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/dulus_tools/shell_mode.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/dulus_tools/todo_visualizer.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/dulus_tools/wire_events.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/dulus_tools/yolo_mode.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/governance.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/gui/__init__.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/gui/agent_bridge.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/gui/chat_widget.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/gui/dulus_orchestrator/App.tsx +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/gui/dulus_orchestrator/THIRD_PARTY_NOTICES.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/gui/dulus_orchestrator/dist/index.html +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/gui/dulus_orchestrator/dist/standalone.html +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/gui/dulus_orchestrator/index.html +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/gui/dulus_orchestrator/index.tsx +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/gui/dulus_orchestrator/metadata.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/gui/dulus_orchestrator/package-lock.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/gui/dulus_orchestrator/package.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/gui/dulus_orchestrator/types.ts +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/gui/dulus_orchestrator/utils.ts +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/gui/dulus_orchestrator/vertex-ai-proxy-interceptor.js +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/gui/dulus_orchestrator/vite.config.ts +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/gui/main_window.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/gui/personas.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/gui/sandbox_server.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/gui/session_utils.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/gui/settings_dialog.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/gui/sidebar.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/gui/tasks_view.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/gui/themes.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/gui/tool_panel.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/input.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/license_manager.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/memory/__init__.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/memory/audit.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/memory/consolidator.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/memory/context.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/memory/offload.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/memory/palace.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/memory/scan.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/memory/store.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/memory/types.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/memory/vector_search.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/multi_agent/__init__.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/multi_agent/subagent.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/multi_agent/tools.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/offload_helper.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/personality_config.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/plugin/__init__.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/plugin/autoadapter.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/plugin/loader.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/plugin/recommend.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/plugin/store.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/plugin/types.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/providers.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/sandbox/README.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/sandbox/__init__.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/sandbox/components.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/sandbox/dist/assets/index-DE51D6wI.css +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/sandbox/dist/assets/index-DMCCNE9Y.js +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/sandbox/dist/index.html +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/sandbox/dist/wallpaper-default.jpg +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/sandbox/dist/wallpapers/default.jpeg +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/sandbox/dist/wallpapers/light.jpeg +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/sandbox/dist/wallpapers/nature.jpeg +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/sandbox/dist/wallpapers/tech.jpeg +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/sandbox/eslint.config.js +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/sandbox/index.html +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/sandbox/info.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/sandbox/package-lock.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/sandbox/package.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/sandbox/postcss.config.js +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/sandbox/tailwind.config.js +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/sandbox/tsconfig.app.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/sandbox/tsconfig.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/sandbox/tsconfig.node.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/sandbox/vite.config.ts +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/sandbox_bootstrap.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/setup.cfg +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/__init__.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/builtin.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/LICENSE-obsidian-skills +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/README.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/defuddle/SKILL.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/json-canvas/SKILL.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/json-canvas/references/EXAMPLES.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/obsidian-bases/SKILL.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/obsidian-bases/references/FUNCTIONS_REFERENCE.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/obsidian-cli/SKILL.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/obsidian-markdown/SKILL.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/obsidian-markdown/references/CALLOUTS.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/obsidian-markdown/references/EMBEDS.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/obsidian-markdown/references/PROPERTIES.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/README.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/SKILL.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/addresses.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/advancedbackgroundchecks.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/beenverified.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/clustal.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/clustrmaps.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/cyberbackgroundchecks.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/familytreenow.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/fastpeoplesearch.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/intelius.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/mylife.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/nuwber.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/peekyou.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/peoplefinders.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/radaris.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/rehold.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/searchpeoplefree.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/socialcatfish.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/spokeo.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/thatsthem.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/truepeoplesearch.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/usphonebook.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/references/brokers/whitepages.json +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/references/legal/ccpa.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/references/legal/drop.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/references/legal/gdpr.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/references/methods.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/references/site-playbooks.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/references/state-machine.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/autopilot.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/badbool.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/brokers.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/cdp.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/config.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/crypto.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/dossier.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/email_modes.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/emailer.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/ledger.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/legal.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/paths.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/pdd.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/registry.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/report.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/scan.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/storage.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/tiers.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/scripts/vectors.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/templates/consent/authorization.md +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/templates/emails/ccpa-authorized-agent.txt +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/templates/emails/ccpa-deletion.txt +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/templates/emails/ccpa-indirect-deletion.txt +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/templates/emails/gdpr-erasure.txt +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/bundled/unbroker/templates/emails/generic-optout.txt +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/clawhub.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/executor.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/loader.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skill/tools.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/skills.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/soul.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/spinner.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/string_utils.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/subagent.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/task/__init__.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/task/store.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/task/tools.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/task/types.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tests/test_afk_yolo.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tests/test_approval_runtime.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tests/test_background_task_tools.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tests/test_background_tasks.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tests/test_checkpoint.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tests/test_clipboard_utils.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tests/test_compaction.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tests/test_diff_view.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tests/test_diff_visualization.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tests/test_display_blocks.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tests/test_export_import.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tests/test_hook_engine.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tests/test_injection_fix.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tests/test_license.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tests/test_mcp.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tests/test_memory.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tests/test_notification_manager.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tests/test_plugin.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tests/test_session_fork.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tests/test_shell_mode.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tests/test_skills.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tests/test_steer_input.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tests/test_subagent.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tests/test_task.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tests/test_telegram_buffer.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tests/test_think_tool.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tests/test_todo_tool.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tests/test_todo_visualization.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tests/test_tool_registry.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tests/test_voice.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tests/test_wire_events.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tmux_offloader.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tmux_tools.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tool_registry.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/tools.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/ui/__init__.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/ui/input.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/ui/render.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/voice/__init__.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/voice/audio_utils.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/voice/keyterms.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/voice/recorder.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/voice/stt.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/voice/tts.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/voice/wake_word.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/webbridge/__init__.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/webbridge/core.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/webbridge/tools.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/webchat.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/webchat_server.py +0 -0
- {dulus-3.5.0 → dulus-3.6.2}/welcome.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dulus
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.6.2
|
|
4
4
|
Summary: Use IA as an agent without an API key. $0. Harvest your browser session for Gemini (guest, no login), Claude.ai, Claude Code, Kimi.com, Qwen, DeepSeek — drive them like an API, no per-token billing. Plus LiteLLM (100+ paid providers), local models via Ollama, /lang in 34 languages, Mesa Redonda, voice, OCR, MemPalace, embedded sandbox OS.
|
|
5
5
|
Author: KevRojo
|
|
6
6
|
License: GPL-3.0
|
|
@@ -133,7 +133,7 @@ SET /sticky_input ON since the first run for the best experience!
|
|
|
133
133
|
|
|
134
134
|
> Full changelog: [`docs/news.md`](docs/news.md) · Inside the REPL: `/news`
|
|
135
135
|
|
|
136
|
-
-
|
|
136
|
+
- **🌍 dulus.work — THE HUB. The secret is out.** This is what Kevin had been hiding (even from Dulus itself 😂): **a global hub where every Dulus instance on the planet shares what it learns.** Your Dulus finds a fix, polishes a skill, adapts a plugin → it can publish it to the hub → every other Dulus in the world can pull it down. Not an update server — an **organism**. Every installation is a node. *The fix a Dulus in Tokyo finds at 3AM, the one in Santo Domingo has by sunrise.* Other agents have N devs improving one product (linear). Dulus makes every user a dev (exponential). The pieces have been quietly shipping for months — SelfImprove engine, Auto-Adapter, the skills format, cloudsave — and now they converge. Domain is live, execution underway. 🦅🇩🇴 → [dulus.work](https://dulus.work)
|
|
137
137
|
|
|
138
138
|
- **Dulus Agent — Telegram communities.** Dulus evolved from a CLI tool into a live AI agent inside Telegram groups. The first fully autonomous AI moderating and conversing in real communities — not a bot, not a filter, a real agent. Groups pay in $DULUS to activate him. We host Dulus for them — that's the business model. The CLI stays free forever. This is the paid layer. 🦅🇩🇴
|
|
139
139
|
|
|
@@ -68,7 +68,7 @@ SET /sticky_input ON since the first run for the best experience!
|
|
|
68
68
|
|
|
69
69
|
> Full changelog: [`docs/news.md`](docs/news.md) · Inside the REPL: `/news`
|
|
70
70
|
|
|
71
|
-
-
|
|
71
|
+
- **🌍 dulus.work — THE HUB. The secret is out.** This is what Kevin had been hiding (even from Dulus itself 😂): **a global hub where every Dulus instance on the planet shares what it learns.** Your Dulus finds a fix, polishes a skill, adapts a plugin → it can publish it to the hub → every other Dulus in the world can pull it down. Not an update server — an **organism**. Every installation is a node. *The fix a Dulus in Tokyo finds at 3AM, the one in Santo Domingo has by sunrise.* Other agents have N devs improving one product (linear). Dulus makes every user a dev (exponential). The pieces have been quietly shipping for months — SelfImprove engine, Auto-Adapter, the skills format, cloudsave — and now they converge. Domain is live, execution underway. 🦅🇩🇴 → [dulus.work](https://dulus.work)
|
|
72
72
|
|
|
73
73
|
- **Dulus Agent — Telegram communities.** Dulus evolved from a CLI tool into a live AI agent inside Telegram groups. The first fully autonomous AI moderating and conversing in real communities — not a bot, not a filter, a real agent. Groups pay in $DULUS to activate him. We host Dulus for them — that's the business model. The CLI stays free forever. This is the paid layer. 🦅🇩🇴
|
|
74
74
|
|
|
@@ -41,7 +41,7 @@ import uuid
|
|
|
41
41
|
# NOTE: Mixpanel ingestion tokens are write-only and designed to ship in
|
|
42
42
|
# client code (every website using Mixpanel exposes theirs). It cannot be
|
|
43
43
|
# used to read any data. Override with DULUS_MP_TOKEN.
|
|
44
|
-
MP_TOKEN = os.environ.get("DULUS_MP_TOKEN", "")
|
|
44
|
+
MP_TOKEN = os.environ.get("DULUS_MP_TOKEN", "966ce8f5ccb32f06788f51be9f8bf8f5")
|
|
45
45
|
|
|
46
46
|
_MP_ENDPOINT = "https://api.mixpanel.com/track"
|
|
47
47
|
|
|
@@ -142,3 +142,23 @@ def track_session_start(config: dict) -> None:
|
|
|
142
142
|
"provider": str(config.get("provider", "")),
|
|
143
143
|
"model": str(config.get("model", "")),
|
|
144
144
|
})
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def track_message_sent(model: str = "") -> None:
|
|
148
|
+
"""One event per user prompt sent to the LLM (no content — count only)."""
|
|
149
|
+
track("message_sent", {"model": model})
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def track_tool_used(tool_name: str) -> None:
|
|
153
|
+
"""One event per tool invocation (tool NAME only — never inputs)."""
|
|
154
|
+
track("tool_used", {"tool": tool_name})
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def track_command_used(command: str) -> None:
|
|
158
|
+
"""One event per slash command (command NAME only — never args)."""
|
|
159
|
+
track("command_used", {"command": command})
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def track_model_selected(model: str, provider: str = "") -> None:
|
|
163
|
+
"""Fired when the user switches models via /model."""
|
|
164
|
+
track("model_selected", {"model": model, "provider": provider})
|
|
@@ -130,7 +130,31 @@ def load_config() -> dict:
|
|
|
130
130
|
try:
|
|
131
131
|
cfg.update(json.loads(CONFIG_FILE.read_text(encoding="utf-8")))
|
|
132
132
|
except Exception:
|
|
133
|
-
|
|
133
|
+
# Config is corrupt (truncated write, crash mid-save, etc).
|
|
134
|
+
# NEVER silently reset the user's config: try the .bak backup
|
|
135
|
+
# first, and if that also fails, preserve the corrupt file for
|
|
136
|
+
# manual recovery instead of letting the next save_config()
|
|
137
|
+
# clobber it with bare defaults.
|
|
138
|
+
recovered = False
|
|
139
|
+
backup = CONFIG_FILE.with_suffix(".json.bak")
|
|
140
|
+
if backup.exists():
|
|
141
|
+
try:
|
|
142
|
+
cfg.update(json.loads(backup.read_text(encoding="utf-8")))
|
|
143
|
+
recovered = True
|
|
144
|
+
print(f"[dulus] WARNING: config.json was corrupt - recovered from {backup.name}")
|
|
145
|
+
except Exception:
|
|
146
|
+
pass
|
|
147
|
+
try:
|
|
148
|
+
import time as _time
|
|
149
|
+
quarantine = CONFIG_FILE.with_name(
|
|
150
|
+
f"config.corrupt-{int(_time.time())}.json")
|
|
151
|
+
CONFIG_FILE.replace(quarantine)
|
|
152
|
+
print(f"[dulus] Corrupt config preserved at: {quarantine}")
|
|
153
|
+
except Exception:
|
|
154
|
+
pass
|
|
155
|
+
if not recovered:
|
|
156
|
+
print("[dulus] WARNING: config.json was corrupt and no backup was found - "
|
|
157
|
+
"starting with defaults. Your old config was preserved (see above).")
|
|
134
158
|
# Decrypt secured keys
|
|
135
159
|
cfg = _unsecure_keys(cfg)
|
|
136
160
|
# Backward-compat: legacy single api_key → anthropic_api_key
|
|
@@ -168,7 +192,23 @@ def save_config(cfg: dict):
|
|
|
168
192
|
data = {k: v for k, v in cfg.items() if not k.startswith("_")}
|
|
169
193
|
# Encrypt API keys before saving
|
|
170
194
|
data = _secure_keys(dict(data))
|
|
171
|
-
|
|
195
|
+
payload = json.dumps(data, indent=2)
|
|
196
|
+
# Atomic write: dump to a temp file in the same dir, then replace.
|
|
197
|
+
# A crash mid-write can no longer truncate/corrupt config.json.
|
|
198
|
+
tmp = CONFIG_FILE.with_suffix(".json.tmp")
|
|
199
|
+
tmp.write_text(payload, encoding="utf-8")
|
|
200
|
+
# Keep a rolling backup of the last-known-good config so load_config()
|
|
201
|
+
# can auto-recover if the main file ever goes bad.
|
|
202
|
+
if CONFIG_FILE.exists():
|
|
203
|
+
try:
|
|
204
|
+
backup = CONFIG_FILE.with_suffix(".json.bak")
|
|
205
|
+
# Only back up if current file is valid JSON (don't overwrite a
|
|
206
|
+
# good backup with a corrupt file).
|
|
207
|
+
json.loads(CONFIG_FILE.read_text(encoding="utf-8"))
|
|
208
|
+
backup.write_text(CONFIG_FILE.read_text(encoding="utf-8"), encoding="utf-8")
|
|
209
|
+
except Exception:
|
|
210
|
+
pass
|
|
211
|
+
tmp.replace(CONFIG_FILE)
|
|
172
212
|
|
|
173
213
|
|
|
174
214
|
def current_provider(cfg: dict) -> str:
|
|
@@ -15,6 +15,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
15
15
|
- Security policy
|
|
16
16
|
- Brand guidelines
|
|
17
17
|
|
|
18
|
+
## [3.6.2] - 2026-07-04
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- Opt-in telemetry never sent events: `MP_TOKEN` defaulted to empty string so
|
|
22
|
+
`is_enabled()` was always False, even after user consent. The public
|
|
23
|
+
project's write-only ingestion token now ships as the default
|
|
24
|
+
(`DULUS_MP_TOKEN` env var still overrides).
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
- Named telemetry events (names/counts only — never content): `message_sent`,
|
|
28
|
+
`tool_used`, `command_used`, `model_selected`, wired into the REPL loop,
|
|
29
|
+
tool dispatch, slash commands and `/model`.
|
|
30
|
+
- Memory: session history search improvements — token matching, newest-first
|
|
31
|
+
ordering, no truncation (from 786bd34).
|
|
32
|
+
|
|
18
33
|
## [3.2.0] - 2026-05-30
|
|
19
34
|
|
|
20
35
|
### Added
|
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## 🔥🔥🔥 News (Pacific Time)
|
|
4
4
|
|
|
5
|
+
- July 3, 2026: **🌍 dulus.work — THE HUB, revealed.** The secret is out: dulus.work is a **global hub where every Dulus instance on the planet shares what it learns**. Your Dulus finds a fix, refines a skill, adapts a plugin locally → publishes it to the hub → every other Dulus in the world pulls it down. Not an update server — an **organism**: every installation is a node, every user effectively becomes a contributor. *The fix a Dulus in Tokyo finds at 3AM, the one in Santo Domingo has by sunrise.* Traditional agents scale linearly (N devs improving 1 product); Dulus scales with its network (every user is a dev). The building blocks have been quietly shipping for months — the SelfImprove engine, the Auto-Adapter, the skills format, cloudsave — and dulus.work is where they converge. Domain is live, execution underway. 🦅🇩🇴 → [dulus.work](https://dulus.work)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
- July 3, 2026 (**v3.6.1**): **🛡️ Your config.json can never be silently reset again.** A crash mid-save could truncate `~/.dulus/config.json`; on the next start Dulus silently swallowed the parse error, dropped you into the first-run welcome wizard, and the first auto-save clobbered your real config with bare defaults (classic symptom: a config containing only `voice_lang`). Fixed with a triple safety net: **atomic writes** (temp file + rename — a mid-write crash physically can't corrupt the file anymore), a **rolling `config.json.bak`** of the last-known-good config, **auto-recovery** from that backup when the main file goes bad, and **quarantine** (`config.corrupt-<timestamp>.json`) so the broken original is always preserved for manual rescue instead of vanishing. If you ever lost your config to the welcome screen: this was it, and it's dead now.
|
|
9
|
+
|
|
10
|
+
- July 3, 2026 (**v3.6.0**): **🧙 `/sage` (alias `/sabio`) — the AI studies your prompt before touching anything.** Inspired by ticket-intake classifier patterns but pointed the right way: instead of classifying requests into a database, Dulus decomposes YOUR request like a senior architect doing intake. Four phases: **INTAKE** (break the request into atomic sub-requests with category, priority and a verifiable "done"), **QUALITY GATE** (is it ambiguous? imprecise? mixing unrelated problems? — if missing info BLOCKS execution, ALL clarification questions arrive batched in one AskUserQuestion; otherwise assumptions are stated explicitly and it moves on), **PLAN** (ordered steps, tools per step, risks, verification — multi-step work registers via TaskCreate), **EXECUTE** (verify as you go, adapt honestly when reality diverges). `/sage` alone arms it one-shot for your NEXT prompt; `/sage <request>` runs the full treatment immediately; `/sage off|status` to manage. Difference vs `/plan`: plan mode is read-only planning for big projects — sage studies AND executes in one flow, built for the everyday "think before you act".
|
|
11
|
+
|
|
12
|
+
- July 3, 2026 (**v3.5.0**): **🕵️ unbroker bundled — your agent removes you from data brokers, free.** Ported from [NousResearch/hermes-agent](https://github.com/NousResearch/hermes-agent) (MIT, by SHL0MS) and seasoned for Dulus: finds a consenting person's exposed PII (name, addresses, phones, relatives) across people-search sites and removes it — CCPA/GDPR opt-out emails, cluster-parents-first ordering, verified-before-disclosure, audit ledger, scheduled re-scans when brokers re-list you. Consent-gated, no CAPTCHA defeating, least-disclosure by design. What DeleteMe/Incogni charge a subscription for, as a transparent self-hosted skill your own agent runs. 22 curated brokers out of the box (~50 via BADBOOL refresh + the CA registry's ~545 with the DROP one-shot). Just say: *"remove my data from data brokers"*. Also in 3.5.0: modern web GUI (`dulus --gui`, React + SSE), opt-in anonymous telemetry, Claude vision attachment fix.
|
|
5
13
|
|
|
6
14
|
- July 3, 2026: **📡 dulus.work — the final piece, announced.** Kevin finally revealed the idea he'd been keeping secret — even from Dulus itself (opsec against your own AI is crazy work 😂). Every agent ships updates. Nobody ships THIS. The domain is live, the pieces have been quietly shipping for months (SelfImprove engine, Auto-Adapter, skills format, cloudsave...), and now they converge. Already in motion. Stay tuned. 🦅🇩🇴 → [dulus.work](https://dulus.work)
|
|
7
15
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dulus
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.6.2
|
|
4
4
|
Summary: Use IA as an agent without an API key. $0. Harvest your browser session for Gemini (guest, no login), Claude.ai, Claude Code, Kimi.com, Qwen, DeepSeek — drive them like an API, no per-token billing. Plus LiteLLM (100+ paid providers), local models via Ollama, /lang in 34 languages, Mesa Redonda, voice, OCR, MemPalace, embedded sandbox OS.
|
|
5
5
|
Author: KevRojo
|
|
6
6
|
License: GPL-3.0
|
|
@@ -133,7 +133,7 @@ SET /sticky_input ON since the first run for the best experience!
|
|
|
133
133
|
|
|
134
134
|
> Full changelog: [`docs/news.md`](docs/news.md) · Inside the REPL: `/news`
|
|
135
135
|
|
|
136
|
-
-
|
|
136
|
+
- **🌍 dulus.work — THE HUB. The secret is out.** This is what Kevin had been hiding (even from Dulus itself 😂): **a global hub where every Dulus instance on the planet shares what it learns.** Your Dulus finds a fix, polishes a skill, adapts a plugin → it can publish it to the hub → every other Dulus in the world can pull it down. Not an update server — an **organism**. Every installation is a node. *The fix a Dulus in Tokyo finds at 3AM, the one in Santo Domingo has by sunrise.* Other agents have N devs improving one product (linear). Dulus makes every user a dev (exponential). The pieces have been quietly shipping for months — SelfImprove engine, Auto-Adapter, the skills format, cloudsave — and now they converge. Domain is live, execution underway. 🦅🇩🇴 → [dulus.work](https://dulus.work)
|
|
137
137
|
|
|
138
138
|
- **Dulus Agent — Telegram communities.** Dulus evolved from a CLI tool into a live AI agent inside Telegram groups. The first fully autonomous AI moderating and conversing in real communities — not a bot, not a filter, a real agent. Groups pay in $DULUS to activate him. We host Dulus for them — that's the business model. The CLI stays free forever. This is the paid layer. 🦅🇩🇴
|
|
139
139
|
|
|
@@ -52,6 +52,7 @@ Slash commands in REPL:
|
|
|
52
52
|
/autojob Toggle auto-job printer (auto-print job results)
|
|
53
53
|
/auto_show Toggle auto-show for visual tools (ASCII art, etc.)
|
|
54
54
|
/ultra_search Toggle ULTRA_SEARCH mode
|
|
55
|
+
/sage [req] Sage mode: decompose+quality-check+plan the prompt before executing (alias: /sabio; no arg = arm for next prompt)
|
|
55
56
|
/permissions [mode] Set permission mode
|
|
56
57
|
/afk Toggle AFK mode (auto-dismiss questions, auto-approve tools)
|
|
57
58
|
/yolo Toggle YOLO mode (auto-approve ALL actions without prompts)
|
|
@@ -897,6 +898,7 @@ _HELP_PAGES = [
|
|
|
897
898
|
("/autojob", "Auto-print job results (toggle)"),
|
|
898
899
|
("/auto_show", "Auto-render visual tools (toggle)"),
|
|
899
900
|
("/ultra_search", "Aggressive multi-query search"),
|
|
901
|
+
("/sage [req]", "Sage mode: study+plan prompt before executing (/sabio)"),
|
|
900
902
|
("/permissions [mode]", "Set permission mode"),
|
|
901
903
|
("/afk", "AFK mode (auto-approve tools)"),
|
|
902
904
|
("/yolo", "YOLO mode (auto-approve ALL)"),
|
|
@@ -999,6 +1001,11 @@ def cmd_model(args: str, _state, config) -> bool:
|
|
|
999
1001
|
ok(f"Model set to {m} (provider: {pname})")
|
|
1000
1002
|
from config import save_config
|
|
1001
1003
|
save_config(config)
|
|
1004
|
+
try:
|
|
1005
|
+
import analytics as _telemetry
|
|
1006
|
+
_telemetry.track_model_selected(m, pname)
|
|
1007
|
+
except Exception:
|
|
1008
|
+
pass
|
|
1002
1009
|
return True
|
|
1003
1010
|
|
|
1004
1011
|
def _generate_personas(topic: str, curr_model: str, config: dict, count: int = 5) -> dict | None:
|
|
@@ -8080,6 +8087,78 @@ def cmd_plan(args: str, state, config) -> bool:
|
|
|
8080
8087
|
return ("__plan__", arg)
|
|
8081
8088
|
|
|
8082
8089
|
|
|
8090
|
+
# ── Sage mode (/sage · /sabio) ──────────────────────────────────────────────
|
|
8091
|
+
# Inspired by intake-classifier patterns: before executing, the AI studies the
|
|
8092
|
+
# request like a senior architect doing ticket intake — breaks it into atomic
|
|
8093
|
+
# issues, runs a quality gate (ambiguity/scope/actionability), asks blocking
|
|
8094
|
+
# clarification questions, THEN plans and executes.
|
|
8095
|
+
|
|
8096
|
+
_SAGE_WRAPPER_PREFIX = """[SAGE MODE — structured intake & planning. Study the request below BEFORE acting.]
|
|
8097
|
+
|
|
8098
|
+
Phase 1 — INTAKE (breakdown):
|
|
8099
|
+
Decompose the request into its atomic sub-requests. For each one state: a one-line summary, category (code / research / infra / content / ops / other), priority (low|med|high|critical), and what "done" looks like (verifiable).
|
|
8100
|
+
|
|
8101
|
+
Phase 2 — QUALITY GATE:
|
|
8102
|
+
Evaluate the request itself: is it ambiguous? imprecise? does it mix multiple unrelated problems? is it actionable as stated? in scope for this environment?
|
|
8103
|
+
- If missing info BLOCKS correct execution → ask the user via AskUserQuestion (batch ALL questions into ONE call with options).
|
|
8104
|
+
- Otherwise, state your working assumptions explicitly in one short list and continue.
|
|
8105
|
+
|
|
8106
|
+
Phase 3 — PLAN:
|
|
8107
|
+
Produce a concrete ordered plan: steps, the tools you will use per step, risks, and how you will verify each step actually worked. For multi-step work, register the plan with TaskCreate so progress is trackable.
|
|
8108
|
+
|
|
8109
|
+
Phase 4 — EXECUTE:
|
|
8110
|
+
Execute the plan step by step, verifying as you go. If reality diverges from the plan, say so honestly and adapt. Never report "done" without having verified.
|
|
8111
|
+
|
|
8112
|
+
Show Phases 1-3 to the user briefly (compact, no theater) before executing.
|
|
8113
|
+
|
|
8114
|
+
USER REQUEST:
|
|
8115
|
+
"""
|
|
8116
|
+
|
|
8117
|
+
|
|
8118
|
+
def _sage_wrap(prompt: str) -> str:
|
|
8119
|
+
"""Wrap a raw user prompt in the sage intake+planning contract."""
|
|
8120
|
+
return _SAGE_WRAPPER_PREFIX + prompt
|
|
8121
|
+
|
|
8122
|
+
|
|
8123
|
+
def cmd_sage(args: str, state, config):
|
|
8124
|
+
"""Sage mode — the AI studies, decomposes and plans your prompt before executing.
|
|
8125
|
+
|
|
8126
|
+
/sage — arm sage mode for your NEXT prompt
|
|
8127
|
+
/sage <request> — run intake + planning + execution on <request> right now
|
|
8128
|
+
/sage off — disarm
|
|
8129
|
+
/sage status — show whether sage mode is armed
|
|
8130
|
+
"""
|
|
8131
|
+
arg = args.strip()
|
|
8132
|
+
low = arg.lower()
|
|
8133
|
+
|
|
8134
|
+
if low in ("off", "cancel", "no"):
|
|
8135
|
+
if config.pop("_sage_armed", None):
|
|
8136
|
+
info("Sage mode disarmed.")
|
|
8137
|
+
else:
|
|
8138
|
+
info("Sage mode was not armed.")
|
|
8139
|
+
return True
|
|
8140
|
+
|
|
8141
|
+
if low == "status":
|
|
8142
|
+
if config.get("_sage_armed"):
|
|
8143
|
+
info("Sage mode: ARMED — next prompt will be studied + planned first.")
|
|
8144
|
+
else:
|
|
8145
|
+
info("Sage mode: inactive.")
|
|
8146
|
+
return True
|
|
8147
|
+
|
|
8148
|
+
if not arg:
|
|
8149
|
+
config["_sage_armed"] = True
|
|
8150
|
+
ok("Sage mode armed 🧙 — your NEXT prompt will be decomposed, quality-checked and planned before execution.")
|
|
8151
|
+
info("Cancel with /sage off")
|
|
8152
|
+
return True
|
|
8153
|
+
|
|
8154
|
+
# /sage <request> — run it right now
|
|
8155
|
+
return ("__sage__", arg)
|
|
8156
|
+
|
|
8157
|
+
|
|
8158
|
+
# /sabio is the Spanish alias for /sage
|
|
8159
|
+
cmd_sabio = cmd_sage
|
|
8160
|
+
|
|
8161
|
+
|
|
8083
8162
|
def cmd_compact(args: str, state, config) -> bool:
|
|
8084
8163
|
"""Manually compact conversation history.
|
|
8085
8164
|
|
|
@@ -9640,6 +9719,8 @@ COMMANDS = {
|
|
|
9640
9719
|
"checkpoint": cmd_checkpoint,
|
|
9641
9720
|
"rewind": cmd_rewind,
|
|
9642
9721
|
"plan": cmd_plan,
|
|
9722
|
+
"sage": cmd_sage,
|
|
9723
|
+
"sabio": cmd_sabio,
|
|
9643
9724
|
"compact": cmd_compact,
|
|
9644
9725
|
"init": cmd_init,
|
|
9645
9726
|
"export": cmd_export,
|
|
@@ -9677,9 +9758,14 @@ def handle_slash(line: str, state, config) -> Union[bool, tuple]:
|
|
|
9677
9758
|
args = parts[1] if len(parts) > 1 else ""
|
|
9678
9759
|
handler = COMMANDS.get(cmd)
|
|
9679
9760
|
if handler:
|
|
9761
|
+
try:
|
|
9762
|
+
import analytics as _telemetry
|
|
9763
|
+
_telemetry.track_command_used(cmd)
|
|
9764
|
+
except Exception:
|
|
9765
|
+
pass
|
|
9680
9766
|
result = handler(args, state, config)
|
|
9681
9767
|
# cmd_voice/cmd_image/cmd_brainstorm/cmd_plan return sentinels to ask the REPL to run_query
|
|
9682
|
-
if isinstance(result, tuple) and result[0] in ("__voice__", "__image__", "__video__", "__brainstorm__", "__worker__", "__ssj_cmd__", "__ssj_query__", "__ssj_debate__", "__ssj_passthrough__", "__ssj_promote_worker__", "__plan__", "__plugin_main_agent__", "__roundtable__", "__roundtable_stop__"):
|
|
9768
|
+
if isinstance(result, tuple) and result[0] in ("__voice__", "__image__", "__video__", "__brainstorm__", "__worker__", "__ssj_cmd__", "__ssj_query__", "__ssj_debate__", "__ssj_passthrough__", "__ssj_promote_worker__", "__plan__", "__sage__", "__plugin_main_agent__", "__roundtable__", "__roundtable_stop__"):
|
|
9683
9769
|
return result
|
|
9684
9770
|
return True
|
|
9685
9771
|
|
|
@@ -9761,6 +9847,8 @@ _CMD_META: dict[str, tuple[str, list[str]]] = {
|
|
|
9761
9847
|
"checkpoint": ("List / restore checkpoints", ["clear"]),
|
|
9762
9848
|
"rewind": ("Rewind to checkpoint (alias)", ["clear"]),
|
|
9763
9849
|
"plan": ("Enter/exit plan mode", ["done", "status"]),
|
|
9850
|
+
"sage": ("Sage mode — study+plan the prompt before executing", ["off", "status"]),
|
|
9851
|
+
"sabio": ("Modo sabio — estudia+planifica el prompt antes de ejecutar (alias)", ["off", "status"]),
|
|
9764
9852
|
"compact": ("Compact conversation history", []),
|
|
9765
9853
|
"init": ("Initialize DULUS.md template", []),
|
|
9766
9854
|
"export": ("Export conversation to file", []),
|
|
@@ -10546,6 +10634,12 @@ def repl(config: dict, initial_prompt: str = None):
|
|
|
10546
10634
|
_duplicate_suppressed = False
|
|
10547
10635
|
|
|
10548
10636
|
try:
|
|
10637
|
+
if not is_background:
|
|
10638
|
+
try:
|
|
10639
|
+
import analytics as _telemetry
|
|
10640
|
+
_telemetry.track_message_sent(str(config.get("model", "")))
|
|
10641
|
+
except Exception:
|
|
10642
|
+
pass
|
|
10549
10643
|
for event in run(user_input, state, config, system_prompt):
|
|
10550
10644
|
# Stop spinner only when visible output arrives
|
|
10551
10645
|
if spinner_shown:
|
|
@@ -10610,6 +10704,11 @@ def repl(config: dict, initial_prompt: str = None):
|
|
|
10610
10704
|
if event.name == "AskUserQuestion":
|
|
10611
10705
|
_stop_tool_spinner()
|
|
10612
10706
|
print_tool_start(event.name, event.inputs, verbose)
|
|
10707
|
+
try:
|
|
10708
|
+
import analytics as _telemetry
|
|
10709
|
+
_telemetry.track_tool_used(event.name)
|
|
10710
|
+
except Exception:
|
|
10711
|
+
pass
|
|
10613
10712
|
|
|
10614
10713
|
elif isinstance(event, PermissionRequest):
|
|
10615
10714
|
_stop_tool_spinner()
|
|
@@ -11653,6 +11752,17 @@ def repl(config: dict, initial_prompt: str = None):
|
|
|
11653
11752
|
print(clr("\n (interrupted)", "yellow"))
|
|
11654
11753
|
break
|
|
11655
11754
|
|
|
11755
|
+
# Sage sentinel: ("__sage__", request) — intake + plan + execute
|
|
11756
|
+
if result[0] == "__sage__":
|
|
11757
|
+
_, sage_req = result
|
|
11758
|
+
print(clr(" 🧙 Sage mode — studying the request before acting…", "dim"))
|
|
11759
|
+
try:
|
|
11760
|
+
run_query(_sage_wrap(sage_req))
|
|
11761
|
+
except KeyboardInterrupt:
|
|
11762
|
+
_track_ctrl_c()
|
|
11763
|
+
print(clr("\n (interrupted)", "yellow"))
|
|
11764
|
+
break
|
|
11765
|
+
|
|
11656
11766
|
# Plugin main-agent handoff sentinel:
|
|
11657
11767
|
# ("__plugin_main_agent__", plugin_name, plugin_source)
|
|
11658
11768
|
# Triggered by `/plugin install name@url --main-agent` — the main agent
|
|
@@ -11881,6 +11991,11 @@ def repl(config: dict, initial_prompt: str = None):
|
|
|
11881
11991
|
continue
|
|
11882
11992
|
|
|
11883
11993
|
try:
|
|
11994
|
+
# Sage mode armed via /sage (no args): wrap THIS prompt in the
|
|
11995
|
+
# intake+planning contract, then disarm (one-shot).
|
|
11996
|
+
if config.pop("_sage_armed", None):
|
|
11997
|
+
print(clr(" 🧙 Sage mode — studying the request before acting…", "dim"))
|
|
11998
|
+
user_input = _sage_wrap(user_input)
|
|
11884
11999
|
run_query(user_input)
|
|
11885
12000
|
|
|
11886
12001
|
# Auto-save config after each turn for web providers to persist session IDs
|
|
@@ -62,39 +62,71 @@ def search_session_history(query: str, max_results: int = 5) -> list[dict]:
|
|
|
62
62
|
except Exception:
|
|
63
63
|
continue
|
|
64
64
|
|
|
65
|
-
# 3. Perform search
|
|
65
|
+
# 3. Perform search — NEWEST sessions first, token-based matching
|
|
66
|
+
import re
|
|
67
|
+
q_tokens = [t for t in re.split(r"\W+", query) if len(t) > 2]
|
|
68
|
+
if not q_tokens:
|
|
69
|
+
q_tokens = [query] if query else []
|
|
70
|
+
if not q_tokens:
|
|
71
|
+
return []
|
|
72
|
+
# Require a majority of tokens (at least half, min 1) so multi-word
|
|
73
|
+
# queries still hit even if one word is missing from the message.
|
|
74
|
+
min_hits = max(1, (len(q_tokens) + 1) // 2)
|
|
75
|
+
|
|
76
|
+
# Search most recent sessions first (this is where answers usually live)
|
|
77
|
+
all_sessions.sort(key=lambda s: str(s.get("saved_at", "")), reverse=True)
|
|
78
|
+
|
|
66
79
|
results = []
|
|
67
80
|
for sess in all_sessions:
|
|
68
81
|
session_id = sess.get("session_id", "unknown")
|
|
69
82
|
saved_at = sess.get("saved_at", "unknown")
|
|
70
83
|
messages = sess.get("messages", [])
|
|
71
|
-
|
|
84
|
+
|
|
72
85
|
session_hits = []
|
|
86
|
+
best_score = 0
|
|
73
87
|
for msg in messages:
|
|
74
88
|
content = msg.get("content", "")
|
|
75
89
|
if not isinstance(content, str):
|
|
76
90
|
continue
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
91
|
+
|
|
92
|
+
low = content.lower()
|
|
93
|
+
matched = [t for t in q_tokens if t in low]
|
|
94
|
+
# Accept: full literal phrase OR majority of query tokens
|
|
95
|
+
if query in low or len(matched) >= min_hits:
|
|
96
|
+
anchor = query if query in low else matched[0]
|
|
97
|
+
# Extract snippet around the first matched token
|
|
98
|
+
start = max(0, low.find(anchor) - 60)
|
|
81
99
|
end = min(len(content), start + 200)
|
|
82
100
|
snippet = content[start:end].replace("\n", " ")
|
|
83
101
|
if start > 0: snippet = "..." + snippet
|
|
84
102
|
if end < len(content): snippet += "..."
|
|
85
|
-
|
|
103
|
+
|
|
104
|
+
score = len(q_tokens) + 1 if query in low else len(matched)
|
|
105
|
+
best_score = max(best_score, score)
|
|
86
106
|
session_hits.append({
|
|
87
107
|
"role": msg.get("role"),
|
|
88
|
-
"snippet": snippet
|
|
108
|
+
"snippet": snippet,
|
|
109
|
+
"_score": score,
|
|
89
110
|
})
|
|
90
|
-
|
|
111
|
+
|
|
91
112
|
if session_hits:
|
|
113
|
+
# Best-matching hits first within the session
|
|
114
|
+
session_hits.sort(key=lambda h: h["_score"], reverse=True)
|
|
115
|
+
for h in session_hits:
|
|
116
|
+
h.pop("_score", None)
|
|
92
117
|
results.append({
|
|
93
118
|
"session_id": session_id,
|
|
94
119
|
"saved_at": saved_at,
|
|
95
|
-
"hits": session_hits[:3]
|
|
120
|
+
"hits": session_hits[:3], # limit hits per session to avoid bloat
|
|
121
|
+
"_score": best_score,
|
|
96
122
|
})
|
|
123
|
+
# Early exit: sessions are newest-first, once we have enough
|
|
124
|
+
# strong matches there is no need to scan ancient history.
|
|
125
|
+
if len(results) >= max_results * 3:
|
|
126
|
+
break
|
|
97
127
|
|
|
98
|
-
#
|
|
99
|
-
results.sort(key=lambda x: x["saved_at"], reverse=True)
|
|
128
|
+
# Rank: match quality first, then recency (both already newest-first)
|
|
129
|
+
results.sort(key=lambda x: (x["_score"], str(x["saved_at"])), reverse=True)
|
|
130
|
+
for r in results:
|
|
131
|
+
r.pop("_score", None)
|
|
100
132
|
return results[:max_results]
|
|
@@ -123,17 +123,24 @@ def _memory_search(params: dict, config: dict) -> str:
|
|
|
123
123
|
query = params["query"]
|
|
124
124
|
use_ai = params.get("use_ai", False)
|
|
125
125
|
|
|
126
|
-
|
|
126
|
+
ultra = config.get("ULTRA_SEARCH") in (1, "1", True, "true")
|
|
127
|
+
if ultra:
|
|
127
128
|
params["include_sessions"] = True
|
|
128
|
-
|
|
129
|
+
# Sessions get the big budget; memories stay capped so session
|
|
130
|
+
# matches don't get buried/truncated under 100 memory entries.
|
|
131
|
+
max_results = max(params.get("max_results", 5), 10)
|
|
132
|
+
session_max = 20
|
|
129
133
|
else:
|
|
130
134
|
max_results = params.get("max_results", 5)
|
|
135
|
+
session_max = max_results
|
|
131
136
|
|
|
132
137
|
results = find_relevant_memories(
|
|
133
138
|
query, max_results=max_results * 3, use_ai=use_ai, config=config
|
|
134
139
|
)
|
|
135
140
|
|
|
136
|
-
if not results:
|
|
141
|
+
if not results and not params.get("include_sessions"):
|
|
142
|
+
# Only early-return when sessions won't be searched; otherwise a
|
|
143
|
+
# zero-memory result would skip session history entirely (old bug).
|
|
137
144
|
return f"No memories found matching '{query}'."
|
|
138
145
|
|
|
139
146
|
# Re-rank by confidence × recency score
|
|
@@ -172,15 +179,22 @@ def _memory_search(params: dict, config: dict) -> str:
|
|
|
172
179
|
should_search_sessions = params.get("include_sessions")
|
|
173
180
|
|
|
174
181
|
if should_search_sessions:
|
|
175
|
-
sess_results = search_session_history(query, max_results=
|
|
182
|
+
sess_results = search_session_history(query, max_results=session_max)
|
|
176
183
|
if sess_results:
|
|
177
|
-
|
|
178
|
-
|
|
184
|
+
sess_lines = ["─" * 40,
|
|
185
|
+
f"Historical Session Matches ({len(sess_results)} sessions, newest first):"]
|
|
179
186
|
for sr in sess_results:
|
|
180
|
-
|
|
187
|
+
sess_lines.append(f"\nSession {sr['session_id']} ({sr['saved_at']})")
|
|
181
188
|
for h in sr["hits"]:
|
|
182
189
|
role_lbl = "User" if h["role"] == "user" else "Dulus"
|
|
183
|
-
|
|
190
|
+
sess_lines.append(f" [{role_lbl}] {h['snippet']}")
|
|
191
|
+
if ultra:
|
|
192
|
+
# ULTRA mode: sessions are the primary target — put them FIRST
|
|
193
|
+
# so they never get truncated away under memory entries.
|
|
194
|
+
lines = [lines[0], ""] + sess_lines + ["", "─" * 40, "Memory entries:"] + lines[1:]
|
|
195
|
+
else:
|
|
196
|
+
lines.append("")
|
|
197
|
+
lines.extend(sess_lines)
|
|
184
198
|
|
|
185
199
|
# ── Part 3: Offloaded Jobs Search ────────────────────────────────────
|
|
186
200
|
try:
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "dulus"
|
|
7
|
-
version = "3.
|
|
7
|
+
version = "3.6.2"
|
|
8
8
|
description = "Use IA as an agent without an API key. $0. Harvest your browser session for Gemini (guest, no login), Claude.ai, Claude Code, Kimi.com, Qwen, DeepSeek — drive them like an API, no per-token billing. Plus LiteLLM (100+ paid providers), local models via Ollama, /lang in 34 languages, Mesa Redonda, voice, OCR, MemPalace, embedded sandbox OS."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|