dulus 3.10.3__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.3/dulus.egg-info → dulus-3.10.4}/PKG-INFO +1 -1
- {dulus-3.10.3 → dulus-3.10.4}/compaction.py +21 -0
- {dulus-3.10.3 → dulus-3.10.4/dulus.egg-info}/PKG-INFO +1 -1
- {dulus-3.10.3 → dulus-3.10.4}/dulus.py +22 -1
- {dulus-3.10.3 → dulus-3.10.4}/dulus_mcp/client.py +19 -0
- {dulus-3.10.3 → dulus-3.10.4}/pyproject.toml +1 -1
- {dulus-3.10.3 → dulus-3.10.4}/skill/clawhub.py +200 -0
- {dulus-3.10.3 → dulus-3.10.4}/LICENSE +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/MANIFEST.in +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/README.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/agent.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/analytics.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/backend/__init__.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/backend/agents_bridge.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/backend/compressor.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/backend/context.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/backend/githook.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/backend/marketplace.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/backend/mempalace_bridge.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/backend/personas.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/backend/plugins.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/backend/server.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/backend/tasks.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/batch_api.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/checkpoint/__init__.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/checkpoint/hooks.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/checkpoint/store.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/checkpoint/types.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/claude_code_watcher.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/clipboard_utils.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/cloudsave.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/common.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/config.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/context.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/data/__init__.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/data/active_persona.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/data/marketplace.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/data/personas.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/data/plugins/__init__.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/data/plugins/composio/__init__.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/data/plugins/composio/composio_plugin/__init__.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/data/plugins/composio/composio_plugin/session_manager.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/data/plugins/composio/composio_plugin/tool_generator.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/data/plugins/composio/plugin.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/data/plugins/composio/plugin_tool.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/data/tasks.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/display_blocks.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/API.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/BRAND_GUIDE.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/CHANGELOG.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/CONTRIBUTING.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/DEPLOYMENT.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/Dulus_AI_Whitepaper_(v2.0).pdf +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/FAQ.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/GETTING_STARTED.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/INVESTOR_BRIEF.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/README.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/README_AR.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/README_EN.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/README_ES.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/README_FR.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/README_JA.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/README_KO.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/README_PT.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/README_RU.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/README_ZH.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/SECURITY.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/__init__.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/api.html +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/architecture.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/azure-speech-template.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/dashboard/index.html +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/divider.svg +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/dulus-bird.png +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/generate.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/hero.svg +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/index.html +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/news.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/nvidia-models.svg +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/particle-playground.html +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/personas/index.html +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/poetry-banner.png +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/preview.html +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/sec-agents.svg +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/sec-brainstorm.svg +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/sec-bridges.svg +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/sec-features.svg +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/sec-freetier.svg +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/sec-memory.svg +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/sec-models.svg +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/sec-perms.svg +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/sec-plugins.svg +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/sec-quickstart.svg +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/sec-ssj.svg +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/spinners.svg +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/split-pane.svg +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/docs/terminal-boot.svg +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/dulus.egg-info/SOURCES.txt +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/dulus.egg-info/dependency_links.txt +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/dulus.egg-info/entry_points.txt +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/dulus.egg-info/requires.txt +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/dulus.egg-info/top_level.txt +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/dulus_gui.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/dulus_gui_web.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/dulus_mcp/__init__.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/dulus_mcp/commands.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/dulus_mcp/config.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/dulus_mcp/hub.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/dulus_mcp/tools.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/dulus_mcp/types.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/dulus_tools/__init__.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/dulus_tools/add_dir_manager.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/dulus_tools/afk_mode.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/dulus_tools/approval_runtime.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/dulus_tools/background_tasks.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/dulus_tools/clipboard_utils.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/dulus_tools/diff_visualizer.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/dulus_tools/export_import.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/dulus_tools/hook_engine.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/dulus_tools/notification_manager.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/dulus_tools/session_fork.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/dulus_tools/shell_mode.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/dulus_tools/todo_visualizer.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/dulus_tools/tree_ls.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/dulus_tools/wire_events.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/dulus_tools/yolo_mode.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/file_filter.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/governance.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/gui/__init__.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/gui/agent_bridge.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/gui/chat_widget.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/gui/dulus_orchestrator/App.tsx +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/gui/dulus_orchestrator/THIRD_PARTY_NOTICES.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/gui/dulus_orchestrator/dist/index.html +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/gui/dulus_orchestrator/dist/standalone.html +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/gui/dulus_orchestrator/index.html +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/gui/dulus_orchestrator/index.tsx +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/gui/dulus_orchestrator/metadata.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/gui/dulus_orchestrator/package-lock.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/gui/dulus_orchestrator/package.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/gui/dulus_orchestrator/types.ts +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/gui/dulus_orchestrator/utils.ts +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/gui/dulus_orchestrator/vertex-ai-proxy-interceptor.js +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/gui/dulus_orchestrator/vite.config.ts +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/gui/main_window.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/gui/personas.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/gui/sandbox_server.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/gui/session_utils.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/gui/settings_dialog.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/gui/sidebar.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/gui/tasks_view.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/gui/themes.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/gui/tool_panel.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/input.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/license_manager.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/memory/__init__.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/memory/audit.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/memory/consolidator.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/memory/context.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/memory/offload.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/memory/palace.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/memory/scan.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/memory/sessions.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/memory/store.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/memory/tools.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/memory/types.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/memory/vector_search.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/multi_agent/__init__.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/multi_agent/subagent.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/multi_agent/tools.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/offload_helper.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/paste_placeholders.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/personality_config.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/plugin/__init__.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/plugin/autoadapter.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/plugin/loader.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/plugin/recommend.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/plugin/store.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/plugin/types.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/profiles.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/providers.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/sandbox/README.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/sandbox/__init__.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/sandbox/components.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/sandbox/dist/assets/index-DE51D6wI.css +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/sandbox/dist/assets/index-DMCCNE9Y.js +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/sandbox/dist/index.html +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/sandbox/dist/wallpaper-default.jpg +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/sandbox/dist/wallpapers/default.jpeg +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/sandbox/dist/wallpapers/light.jpeg +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/sandbox/dist/wallpapers/nature.jpeg +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/sandbox/dist/wallpapers/tech.jpeg +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/sandbox/eslint.config.js +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/sandbox/index.html +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/sandbox/info.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/sandbox/package-lock.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/sandbox/package.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/sandbox/postcss.config.js +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/sandbox/tailwind.config.js +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/sandbox/tsconfig.app.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/sandbox/tsconfig.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/sandbox/tsconfig.node.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/sandbox/vite.config.ts +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/sandbox_bootstrap.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/setup.cfg +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/__init__.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/builtin.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/LICENSE-obsidian-skills +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/README.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/defuddle/SKILL.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/json-canvas/SKILL.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/json-canvas/references/EXAMPLES.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/obsidian-bases/SKILL.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/obsidian-bases/references/FUNCTIONS_REFERENCE.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/obsidian-cli/SKILL.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/obsidian-markdown/SKILL.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/obsidian-markdown/references/CALLOUTS.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/obsidian-markdown/references/EMBEDS.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/obsidian-markdown/references/PROPERTIES.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/README.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/SKILL.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/addresses.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/advancedbackgroundchecks.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/beenverified.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/clustal.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/clustrmaps.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/cyberbackgroundchecks.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/familytreenow.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/fastpeoplesearch.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/intelius.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/mylife.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/nuwber.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/peekyou.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/peoplefinders.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/radaris.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/rehold.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/searchpeoplefree.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/socialcatfish.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/spokeo.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/thatsthem.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/truepeoplesearch.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/usphonebook.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/whitepages.json +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/legal/ccpa.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/legal/drop.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/legal/gdpr.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/methods.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/site-playbooks.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/state-machine.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/autopilot.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/badbool.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/brokers.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/cdp.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/config.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/crypto.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/dossier.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/email_modes.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/emailer.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/ledger.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/legal.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/paths.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/pdd.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/registry.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/report.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/scan.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/storage.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/tiers.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/vectors.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/templates/consent/authorization.md +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/templates/emails/ccpa-authorized-agent.txt +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/templates/emails/ccpa-deletion.txt +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/templates/emails/ccpa-indirect-deletion.txt +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/templates/emails/gdpr-erasure.txt +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/templates/emails/generic-optout.txt +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/executor.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/loader.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skill/tools.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/skills.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/soul.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/spinner.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/steer_input.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/string_utils.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/subagent.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/task/__init__.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/task/store.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/task/tools.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/task/types.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tests/test_afk_yolo.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tests/test_approval_runtime.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tests/test_background_task_tools.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tests/test_background_tasks.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tests/test_checkpoint.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tests/test_clipboard_utils.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tests/test_compaction.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tests/test_diff_view.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tests/test_diff_visualization.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tests/test_display_blocks.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tests/test_export_import.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tests/test_hook_engine.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tests/test_injection_fix.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tests/test_license.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tests/test_mcp.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tests/test_memory.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tests/test_notification_manager.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tests/test_plugin.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tests/test_session_fork.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tests/test_shell_mode.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tests/test_skills.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tests/test_steer_input.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tests/test_subagent.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tests/test_task.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tests/test_telegram_buffer.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tests/test_think_tool.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tests/test_todo_tool.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tests/test_todo_visualization.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tests/test_tool_registry.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tests/test_tree_ls.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tests/test_voice.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tests/test_wire_events.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tmux_offloader.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tmux_tools.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tool_registry.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tools.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tools_background.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tools_think.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/tools_todo.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/ui/__init__.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/ui/input.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/ui/render.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/updater.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/voice/__init__.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/voice/audio_utils.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/voice/keyterms.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/voice/recorder.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/voice/stt.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/voice/tts.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/voice/wake_word.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/webbridge/__init__.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/webbridge/core.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/webbridge/tools.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/webchat.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/webchat_server.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/webchat_ui/__init__.py +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/webchat_ui/app.js +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/webchat_ui/index.html +0 -0
- {dulus-3.10.3 → dulus-3.10.4}/webchat_ui/style.css +0 -0
- {dulus-3.10.3 → 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
|
|
@@ -5085,6 +5085,7 @@ def cmd_skill(args: str, state, config) -> bool:
|
|
|
5085
5085
|
list_awesome_remote, list_composio_toolkits,
|
|
5086
5086
|
install_awesome_remote,
|
|
5087
5087
|
list_dulus_remote, install_dulus_remote,
|
|
5088
|
+
search_skillssh, install_skillssh,
|
|
5088
5089
|
)
|
|
5089
5090
|
from pathlib import Path as _Path
|
|
5090
5091
|
|
|
@@ -5122,11 +5123,12 @@ def cmd_skill(args: str, state, config) -> bool:
|
|
|
5122
5123
|
print(f" 4) {clr('local', 'yellow')} — Anthropic + awesome marketplaces on disk (~/.claude/...)")
|
|
5123
5124
|
print(f" 5) {clr('installed', 'yellow')} — skills already in ~/.dulus/skills/")
|
|
5124
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)")
|
|
5125
5127
|
try:
|
|
5126
5128
|
choice = input(clr(" > ", "cyan")).strip().lower()
|
|
5127
5129
|
except (EOFError, KeyboardInterrupt):
|
|
5128
5130
|
return True
|
|
5129
|
-
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"}
|
|
5130
5132
|
rest = mapping.get(choice, choice)
|
|
5131
5133
|
|
|
5132
5134
|
if rest.startswith("dulus"):
|
|
@@ -5235,6 +5237,23 @@ def cmd_skill(args: str, state, config) -> bool:
|
|
|
5235
5237
|
print(f" {clr(r['slug'], 'cyan'):30s} {r.get('description','')[:60]}")
|
|
5236
5238
|
return True
|
|
5237
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
|
+
|
|
5238
5257
|
if rest.startswith("installed"):
|
|
5239
5258
|
query = rest[9:].strip()
|
|
5240
5259
|
skills = list_installed(query)
|
|
@@ -5342,6 +5361,8 @@ def cmd_skill(args: str, state, config) -> bool:
|
|
|
5342
5361
|
if rest.startswith("clawhub:"):
|
|
5343
5362
|
slug = rest[8:]
|
|
5344
5363
|
success, msg = install_clawhub(slug)
|
|
5364
|
+
elif rest.startswith("skillssh/"):
|
|
5365
|
+
success, msg = install_skillssh(rest)
|
|
5345
5366
|
elif rest.startswith("dulus/"):
|
|
5346
5367
|
success, msg = install_dulus_remote(rest)
|
|
5347
5368
|
elif rest.startswith("awesome/"):
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|