code-puppy-core-plugins 0.0.2__py3-none-any.whl

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 (313) hide show
  1. code_puppy_core_plugins/__init__.py +5 -0
  2. code_puppy_core_plugins/acp/README.md +266 -0
  3. code_puppy_core_plugins/acp/__init__.py +8 -0
  4. code_puppy_core_plugins/acp/agent.py +494 -0
  5. code_puppy_core_plugins/acp/bridge.py +309 -0
  6. code_puppy_core_plugins/acp/capabilities.py +73 -0
  7. code_puppy_core_plugins/acp/commands.py +116 -0
  8. code_puppy_core_plugins/acp/content.py +115 -0
  9. code_puppy_core_plugins/acp/io_delegation.py +231 -0
  10. code_puppy_core_plugins/acp/mcp_config.py +66 -0
  11. code_puppy_core_plugins/acp/permissions.py +159 -0
  12. code_puppy_core_plugins/acp/persistence.py +197 -0
  13. code_puppy_core_plugins/acp/register_callbacks.py +110 -0
  14. code_puppy_core_plugins/acp/replay.py +114 -0
  15. code_puppy_core_plugins/acp/session.py +329 -0
  16. code_puppy_core_plugins/acp/session_config.py +179 -0
  17. code_puppy_core_plugins/acp/state.py +135 -0
  18. code_puppy_core_plugins/agent_skills/__init__.py +22 -0
  19. code_puppy_core_plugins/agent_skills/config.py +208 -0
  20. code_puppy_core_plugins/agent_skills/discovery.py +337 -0
  21. code_puppy_core_plugins/agent_skills/downloader.py +392 -0
  22. code_puppy_core_plugins/agent_skills/enabled_skills.py +82 -0
  23. code_puppy_core_plugins/agent_skills/installer.py +22 -0
  24. code_puppy_core_plugins/agent_skills/metadata.py +219 -0
  25. code_puppy_core_plugins/agent_skills/prompt_builder.py +49 -0
  26. code_puppy_core_plugins/agent_skills/provider.py +52 -0
  27. code_puppy_core_plugins/agent_skills/register_callbacks.py +309 -0
  28. code_puppy_core_plugins/agent_skills/remote_catalog.py +322 -0
  29. code_puppy_core_plugins/agent_skills/skill_catalog.py +257 -0
  30. code_puppy_core_plugins/agent_skills/skill_commands.py +102 -0
  31. code_puppy_core_plugins/agent_skills/skills_install_menu.py +698 -0
  32. code_puppy_core_plugins/agent_skills/skills_menu.py +794 -0
  33. code_puppy_core_plugins/aws_bedrock/__init__.py +14 -0
  34. code_puppy_core_plugins/aws_bedrock/config.py +101 -0
  35. code_puppy_core_plugins/aws_bedrock/register_callbacks.py +243 -0
  36. code_puppy_core_plugins/aws_bedrock/utils.py +186 -0
  37. code_puppy_core_plugins/azure_foundry/README.md +238 -0
  38. code_puppy_core_plugins/azure_foundry/__init__.py +15 -0
  39. code_puppy_core_plugins/azure_foundry/config.py +126 -0
  40. code_puppy_core_plugins/azure_foundry/discovery.py +189 -0
  41. code_puppy_core_plugins/azure_foundry/register_callbacks.py +506 -0
  42. code_puppy_core_plugins/azure_foundry/token.py +182 -0
  43. code_puppy_core_plugins/azure_foundry/utils.py +375 -0
  44. code_puppy_core_plugins/btw/__init__.py +1 -0
  45. code_puppy_core_plugins/btw/inline_view.py +135 -0
  46. code_puppy_core_plugins/btw/register_callbacks.py +106 -0
  47. code_puppy_core_plugins/btw/side_query.py +91 -0
  48. code_puppy_core_plugins/chatgpt_oauth/IMAGEGEN_SKILL.md +83 -0
  49. code_puppy_core_plugins/chatgpt_oauth/__init__.py +8 -0
  50. code_puppy_core_plugins/chatgpt_oauth/config.py +52 -0
  51. code_puppy_core_plugins/chatgpt_oauth/image_generation.py +253 -0
  52. code_puppy_core_plugins/chatgpt_oauth/image_tool.py +70 -0
  53. code_puppy_core_plugins/chatgpt_oauth/oauth_flow.py +461 -0
  54. code_puppy_core_plugins/chatgpt_oauth/register_callbacks.py +288 -0
  55. code_puppy_core_plugins/chatgpt_oauth/test_plugin.py +663 -0
  56. code_puppy_core_plugins/chatgpt_oauth/usage.py +113 -0
  57. code_puppy_core_plugins/chatgpt_oauth/utils.py +534 -0
  58. code_puppy_core_plugins/claude_code_hooks/__init__.py +1 -0
  59. code_puppy_core_plugins/claude_code_hooks/config.py +137 -0
  60. code_puppy_core_plugins/claude_code_hooks/register_callbacks.py +383 -0
  61. code_puppy_core_plugins/claude_code_oauth/README.md +167 -0
  62. code_puppy_core_plugins/claude_code_oauth/SETUP.md +93 -0
  63. code_puppy_core_plugins/claude_code_oauth/__init__.py +25 -0
  64. code_puppy_core_plugins/claude_code_oauth/config.py +59 -0
  65. code_puppy_core_plugins/claude_code_oauth/fast_mode.py +122 -0
  66. code_puppy_core_plugins/claude_code_oauth/prompt_handler.py +64 -0
  67. code_puppy_core_plugins/claude_code_oauth/register_callbacks.py +719 -0
  68. code_puppy_core_plugins/claude_code_oauth/test_plugin.py +285 -0
  69. code_puppy_core_plugins/claude_code_oauth/token_refresh_heartbeat.py +240 -0
  70. code_puppy_core_plugins/claude_code_oauth/utils.py +663 -0
  71. code_puppy_core_plugins/code_puppy_agent/AGENTS_AND_TOOLS.md +155 -0
  72. code_puppy_core_plugins/code_puppy_agent/MODELS_AND_MCP.md +104 -0
  73. code_puppy_core_plugins/code_puppy_agent/PLUGINS_AND_CALLBACKS.md +147 -0
  74. code_puppy_core_plugins/code_puppy_agent/SESSIONS_AND_HISTORY.md +39 -0
  75. code_puppy_core_plugins/code_puppy_agent/SKILL.md +180 -0
  76. code_puppy_core_plugins/code_puppy_agent/SKILLS_SYSTEM.md +136 -0
  77. code_puppy_core_plugins/code_puppy_agent/SYSTEM_PROMPT_CONFIG_AND_I18N.md +160 -0
  78. code_puppy_core_plugins/code_puppy_agent/__init__.py +5 -0
  79. code_puppy_core_plugins/code_puppy_agent/register_callbacks.py +46 -0
  80. code_puppy_core_plugins/computer_use/NativeCapture.swift +123 -0
  81. code_puppy_core_plugins/computer_use/README.md +109 -0
  82. code_puppy_core_plugins/computer_use/__init__.py +1 -0
  83. code_puppy_core_plugins/computer_use/accessibility.py +163 -0
  84. code_puppy_core_plugins/computer_use/activation.py +75 -0
  85. code_puppy_core_plugins/computer_use/backend.py +584 -0
  86. code_puppy_core_plugins/computer_use/backend_types.py +5 -0
  87. code_puppy_core_plugins/computer_use/capture.py +110 -0
  88. code_puppy_core_plugins/computer_use/commands.py +62 -0
  89. code_puppy_core_plugins/computer_use/geometry.py +81 -0
  90. code_puppy_core_plugins/computer_use/inline_image.py +56 -0
  91. code_puppy_core_plugins/computer_use/keycodes.py +47 -0
  92. code_puppy_core_plugins/computer_use/policy.py +144 -0
  93. code_puppy_core_plugins/computer_use/register_callbacks.py +96 -0
  94. code_puppy_core_plugins/computer_use/safety.py +18 -0
  95. code_puppy_core_plugins/computer_use/settle.py +64 -0
  96. code_puppy_core_plugins/computer_use/state.py +86 -0
  97. code_puppy_core_plugins/computer_use/tools.py +469 -0
  98. code_puppy_core_plugins/context_indicator/__init__.py +14 -0
  99. code_puppy_core_plugins/context_indicator/register_callbacks.py +249 -0
  100. code_puppy_core_plugins/context_indicator/usage.py +40 -0
  101. code_puppy_core_plugins/copilot_auth/__init__.py +11 -0
  102. code_puppy_core_plugins/copilot_auth/config.py +93 -0
  103. code_puppy_core_plugins/copilot_auth/reasoning_client.py +409 -0
  104. code_puppy_core_plugins/copilot_auth/register_callbacks.py +460 -0
  105. code_puppy_core_plugins/copilot_auth/utils.py +587 -0
  106. code_puppy_core_plugins/customizable_commands/__init__.py +0 -0
  107. code_puppy_core_plugins/customizable_commands/register_callbacks.py +464 -0
  108. code_puppy_core_plugins/dbos_durable_exec/__init__.py +1 -0
  109. code_puppy_core_plugins/dbos_durable_exec/cancel.py +15 -0
  110. code_puppy_core_plugins/dbos_durable_exec/commands.py +31 -0
  111. code_puppy_core_plugins/dbos_durable_exec/config.py +31 -0
  112. code_puppy_core_plugins/dbos_durable_exec/lifecycle.py +83 -0
  113. code_puppy_core_plugins/dbos_durable_exec/register_callbacks.py +45 -0
  114. code_puppy_core_plugins/dbos_durable_exec/runtime.py +65 -0
  115. code_puppy_core_plugins/dbos_durable_exec/startup_lock.py +155 -0
  116. code_puppy_core_plugins/dbos_durable_exec/workflow_ids.py +16 -0
  117. code_puppy_core_plugins/dbos_durable_exec/wrapper.py +58 -0
  118. code_puppy_core_plugins/destructive_command_guard/AGENTS.md +79 -0
  119. code_puppy_core_plugins/destructive_command_guard/__init__.py +14 -0
  120. code_puppy_core_plugins/destructive_command_guard/detector.py +374 -0
  121. code_puppy_core_plugins/destructive_command_guard/register_callbacks.py +165 -0
  122. code_puppy_core_plugins/emoji_filter/__init__.py +12 -0
  123. code_puppy_core_plugins/emoji_filter/config.py +26 -0
  124. code_puppy_core_plugins/emoji_filter/register_callbacks.py +238 -0
  125. code_puppy_core_plugins/emoji_filter/stripper.py +45 -0
  126. code_puppy_core_plugins/empty_html_comment_filter/register_callbacks.py +116 -0
  127. code_puppy_core_plugins/example_custom_command/README.md +328 -0
  128. code_puppy_core_plugins/example_custom_command/register_callbacks.py +66 -0
  129. code_puppy_core_plugins/file_permission_handler/__init__.py +4 -0
  130. code_puppy_core_plugins/file_permission_handler/register_callbacks.py +575 -0
  131. code_puppy_core_plugins/flux_bootstrap/__init__.py +5 -0
  132. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/_docs/README.md +173 -0
  133. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/_docs/cheatsheet.md +86 -0
  134. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/_docs/pipeline.md +167 -0
  135. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/_docs/synopsis.md +392 -0
  136. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/about.md +51 -0
  137. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/address-feedback.md +129 -0
  138. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/ask.md +211 -0
  139. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/aug.md +181 -0
  140. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/auto-pilot.md +71 -0
  141. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/cheatsheet.md +16 -0
  142. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/commit.md +103 -0
  143. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/config.md +108 -0
  144. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/create-pr.md +195 -0
  145. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/exec.md +221 -0
  146. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/new.md +151 -0
  147. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/qa.md +208 -0
  148. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/rebase.md +180 -0
  149. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/review.md +304 -0
  150. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/split.md +120 -0
  151. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/squash-commits.md +179 -0
  152. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/status.md +46 -0
  153. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/tests.md +155 -0
  154. code_puppy_core_plugins/flux_bootstrap/bundled/scripts/flux_about.py +154 -0
  155. code_puppy_core_plugins/flux_bootstrap/bundled/scripts/flux_cheatsheet.py +248 -0
  156. code_puppy_core_plugins/flux_bootstrap/bundled/scripts/flux_status.py +345 -0
  157. code_puppy_core_plugins/flux_bootstrap/installer.py +256 -0
  158. code_puppy_core_plugins/flux_bootstrap/register_callbacks.py +71 -0
  159. code_puppy_core_plugins/force_push_guard/__init__.py +5 -0
  160. code_puppy_core_plugins/force_push_guard/detector.py +96 -0
  161. code_puppy_core_plugins/force_push_guard/register_callbacks.py +161 -0
  162. code_puppy_core_plugins/fork/__init__.py +7 -0
  163. code_puppy_core_plugins/fork/register_callbacks.py +445 -0
  164. code_puppy_core_plugins/frontend_emitter/__init__.py +25 -0
  165. code_puppy_core_plugins/frontend_emitter/emitter.py +249 -0
  166. code_puppy_core_plugins/frontend_emitter/register_callbacks.py +412 -0
  167. code_puppy_core_plugins/frontend_emitter/session_context.py +50 -0
  168. code_puppy_core_plugins/grok_oauth/__init__.py +6 -0
  169. code_puppy_core_plugins/grok_oauth/config.py +49 -0
  170. code_puppy_core_plugins/grok_oauth/oauth_flow.py +213 -0
  171. code_puppy_core_plugins/grok_oauth/register_callbacks.py +132 -0
  172. code_puppy_core_plugins/grok_oauth/utils.py +195 -0
  173. code_puppy_core_plugins/herdr/README.md +168 -0
  174. code_puppy_core_plugins/herdr/__init__.py +9 -0
  175. code_puppy_core_plugins/herdr/client.py +339 -0
  176. code_puppy_core_plugins/herdr/register_callbacks.py +140 -0
  177. code_puppy_core_plugins/herdr/reporter.py +212 -0
  178. code_puppy_core_plugins/herdr/smoke.py +143 -0
  179. code_puppy_core_plugins/herdr/sources.py +133 -0
  180. code_puppy_core_plugins/hook_creator/__init__.py +1 -0
  181. code_puppy_core_plugins/hook_creator/register_callbacks.py +33 -0
  182. code_puppy_core_plugins/hook_manager/__init__.py +1 -0
  183. code_puppy_core_plugins/hook_manager/config.py +290 -0
  184. code_puppy_core_plugins/hook_manager/hooks_menu.py +571 -0
  185. code_puppy_core_plugins/hook_manager/register_callbacks.py +227 -0
  186. code_puppy_core_plugins/meta_oauth/__init__.py +1 -0
  187. code_puppy_core_plugins/meta_oauth/config.py +55 -0
  188. code_puppy_core_plugins/meta_oauth/oauth_flow.py +109 -0
  189. code_puppy_core_plugins/meta_oauth/register_callbacks.py +154 -0
  190. code_puppy_core_plugins/meta_oauth/utils.py +252 -0
  191. code_puppy_core_plugins/namespace_skill_search/README.md +118 -0
  192. code_puppy_core_plugins/namespace_skill_search/__init__.py +15 -0
  193. code_puppy_core_plugins/namespace_skill_search/namespaces.py +125 -0
  194. code_puppy_core_plugins/namespace_skill_search/register_callbacks.py +99 -0
  195. code_puppy_core_plugins/namespace_skill_search/search_tool.py +158 -0
  196. code_puppy_core_plugins/no_tools/__init__.py +0 -0
  197. code_puppy_core_plugins/no_tools/register_callbacks.py +42 -0
  198. code_puppy_core_plugins/oauth_pasteback.py +110 -0
  199. code_puppy_core_plugins/oauth_puppy_html.py +194 -0
  200. code_puppy_core_plugins/obsidian_agent/README.md +52 -0
  201. code_puppy_core_plugins/obsidian_agent/__init__.py +8 -0
  202. code_puppy_core_plugins/obsidian_agent/agent_obsidian.py +147 -0
  203. code_puppy_core_plugins/obsidian_agent/register_callbacks.py +13 -0
  204. code_puppy_core_plugins/ollama/__init__.py +1 -0
  205. code_puppy_core_plugins/ollama/register_callbacks.py +127 -0
  206. code_puppy_core_plugins/ollama_setup/__init__.py +5 -0
  207. code_puppy_core_plugins/ollama_setup/completer.py +38 -0
  208. code_puppy_core_plugins/ollama_setup/register_callbacks.py +419 -0
  209. code_puppy_core_plugins/plugin_list/__init__.py +0 -0
  210. code_puppy_core_plugins/plugin_list/plugin_contributions.py +348 -0
  211. code_puppy_core_plugins/plugin_list/plugin_meta.py +67 -0
  212. code_puppy_core_plugins/plugin_list/plugin_text_utils.py +173 -0
  213. code_puppy_core_plugins/plugin_list/plugins_menu.py +455 -0
  214. code_puppy_core_plugins/plugin_list/plugins_menu_layout.py +194 -0
  215. code_puppy_core_plugins/plugin_list/plugins_menu_render.py +431 -0
  216. code_puppy_core_plugins/plugin_list/project_trust_flow.py +162 -0
  217. code_puppy_core_plugins/plugin_list/register_callbacks.py +281 -0
  218. code_puppy_core_plugins/pop_command/__init__.py +1 -0
  219. code_puppy_core_plugins/pop_command/register_callbacks.py +191 -0
  220. code_puppy_core_plugins/prompt_newline/__init__.py +13 -0
  221. code_puppy_core_plugins/prompt_newline/config.py +21 -0
  222. code_puppy_core_plugins/prompt_newline/register_callbacks.py +163 -0
  223. code_puppy_core_plugins/prune/__init__.py +1 -0
  224. code_puppy_core_plugins/prune/prune_menu.py +308 -0
  225. code_puppy_core_plugins/prune/prune_model.py +517 -0
  226. code_puppy_core_plugins/prune/prune_render.py +354 -0
  227. code_puppy_core_plugins/prune/register_callbacks.py +388 -0
  228. code_puppy_core_plugins/puppy_kennel/README.md +203 -0
  229. code_puppy_core_plugins/puppy_kennel/__init__.py +6 -0
  230. code_puppy_core_plugins/puppy_kennel/commands.py +206 -0
  231. code_puppy_core_plugins/puppy_kennel/config.py +36 -0
  232. code_puppy_core_plugins/puppy_kennel/kennel.py +368 -0
  233. code_puppy_core_plugins/puppy_kennel/packer.py +152 -0
  234. code_puppy_core_plugins/puppy_kennel/recorder.py +83 -0
  235. code_puppy_core_plugins/puppy_kennel/register_callbacks.py +109 -0
  236. code_puppy_core_plugins/puppy_kennel/retriever.py +28 -0
  237. code_puppy_core_plugins/puppy_kennel/schema.py +78 -0
  238. code_puppy_core_plugins/puppy_kennel/state.py +68 -0
  239. code_puppy_core_plugins/puppy_kennel/tools.py +449 -0
  240. code_puppy_core_plugins/puppy_kennel/wings.py +110 -0
  241. code_puppy_core_plugins/puppy_spinner/README.md +93 -0
  242. code_puppy_core_plugins/puppy_spinner/__init__.py +11 -0
  243. code_puppy_core_plugins/puppy_spinner/builtin_frames.py +172 -0
  244. code_puppy_core_plugins/puppy_spinner/commands.py +116 -0
  245. code_puppy_core_plugins/puppy_spinner/picker.py +379 -0
  246. code_puppy_core_plugins/puppy_spinner/register_callbacks.py +193 -0
  247. code_puppy_core_plugins/puppy_spinner/spinners.py +444 -0
  248. code_puppy_core_plugins/quick_resume/__init__.py +0 -0
  249. code_puppy_core_plugins/quick_resume/register_callbacks.py +77 -0
  250. code_puppy_core_plugins/review_pr/__init__.py +1 -0
  251. code_puppy_core_plugins/review_pr/register_callbacks.py +162 -0
  252. code_puppy_core_plugins/shell_safety/__init__.py +6 -0
  253. code_puppy_core_plugins/shell_safety/agent_shell_safety.py +69 -0
  254. code_puppy_core_plugins/shell_safety/command_cache.py +156 -0
  255. code_puppy_core_plugins/shell_safety/register_callbacks.py +203 -0
  256. code_puppy_core_plugins/stack_dump/__init__.py +1 -0
  257. code_puppy_core_plugins/stack_dump/register_callbacks.py +79 -0
  258. code_puppy_core_plugins/statusline/__init__.py +14 -0
  259. code_puppy_core_plugins/statusline/config.py +68 -0
  260. code_puppy_core_plugins/statusline/payload.py +138 -0
  261. code_puppy_core_plugins/statusline/prompt_patch.py +76 -0
  262. code_puppy_core_plugins/statusline/register_callbacks.py +45 -0
  263. code_puppy_core_plugins/statusline/runner.py +122 -0
  264. code_puppy_core_plugins/statusline/statusline_command.py +197 -0
  265. code_puppy_core_plugins/steer_queue/__init__.py +10 -0
  266. code_puppy_core_plugins/steer_queue/queue_menu.py +444 -0
  267. code_puppy_core_plugins/steer_queue/register_callbacks.py +138 -0
  268. code_puppy_core_plugins/subagent_panel/README.md +102 -0
  269. code_puppy_core_plugins/subagent_panel/__init__.py +24 -0
  270. code_puppy_core_plugins/subagent_panel/coalesce_patch.py +242 -0
  271. code_puppy_core_plugins/subagent_panel/panel_render.py +145 -0
  272. code_puppy_core_plugins/subagent_panel/register_callbacks.py +535 -0
  273. code_puppy_core_plugins/subagent_panel/resume_repaint.py +112 -0
  274. code_puppy_core_plugins/subagent_panel/state.py +231 -0
  275. code_puppy_core_plugins/switch_agent_resume/__init__.py +5 -0
  276. code_puppy_core_plugins/switch_agent_resume/register_callbacks.py +314 -0
  277. code_puppy_core_plugins/theme/README.md +86 -0
  278. code_puppy_core_plugins/theme/__init__.py +14 -0
  279. code_puppy_core_plugins/theme/bundled_palettes.py +346 -0
  280. code_puppy_core_plugins/theme/content_styles.py +146 -0
  281. code_puppy_core_plugins/theme/osc_palette.py +178 -0
  282. code_puppy_core_plugins/theme/picker.py +358 -0
  283. code_puppy_core_plugins/theme/prompt_toolkit_theme.py +113 -0
  284. code_puppy_core_plugins/theme/register_callbacks.py +348 -0
  285. code_puppy_core_plugins/theme/rich_themes.py +202 -0
  286. code_puppy_core_plugins/theme/themes.py +720 -0
  287. code_puppy_core_plugins/timestamp_heartbeat/__init__.py +1 -0
  288. code_puppy_core_plugins/timestamp_heartbeat/register_callbacks.py +114 -0
  289. code_puppy_core_plugins/token_ratio_learner/__init__.py +5 -0
  290. code_puppy_core_plugins/token_ratio_learner/ratios.py +208 -0
  291. code_puppy_core_plugins/token_ratio_learner/register_callbacks.py +213 -0
  292. code_puppy_core_plugins/universal_constructor/__init__.py +13 -0
  293. code_puppy_core_plugins/universal_constructor/models.py +138 -0
  294. code_puppy_core_plugins/universal_constructor/provider.py +62 -0
  295. code_puppy_core_plugins/universal_constructor/register_callbacks.py +75 -0
  296. code_puppy_core_plugins/universal_constructor/registry.py +302 -0
  297. code_puppy_core_plugins/universal_constructor/sandbox.py +584 -0
  298. code_puppy_core_plugins/wide_completion_menu/__init__.py +6 -0
  299. code_puppy_core_plugins/wide_completion_menu/register_callbacks.py +165 -0
  300. code_puppy_core_plugins/wiggum/__init__.py +1 -0
  301. code_puppy_core_plugins/wiggum/goal_runs.py +91 -0
  302. code_puppy_core_plugins/wiggum/judge.py +299 -0
  303. code_puppy_core_plugins/wiggum/judge_config.py +249 -0
  304. code_puppy_core_plugins/wiggum/judges_menu.py +814 -0
  305. code_puppy_core_plugins/wiggum/register_callbacks.py +569 -0
  306. code_puppy_core_plugins/wiggum/state.py +89 -0
  307. code_puppy_core_plugins/yolo_cli/__init__.py +1 -0
  308. code_puppy_core_plugins/yolo_cli/register_callbacks.py +41 -0
  309. code_puppy_core_plugins-0.0.2.dist-info/METADATA +64 -0
  310. code_puppy_core_plugins-0.0.2.dist-info/RECORD +313 -0
  311. code_puppy_core_plugins-0.0.2.dist-info/WHEEL +4 -0
  312. code_puppy_core_plugins-0.0.2.dist-info/entry_points.txt +55 -0
  313. code_puppy_core_plugins-0.0.2.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,322 @@
