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.
Files changed (347) hide show
  1. {dulus-3.10.3/dulus.egg-info → dulus-3.10.4}/PKG-INFO +1 -1
  2. {dulus-3.10.3 → dulus-3.10.4}/compaction.py +21 -0
  3. {dulus-3.10.3 → dulus-3.10.4/dulus.egg-info}/PKG-INFO +1 -1
  4. {dulus-3.10.3 → dulus-3.10.4}/dulus.py +22 -1
  5. {dulus-3.10.3 → dulus-3.10.4}/dulus_mcp/client.py +19 -0
  6. {dulus-3.10.3 → dulus-3.10.4}/pyproject.toml +1 -1
  7. {dulus-3.10.3 → dulus-3.10.4}/skill/clawhub.py +200 -0
  8. {dulus-3.10.3 → dulus-3.10.4}/LICENSE +0 -0
  9. {dulus-3.10.3 → dulus-3.10.4}/MANIFEST.in +0 -0
  10. {dulus-3.10.3 → dulus-3.10.4}/README.md +0 -0
  11. {dulus-3.10.3 → dulus-3.10.4}/agent.py +0 -0
  12. {dulus-3.10.3 → dulus-3.10.4}/analytics.py +0 -0
  13. {dulus-3.10.3 → dulus-3.10.4}/backend/__init__.py +0 -0
  14. {dulus-3.10.3 → dulus-3.10.4}/backend/agents_bridge.py +0 -0
  15. {dulus-3.10.3 → dulus-3.10.4}/backend/compressor.py +0 -0
  16. {dulus-3.10.3 → dulus-3.10.4}/backend/context.py +0 -0
  17. {dulus-3.10.3 → dulus-3.10.4}/backend/githook.py +0 -0
  18. {dulus-3.10.3 → dulus-3.10.4}/backend/marketplace.py +0 -0
  19. {dulus-3.10.3 → dulus-3.10.4}/backend/mempalace_bridge.py +0 -0
  20. {dulus-3.10.3 → dulus-3.10.4}/backend/personas.py +0 -0
  21. {dulus-3.10.3 → dulus-3.10.4}/backend/plugins.py +0 -0
  22. {dulus-3.10.3 → dulus-3.10.4}/backend/server.py +0 -0
  23. {dulus-3.10.3 → dulus-3.10.4}/backend/tasks.py +0 -0
  24. {dulus-3.10.3 → dulus-3.10.4}/batch_api.py +0 -0
  25. {dulus-3.10.3 → dulus-3.10.4}/checkpoint/__init__.py +0 -0
  26. {dulus-3.10.3 → dulus-3.10.4}/checkpoint/hooks.py +0 -0
  27. {dulus-3.10.3 → dulus-3.10.4}/checkpoint/store.py +0 -0
  28. {dulus-3.10.3 → dulus-3.10.4}/checkpoint/types.py +0 -0
  29. {dulus-3.10.3 → dulus-3.10.4}/claude_code_watcher.py +0 -0
  30. {dulus-3.10.3 → dulus-3.10.4}/clipboard_utils.py +0 -0
  31. {dulus-3.10.3 → dulus-3.10.4}/cloudsave.py +0 -0
  32. {dulus-3.10.3 → dulus-3.10.4}/common.py +0 -0
  33. {dulus-3.10.3 → dulus-3.10.4}/config.py +0 -0
  34. {dulus-3.10.3 → dulus-3.10.4}/context.py +0 -0
  35. {dulus-3.10.3 → dulus-3.10.4}/data/__init__.py +0 -0
  36. {dulus-3.10.3 → dulus-3.10.4}/data/active_persona.json +0 -0
  37. {dulus-3.10.3 → dulus-3.10.4}/data/marketplace.json +0 -0
  38. {dulus-3.10.3 → dulus-3.10.4}/data/personas.json +0 -0
  39. {dulus-3.10.3 → dulus-3.10.4}/data/plugins/__init__.py +0 -0
  40. {dulus-3.10.3 → dulus-3.10.4}/data/plugins/composio/__init__.py +0 -0
  41. {dulus-3.10.3 → dulus-3.10.4}/data/plugins/composio/composio_plugin/__init__.py +0 -0
  42. {dulus-3.10.3 → dulus-3.10.4}/data/plugins/composio/composio_plugin/session_manager.py +0 -0
  43. {dulus-3.10.3 → dulus-3.10.4}/data/plugins/composio/composio_plugin/tool_generator.py +0 -0
  44. {dulus-3.10.3 → dulus-3.10.4}/data/plugins/composio/plugin.json +0 -0
  45. {dulus-3.10.3 → dulus-3.10.4}/data/plugins/composio/plugin_tool.py +0 -0
  46. {dulus-3.10.3 → dulus-3.10.4}/data/tasks.json +0 -0
  47. {dulus-3.10.3 → dulus-3.10.4}/display_blocks.py +0 -0
  48. {dulus-3.10.3 → dulus-3.10.4}/docs/API.md +0 -0
  49. {dulus-3.10.3 → dulus-3.10.4}/docs/BRAND_GUIDE.md +0 -0
  50. {dulus-3.10.3 → dulus-3.10.4}/docs/CHANGELOG.md +0 -0
  51. {dulus-3.10.3 → dulus-3.10.4}/docs/CONTRIBUTING.md +0 -0
  52. {dulus-3.10.3 → dulus-3.10.4}/docs/DEPLOYMENT.md +0 -0
  53. {dulus-3.10.3 → dulus-3.10.4}/docs/Dulus_AI_Whitepaper_(v2.0).pdf +0 -0
  54. {dulus-3.10.3 → dulus-3.10.4}/docs/FAQ.md +0 -0
  55. {dulus-3.10.3 → dulus-3.10.4}/docs/GETTING_STARTED.md +0 -0
  56. {dulus-3.10.3 → dulus-3.10.4}/docs/INVESTOR_BRIEF.md +0 -0
  57. {dulus-3.10.3 → dulus-3.10.4}/docs/README.md +0 -0
  58. {dulus-3.10.3 → dulus-3.10.4}/docs/README_AR.md +0 -0
  59. {dulus-3.10.3 → dulus-3.10.4}/docs/README_EN.md +0 -0
  60. {dulus-3.10.3 → dulus-3.10.4}/docs/README_ES.md +0 -0
  61. {dulus-3.10.3 → dulus-3.10.4}/docs/README_FR.md +0 -0
  62. {dulus-3.10.3 → dulus-3.10.4}/docs/README_JA.md +0 -0
  63. {dulus-3.10.3 → dulus-3.10.4}/docs/README_KO.md +0 -0
  64. {dulus-3.10.3 → dulus-3.10.4}/docs/README_PT.md +0 -0
  65. {dulus-3.10.3 → dulus-3.10.4}/docs/README_RU.md +0 -0
  66. {dulus-3.10.3 → dulus-3.10.4}/docs/README_ZH.md +0 -0
  67. {dulus-3.10.3 → dulus-3.10.4}/docs/SECURITY.md +0 -0
  68. {dulus-3.10.3 → dulus-3.10.4}/docs/__init__.py +0 -0
  69. {dulus-3.10.3 → dulus-3.10.4}/docs/api.html +0 -0
  70. {dulus-3.10.3 → dulus-3.10.4}/docs/architecture.md +0 -0
  71. {dulus-3.10.3 → dulus-3.10.4}/docs/azure-speech-template.json +0 -0
  72. {dulus-3.10.3 → dulus-3.10.4}/docs/dashboard/index.html +0 -0
  73. {dulus-3.10.3 → dulus-3.10.4}/docs/divider.svg +0 -0
  74. {dulus-3.10.3 → dulus-3.10.4}/docs/dulus-bird.png +0 -0
  75. {dulus-3.10.3 → dulus-3.10.4}/docs/generate.py +0 -0
  76. {dulus-3.10.3 → dulus-3.10.4}/docs/hero.svg +0 -0
  77. {dulus-3.10.3 → dulus-3.10.4}/docs/index.html +0 -0
  78. {dulus-3.10.3 → dulus-3.10.4}/docs/news.md +0 -0
  79. {dulus-3.10.3 → dulus-3.10.4}/docs/nvidia-models.svg +0 -0
  80. {dulus-3.10.3 → dulus-3.10.4}/docs/particle-playground.html +0 -0
  81. {dulus-3.10.3 → dulus-3.10.4}/docs/personas/index.html +0 -0
  82. {dulus-3.10.3 → dulus-3.10.4}/docs/poetry-banner.png +0 -0
  83. {dulus-3.10.3 → dulus-3.10.4}/docs/preview.html +0 -0
  84. {dulus-3.10.3 → dulus-3.10.4}/docs/sec-agents.svg +0 -0
  85. {dulus-3.10.3 → dulus-3.10.4}/docs/sec-brainstorm.svg +0 -0
  86. {dulus-3.10.3 → dulus-3.10.4}/docs/sec-bridges.svg +0 -0
  87. {dulus-3.10.3 → dulus-3.10.4}/docs/sec-features.svg +0 -0
  88. {dulus-3.10.3 → dulus-3.10.4}/docs/sec-freetier.svg +0 -0
  89. {dulus-3.10.3 → dulus-3.10.4}/docs/sec-memory.svg +0 -0
  90. {dulus-3.10.3 → dulus-3.10.4}/docs/sec-models.svg +0 -0
  91. {dulus-3.10.3 → dulus-3.10.4}/docs/sec-perms.svg +0 -0
  92. {dulus-3.10.3 → dulus-3.10.4}/docs/sec-plugins.svg +0 -0
  93. {dulus-3.10.3 → dulus-3.10.4}/docs/sec-quickstart.svg +0 -0
  94. {dulus-3.10.3 → dulus-3.10.4}/docs/sec-ssj.svg +0 -0
  95. {dulus-3.10.3 → dulus-3.10.4}/docs/spinners.svg +0 -0
  96. {dulus-3.10.3 → dulus-3.10.4}/docs/split-pane.svg +0 -0
  97. {dulus-3.10.3 → dulus-3.10.4}/docs/terminal-boot.svg +0 -0
  98. {dulus-3.10.3 → dulus-3.10.4}/dulus.egg-info/SOURCES.txt +0 -0
  99. {dulus-3.10.3 → dulus-3.10.4}/dulus.egg-info/dependency_links.txt +0 -0
  100. {dulus-3.10.3 → dulus-3.10.4}/dulus.egg-info/entry_points.txt +0 -0
  101. {dulus-3.10.3 → dulus-3.10.4}/dulus.egg-info/requires.txt +0 -0
  102. {dulus-3.10.3 → dulus-3.10.4}/dulus.egg-info/top_level.txt +0 -0
  103. {dulus-3.10.3 → dulus-3.10.4}/dulus_gui.py +0 -0
  104. {dulus-3.10.3 → dulus-3.10.4}/dulus_gui_web.py +0 -0
  105. {dulus-3.10.3 → dulus-3.10.4}/dulus_mcp/__init__.py +0 -0
  106. {dulus-3.10.3 → dulus-3.10.4}/dulus_mcp/commands.py +0 -0
  107. {dulus-3.10.3 → dulus-3.10.4}/dulus_mcp/config.py +0 -0
  108. {dulus-3.10.3 → dulus-3.10.4}/dulus_mcp/hub.py +0 -0
  109. {dulus-3.10.3 → dulus-3.10.4}/dulus_mcp/tools.py +0 -0
  110. {dulus-3.10.3 → dulus-3.10.4}/dulus_mcp/types.py +0 -0
  111. {dulus-3.10.3 → dulus-3.10.4}/dulus_tools/__init__.py +0 -0
  112. {dulus-3.10.3 → dulus-3.10.4}/dulus_tools/add_dir_manager.py +0 -0
  113. {dulus-3.10.3 → dulus-3.10.4}/dulus_tools/afk_mode.py +0 -0
  114. {dulus-3.10.3 → dulus-3.10.4}/dulus_tools/approval_runtime.py +0 -0
  115. {dulus-3.10.3 → dulus-3.10.4}/dulus_tools/background_tasks.py +0 -0
  116. {dulus-3.10.3 → dulus-3.10.4}/dulus_tools/clipboard_utils.py +0 -0
  117. {dulus-3.10.3 → dulus-3.10.4}/dulus_tools/diff_visualizer.py +0 -0
  118. {dulus-3.10.3 → dulus-3.10.4}/dulus_tools/export_import.py +0 -0
  119. {dulus-3.10.3 → dulus-3.10.4}/dulus_tools/hook_engine.py +0 -0
  120. {dulus-3.10.3 → dulus-3.10.4}/dulus_tools/notification_manager.py +0 -0
  121. {dulus-3.10.3 → dulus-3.10.4}/dulus_tools/session_fork.py +0 -0
  122. {dulus-3.10.3 → dulus-3.10.4}/dulus_tools/shell_mode.py +0 -0
  123. {dulus-3.10.3 → dulus-3.10.4}/dulus_tools/todo_visualizer.py +0 -0
  124. {dulus-3.10.3 → dulus-3.10.4}/dulus_tools/tree_ls.py +0 -0
  125. {dulus-3.10.3 → dulus-3.10.4}/dulus_tools/wire_events.py +0 -0
  126. {dulus-3.10.3 → dulus-3.10.4}/dulus_tools/yolo_mode.py +0 -0
  127. {dulus-3.10.3 → dulus-3.10.4}/file_filter.py +0 -0
  128. {dulus-3.10.3 → dulus-3.10.4}/governance.py +0 -0
  129. {dulus-3.10.3 → dulus-3.10.4}/gui/__init__.py +0 -0
  130. {dulus-3.10.3 → dulus-3.10.4}/gui/agent_bridge.py +0 -0
  131. {dulus-3.10.3 → dulus-3.10.4}/gui/chat_widget.py +0 -0
  132. {dulus-3.10.3 → dulus-3.10.4}/gui/dulus_orchestrator/App.tsx +0 -0
  133. {dulus-3.10.3 → dulus-3.10.4}/gui/dulus_orchestrator/THIRD_PARTY_NOTICES.md +0 -0
  134. {dulus-3.10.3 → dulus-3.10.4}/gui/dulus_orchestrator/dist/index.html +0 -0
  135. {dulus-3.10.3 → dulus-3.10.4}/gui/dulus_orchestrator/dist/standalone.html +0 -0
  136. {dulus-3.10.3 → dulus-3.10.4}/gui/dulus_orchestrator/index.html +0 -0
  137. {dulus-3.10.3 → dulus-3.10.4}/gui/dulus_orchestrator/index.tsx +0 -0
  138. {dulus-3.10.3 → dulus-3.10.4}/gui/dulus_orchestrator/metadata.json +0 -0
  139. {dulus-3.10.3 → dulus-3.10.4}/gui/dulus_orchestrator/package-lock.json +0 -0
  140. {dulus-3.10.3 → dulus-3.10.4}/gui/dulus_orchestrator/package.json +0 -0
  141. {dulus-3.10.3 → dulus-3.10.4}/gui/dulus_orchestrator/types.ts +0 -0
  142. {dulus-3.10.3 → dulus-3.10.4}/gui/dulus_orchestrator/utils.ts +0 -0
  143. {dulus-3.10.3 → dulus-3.10.4}/gui/dulus_orchestrator/vertex-ai-proxy-interceptor.js +0 -0
  144. {dulus-3.10.3 → dulus-3.10.4}/gui/dulus_orchestrator/vite.config.ts +0 -0
  145. {dulus-3.10.3 → dulus-3.10.4}/gui/main_window.py +0 -0
  146. {dulus-3.10.3 → dulus-3.10.4}/gui/personas.py +0 -0
  147. {dulus-3.10.3 → dulus-3.10.4}/gui/sandbox_server.py +0 -0
  148. {dulus-3.10.3 → dulus-3.10.4}/gui/session_utils.py +0 -0
  149. {dulus-3.10.3 → dulus-3.10.4}/gui/settings_dialog.py +0 -0
  150. {dulus-3.10.3 → dulus-3.10.4}/gui/sidebar.py +0 -0
  151. {dulus-3.10.3 → dulus-3.10.4}/gui/tasks_view.py +0 -0
  152. {dulus-3.10.3 → dulus-3.10.4}/gui/themes.py +0 -0
  153. {dulus-3.10.3 → dulus-3.10.4}/gui/tool_panel.py +0 -0
  154. {dulus-3.10.3 → dulus-3.10.4}/input.py +0 -0
  155. {dulus-3.10.3 → dulus-3.10.4}/license_manager.py +0 -0
  156. {dulus-3.10.3 → dulus-3.10.4}/memory/__init__.py +0 -0
  157. {dulus-3.10.3 → dulus-3.10.4}/memory/audit.py +0 -0
  158. {dulus-3.10.3 → dulus-3.10.4}/memory/consolidator.py +0 -0
  159. {dulus-3.10.3 → dulus-3.10.4}/memory/context.py +0 -0
  160. {dulus-3.10.3 → dulus-3.10.4}/memory/offload.py +0 -0
  161. {dulus-3.10.3 → dulus-3.10.4}/memory/palace.py +0 -0
  162. {dulus-3.10.3 → dulus-3.10.4}/memory/scan.py +0 -0
  163. {dulus-3.10.3 → dulus-3.10.4}/memory/sessions.py +0 -0
  164. {dulus-3.10.3 → dulus-3.10.4}/memory/store.py +0 -0
  165. {dulus-3.10.3 → dulus-3.10.4}/memory/tools.py +0 -0
  166. {dulus-3.10.3 → dulus-3.10.4}/memory/types.py +0 -0
  167. {dulus-3.10.3 → dulus-3.10.4}/memory/vector_search.py +0 -0
  168. {dulus-3.10.3 → dulus-3.10.4}/multi_agent/__init__.py +0 -0
  169. {dulus-3.10.3 → dulus-3.10.4}/multi_agent/subagent.py +0 -0
  170. {dulus-3.10.3 → dulus-3.10.4}/multi_agent/tools.py +0 -0
  171. {dulus-3.10.3 → dulus-3.10.4}/offload_helper.py +0 -0
  172. {dulus-3.10.3 → dulus-3.10.4}/paste_placeholders.py +0 -0
  173. {dulus-3.10.3 → dulus-3.10.4}/personality_config.py +0 -0
  174. {dulus-3.10.3 → dulus-3.10.4}/plugin/__init__.py +0 -0
  175. {dulus-3.10.3 → dulus-3.10.4}/plugin/autoadapter.py +0 -0
  176. {dulus-3.10.3 → dulus-3.10.4}/plugin/loader.py +0 -0
  177. {dulus-3.10.3 → dulus-3.10.4}/plugin/recommend.py +0 -0
  178. {dulus-3.10.3 → dulus-3.10.4}/plugin/store.py +0 -0
  179. {dulus-3.10.3 → dulus-3.10.4}/plugin/types.py +0 -0
  180. {dulus-3.10.3 → dulus-3.10.4}/profiles.py +0 -0
  181. {dulus-3.10.3 → dulus-3.10.4}/providers.py +0 -0
  182. {dulus-3.10.3 → dulus-3.10.4}/sandbox/README.md +0 -0
  183. {dulus-3.10.3 → dulus-3.10.4}/sandbox/__init__.py +0 -0
  184. {dulus-3.10.3 → dulus-3.10.4}/sandbox/components.json +0 -0
  185. {dulus-3.10.3 → dulus-3.10.4}/sandbox/dist/assets/index-DE51D6wI.css +0 -0
  186. {dulus-3.10.3 → dulus-3.10.4}/sandbox/dist/assets/index-DMCCNE9Y.js +0 -0
  187. {dulus-3.10.3 → dulus-3.10.4}/sandbox/dist/index.html +0 -0
  188. {dulus-3.10.3 → dulus-3.10.4}/sandbox/dist/wallpaper-default.jpg +0 -0
  189. {dulus-3.10.3 → dulus-3.10.4}/sandbox/dist/wallpapers/default.jpeg +0 -0
  190. {dulus-3.10.3 → dulus-3.10.4}/sandbox/dist/wallpapers/light.jpeg +0 -0
  191. {dulus-3.10.3 → dulus-3.10.4}/sandbox/dist/wallpapers/nature.jpeg +0 -0
  192. {dulus-3.10.3 → dulus-3.10.4}/sandbox/dist/wallpapers/tech.jpeg +0 -0
  193. {dulus-3.10.3 → dulus-3.10.4}/sandbox/eslint.config.js +0 -0
  194. {dulus-3.10.3 → dulus-3.10.4}/sandbox/index.html +0 -0
  195. {dulus-3.10.3 → dulus-3.10.4}/sandbox/info.md +0 -0
  196. {dulus-3.10.3 → dulus-3.10.4}/sandbox/package-lock.json +0 -0
  197. {dulus-3.10.3 → dulus-3.10.4}/sandbox/package.json +0 -0
  198. {dulus-3.10.3 → dulus-3.10.4}/sandbox/postcss.config.js +0 -0
  199. {dulus-3.10.3 → dulus-3.10.4}/sandbox/tailwind.config.js +0 -0
  200. {dulus-3.10.3 → dulus-3.10.4}/sandbox/tsconfig.app.json +0 -0
  201. {dulus-3.10.3 → dulus-3.10.4}/sandbox/tsconfig.json +0 -0
  202. {dulus-3.10.3 → dulus-3.10.4}/sandbox/tsconfig.node.json +0 -0
  203. {dulus-3.10.3 → dulus-3.10.4}/sandbox/vite.config.ts +0 -0
  204. {dulus-3.10.3 → dulus-3.10.4}/sandbox_bootstrap.py +0 -0
  205. {dulus-3.10.3 → dulus-3.10.4}/setup.cfg +0 -0
  206. {dulus-3.10.3 → dulus-3.10.4}/skill/__init__.py +0 -0
  207. {dulus-3.10.3 → dulus-3.10.4}/skill/builtin.py +0 -0
  208. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/LICENSE-obsidian-skills +0 -0
  209. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/README.md +0 -0
  210. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/defuddle/SKILL.md +0 -0
  211. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/json-canvas/SKILL.md +0 -0
  212. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/json-canvas/references/EXAMPLES.md +0 -0
  213. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/obsidian-bases/SKILL.md +0 -0
  214. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/obsidian-bases/references/FUNCTIONS_REFERENCE.md +0 -0
  215. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/obsidian-cli/SKILL.md +0 -0
  216. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/obsidian-markdown/SKILL.md +0 -0
  217. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/obsidian-markdown/references/CALLOUTS.md +0 -0
  218. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/obsidian-markdown/references/EMBEDS.md +0 -0
  219. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/obsidian-markdown/references/PROPERTIES.md +0 -0
  220. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/README.md +0 -0
  221. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/SKILL.md +0 -0
  222. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/addresses.json +0 -0
  223. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/advancedbackgroundchecks.json +0 -0
  224. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/beenverified.json +0 -0
  225. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/clustal.json +0 -0
  226. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/clustrmaps.json +0 -0
  227. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/cyberbackgroundchecks.json +0 -0
  228. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/familytreenow.json +0 -0
  229. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/fastpeoplesearch.json +0 -0
  230. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/intelius.json +0 -0
  231. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/mylife.json +0 -0
  232. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/nuwber.json +0 -0
  233. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/peekyou.json +0 -0
  234. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/peoplefinders.json +0 -0
  235. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/radaris.json +0 -0
  236. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/rehold.json +0 -0
  237. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/searchpeoplefree.json +0 -0
  238. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/socialcatfish.json +0 -0
  239. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/spokeo.json +0 -0
  240. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/thatsthem.json +0 -0
  241. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/truepeoplesearch.json +0 -0
  242. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/usphonebook.json +0 -0
  243. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/brokers/whitepages.json +0 -0
  244. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/legal/ccpa.md +0 -0
  245. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/legal/drop.md +0 -0
  246. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/legal/gdpr.md +0 -0
  247. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/methods.md +0 -0
  248. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/site-playbooks.md +0 -0
  249. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/references/state-machine.md +0 -0
  250. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/autopilot.py +0 -0
  251. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/badbool.py +0 -0
  252. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/brokers.py +0 -0
  253. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/cdp.py +0 -0
  254. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/config.py +0 -0
  255. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/crypto.py +0 -0
  256. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/dossier.py +0 -0
  257. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/email_modes.py +0 -0
  258. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/emailer.py +0 -0
  259. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/ledger.py +0 -0
  260. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/legal.py +0 -0
  261. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/paths.py +0 -0
  262. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/pdd.py +0 -0
  263. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/registry.py +0 -0
  264. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/report.py +0 -0
  265. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/scan.py +0 -0
  266. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/storage.py +0 -0
  267. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/tiers.py +0 -0
  268. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/scripts/vectors.py +0 -0
  269. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/templates/consent/authorization.md +0 -0
  270. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/templates/emails/ccpa-authorized-agent.txt +0 -0
  271. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/templates/emails/ccpa-deletion.txt +0 -0
  272. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/templates/emails/ccpa-indirect-deletion.txt +0 -0
  273. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/templates/emails/gdpr-erasure.txt +0 -0
  274. {dulus-3.10.3 → dulus-3.10.4}/skill/bundled/unbroker/templates/emails/generic-optout.txt +0 -0
  275. {dulus-3.10.3 → dulus-3.10.4}/skill/executor.py +0 -0
  276. {dulus-3.10.3 → dulus-3.10.4}/skill/loader.py +0 -0
  277. {dulus-3.10.3 → dulus-3.10.4}/skill/tools.py +0 -0
  278. {dulus-3.10.3 → dulus-3.10.4}/skills.py +0 -0
  279. {dulus-3.10.3 → dulus-3.10.4}/soul.py +0 -0
  280. {dulus-3.10.3 → dulus-3.10.4}/spinner.py +0 -0
  281. {dulus-3.10.3 → dulus-3.10.4}/steer_input.py +0 -0
  282. {dulus-3.10.3 → dulus-3.10.4}/string_utils.py +0 -0
  283. {dulus-3.10.3 → dulus-3.10.4}/subagent.py +0 -0
  284. {dulus-3.10.3 → dulus-3.10.4}/task/__init__.py +0 -0
  285. {dulus-3.10.3 → dulus-3.10.4}/task/store.py +0 -0
  286. {dulus-3.10.3 → dulus-3.10.4}/task/tools.py +0 -0
  287. {dulus-3.10.3 → dulus-3.10.4}/task/types.py +0 -0
  288. {dulus-3.10.3 → dulus-3.10.4}/tests/test_afk_yolo.py +0 -0
  289. {dulus-3.10.3 → dulus-3.10.4}/tests/test_approval_runtime.py +0 -0
  290. {dulus-3.10.3 → dulus-3.10.4}/tests/test_background_task_tools.py +0 -0
  291. {dulus-3.10.3 → dulus-3.10.4}/tests/test_background_tasks.py +0 -0
  292. {dulus-3.10.3 → dulus-3.10.4}/tests/test_checkpoint.py +0 -0
  293. {dulus-3.10.3 → dulus-3.10.4}/tests/test_clipboard_utils.py +0 -0
  294. {dulus-3.10.3 → dulus-3.10.4}/tests/test_compaction.py +0 -0
  295. {dulus-3.10.3 → dulus-3.10.4}/tests/test_diff_view.py +0 -0
  296. {dulus-3.10.3 → dulus-3.10.4}/tests/test_diff_visualization.py +0 -0
  297. {dulus-3.10.3 → dulus-3.10.4}/tests/test_display_blocks.py +0 -0
  298. {dulus-3.10.3 → dulus-3.10.4}/tests/test_export_import.py +0 -0
  299. {dulus-3.10.3 → dulus-3.10.4}/tests/test_hook_engine.py +0 -0
  300. {dulus-3.10.3 → dulus-3.10.4}/tests/test_injection_fix.py +0 -0
  301. {dulus-3.10.3 → dulus-3.10.4}/tests/test_license.py +0 -0
  302. {dulus-3.10.3 → dulus-3.10.4}/tests/test_mcp.py +0 -0
  303. {dulus-3.10.3 → dulus-3.10.4}/tests/test_memory.py +0 -0
  304. {dulus-3.10.3 → dulus-3.10.4}/tests/test_notification_manager.py +0 -0
  305. {dulus-3.10.3 → dulus-3.10.4}/tests/test_plugin.py +0 -0
  306. {dulus-3.10.3 → dulus-3.10.4}/tests/test_session_fork.py +0 -0
  307. {dulus-3.10.3 → dulus-3.10.4}/tests/test_shell_mode.py +0 -0
  308. {dulus-3.10.3 → dulus-3.10.4}/tests/test_skills.py +0 -0
  309. {dulus-3.10.3 → dulus-3.10.4}/tests/test_steer_input.py +0 -0
  310. {dulus-3.10.3 → dulus-3.10.4}/tests/test_subagent.py +0 -0
  311. {dulus-3.10.3 → dulus-3.10.4}/tests/test_task.py +0 -0
  312. {dulus-3.10.3 → dulus-3.10.4}/tests/test_telegram_buffer.py +0 -0
  313. {dulus-3.10.3 → dulus-3.10.4}/tests/test_think_tool.py +0 -0
  314. {dulus-3.10.3 → dulus-3.10.4}/tests/test_todo_tool.py +0 -0
  315. {dulus-3.10.3 → dulus-3.10.4}/tests/test_todo_visualization.py +0 -0
  316. {dulus-3.10.3 → dulus-3.10.4}/tests/test_tool_registry.py +0 -0
  317. {dulus-3.10.3 → dulus-3.10.4}/tests/test_tree_ls.py +0 -0
  318. {dulus-3.10.3 → dulus-3.10.4}/tests/test_voice.py +0 -0
  319. {dulus-3.10.3 → dulus-3.10.4}/tests/test_wire_events.py +0 -0
  320. {dulus-3.10.3 → dulus-3.10.4}/tmux_offloader.py +0 -0
  321. {dulus-3.10.3 → dulus-3.10.4}/tmux_tools.py +0 -0
  322. {dulus-3.10.3 → dulus-3.10.4}/tool_registry.py +0 -0
  323. {dulus-3.10.3 → dulus-3.10.4}/tools.py +0 -0
  324. {dulus-3.10.3 → dulus-3.10.4}/tools_background.py +0 -0
  325. {dulus-3.10.3 → dulus-3.10.4}/tools_think.py +0 -0
  326. {dulus-3.10.3 → dulus-3.10.4}/tools_todo.py +0 -0
  327. {dulus-3.10.3 → dulus-3.10.4}/ui/__init__.py +0 -0
  328. {dulus-3.10.3 → dulus-3.10.4}/ui/input.py +0 -0
  329. {dulus-3.10.3 → dulus-3.10.4}/ui/render.py +0 -0
  330. {dulus-3.10.3 → dulus-3.10.4}/updater.py +0 -0
  331. {dulus-3.10.3 → dulus-3.10.4}/voice/__init__.py +0 -0
  332. {dulus-3.10.3 → dulus-3.10.4}/voice/audio_utils.py +0 -0
  333. {dulus-3.10.3 → dulus-3.10.4}/voice/keyterms.py +0 -0
  334. {dulus-3.10.3 → dulus-3.10.4}/voice/recorder.py +0 -0
  335. {dulus-3.10.3 → dulus-3.10.4}/voice/stt.py +0 -0
  336. {dulus-3.10.3 → dulus-3.10.4}/voice/tts.py +0 -0
  337. {dulus-3.10.3 → dulus-3.10.4}/voice/wake_word.py +0 -0
  338. {dulus-3.10.3 → dulus-3.10.4}/webbridge/__init__.py +0 -0
  339. {dulus-3.10.3 → dulus-3.10.4}/webbridge/core.py +0 -0
  340. {dulus-3.10.3 → dulus-3.10.4}/webbridge/tools.py +0 -0
  341. {dulus-3.10.3 → dulus-3.10.4}/webchat.py +0 -0
  342. {dulus-3.10.3 → dulus-3.10.4}/webchat_server.py +0 -0
  343. {dulus-3.10.3 → dulus-3.10.4}/webchat_ui/__init__.py +0 -0
  344. {dulus-3.10.3 → dulus-3.10.4}/webchat_ui/app.js +0 -0
  345. {dulus-3.10.3 → dulus-3.10.4}/webchat_ui/index.html +0 -0
  346. {dulus-3.10.3 → dulus-3.10.4}/webchat_ui/style.css +0 -0
  347. {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
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
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.3"
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