dulus 3.10.2__tar.gz → 3.10.4__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.10.2/dulus.egg-info → dulus-3.10.4}/PKG-INFO +1 -1
- {dulus-3.10.2 → dulus-3.10.4}/compaction.py +21 -0
- {dulus-3.10.2 → dulus-3.10.4/dulus.egg-info}/PKG-INFO +1 -1
- {dulus-3.10.2 → dulus-3.10.4}/dulus.py +194 -1
- {dulus-3.10.2 → dulus-3.10.4}/dulus_mcp/client.py +19 -0
- {dulus-3.10.2 → dulus-3.10.4}/pyproject.toml +1 -1
- {dulus-3.10.2 → dulus-3.10.4}/skill/clawhub.py +200 -0
- {dulus-3.10.2 → dulus-3.10.4}/LICENSE +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/MANIFEST.in +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/README.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/agent.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/analytics.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/backend/__init__.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/backend/agents_bridge.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/backend/compressor.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/backend/context.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/backend/githook.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/backend/marketplace.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/backend/mempalace_bridge.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/backend/personas.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/backend/plugins.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/backend/server.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/backend/tasks.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/batch_api.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/checkpoint/__init__.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/checkpoint/hooks.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/checkpoint/store.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/checkpoint/types.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/claude_code_watcher.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/clipboard_utils.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/cloudsave.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/common.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/config.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/context.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/data/__init__.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/data/active_persona.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/data/marketplace.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/data/personas.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/data/plugins/__init__.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/data/plugins/composio/__init__.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/data/plugins/composio/composio_plugin/__init__.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/data/plugins/composio/composio_plugin/session_manager.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/data/plugins/composio/composio_plugin/tool_generator.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/data/plugins/composio/plugin.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/data/plugins/composio/plugin_tool.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/data/tasks.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/display_blocks.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/API.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/BRAND_GUIDE.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/CHANGELOG.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/CONTRIBUTING.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/DEPLOYMENT.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/Dulus_AI_Whitepaper_(v2.0).pdf +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/FAQ.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/GETTING_STARTED.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/INVESTOR_BRIEF.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/README.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/README_AR.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/README_EN.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/README_ES.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/README_FR.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/README_JA.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/README_KO.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/README_PT.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/README_RU.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/README_ZH.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/SECURITY.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/__init__.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/api.html +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/architecture.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/azure-speech-template.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/dashboard/index.html +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/divider.svg +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/dulus-bird.png +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/generate.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/hero.svg +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/index.html +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/news.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/nvidia-models.svg +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/particle-playground.html +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/personas/index.html +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/poetry-banner.png +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/preview.html +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/sec-agents.svg +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/sec-brainstorm.svg +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/sec-bridges.svg +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/sec-features.svg +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/sec-freetier.svg +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/sec-memory.svg +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/sec-models.svg +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/sec-perms.svg +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/sec-plugins.svg +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/sec-quickstart.svg +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/sec-ssj.svg +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/spinners.svg +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/split-pane.svg +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/docs/terminal-boot.svg +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/dulus.egg-info/SOURCES.txt +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/dulus.egg-info/dependency_links.txt +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/dulus.egg-info/entry_points.txt +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/dulus.egg-info/requires.txt +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/dulus.egg-info/top_level.txt +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/dulus_gui.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/dulus_gui_web.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/dulus_mcp/__init__.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/dulus_mcp/commands.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/dulus_mcp/config.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/dulus_mcp/hub.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/dulus_mcp/tools.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/dulus_mcp/types.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/dulus_tools/__init__.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/dulus_tools/add_dir_manager.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/dulus_tools/afk_mode.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/dulus_tools/approval_runtime.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/dulus_tools/background_tasks.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/dulus_tools/clipboard_utils.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/dulus_tools/diff_visualizer.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/dulus_tools/export_import.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/dulus_tools/hook_engine.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/dulus_tools/notification_manager.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/dulus_tools/session_fork.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/dulus_tools/shell_mode.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/dulus_tools/todo_visualizer.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/dulus_tools/tree_ls.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/dulus_tools/wire_events.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/dulus_tools/yolo_mode.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/file_filter.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/governance.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/gui/__init__.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/gui/agent_bridge.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/gui/chat_widget.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/gui/dulus_orchestrator/App.tsx +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/gui/dulus_orchestrator/THIRD_PARTY_NOTICES.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/gui/dulus_orchestrator/dist/index.html +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/gui/dulus_orchestrator/dist/standalone.html +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/gui/dulus_orchestrator/index.html +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/gui/dulus_orchestrator/index.tsx +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/gui/dulus_orchestrator/metadata.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/gui/dulus_orchestrator/package-lock.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/gui/dulus_orchestrator/package.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/gui/dulus_orchestrator/types.ts +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/gui/dulus_orchestrator/utils.ts +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/gui/dulus_orchestrator/vertex-ai-proxy-interceptor.js +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/gui/dulus_orchestrator/vite.config.ts +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/gui/main_window.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/gui/personas.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/gui/sandbox_server.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/gui/session_utils.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/gui/settings_dialog.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/gui/sidebar.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/gui/tasks_view.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/gui/themes.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/gui/tool_panel.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/input.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/license_manager.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/memory/__init__.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/memory/audit.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/memory/consolidator.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/memory/context.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/memory/offload.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/memory/palace.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/memory/scan.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/memory/sessions.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/memory/store.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/memory/tools.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/memory/types.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/memory/vector_search.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/multi_agent/__init__.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/multi_agent/subagent.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/multi_agent/tools.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/offload_helper.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/paste_placeholders.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/personality_config.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/plugin/__init__.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/plugin/autoadapter.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/plugin/loader.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/plugin/recommend.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/plugin/store.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/plugin/types.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/profiles.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/providers.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/sandbox/README.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/sandbox/__init__.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/sandbox/components.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/sandbox/dist/assets/index-DE51D6wI.css +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/sandbox/dist/assets/index-DMCCNE9Y.js +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/sandbox/dist/index.html +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/sandbox/dist/wallpaper-default.jpg +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/sandbox/dist/wallpapers/default.jpeg +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/sandbox/dist/wallpapers/light.jpeg +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/sandbox/dist/wallpapers/nature.jpeg +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/sandbox/dist/wallpapers/tech.jpeg +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/sandbox/eslint.config.js +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/sandbox/index.html +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/sandbox/info.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/sandbox/package-lock.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/sandbox/package.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/sandbox/postcss.config.js +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/sandbox/tailwind.config.js +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/sandbox/tsconfig.app.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/sandbox/tsconfig.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/sandbox/tsconfig.node.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/sandbox/vite.config.ts +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/sandbox_bootstrap.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/setup.cfg +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/__init__.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/builtin.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/LICENSE-obsidian-skills +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/README.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/defuddle/SKILL.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/json-canvas/SKILL.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/json-canvas/references/EXAMPLES.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/obsidian-bases/SKILL.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/obsidian-bases/references/FUNCTIONS_REFERENCE.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/obsidian-cli/SKILL.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/obsidian-markdown/SKILL.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/obsidian-markdown/references/CALLOUTS.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/obsidian-markdown/references/EMBEDS.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/obsidian-markdown/references/PROPERTIES.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/README.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/SKILL.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/addresses.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/advancedbackgroundchecks.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/beenverified.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/clustal.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/clustrmaps.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/cyberbackgroundchecks.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/familytreenow.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/fastpeoplesearch.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/intelius.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/mylife.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/nuwber.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/peekyou.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/peoplefinders.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/radaris.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/rehold.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/searchpeoplefree.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/socialcatfish.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/spokeo.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/thatsthem.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/truepeoplesearch.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/usphonebook.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/whitepages.json +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/references/legal/ccpa.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/references/legal/drop.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/references/legal/gdpr.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/references/methods.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/references/site-playbooks.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/references/state-machine.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/scripts/autopilot.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/scripts/badbool.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/scripts/brokers.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/scripts/cdp.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/scripts/config.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/scripts/crypto.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/scripts/dossier.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/scripts/email_modes.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/scripts/emailer.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/scripts/ledger.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/scripts/legal.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/scripts/paths.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/scripts/pdd.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/scripts/registry.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/scripts/report.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/scripts/scan.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/scripts/storage.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/scripts/tiers.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/scripts/vectors.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/templates/consent/authorization.md +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/templates/emails/ccpa-authorized-agent.txt +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/templates/emails/ccpa-deletion.txt +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/templates/emails/ccpa-indirect-deletion.txt +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/templates/emails/gdpr-erasure.txt +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/bundled/unbroker/templates/emails/generic-optout.txt +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/executor.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/loader.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skill/tools.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/skills.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/soul.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/spinner.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/steer_input.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/string_utils.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/subagent.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/task/__init__.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/task/store.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/task/tools.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/task/types.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tests/test_afk_yolo.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tests/test_approval_runtime.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tests/test_background_task_tools.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tests/test_background_tasks.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tests/test_checkpoint.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tests/test_clipboard_utils.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tests/test_compaction.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tests/test_diff_view.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tests/test_diff_visualization.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tests/test_display_blocks.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tests/test_export_import.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tests/test_hook_engine.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tests/test_injection_fix.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tests/test_license.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tests/test_mcp.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tests/test_memory.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tests/test_notification_manager.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tests/test_plugin.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tests/test_session_fork.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tests/test_shell_mode.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tests/test_skills.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tests/test_steer_input.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tests/test_subagent.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tests/test_task.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tests/test_telegram_buffer.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tests/test_think_tool.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tests/test_todo_tool.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tests/test_todo_visualization.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tests/test_tool_registry.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tests/test_tree_ls.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tests/test_voice.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tests/test_wire_events.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tmux_offloader.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tmux_tools.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tool_registry.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tools.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tools_background.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tools_think.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/tools_todo.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/ui/__init__.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/ui/input.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/ui/render.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/updater.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/voice/__init__.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/voice/audio_utils.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/voice/keyterms.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/voice/recorder.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/voice/stt.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/voice/tts.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/voice/wake_word.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/webbridge/__init__.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/webbridge/core.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/webbridge/tools.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/webchat.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/webchat_server.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/webchat_ui/__init__.py +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/webchat_ui/app.js +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/webchat_ui/index.html +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/webchat_ui/style.css +0 -0
- {dulus-3.10.2 → dulus-3.10.4}/welcome.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dulus
|
|
3
|
-
Version: 3.10.
|
|
3
|
+
Version: 3.10.4
|
|
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
|
|
@@ -78,6 +78,27 @@ def estimate_tokens(messages: list, model: str = "", config: dict | None = None,
|
|
|
78
78
|
for v in tc.values():
|
|
79
79
|
if isinstance(v, str):
|
|
80
80
|
total_chars += len(v)
|
|
81
|
+
# ── Images / videos (2026-07-13 fix) ──────────────────────────────
|
|
82
|
+
# agent.py attaches /image and /video payloads as sibling keys
|
|
83
|
+
# user_msg["images"] / user_msg["videos"] (base64 strings), NOT
|
|
84
|
+
# inside "content". This loop used to never look at them, so a
|
|
85
|
+
# single video attachment (often 200-800k+ base64 chars) was
|
|
86
|
+
# completely invisible to the token estimate. Result: compaction
|
|
87
|
+
# thought the conversation was small and never fired auto-compact,
|
|
88
|
+
# while the model actually receiving that payload (e.g. Kimi's
|
|
89
|
+
# multimodal video support) silently ballooned past its real
|
|
90
|
+
# context window and degraded. Count them like any other content.
|
|
91
|
+
for key in ("images", "videos"):
|
|
92
|
+
items = m.get(key)
|
|
93
|
+
if not items:
|
|
94
|
+
continue
|
|
95
|
+
for item in items:
|
|
96
|
+
if isinstance(item, str):
|
|
97
|
+
total_chars += len(item)
|
|
98
|
+
elif isinstance(item, dict):
|
|
99
|
+
data = item.get("data", "")
|
|
100
|
+
if isinstance(data, str):
|
|
101
|
+
total_chars += len(data)
|
|
81
102
|
content_tokens = int(total_chars / 2.8)
|
|
82
103
|
framing_tokens = msg_count * 4 # role + delimiters overhead per msg
|
|
83
104
|
return int((content_tokens + framing_tokens) * 1.1)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dulus
|
|
3
|
-
Version: 3.10.
|
|
3
|
+
Version: 3.10.4
|
|
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
|
|
@@ -121,6 +121,7 @@ Slash commands in REPL:
|
|
|
121
121
|
/roundtable Start a multi-model roundtable discussion
|
|
122
122
|
/fork Fork session at a given turn
|
|
123
123
|
/undo Undo last turn
|
|
124
|
+
/workspace [cmd] Manage Dulus workspaces (switch/list/default)
|
|
124
125
|
/add-dir [path] Manage additional workspace directories
|
|
125
126
|
/import <file> Import conversation from file or session
|
|
126
127
|
/harvest Harvest Claude.ai cookies (alias: /harvest-claude)
|
|
@@ -854,6 +855,7 @@ _HELP_PAGES = [
|
|
|
854
855
|
("/cost", "Show API cost this session"),
|
|
855
856
|
("/fork", "Fork session at a given turn"),
|
|
856
857
|
("/undo", "Undo last turn"),
|
|
858
|
+
("/workspace [cmd]", "Manage Dulus workspaces (switch/list/default)"),
|
|
857
859
|
("/import <file>","Import conversation from file/session"),
|
|
858
860
|
("/add-dir [path]","Manage additional workspace directories"),
|
|
859
861
|
("/batch", "Manage Kimi Batch tasks"),
|
|
@@ -4012,6 +4014,167 @@ def cmd_cwd(args: str, _state, config) -> bool:
|
|
|
4012
4014
|
err(str(e))
|
|
4013
4015
|
return True
|
|
4014
4016
|
|
|
4017
|
+
|
|
4018
|
+
# ── Workspace manager ───────────────────────────────────────────────────────
|
|
4019
|
+
|
|
4020
|
+
_WORKSPACES_DIR: Path = Path.home() / ".dulus" / "workspaces"
|
|
4021
|
+
_DEFAULT_WORKSPACE: str = "workspace1"
|
|
4022
|
+
|
|
4023
|
+
|
|
4024
|
+
def _workspace_path(name: str) -> Path:
|
|
4025
|
+
return _WORKSPACES_DIR / name
|
|
4026
|
+
|
|
4027
|
+
|
|
4028
|
+
def _ensure_workspace(name: str) -> Path:
|
|
4029
|
+
"""Create workspace dir if missing and return its path."""
|
|
4030
|
+
ws = _workspace_path(name)
|
|
4031
|
+
ws.mkdir(parents=True, exist_ok=True)
|
|
4032
|
+
return ws
|
|
4033
|
+
|
|
4034
|
+
|
|
4035
|
+
def _list_workspaces() -> list[str]:
|
|
4036
|
+
if not _WORKSPACES_DIR.exists():
|
|
4037
|
+
return []
|
|
4038
|
+
return sorted(p.name for p in _WORKSPACES_DIR.iterdir() if p.is_dir())
|
|
4039
|
+
|
|
4040
|
+
|
|
4041
|
+
def _current_workspace_name() -> str | None:
|
|
4042
|
+
"""Return the workspace name if cwd is inside ~/.dulus/workspaces/."""
|
|
4043
|
+
try:
|
|
4044
|
+
cwd = Path.cwd().resolve()
|
|
4045
|
+
root = _WORKSPACES_DIR.resolve()
|
|
4046
|
+
if root in cwd.parents or cwd == root:
|
|
4047
|
+
rel = cwd.relative_to(root)
|
|
4048
|
+
first = rel.parts[0] if rel.parts else None
|
|
4049
|
+
if first and _workspace_path(first).is_dir():
|
|
4050
|
+
return first
|
|
4051
|
+
except Exception:
|
|
4052
|
+
pass
|
|
4053
|
+
return None
|
|
4054
|
+
|
|
4055
|
+
|
|
4056
|
+
def _activate_workspace(name: str, config: dict) -> bool:
|
|
4057
|
+
"""Change cwd into workspace, create it if missing, and persist as last used."""
|
|
4058
|
+
from config import save_config
|
|
4059
|
+
ws = _ensure_workspace(name)
|
|
4060
|
+
try:
|
|
4061
|
+
os.chdir(ws)
|
|
4062
|
+
config["workspace_last"] = name
|
|
4063
|
+
save_config(config)
|
|
4064
|
+
# Directory changed — git info is stale
|
|
4065
|
+
if _git_prompt is not None:
|
|
4066
|
+
_git_prompt.reset_git_cache()
|
|
4067
|
+
return True
|
|
4068
|
+
except Exception as e:
|
|
4069
|
+
err(f"No pude cambiar al workspace '{name}': {e}")
|
|
4070
|
+
return False
|
|
4071
|
+
|
|
4072
|
+
|
|
4073
|
+
def _apply_workspace(config: dict) -> None:
|
|
4074
|
+
"""At boot, move cwd into the last-used workspace (or workspace1)."""
|
|
4075
|
+
last = config.get("workspace_last") or _DEFAULT_WORKSPACE
|
|
4076
|
+
ws = _workspace_path(last)
|
|
4077
|
+
if not ws.exists():
|
|
4078
|
+
_ensure_workspace(last)
|
|
4079
|
+
try:
|
|
4080
|
+
os.chdir(ws)
|
|
4081
|
+
if config.get("verbose", False):
|
|
4082
|
+
info(f"Workspace activo: {last}")
|
|
4083
|
+
except Exception as e:
|
|
4084
|
+
warn(f"No pude entrar al workspace '{last}': {e}")
|
|
4085
|
+
|
|
4086
|
+
|
|
4087
|
+
def cmd_workspace(args: str, _state, config) -> bool:
|
|
4088
|
+
"""Manage Dulus workspaces under ~/.dulus/workspaces.
|
|
4089
|
+
|
|
4090
|
+
/workspace — show current workspace + cwd
|
|
4091
|
+
/workspace current — same as above
|
|
4092
|
+
/workspace list — list workspaces
|
|
4093
|
+
/workspace switch <name> — change to workspace (creates if missing)
|
|
4094
|
+
/workspace default [name] — show or set the startup workspace
|
|
4095
|
+
/workspace create <name> — create a workspace without switching
|
|
4096
|
+
/workspace delete <name> — delete a workspace (must be empty)
|
|
4097
|
+
"""
|
|
4098
|
+
from config import save_config
|
|
4099
|
+
parts = args.strip().split(None, 1)
|
|
4100
|
+
subcmd = parts[0].lower() if parts else "current"
|
|
4101
|
+
rest = parts[1].strip() if len(parts) > 1 else ""
|
|
4102
|
+
|
|
4103
|
+
if subcmd in ("current", "cwd", ""):
|
|
4104
|
+
current = _current_workspace_name()
|
|
4105
|
+
if current:
|
|
4106
|
+
info(f"Workspace: {current}")
|
|
4107
|
+
else:
|
|
4108
|
+
info("No estás dentro de un workspace de Dulus.")
|
|
4109
|
+
info(f"Working directory: {os.getcwd()}")
|
|
4110
|
+
return True
|
|
4111
|
+
|
|
4112
|
+
if subcmd == "list":
|
|
4113
|
+
workspaces = _list_workspaces()
|
|
4114
|
+
current = _current_workspace_name()
|
|
4115
|
+
if not workspaces:
|
|
4116
|
+
info("No hay workspaces todavía. Usa /workspace create <nombre>.")
|
|
4117
|
+
return True
|
|
4118
|
+
info(f"Workspaces en {_WORKSPACES_DIR}:")
|
|
4119
|
+
for w in workspaces:
|
|
4120
|
+
mark = " → " if w == current else " "
|
|
4121
|
+
print(f"{mark}{w}")
|
|
4122
|
+
return True
|
|
4123
|
+
|
|
4124
|
+
if subcmd == "switch":
|
|
4125
|
+
if not rest:
|
|
4126
|
+
err("Uso: /workspace switch <nombre>")
|
|
4127
|
+
return True
|
|
4128
|
+
name = rest.split()[0]
|
|
4129
|
+
if _activate_workspace(name, config):
|
|
4130
|
+
ok(f"Workspace cambiado a: {name}")
|
|
4131
|
+
return True
|
|
4132
|
+
|
|
4133
|
+
if subcmd == "default":
|
|
4134
|
+
if not rest:
|
|
4135
|
+
current_default = config.get("workspace_last") or _DEFAULT_WORKSPACE
|
|
4136
|
+
info(f"Workspace por defecto: {current_default}")
|
|
4137
|
+
return True
|
|
4138
|
+
name = rest.split()[0]
|
|
4139
|
+
_ensure_workspace(name)
|
|
4140
|
+
config["workspace_last"] = name
|
|
4141
|
+
save_config(config)
|
|
4142
|
+
ok(f"Workspace por defecto ahora: {name}")
|
|
4143
|
+
return True
|
|
4144
|
+
|
|
4145
|
+
if subcmd == "create":
|
|
4146
|
+
if not rest:
|
|
4147
|
+
err("Uso: /workspace create <nombre>")
|
|
4148
|
+
return True
|
|
4149
|
+
name = rest.split()[0]
|
|
4150
|
+
_ensure_workspace(name)
|
|
4151
|
+
ok(f"Workspace creado: {name}")
|
|
4152
|
+
return True
|
|
4153
|
+
|
|
4154
|
+
if subcmd == "delete":
|
|
4155
|
+
if not rest:
|
|
4156
|
+
err("Uso: /workspace delete <nombre>")
|
|
4157
|
+
return True
|
|
4158
|
+
name = rest.split()[0]
|
|
4159
|
+
target = _workspace_path(name)
|
|
4160
|
+
if not target.exists():
|
|
4161
|
+
err(f"Workspace '{name}' no existe.")
|
|
4162
|
+
return True
|
|
4163
|
+
current = _current_workspace_name()
|
|
4164
|
+
if name == current:
|
|
4165
|
+
err("No puedes borrar el workspace en el que estás. Cambia primero con /workspace switch.")
|
|
4166
|
+
return True
|
|
4167
|
+
try:
|
|
4168
|
+
target.rmdir()
|
|
4169
|
+
ok(f"Workspace borrado: {name}")
|
|
4170
|
+
except OSError as e:
|
|
4171
|
+
err(f"No se pudo borrar '{name}': {e}. Asegúrate de que esté vacío.")
|
|
4172
|
+
return True
|
|
4173
|
+
|
|
4174
|
+
err(f"Subcomando desconocido: /workspace {subcmd}")
|
|
4175
|
+
return True
|
|
4176
|
+
|
|
4177
|
+
|
|
4015
4178
|
def _build_session_data(state, session_id: str | None = None) -> dict:
|
|
4016
4179
|
"""Serialize current conversation state to a JSON-serializable dict."""
|
|
4017
4180
|
import uuid
|
|
@@ -4922,6 +5085,7 @@ def cmd_skill(args: str, state, config) -> bool:
|
|
|
4922
5085
|
list_awesome_remote, list_composio_toolkits,
|
|
4923
5086
|
install_awesome_remote,
|
|
4924
5087
|
list_dulus_remote, install_dulus_remote,
|
|
5088
|
+
search_skillssh, install_skillssh,
|
|
4925
5089
|
)
|
|
4926
5090
|
from pathlib import Path as _Path
|
|
4927
5091
|
|
|
@@ -4959,11 +5123,12 @@ def cmd_skill(args: str, state, config) -> bool:
|
|
|
4959
5123
|
print(f" 4) {clr('local', 'yellow')} — Anthropic + awesome marketplaces on disk (~/.claude/...)")
|
|
4960
5124
|
print(f" 5) {clr('installed', 'yellow')} — skills already in ~/.dulus/skills/")
|
|
4961
5125
|
print(f" 6) {clr('all', 'yellow')} — combine dulus + awesome + composio + local")
|
|
5126
|
+
print(f" 7) {clr('skillssh', 'yellow')} — skills.sh open directory (100K+ skills, needs a search term)")
|
|
4962
5127
|
try:
|
|
4963
5128
|
choice = input(clr(" > ", "cyan")).strip().lower()
|
|
4964
5129
|
except (EOFError, KeyboardInterrupt):
|
|
4965
5130
|
return True
|
|
4966
|
-
mapping = {"1": "dulus", "2": "awesome", "3": "composio", "4": "local", "5": "installed", "6": "all"}
|
|
5131
|
+
mapping = {"1": "dulus", "2": "awesome", "3": "composio", "4": "local", "5": "installed", "6": "all", "7": "skillssh"}
|
|
4967
5132
|
rest = mapping.get(choice, choice)
|
|
4968
5133
|
|
|
4969
5134
|
if rest.startswith("dulus"):
|
|
@@ -5072,6 +5237,23 @@ def cmd_skill(args: str, state, config) -> bool:
|
|
|
5072
5237
|
print(f" {clr(r['slug'], 'cyan'):30s} {r.get('description','')[:60]}")
|
|
5073
5238
|
return True
|
|
5074
5239
|
|
|
5240
|
+
if rest.startswith("skillssh"):
|
|
5241
|
+
q = rest[8:].strip()
|
|
5242
|
+
if not q:
|
|
5243
|
+
info("skills.sh has no browse endpoint — give me a search term: /skill list skillssh <query>")
|
|
5244
|
+
return True
|
|
5245
|
+
info(f"Searching skills.sh for '{q}'...")
|
|
5246
|
+
results = search_skillssh(q, limit=25)
|
|
5247
|
+
if not results:
|
|
5248
|
+
err("No skills found on skills.sh (network or empty result).")
|
|
5249
|
+
return True
|
|
5250
|
+
lines = [
|
|
5251
|
+
f" {clr(s['id'], 'cyan'):60s} {s.get('description', '')[:70]}"
|
|
5252
|
+
for s in results
|
|
5253
|
+
]
|
|
5254
|
+
_pager(f"skills.sh results ({len(results)}) — /skill get <id> to install — n=next q=quit", lines)
|
|
5255
|
+
return True
|
|
5256
|
+
|
|
5075
5257
|
if rest.startswith("installed"):
|
|
5076
5258
|
query = rest[9:].strip()
|
|
5077
5259
|
skills = list_installed(query)
|
|
@@ -5179,6 +5361,8 @@ def cmd_skill(args: str, state, config) -> bool:
|
|
|
5179
5361
|
if rest.startswith("clawhub:"):
|
|
5180
5362
|
slug = rest[8:]
|
|
5181
5363
|
success, msg = install_clawhub(slug)
|
|
5364
|
+
elif rest.startswith("skillssh/"):
|
|
5365
|
+
success, msg = install_skillssh(rest)
|
|
5182
5366
|
elif rest.startswith("dulus/"):
|
|
5183
5367
|
success, msg = install_dulus_remote(rest)
|
|
5184
5368
|
elif rest.startswith("awesome/"):
|
|
@@ -9935,6 +10119,7 @@ COMMANDS = {
|
|
|
9935
10119
|
"export": cmd_export,
|
|
9936
10120
|
"fork": cmd_fork,
|
|
9937
10121
|
"undo": cmd_undo,
|
|
10122
|
+
"workspace": cmd_workspace,
|
|
9938
10123
|
"add-dir": cmd_add_dir,
|
|
9939
10124
|
"import": cmd_import,
|
|
9940
10125
|
"copy": cmd_copy,
|
|
@@ -10068,6 +10253,7 @@ _CMD_META: dict[str, tuple[str, list[str]]] = {
|
|
|
10068
10253
|
"export": ("Export conversation to file", []),
|
|
10069
10254
|
"fork": ("Fork session at a turn", []),
|
|
10070
10255
|
"undo": ("Undo last turn", []),
|
|
10256
|
+
"workspace": ("Manage Dulus workspaces", ["current", "list", "switch", "default", "create", "delete"]),
|
|
10071
10257
|
"add-dir": ("Manage additional workspace dirs", ["list", "remove"]),
|
|
10072
10258
|
"import": ("Import from file or session", []),
|
|
10073
10259
|
"copy": ("Copy last response or file to clipboard", ["file"]),
|
|
@@ -10518,6 +10704,13 @@ def repl(config: dict, initial_prompt: str = None):
|
|
|
10518
10704
|
except Exception:
|
|
10519
10705
|
pass
|
|
10520
10706
|
|
|
10707
|
+
# ── Workspace: start in the last-used workspace (or workspace1) ──────────
|
|
10708
|
+
try:
|
|
10709
|
+
_apply_workspace(config)
|
|
10710
|
+
except Exception as _e:
|
|
10711
|
+
if config.get("verbose", False):
|
|
10712
|
+
warn(f"Workspace init skipped: {_e}")
|
|
10713
|
+
|
|
10521
10714
|
# ── Auto-update check (on by default; /update off to disable) ─────────
|
|
10522
10715
|
# Keep every Dulus in the world on the latest release: the organism
|
|
10523
10716
|
# heals fastest when fixes propagate instantly. This is intentionally
|
|
@@ -36,6 +36,17 @@ class StdioTransport:
|
|
|
36
36
|
|
|
37
37
|
def start(self) -> None:
|
|
38
38
|
env = {**os.environ, **(self._config.env or {})}
|
|
39
|
+
# ── Guard: empty/blank command ────────────────────────────────────
|
|
40
|
+
# A corrupted or half-filled-out .mcp.json entry (e.g. {"command": ""})
|
|
41
|
+
# used to reach subprocess.Popen([""], ...) directly. On Windows that
|
|
42
|
+
# raises a bare OSError [WinError 87] "The parameter is incorrect" with
|
|
43
|
+
# zero context — impossible to debug from the GUI toast. Fail fast with
|
|
44
|
+
# a message that names the actual broken server instead.
|
|
45
|
+
if not (self._config.command or "").strip():
|
|
46
|
+
raise RuntimeError(
|
|
47
|
+
f"MCP server '{self._config.name}' has no command configured "
|
|
48
|
+
f"(empty 'command' field in mcp.json). Fix or remove this entry."
|
|
49
|
+
)
|
|
39
50
|
# ── Windows launcher resolution ──────────────────────────────────
|
|
40
51
|
# On Windows, node tooling ships as .cmd shims (npx.cmd, npm.cmd) and
|
|
41
52
|
# Python's subprocess won't find a bare "npx" without the extension.
|
|
@@ -46,6 +57,14 @@ class StdioTransport:
|
|
|
46
57
|
_resolved = _shutil.which(_launcher)
|
|
47
58
|
if _resolved:
|
|
48
59
|
_launcher = _resolved
|
|
60
|
+
elif os.name == "nt" and not _launcher.lower().endswith((".exe", ".cmd", ".bat")):
|
|
61
|
+
# Launcher wasn't found on PATH at all (typo, uninstalled runtime,
|
|
62
|
+
# etc). Raise a clean, actionable error instead of letting Popen
|
|
63
|
+
# attempt to launch a bare/garbled name and blow up with WinError87.
|
|
64
|
+
raise RuntimeError(
|
|
65
|
+
f"MCP server '{self._config.name}': launcher '{_launcher}' not found on PATH. "
|
|
66
|
+
f"Install it or fix the 'command' field in mcp.json."
|
|
67
|
+
)
|
|
49
68
|
cmd = [_launcher] + list(self._config.args or [])
|
|
50
69
|
# On Windows, .cmd/.bat shims must run through the shell layer.
|
|
51
70
|
_use_shell = os.name == "nt" and str(_launcher).lower().endswith((".cmd", ".bat"))
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "dulus"
|
|
7
|
-
version = "3.10.
|
|
7
|
+
version = "3.10.4"
|
|
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"
|
|
@@ -731,6 +731,206 @@ def install_clawhub(slug: str) -> tuple[bool, str]:
|
|
|
731
731
|
return False, f"ClawHub API endpoint not yet mapped. Try: /skill get local/{slug}"
|
|
732
732
|
|
|
733
733
|
|
|
734
|
+
# ── SKILLS.SH (skills.sh — the open agent skills directory) ────────────────
|
|
735
|
+
# skills.sh indexes 100K+ agent skills across GitHub repos. Search hits its
|
|
736
|
+
# public JSON API; install downloads the skill folder straight from the source
|
|
737
|
+
# repo via raw.githubusercontent.com — no npx, no clone. Fallback catalog:
|
|
738
|
+
# skillsdirectory.com public registry.
|
|
739
|
+
|
|
740
|
+
_SKILLSSH_API = "https://skills.sh/api/search"
|
|
741
|
+
_SKILLSSH_FALLBACK_API = "https://skillsdirectory.com/api/registry"
|
|
742
|
+
_SKILLSSH_TIMEOUT = 15
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
def _skillssh_gh_headers() -> dict:
|
|
746
|
+
"""GitHub API headers — uses GITHUB_TOKEN/GH_TOKEN if present for rate limits."""
|
|
747
|
+
import os as _os
|
|
748
|
+
headers = {"User-Agent": "dulus-skill-manager"}
|
|
749
|
+
token = _os.environ.get("GITHUB_TOKEN") or _os.environ.get("GH_TOKEN")
|
|
750
|
+
if token:
|
|
751
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
752
|
+
return headers
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
def search_skillssh(query: Optional[str], limit: int = 10) -> list[dict]:
|
|
756
|
+
"""Search skills.sh for skills matching query.
|
|
757
|
+
|
|
758
|
+
Returns entries shaped like the other providers:
|
|
759
|
+
{id: "skillssh/<owner>/<repo>/<skill>", skill, name, description, source}
|
|
760
|
+
An empty query returns [] (skills.sh has no browse endpoint; use a term).
|
|
761
|
+
"""
|
|
762
|
+
if not query or not str(query).strip():
|
|
763
|
+
return []
|
|
764
|
+
|
|
765
|
+
url = f"{_SKILLSSH_API}?q={urllib.parse.quote(str(query).strip())}"
|
|
766
|
+
data = None
|
|
767
|
+
try:
|
|
768
|
+
req = urllib.request.Request(url, headers={"User-Agent": "dulus-skill-manager"})
|
|
769
|
+
with urllib.request.urlopen(req, timeout=_SKILLSSH_TIMEOUT) as resp:
|
|
770
|
+
data = json.loads(resp.read())
|
|
771
|
+
except Exception:
|
|
772
|
+
data = None
|
|
773
|
+
|
|
774
|
+
skills: list[dict] = []
|
|
775
|
+
if data and isinstance(data.get("skills"), list):
|
|
776
|
+
for it in data["skills"][: max(limit, 1)]:
|
|
777
|
+
sid = it.get("id", "") # "owner/repo/skillId"
|
|
778
|
+
repo = it.get("source", "") # "owner/repo"
|
|
779
|
+
name = it.get("skillId") or it.get("name", "")
|
|
780
|
+
installs = it.get("installs", 0)
|
|
781
|
+
if not sid or not name:
|
|
782
|
+
continue
|
|
783
|
+
skills.append({
|
|
784
|
+
"id": f"skillssh/{sid}",
|
|
785
|
+
"skill": name,
|
|
786
|
+
"name": name,
|
|
787
|
+
"description": f"{installs:,} installs · {repo} (skills.sh)",
|
|
788
|
+
"installs": installs,
|
|
789
|
+
"repo": repo,
|
|
790
|
+
"source": "skillssh",
|
|
791
|
+
})
|
|
792
|
+
return skills
|
|
793
|
+
|
|
794
|
+
# Fallback: skillsdirectory.com public registry (no auth, ~97K skills)
|
|
795
|
+
try:
|
|
796
|
+
q = urllib.parse.quote(str(query).strip())
|
|
797
|
+
req = urllib.request.Request(
|
|
798
|
+
f"{_SKILLSSH_FALLBACK_API}?search={q}",
|
|
799
|
+
headers={"User-Agent": "dulus-skill-manager"},
|
|
800
|
+
)
|
|
801
|
+
with urllib.request.urlopen(req, timeout=_SKILLSSH_TIMEOUT) as resp:
|
|
802
|
+
reg = json.loads(resp.read())
|
|
803
|
+
entries = reg if isinstance(reg, list) else reg.get("skills", reg.get("data", []))
|
|
804
|
+
for it in entries[: max(limit, 1)]:
|
|
805
|
+
repo = it.get("repository", "") or it.get("repo", "")
|
|
806
|
+
name = it.get("slug") or it.get("name", "")
|
|
807
|
+
if not repo or not name:
|
|
808
|
+
continue
|
|
809
|
+
skills.append({
|
|
810
|
+
"id": f"skillssh/{repo}/{name}",
|
|
811
|
+
"skill": name,
|
|
812
|
+
"name": name,
|
|
813
|
+
"description": (it.get("description") or "")[:160],
|
|
814
|
+
"repo": repo,
|
|
815
|
+
"source": "skillssh",
|
|
816
|
+
})
|
|
817
|
+
except Exception:
|
|
818
|
+
pass
|
|
819
|
+
return skills
|
|
820
|
+
|
|
821
|
+
|
|
822
|
+
def _skillssh_default_branch(owner_repo: str) -> str:
|
|
823
|
+
"""Resolve a repo's default branch (main/master/…)."""
|
|
824
|
+
try:
|
|
825
|
+
req = urllib.request.Request(
|
|
826
|
+
f"https://api.github.com/repos/{owner_repo}", headers=_skillssh_gh_headers()
|
|
827
|
+
)
|
|
828
|
+
with urllib.request.urlopen(req, timeout=_SKILLSSH_TIMEOUT) as resp:
|
|
829
|
+
return json.loads(resp.read()).get("default_branch", "main")
|
|
830
|
+
except Exception:
|
|
831
|
+
return "main"
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
def install_skillssh(slug: str) -> tuple[bool, str]:
|
|
835
|
+
"""Install a skills.sh skill into ~/.dulus/skills/.
|
|
836
|
+
|
|
837
|
+
Accepts: skillssh/<owner>/<repo>/<skill> · <owner>/<repo>/<skill>
|
|
838
|
+
<owner>/<repo> (repo whose root SKILL.md IS the skill)
|
|
839
|
+
Downloads the whole skill folder (SKILL.md + assets/scripts/references)
|
|
840
|
+
from the source repo and rewrites frontmatter for Dulus.
|
|
841
|
+
"""
|
|
842
|
+
parts = slug.replace("skillssh/", "", 1).strip("/").split("/")
|
|
843
|
+
if len(parts) < 2:
|
|
844
|
+
return False, (
|
|
845
|
+
f"Invalid skills.sh slug: '{slug}'. "
|
|
846
|
+
f"Expected <owner>/<repo>/<skill> or <owner>/<repo>."
|
|
847
|
+
)
|
|
848
|
+
owner_repo = f"{parts[0]}/{parts[1]}"
|
|
849
|
+
skill_name = parts[-1] if len(parts) >= 3 else parts[1]
|
|
850
|
+
|
|
851
|
+
branch = _skillssh_default_branch(owner_repo)
|
|
852
|
+
|
|
853
|
+
# One tree call to locate the skill dir (mirrors the skills-CLI discovery:
|
|
854
|
+
# skills/<name>/, .claude/skills/<name>/, catalog layouts, or root SKILL.md).
|
|
855
|
+
tree_url = f"https://api.github.com/repos/{owner_repo}/git/trees/{branch}?recursive=1"
|
|
856
|
+
try:
|
|
857
|
+
req = urllib.request.Request(tree_url, headers=_skillssh_gh_headers())
|
|
858
|
+
with urllib.request.urlopen(req, timeout=_SKILLSSH_TIMEOUT) as resp:
|
|
859
|
+
tree = json.loads(resp.read())
|
|
860
|
+
except Exception as e:
|
|
861
|
+
return False, f"Could not read repo tree for '{owner_repo}': {e}"
|
|
862
|
+
|
|
863
|
+
blobs = [
|
|
864
|
+
it.get("path", "")
|
|
865
|
+
for it in tree.get("tree", [])
|
|
866
|
+
if it.get("type") == "blob"
|
|
867
|
+
]
|
|
868
|
+
|
|
869
|
+
# Candidate SKILL.md paths whose parent dir matches the skill name.
|
|
870
|
+
want = skill_name.lower()
|
|
871
|
+
candidates = [
|
|
872
|
+
p for p in blobs
|
|
873
|
+
if p.endswith("/SKILL.md") and p.split("/")[-2].lower() == want
|
|
874
|
+
]
|
|
875
|
+
rel_dir = ""
|
|
876
|
+
if candidates:
|
|
877
|
+
rel_dir = "/".join(sorted(candidates, key=lambda p: p.count("/"))[0].split("/")[:-1])
|
|
878
|
+
elif "SKILL.md" in blobs and len(parts) == 2:
|
|
879
|
+
rel_dir = "" # root-level skill: the repo itself is the skill
|
|
880
|
+
else:
|
|
881
|
+
near = sorted({p.split("/")[-2] for p in blobs if p.endswith("/SKILL.md")})[:8]
|
|
882
|
+
return False, (
|
|
883
|
+
f"No SKILL.md found for '{skill_name}' in {owner_repo}. "
|
|
884
|
+
f"Skills in that repo: {', '.join(near) if near else '(none)'}"
|
|
885
|
+
)
|
|
886
|
+
|
|
887
|
+
dest_dir = DULUS_SKILLS_DIR / skill_name
|
|
888
|
+
dest_dir.mkdir(parents=True, exist_ok=True)
|
|
889
|
+
|
|
890
|
+
prefix = rel_dir + "/" if rel_dir else ""
|
|
891
|
+
targets = (
|
|
892
|
+
[p for p in blobs if p.startswith(prefix)] if rel_dir
|
|
893
|
+
else ["SKILL.md"]
|
|
894
|
+
)
|
|
895
|
+
|
|
896
|
+
raw_base = f"https://raw.githubusercontent.com/{owner_repo}/{branch}"
|
|
897
|
+
downloaded: list[str] = []
|
|
898
|
+
for p in targets:
|
|
899
|
+
rel = p[len(prefix):] if prefix and p.startswith(prefix) else p.split("/")[-1]
|
|
900
|
+
dst = dest_dir / rel
|
|
901
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
902
|
+
try:
|
|
903
|
+
with urllib.request.urlopen(f"{raw_base}/{p}", timeout=_SKILLSSH_TIMEOUT) as r:
|
|
904
|
+
dst.write_bytes(r.read())
|
|
905
|
+
downloaded.append(rel)
|
|
906
|
+
except Exception:
|
|
907
|
+
pass # skip files we can't fetch
|
|
908
|
+
|
|
909
|
+
skill_md = dest_dir / "SKILL.md"
|
|
910
|
+
if not skill_md.exists():
|
|
911
|
+
return False, f"Could not download SKILL.md for '{skill_name}' from {owner_repo}."
|
|
912
|
+
|
|
913
|
+
# Rewrite frontmatter for Dulus (keep original description if present)
|
|
914
|
+
raw = skill_md.read_text(encoding="utf-8")
|
|
915
|
+
orig = _parse_frontmatter(raw)
|
|
916
|
+
body = _strip_frontmatter(raw)
|
|
917
|
+
frontmatter = (
|
|
918
|
+
f"---\n"
|
|
919
|
+
f"name: {skill_name}\n"
|
|
920
|
+
f"description: {orig.get('description', f'Skill from skills.sh ({owner_repo})')}\n"
|
|
921
|
+
f"source: skillssh\n"
|
|
922
|
+
f"repo: {owner_repo}\n"
|
|
923
|
+
f"triggers: [/{skill_name}]\n"
|
|
924
|
+
f"---\n\n"
|
|
925
|
+
)
|
|
926
|
+
skill_md.write_text(frontmatter + body, encoding="utf-8")
|
|
927
|
+
|
|
928
|
+
return True, (
|
|
929
|
+
f"Installed '{skill_name}' from skills.sh ({owner_repo}@{branch}) -> {dest_dir} "
|
|
930
|
+
f"({len(downloaded)} files: {', '.join(downloaded[:5])}{'...' if len(downloaded) > 5 else ''})"
|
|
931
|
+
)
|
|
932
|
+
|
|
933
|
+
|
|
734
934
|
# ── Installed skills ───────────────────────────────────────────────────────
|
|
735
935
|
|
|
736
936
|
def list_installed(query: Optional[str] = None) -> list[dict]:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|