1
+ """Remote skills catalog client.
2
+
3
+ Fetches the remote skills catalog JSON and exposes a cached, parsed view.
4
+
5
+ Design goals:
6
+ - Never crash the app (defensive parsing + broad error handling).
7
+ - Local caching with TTL for fast startup and offline use.
8
+ - Synchronous networking only (httpx.Client).
9
+
10
+ Schema source:
11
+ https://www.llmspec.dev/skills/skills.json
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ import logging
18
+ import time
19
+ from dataclasses import dataclass
20
+ from pathlib import Path
21
+ from typing import Any, Optional
22
+ from urllib.parse import urljoin
23
+
24
+ import httpx
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+ SKILLS_JSON_URL = "https://www.llmspec.dev/skills/skills.json"
29
+
30
+ _CACHE_DIR = Path.home() / ".code_puppy" / "cache"
31
+ _CACHE_PATH = _CACHE_DIR / "skills_catalog.json"
32
+ _CACHE_TTL_SECONDS = 30 * 60
33
+
34
+
35
+ @dataclass(frozen=True, slots=True)
36
+ class RemoteSkillEntry:
37
+ """Flattened remote skill entry."""
38
+
39
+ name: str
40
+ description: str
41
+ group: str
42
+ download_url: str
43
+ zip_size_bytes: int
44
+ file_count: int
45
+ has_scripts: bool
46
+ has_references: bool
47
+ has_license: bool
48
+
49
+
50
+ @dataclass(frozen=True, slots=True)
51
+ class RemoteCatalogData:
52
+ """Parsed remote catalog.
53
+
54
+ Attributes:
55
+ version: Catalog version string.
56
+ base_url: Base URL used to build absolute download_url values.
57
+ total_skills: Total number of skills in the remote catalog.
58
+ groups: Raw group objects from the JSON (kept as dicts for flexibility).
59
+ entries: Flattened list of all skills across all groups.
60
+ """
61
+
62
+ version: str
63
+ base_url: str
64
+ total_skills: int
65
+ groups: list[dict[str, Any]]
66
+ entries: list[RemoteSkillEntry]
67
+
68
+
69
+ def _safe_int(value: Any, default: int = 0) -> int:
70
+ """Convert value to int, returning default on failure."""
71
+
72
+ try:
73
+ if value is None:
74
+ return default
75
+ return int(value)
76
+ except Exception:
77
+ return default
78
+
79
+
80
+ def _safe_bool(value: Any, default: bool = False) -> bool:
81
+ """Convert value to bool, returning default on failure."""
82
+
83
+ if value is None:
84
+ return default
85
+ return bool(value)
86
+
87
+
88
+ def _cache_is_fresh(cache_path: Path, ttl_seconds: int) -> bool:
89
+ """Check whether the on-disk catalog cache is within TTL."""
90
+
91
+ try:
92
+ if not cache_path.exists():
93
+ return False
94
+ age_seconds = time.time() - cache_path.stat().st_mtime
95
+ return age_seconds <= ttl_seconds
96
+ except Exception as e:
97
+ logger.debug(f"Failed to check cache age for {cache_path}: {e}")
98
+ return False
99
+
100
+
101
+ def _read_cache(cache_path: Path) -> Optional[dict[str, Any]]:
102
+ """Read and deserialize the cached catalog JSON from disk."""
103
+
104
+ try:
105
+ if not cache_path.exists():
106
+ return None
107
+ raw = cache_path.read_text(encoding="utf-8")
108
+ data = json.loads(raw)
109
+ if not isinstance(data, dict):
110
+ logger.warning(f"Cache JSON is not an object: {cache_path}")
111
+ return None
112
+ return data
113
+ except Exception as e:
114
+ logger.warning(f"Failed to read cache {cache_path}: {e}")
115
+ return None
116
+
117
+
118
+ def _write_cache(cache_path: Path, data: dict[str, Any]) -> bool:
119
+ """Serialize and write catalog JSON to the disk cache."""
120
+
121
+ try:
122
+ cache_path.parent.mkdir(parents=True, exist_ok=True)
123
+ # Stable formatting so diffs are readable when debugging.
124
+ cache_path.write_text(
125
+ json.dumps(data, indent=2, sort_keys=True) + "\n", encoding="utf-8"
126
+ )
127
+ return True
128
+ except Exception as e:
129
+ logger.warning(f"Failed to write cache {cache_path}: {e}")
130
+ return False
131
+
132
+
133
+ def _fetch_remote_json(url: str) -> Optional[dict[str, Any]]:
134
+ """Fetch the skills catalog JSON from the remote URL."""
135
+
136
+ headers = {
137
+ "Accept": "application/json",
138
+ "User-Agent": "code-puppy/remote-catalog",
139
+ }
140
+
141
+ try:
142
+ with httpx.Client(timeout=15, headers=headers) as client:
143
+ response = client.get(url)
144
+ response.raise_for_status()
145
+ data = response.json()
146
+
147
+ if not isinstance(data, dict):
148
+ logger.error(f"Remote catalog JSON was not an object. Got: {type(data)}")
149
+ return None
150
+
151
+ return data
152
+
153
+ except httpx.HTTPStatusError as e:
154
+ logger.warning(
155
+ "Remote catalog request returned bad status: "
156
+ f"{e.response.status_code} {e.response.reason_phrase}"
157
+ )
158
+ return None
159
+ except (httpx.ConnectError, httpx.TimeoutException, httpx.NetworkError) as e:
160
+ logger.warning(f"Remote catalog network failure: {e}")
161
+ return None
162
+ except json.JSONDecodeError as e:
163
+ logger.warning(f"Remote catalog returned invalid JSON: {e}")
164
+ return None
165
+ except Exception as e:
166
+ logger.exception(f"Unexpected error fetching remote catalog: {e}")
167
+ return None
168
+
169
+
170
+ def _parse_catalog(raw: dict[str, Any]) -> Optional[RemoteCatalogData]:
171
+ """Parse raw JSON dicts into a list of RemoteSkillEntry objects."""
172
+
173
+ try:
174
+ version = str(raw.get("version") or "")
175
+ base_url = str(raw.get("base_url") or "")
176
+ total_skills = _safe_int(raw.get("total_skills"), default=0)
177
+
178
+ raw_groups = raw.get("groups")
179
+ if not isinstance(raw_groups, list):
180
+ logger.warning("Remote catalog 'groups' missing or not a list")
181
+ raw_groups = []
182
+
183
+ groups: list[dict[str, Any]] = []
184
+ entries: list[RemoteSkillEntry] = []
185
+
186
+ # Ensure urljoin behaves (needs trailing slash on base).
187
+ base_for_join = base_url.rstrip("/") + "/" if base_url else ""
188
+
189
+ for group_obj in raw_groups:
190
+ if not isinstance(group_obj, dict):
191
+ continue
192
+ groups.append(group_obj)
193
+
194
+ group_slug = str(group_obj.get("slug") or group_obj.get("name") or "")
195
+ skills = group_obj.get("skills")
196
+ if not isinstance(skills, list):
197
+ continue
198
+
199
+ for skill in skills:
200
+ if not isinstance(skill, dict):
201
+ continue
202
+
203
+ name = str(skill.get("name") or "").strip()
204
+ if not name:
205
+ # If name is missing, it can't be indexed/activated anyway.
206
+ continue
207
+
208
+ description = str(skill.get("description") or "")
209
+ group = str(skill.get("group") or group_slug or "")
210
+
211
+ download_path = str(skill.get("download_url") or "")
212
+ download_url = (
213
+ urljoin(base_for_join, download_path)
214
+ if base_for_join
215
+ else download_path
216
+ )
217
+
218
+ contents = skill.get("contents")
219
+ if not isinstance(contents, dict):
220
+ contents = {}
221
+
222
+ entries.append(
223
+ RemoteSkillEntry(
224
+ name=name,
225
+ description=description,
226
+ group=group,
227
+ download_url=download_url,
228
+ zip_size_bytes=_safe_int(
229
+ skill.get("zip_size_bytes"), default=0
230
+ ),
231
+ file_count=_safe_int(skill.get("file_count"), default=0),
232
+ has_scripts=_safe_bool(
233
+ contents.get("has_scripts"), default=False
234
+ ),
235
+ has_references=_safe_bool(
236
+ contents.get("has_references"), default=False
237
+ ),
238
+ has_license=_safe_bool(
239
+ contents.get("has_license"), default=False
240
+ ),
241
+ )
242
+ )
243
+
244
+ if not version:
245
+ logger.debug("Remote catalog 'version' is missing/empty")
246
+ if not base_url:
247
+ logger.debug("Remote catalog 'base_url' is missing/empty")
248
+
249
+ return RemoteCatalogData(
250
+ version=version,
251
+ base_url=base_url,
252
+ total_skills=total_skills,
253
+ groups=groups,
254
+ entries=entries,
255
+ )
256
+
257
+ except Exception as e:
258
+ logger.exception(f"Failed to parse remote catalog JSON: {e}")
259
+ return None
260
+
261
+
262
+ def fetch_remote_catalog(force_refresh: bool = False) -> Optional[RemoteCatalogData]:
263
+ """Fetch the remote skills catalog with caching and offline fallback.
264
+
265
+ Cache behavior:
266
+ - Cache file: ~/.code_puppy/cache/skills_catalog.json
267
+ - TTL: 30 minutes (based on file mtime)
268
+ - Offline fallback: if network fetch fails, use cache if present (even if expired)
269
+
270
+ Args:
271
+ force_refresh: If True, always attempt a network fetch.
272
+
273
+ Returns:
274
+ Parsed RemoteCatalogData on success, otherwise None.
275
+ """
276
+
277
+ cache_fresh = _cache_is_fresh(_CACHE_PATH, _CACHE_TTL_SECONDS)
278
+
279
+ # Use fresh cache unless forced.
280
+ if not force_refresh and cache_fresh:
281
+ logger.info(f"Using fresh remote catalog cache: {_CACHE_PATH}")
282
+ cached = _read_cache(_CACHE_PATH)
283
+ if cached is None:
284
+ logger.warning("Fresh cache exists but could not be read; refetching")
285
+ else:
286
+ parsed = _parse_catalog(cached)
287
+ if parsed is not None:
288
+ return parsed
289
+ logger.warning("Fresh cache exists but could not be parsed; refetching")
290
+
291
+ if force_refresh:
292
+ logger.info("Force refresh enabled; fetching remote skills catalog")
293
+ elif _CACHE_PATH.exists():
294
+ logger.info(
295
+ "Cache is missing or stale; fetching remote skills catalog "
296
+ f"(cache_path={_CACHE_PATH}, fresh={cache_fresh})"
297
+ )
298
+ else:
299
+ logger.info("No cache present; fetching remote skills catalog")
300
+
301
+ remote_raw = _fetch_remote_json(SKILLS_JSON_URL)
302
+ if remote_raw is not None:
303
+ logger.info("Fetched remote skills catalog successfully")
304
+ _write_cache(_CACHE_PATH, remote_raw)
305
+ parsed = _parse_catalog(remote_raw)
306
+ if parsed is not None:
307
+ return parsed
308
+ logger.warning("Remote catalog fetched but failed to parse")
309
+
310
+ # Offline fallback: use cache even if expired.
311
+ if _CACHE_PATH.exists():
312
+ logger.warning(
313
+ "Remote fetch failed; falling back to cached skills catalog "
314
+ f"(even if expired): {_CACHE_PATH}"
315
+ )
316
+ cached = _read_cache(_CACHE_PATH)
317
+ if cached is None:
318
+ return None
319
+ return _parse_catalog(cached)
320
+
321
+ logger.error("Remote fetch failed and no cache is available")
322
+ return None
@@ -0,0 +1,257 @@
1
+ """Remote-backed skill catalog adapter.
2
+
3
+ This module provides a stable public interface for the rest of the codebase.
4
+ Historically, code_puppy used a local static catalog. We now source skills from
5
+ `remote_catalog.fetch_remote_catalog()` while keeping the same access patterns.
6
+
7
+ Public API:
8
+ from code_puppy_core_plugins.agent_skills.skill_catalog import (
9
+ SkillCatalog,
10
+ SkillCatalogEntry,
11
+ _format_display_name,
12
+ catalog,
13
+ )
14
+
15
+ If the remote catalog can't be fetched (and there's no cache), the catalog is
16
+ empty by default (and we log a warning).
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import logging
22
+ from dataclasses import dataclass, field
23
+ from pathlib import Path
24
+ from typing import List, Optional
25
+
26
+ from code_puppy_core_plugins.agent_skills.remote_catalog import fetch_remote_catalog
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+
31
+ _ACRONYMS = {
32
+ "ai",
33
+ "api",
34
+ "aws",
35
+ "cli",
36
+ "cpu",
37
+ "csv",
38
+ "db",
39
+ "dns",
40
+ "gpu",
41
+ "html",
42
+ "http",
43
+ "https",
44
+ "id",
45
+ "json",
46
+ "jwt",
47
+ "k8s",
48
+ "llm",
49
+ "ml",
50
+ "mvp",
51
+ "oauth",
52
+ "pdf",
53
+ "psql",
54
+ "qa",
55
+ "rest",
56
+ "rpc",
57
+ "sdk",
58
+ "sql",
59
+ "ssh",
60
+ "ssl",
61
+ "tls",
62
+ "tsv",
63
+ "ui",
64
+ "url",
65
+ "utc",
66
+ "uuid",
67
+ "xml",
68
+ "yaml",
69
+ "yml",
70
+ }
71
+
72
+
73
+ def _format_display_name(skill_id: str) -> str:
74
+ """Format a human-readable display name from a skill id.
75
+
76
+ Examples:
77
+ data-exploration -> Data Exploration
78
+ pdf -> PDF
79
+
80
+ This is intentionally simple and predictable.
81
+ """
82
+
83
+ cleaned = (skill_id or "").strip()
84
+ if not cleaned:
85
+ return ""
86
+
87
+ parts = [p for p in cleaned.replace("_", "-").split("-") if p]
88
+ formatted: list[str] = []
89
+
90
+ for part in parts:
91
+ lower = part.lower()
92
+ if lower in _ACRONYMS:
93
+ formatted.append(lower.upper())
94
+ else:
95
+ # Keep existing capitalization for words like "Nextflow" if provided,
96
+ # otherwise just Title Case.
97
+ formatted.append(part[:1].upper() + part[1:].lower())
98
+
99
+ return " ".join(formatted)
100
+
101
+
102
+ @dataclass(frozen=True, slots=True)
103
+ class SkillCatalogEntry:
104
+ """Catalog entry for a skill.
105
+
106
+ Fields are designed to match the historical local catalog interface while
107
+ including remote-only fields (download_url, zip_size_bytes).
108
+ """
109
+
110
+ id: str
111
+ name: str
112
+ display_name: str
113
+ description: str
114
+ category: str
115
+ tags: List[str] = field(default_factory=list)
116
+ source_path: Optional[Path] = None
117
+ has_scripts: bool = False
118
+ has_references: bool = False
119
+ file_count: int = 0
120
+ download_url: str = ""
121
+ zip_size_bytes: int = 0
122
+
123
+
124
+ class SkillCatalog:
125
+ """Remote skill catalog.
126
+
127
+ This class is a simple in-memory index over remote catalog entries.
128
+ """
129
+
130
+ def __init__(self) -> None:
131
+ """Initialize the skill catalog with empty indices."""
132
+
133
+ self._entries: list[SkillCatalogEntry] = []
134
+ self._by_id: dict[str, SkillCatalogEntry] = {}
135
+ self._by_category: dict[str, list[SkillCatalogEntry]] = {}
136
+
137
+ try:
138
+ remote = fetch_remote_catalog()
139
+ except Exception as e:
140
+ # fetch_remote_catalog should already be defensive, but let's be extra safe.
141
+ logger.warning(f"Failed to fetch remote catalog: {e}")
142
+ remote = None
143
+
144
+ if remote is None:
145
+ logger.warning(
146
+ "Remote skill catalog unavailable (no network and no cache). "
147
+ "Catalog will be empty."
148
+ )
149
+ return
150
+
151
+ entries: list[SkillCatalogEntry] = []
152
+
153
+ for remote_entry in remote.entries:
154
+ skill_id = remote_entry.name
155
+ entry = SkillCatalogEntry(
156
+ id=skill_id,
157
+ name=remote_entry.name,
158
+ display_name=_format_display_name(remote_entry.name),
159
+ description=remote_entry.description,
160
+ category=remote_entry.group,
161
+ tags=[],
162
+ source_path=None,
163
+ has_scripts=remote_entry.has_scripts,
164
+ has_references=remote_entry.has_references,
165
+ file_count=remote_entry.file_count,
166
+ download_url=remote_entry.download_url,
167
+ zip_size_bytes=remote_entry.zip_size_bytes,
168
+ )
169
+ entries.append(entry)
170
+
171
+ self._rebuild_indices(entries)
172
+
173
+ logger.info(
174
+ f"Loaded remote skill catalog: {len(self._entries)} skills in "
175
+ f"{len(self._by_category)} categories"
176
+ )
177
+
178
+ def _rebuild_indices(self, entries: list[SkillCatalogEntry]) -> None:
179
+ """Rebuild internal lookup indices from the loaded entries."""
180
+
181
+ self._entries = list(entries)
182
+ self._by_id = {}
183
+ self._by_category = {}
184
+
185
+ for entry in self._entries:
186
+ # Last one wins if duplicates somehow exist.
187
+ self._by_id[entry.id] = entry
188
+
189
+ cat_key = (entry.category or "").casefold()
190
+ self._by_category.setdefault(cat_key, []).append(entry)
191
+
192
+ # Keep category lists stable and predictable.
193
+ for cat_entries in self._by_category.values():
194
+ cat_entries.sort(key=lambda e: e.display_name.casefold())
195
+
196
+ self._entries.sort(key=lambda e: e.display_name.casefold())
197
+
198
+ def list_categories(self) -> List[str]:
199
+ """List all categories."""
200
+
201
+ categories = {e.category for e in self._entries if e.category}
202
+ return sorted(categories, key=lambda c: c.casefold())
203
+
204
+ def get_by_category(self, category: str) -> List[SkillCatalogEntry]:
205
+ """Return all entries in a category (case-insensitive)."""
206
+
207
+ if not category:
208
+ return []
209
+ return list(self._by_category.get(category.casefold(), []))
210
+
211
+ def search(self, query: str) -> List[SkillCatalogEntry]:
212
+ """Search by substring over id/name/display_name/description/tags/category."""
213
+
214
+ q = (query or "").strip().casefold()
215
+ if not q:
216
+ return self.get_all()
217
+
218
+ results: list[SkillCatalogEntry] = []
219
+ for entry in self._entries:
220
+ haystacks = [
221
+ entry.id,
222
+ entry.name,
223
+ entry.display_name,
224
+ entry.description,
225
+ entry.category,
226
+ " ".join(entry.tags),
227
+ ]
228
+
229
+ if any(q in (h or "").casefold() for h in haystacks):
230
+ results.append(entry)
231
+
232
+ return results
233
+
234
+ def get_by_id(self, skill_id: str) -> Optional[SkillCatalogEntry]:
235
+ """Get a skill entry by id (case-sensitive exact match)."""
236
+
237
+ if not skill_id:
238
+ return None
239
+ return self._by_id.get(skill_id)
240
+
241
+ def get_all(self) -> List[SkillCatalogEntry]:
242
+ """Return all entries."""
243
+
244
+ return list(self._entries)
245
+
246
+
247
+ # Singleton instance used by the rest of the codebase.
248
+ # NOTE: This must never crash import-time.
249
+ catalog = SkillCatalog()
250
+
251
+
252
+ __all__ = [
253
+ "SkillCatalog",
254
+ "SkillCatalogEntry",
255
+ "_format_display_name",
256
+ "catalog",
257
+ ]
@@ -0,0 +1,102 @@
1
+ """Per-skill slash commands.
2
+
3
+ Each discovered (and enabled) skill is exposed as a ``/<skill-name>`` slash
4
+ command. Invoking it loads the full ``SKILL.md`` content and submits it
5
+ as the prompt — effectively the same thing the ``activate_skill`` tool
6
+ does, only initiated by the user.
7
+
8
+ This lives in its own module so that ``register_callbacks.py`` stays
9
+ focused on wiring callbacks (SRP, and the file-size cap).
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import logging
15
+ from typing import Any, List, Optional, Tuple
16
+
17
+ from code_puppy.messaging import emit_info
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+ # Slash command names already owned by the skills plugin itself.
22
+ # We must never shadow them with a per-skill command.
23
+ _RESERVED_NAMES = {"skills", "skill"}
24
+
25
+
26
+ def _iter_enabled_skills():
27
+ """Yield ``SkillMetadata`` for every enabled, valid, non-reserved skill.
28
+
29
+ Delegates the disabled / has_skill_md filtering to
30
+ :func:`enabled_skills.iter_enabled_skill_metadata`, so disabled skills
31
+ never get their frontmatter loaded here either.
32
+ """
33
+ from .enabled_skills import iter_enabled_skill_metadata
34
+
35
+ for meta in iter_enabled_skill_metadata():
36
+ if meta.name in _RESERVED_NAMES:
37
+ logger.debug(
38
+ "Skipping skill %r: name collides with reserved slash command",
39
+ meta.name,
40
+ )
41
+ continue
42
+ yield meta
43
+
44
+
45
+ def skill_command_help() -> List[Tuple[str, str]]:
46
+ """Advertise every enabled skill in the ``/help`` menu."""
47
+ entries: List[Tuple[str, str]] = []
48
+ for meta in _iter_enabled_skills():
49
+ desc = meta.description or "(no description)"
50
+ # Trim ridiculously long descriptions for the help table.
51
+ if len(desc) > 80:
52
+ desc = desc[:77] + "..."
53
+ entries.append((meta.name, f"🛠️ Skill: {desc}"))
54
+ return entries
55
+
56
+
57
+ def handle_skill_command(command: str, name: str) -> Optional[Any]:
58
+ """Return the SKILL.md content as a MarkdownCommandResult if ``name``
59
+ matches an enabled skill, otherwise ``None``.
60
+
61
+ Extra args after the command are appended as ``Additional context:``,
62
+ mirroring the customizable_commands plugin's contract.
63
+ """
64
+ if not name or name in _RESERVED_NAMES:
65
+ return None
66
+
67
+ # Lazy import to avoid a hard dependency on customizable_commands at
68
+ # plugin load time (and to keep things tidy if it ever moves).
69
+ try:
70
+ from code_puppy_core_plugins.customizable_commands.register_callbacks import (
71
+ MarkdownCommandResult,
72
+ )
73
+ except ImportError:
74
+ logger.debug("MarkdownCommandResult unavailable; cannot run skill via slash")
75
+ return None
76
+
77
+ # Find a matching enabled skill.
78
+ match = next((m for m in _iter_enabled_skills() if m.name == name), None)
79
+ if match is None:
80
+ return None
81
+
82
+ from .metadata import load_full_skill_content
83
+
84
+ content = load_full_skill_content(match.path)
85
+ if not content:
86
+ logger.warning("Failed to load SKILL.md for %s", match.name)
87
+ return None
88
+
89
+ # Allow user to pass freeform args after the slash command.
90
+ parts = command.split(maxsplit=1)
91
+ args = parts[1].strip() if len(parts) > 1 else ""
92
+
93
+ header = (
94
+ f"You are activating the '{match.name}' skill. Follow its "
95
+ f"SKILL.md instructions below verbatim.\n\n"
96
+ )
97
+ prompt = header + content
98
+ if args:
99
+ prompt = f"{prompt}\n\nAdditional context: {args}"
100
+
101
+ emit_info(f"🛠️ Activating skill via slash command: {match.name}")
102
+ return MarkdownCommandResult(prompt)