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,698 @@
1
+ """Interactive terminal UI for browsing and installing remote agent skills.
2
+
3
+ Launched from `/skills install` (wiring may live elsewhere). Provides a
4
+ split-panel prompt_toolkit UI:
5
+ - Left: categories, then skills within a category
6
+ - Right: live details preview for the current selection
7
+
8
+ Installation happens after the TUI exits, with a confirmation prompt via
9
+ `safe_input()`, and uses `download_and_install_skill()` to fetch and extract
10
+ remote ZIPs.
11
+
12
+ This module is intentionally defensive: if the remote catalog isn't available,
13
+ it shows an empty menu and returns False.
14
+ """
15
+
16
+ import logging
17
+ import sys
18
+ import time
19
+ from pathlib import Path
20
+ from typing import List, Optional
21
+
22
+ from prompt_toolkit.application import Application
23
+ from prompt_toolkit.key_binding import KeyBindings
24
+ from prompt_toolkit.layout import Dimension, Layout, VSplit, Window
25
+ from prompt_toolkit.layout.controls import FormattedTextControl
26
+ from prompt_toolkit.widgets import Frame
27
+
28
+ from code_puppy.command_line.pagination import (
29
+ ensure_visible_page,
30
+ get_page_bounds,
31
+ get_total_pages,
32
+ )
33
+ from code_puppy.command_line.utils import safe_input
34
+ from code_puppy.messaging import emit_error, emit_info, emit_success, emit_warning
35
+ from code_puppy_core_plugins.agent_skills.downloader import download_and_install_skill
36
+ from code_puppy_core_plugins.agent_skills.installer import InstallResult
37
+ from code_puppy_core_plugins.agent_skills.skill_catalog import (
38
+ SkillCatalogEntry,
39
+ catalog,
40
+ )
41
+ from code_puppy.tools.command_runner import set_awaiting_user_input
42
+ from code_puppy.callbacks import on_prompt_toolkit_style
43
+
44
+ logger = logging.getLogger(__name__)
45
+
46
+ PAGE_SIZE = 12
47
+
48
+
49
+ def is_skill_installed(skill_id: str) -> bool:
50
+ """Return True if the skill is already installed locally."""
51
+
52
+ return (Path.home() / ".code_puppy" / "skills" / skill_id / "SKILL.md").is_file()
53
+
54
+
55
+ def _format_bytes(num_bytes: int) -> str:
56
+ """Format bytes into a human-readable string."""
57
+
58
+ try:
59
+ size = float(max(0, int(num_bytes)))
60
+ except Exception:
61
+ return "0 B"
62
+
63
+ for unit in ("B", "KB", "MB", "GB"):
64
+ if size < 1024.0 or unit == "GB":
65
+ if unit == "B":
66
+ return f"{int(size)} {unit}"
67
+ return f"{size:.1f} {unit}"
68
+ size /= 1024.0
69
+ return f"{size:.1f} GB"
70
+
71
+
72
+ def _wrap_text(text: str, width: int) -> List[str]:
73
+ """Simple word-wrap for display in the details panel."""
74
+
75
+ if not text:
76
+ return []
77
+
78
+ words = text.split()
79
+ lines: list[str] = []
80
+ current = ""
81
+
82
+ for word in words:
83
+ if not current:
84
+ current = word
85
+ continue
86
+
87
+ if len(current) + 1 + len(word) > width:
88
+ lines.append(current)
89
+ current = word
90
+ else:
91
+ current = f"{current} {word}"
92
+
93
+ if current:
94
+ lines.append(current)
95
+
96
+ return lines
97
+
98
+
99
+ def _category_key(category: str) -> str:
100
+ """Normalize a category string for icon lookup."""
101
+
102
+ return "".join(ch for ch in (category or "").casefold() if ch.isalnum())
103
+
104
+
105
+ class SkillsInstallMenu:
106
+ """Interactive TUI for browsing and installing remote skills."""
107
+
108
+ def __init__(self):
109
+ """Initialize the skills install menu with catalog data."""
110
+
111
+ self.catalog = catalog
112
+ self.categories: List[str] = []
113
+ self.current_category: Optional[str] = None
114
+ self.current_skills: List[SkillCatalogEntry] = []
115
+
116
+ # State
117
+ self.view_mode = "categories" # categories | skills
118
+ self.selected_category_idx = 0
119
+ self.selected_skill_idx = 0
120
+ self.current_page = 0
121
+ self.result: Optional[str] = None
122
+ self.pending_entry: Optional[SkillCatalogEntry] = None
123
+
124
+ # UI controls
125
+ self.menu_control: Optional[FormattedTextControl] = None
126
+ self.preview_control: Optional[FormattedTextControl] = None
127
+
128
+ self._initialize_catalog()
129
+
130
+ def _initialize_catalog(self) -> None:
131
+ """Load categories from the remote-backed catalog."""
132
+
133
+ try:
134
+ self.categories = self.catalog.list_categories() if self.catalog else []
135
+ except Exception as e:
136
+ emit_error(f"Skill catalog not available: {e}")
137
+ self.categories = []
138
+
139
+ def _get_category_icon(self, category: str) -> str:
140
+ """Return an emoji icon for a skill category name."""
141
+
142
+ icons = {
143
+ "data": "📊",
144
+ "finance": "💰",
145
+ "legal": "⚖️",
146
+ "office": "📄",
147
+ "productmanagement": "📦",
148
+ "sales": "💼",
149
+ "biology": "🧬",
150
+ }
151
+ return icons.get(_category_key(category), "📁")
152
+
153
+ def _get_current_category(self) -> Optional[str]:
154
+ """Get the currently highlighted category name."""
155
+
156
+ if 0 <= self.selected_category_idx < len(self.categories):
157
+ return self.categories[self.selected_category_idx]
158
+ return None
159
+
160
+ def _get_current_skill(self) -> Optional[SkillCatalogEntry]:
161
+ """Get the currently highlighted skill entry."""
162
+
163
+ if self.view_mode == "skills" and self.current_skills:
164
+ if 0 <= self.selected_skill_idx < len(self.current_skills):
165
+ return self.current_skills[self.selected_skill_idx]
166
+ return None
167
+
168
+ def _render_navigation_hints(self, lines: List) -> None:
169
+ """Render keyboard shortcut hints at the bottom."""
170
+
171
+ lines.append(("", "\n"))
172
+ lines.append(("class:tui.help-key", " ↑/↓ "))
173
+ lines.append(("", "Navigate "))
174
+ lines.append(("class:tui.help-key", "←/→ "))
175
+ lines.append(("", "Page\n"))
176
+
177
+ if self.view_mode == "categories":
178
+ lines.append(("class:tui.help-key", " Enter "))
179
+ lines.append(("", "Browse Skills\n"))
180
+ else:
181
+ lines.append(("class:tui.success", " Enter "))
182
+ lines.append(("", "Install Skill\n"))
183
+ lines.append(("class:tui.help-key", " Esc/Back "))
184
+ lines.append(("", "Back\n"))
185
+
186
+ lines.append(("class:tui.help-key", " Ctrl+C "))
187
+ lines.append(("", "Cancel"))
188
+
189
+ def _render_category_list(self) -> List:
190
+ """Render the left panel with category navigation."""
191
+
192
+ lines = []
193
+
194
+ lines.append(("class:tui.title", " 📂 CATEGORIES"))
195
+ lines.append(("", "\n\n"))
196
+
197
+ if not self.categories:
198
+ lines.append(("class:tui.warning", " No remote categories available."))
199
+ lines.append(("", "\n"))
200
+ lines.append(
201
+ (
202
+ "class:tui.muted",
203
+ " (Remote catalog unavailable or empty)\n",
204
+ )
205
+ )
206
+ self._render_navigation_hints(lines)
207
+ return lines
208
+
209
+ total_pages = get_total_pages(len(self.categories), PAGE_SIZE)
210
+ start_idx, end_idx = get_page_bounds(
211
+ self.current_page, len(self.categories), PAGE_SIZE
212
+ )
213
+
214
+ for i in range(start_idx, end_idx):
215
+ category = self.categories[i]
216
+ is_selected = i == self.selected_category_idx
217
+ icon = self._get_category_icon(category)
218
+ count = 0
219
+ try:
220
+ count = (
221
+ len(self.catalog.get_by_category(category)) if self.catalog else 0
222
+ )
223
+ except Exception:
224
+ count = 0
225
+
226
+ prefix = " > " if is_selected else " "
227
+ label = f"{prefix}{icon} {category} ({count})"
228
+
229
+ if is_selected:
230
+ lines.append(("class:tui.selected", label))
231
+ else:
232
+ lines.append(("class:tui.muted", label))
233
+ lines.append(("", "\n"))
234
+
235
+ lines.append(("", "\n"))
236
+ if total_pages > 1:
237
+ lines.append(
238
+ ("class:tui.muted", f" Page {self.current_page + 1}/{total_pages}")
239
+ )
240
+ lines.append(("", "\n"))
241
+
242
+ self._render_navigation_hints(lines)
243
+ return lines
244
+
245
+ def _render_skill_list(self) -> List:
246
+ """Render the middle panel with skills in the selected category."""
247
+
248
+ lines = []
249
+
250
+ if not self.current_category:
251
+ lines.append(("class:tui.warning", " No category selected."))
252
+ lines.append(("", "\n\n"))
253
+ self._render_navigation_hints(lines)
254
+ return lines
255
+
256
+ icon = self._get_category_icon(self.current_category)
257
+ lines.append(("class:tui.title", f" {icon} {self.current_category.upper()}"))
258
+ lines.append(("", "\n\n"))
259
+
260
+ if not self.current_skills:
261
+ lines.append(("class:tui.warning", " No skills in this category."))
262
+ lines.append(("", "\n\n"))
263
+ self._render_navigation_hints(lines)
264
+ return lines
265
+
266
+ total_pages = get_total_pages(len(self.current_skills), PAGE_SIZE)
267
+ start_idx, end_idx = get_page_bounds(
268
+ self.current_page, len(self.current_skills), PAGE_SIZE
269
+ )
270
+
271
+ for i in range(start_idx, end_idx):
272
+ entry = self.current_skills[i]
273
+ is_selected = i == self.selected_skill_idx
274
+
275
+ installed = is_skill_installed(entry.id)
276
+ status_icon = "✓" if installed else "○"
277
+ status_style = "class:tui.success" if installed else "class:tui.muted"
278
+
279
+ prefix = " > " if is_selected else " "
280
+ label = f"{prefix}{status_icon} {entry.display_name}"
281
+
282
+ if is_selected:
283
+ lines.append(("class:tui.selected", label))
284
+ else:
285
+ lines.append((status_style, label))
286
+
287
+ lines.append(("", "\n"))
288
+
289
+ lines.append(("", "\n"))
290
+ if total_pages > 1:
291
+ lines.append(
292
+ ("class:tui.muted", f" Page {self.current_page + 1}/{total_pages}")
293
+ )
294
+ lines.append(("", "\n"))
295
+
296
+ self._render_navigation_hints(lines)
297
+ return lines
298
+
299
+ def _render_details(self) -> List:
300
+ """Render the right panel with details for the selected skill."""
301
+
302
+ lines = []
303
+
304
+ lines.append(("class:tui.title", " 📋 DETAILS"))
305
+ lines.append(("", "\n\n"))
306
+
307
+ if self.view_mode == "categories":
308
+ category = self._get_current_category()
309
+ if not category:
310
+ lines.append(("class:tui.warning", " No category selected."))
311
+ return lines
312
+
313
+ icon = self._get_category_icon(category)
314
+ lines.append(("bold", f" {icon} {category}"))
315
+ lines.append(("", "\n\n"))
316
+
317
+ skills = []
318
+ try:
319
+ skills = self.catalog.get_by_category(category) if self.catalog else []
320
+ except Exception:
321
+ skills = []
322
+
323
+ lines.append(("class:tui.muted", f" {len(skills)} skills available"))
324
+ lines.append(("", "\n\n"))
325
+
326
+ # Show a preview of the first few skills
327
+ if skills:
328
+ lines.append(("bold", " Preview:"))
329
+ lines.append(("", "\n"))
330
+ for entry in skills[:6]:
331
+ lines.append(("class:tui.muted", f" • {entry.display_name}"))
332
+ lines.append(("", "\n"))
333
+
334
+ return lines
335
+
336
+ entry = self._get_current_skill()
337
+ if not entry:
338
+ lines.append(("class:tui.warning", " No skill selected."))
339
+ return lines
340
+
341
+ installed = is_skill_installed(entry.id)
342
+ installed_text = "Installed" if installed else "Not installed"
343
+ installed_style = "class:tui.success" if installed else "class:tui.warning"
344
+
345
+ lines.append(("bold", f" {entry.display_name}"))
346
+ lines.append(("", "\n"))
347
+ lines.append((installed_style, f" {installed_text}"))
348
+ lines.append(("", "\n\n"))
349
+
350
+ lines.append(("bold", " ID:"))
351
+ lines.append(("", "\n"))
352
+ lines.append(("class:tui.muted", f" {entry.id}"))
353
+ lines.append(("", "\n\n"))
354
+
355
+ lines.append(("bold", " Description:"))
356
+ lines.append(("", "\n"))
357
+ desc = entry.description or "No description available"
358
+ for line in _wrap_text(desc, 56):
359
+ lines.append(("class:tui.muted", f" {line}"))
360
+ lines.append(("", "\n"))
361
+ lines.append(("", "\n"))
362
+
363
+ lines.append(("bold", " Category:"))
364
+ lines.append(("", "\n"))
365
+ lines.append(("class:tui.muted", f" {entry.category}"))
366
+ lines.append(("", "\n\n"))
367
+
368
+ lines.append(("bold", " Tags:"))
369
+ lines.append(("", "\n"))
370
+ tags = entry.tags or []
371
+ lines.append(
372
+ ("class:tui.header", f" {', '.join(tags) if tags else '(none)'}")
373
+ )
374
+ lines.append(("", "\n\n"))
375
+
376
+ lines.append(("bold", " Contents:"))
377
+ lines.append(("", "\n"))
378
+ lines.append(
379
+ (
380
+ "class:tui.muted",
381
+ f" scripts: {'yes' if entry.has_scripts else 'no'}",
382
+ )
383
+ )
384
+ lines.append(("", "\n"))
385
+ lines.append(
386
+ (
387
+ "class:tui.muted",
388
+ f" references: {'yes' if entry.has_references else 'no'}",
389
+ )
390
+ )
391
+ lines.append(("", "\n"))
392
+ lines.append(("class:tui.muted", f" files: {entry.file_count}"))
393
+ lines.append(("", "\n\n"))
394
+
395
+ lines.append(("bold", " Download:"))
396
+ lines.append(("", "\n"))
397
+ lines.append(
398
+ (
399
+ "class:tui.muted",
400
+ f" size: {_format_bytes(entry.zip_size_bytes)}",
401
+ )
402
+ )
403
+ lines.append(("", "\n"))
404
+ lines.append(("class:tui.muted", f" url: {entry.download_url}"))
405
+ lines.append(("", "\n"))
406
+
407
+ return lines
408
+
409
+ def update_display(self) -> None:
410
+ """Refresh all three panels of the TUI display."""
411
+
412
+ if self.view_mode == "categories":
413
+ self.menu_control.text = self._render_category_list()
414
+ else:
415
+ self.menu_control.text = self._render_skill_list()
416
+
417
+ self.preview_control.text = self._render_details()
418
+
419
+ def _enter_category(self) -> None:
420
+ """Enter the currently highlighted category to browse skills."""
421
+
422
+ category = self._get_current_category()
423
+ if not category or not self.catalog:
424
+ return
425
+
426
+ self.current_category = category
427
+ try:
428
+ self.current_skills = self.catalog.get_by_category(category)
429
+ except Exception:
430
+ self.current_skills = []
431
+
432
+ self.view_mode = "skills"
433
+ self.selected_skill_idx = 0
434
+ self.current_page = 0
435
+ self.update_display()
436
+
437
+ def _go_back_to_categories(self) -> None:
438
+ """Navigate back from skill list to category list."""
439
+
440
+ self.view_mode = "categories"
441
+ self.current_category = None
442
+ self.current_skills = []
443
+ self.selected_skill_idx = 0
444
+ self.current_page = 0
445
+ self.update_display()
446
+
447
+ def _select_current_skill(self) -> None:
448
+ """Download and install the currently highlighted skill."""
449
+
450
+ entry = self._get_current_skill()
451
+ if entry:
452
+ self.pending_entry = entry
453
+ self.result = "pending_install"
454
+
455
+ def run(self) -> bool:
456
+ """Run the skills install menu.
457
+
458
+ Returns:
459
+ True if a skill was installed, False otherwise.
460
+ """
461
+
462
+ # Build UI
463
+ self.menu_control = FormattedTextControl(text="")
464
+ self.preview_control = FormattedTextControl(text="")
465
+
466
+ menu_window = Window(
467
+ content=self.menu_control, wrap_lines=True, width=Dimension(weight=35)
468
+ )
469
+ preview_window = Window(
470
+ content=self.preview_control, wrap_lines=True, width=Dimension(weight=65)
471
+ )
472
+
473
+ menu_frame = Frame(menu_window, width=Dimension(weight=35), title="Browse")
474
+ preview_frame = Frame(
475
+ preview_window, width=Dimension(weight=65), title="Details"
476
+ )
477
+
478
+ root_container = VSplit([menu_frame, preview_frame])
479
+
480
+ kb = KeyBindings()
481
+
482
+ @kb.add("up")
483
+ def _(event):
484
+ """Move cursor up."""
485
+
486
+ if self.view_mode == "categories":
487
+ if self.selected_category_idx > 0:
488
+ self.selected_category_idx -= 1
489
+ self.current_page = ensure_visible_page(
490
+ self.selected_category_idx,
491
+ self.current_page,
492
+ len(self.categories),
493
+ PAGE_SIZE,
494
+ )
495
+ else:
496
+ if self.selected_skill_idx > 0:
497
+ self.selected_skill_idx -= 1
498
+ self.current_page = ensure_visible_page(
499
+ self.selected_skill_idx,
500
+ self.current_page,
501
+ len(self.current_skills),
502
+ PAGE_SIZE,
503
+ )
504
+ self.update_display()
505
+
506
+ @kb.add("down")
507
+ def _(event):
508
+ """Move cursor down."""
509
+
510
+ if self.view_mode == "categories":
511
+ if self.selected_category_idx < len(self.categories) - 1:
512
+ self.selected_category_idx += 1
513
+ self.current_page = ensure_visible_page(
514
+ self.selected_category_idx,
515
+ self.current_page,
516
+ len(self.categories),
517
+ PAGE_SIZE,
518
+ )
519
+ else:
520
+ if self.selected_skill_idx < len(self.current_skills) - 1:
521
+ self.selected_skill_idx += 1
522
+ self.current_page = ensure_visible_page(
523
+ self.selected_skill_idx,
524
+ self.current_page,
525
+ len(self.current_skills),
526
+ PAGE_SIZE,
527
+ )
528
+ self.update_display()
529
+
530
+ @kb.add("left")
531
+ def _(event):
532
+ """Navigate to previous page."""
533
+
534
+ if self.current_page > 0:
535
+ self.current_page -= 1
536
+ if self.view_mode == "categories":
537
+ self.selected_category_idx = self.current_page * PAGE_SIZE
538
+ else:
539
+ self.selected_skill_idx = self.current_page * PAGE_SIZE
540
+ self.update_display()
541
+
542
+ @kb.add("right")
543
+ def _(event):
544
+ """Navigate to next page."""
545
+
546
+ if self.view_mode == "categories":
547
+ total_items = len(self.categories)
548
+ else:
549
+ total_items = len(self.current_skills)
550
+
551
+ total_pages = get_total_pages(total_items, PAGE_SIZE)
552
+ if self.current_page < total_pages - 1:
553
+ self.current_page += 1
554
+ if self.view_mode == "categories":
555
+ self.selected_category_idx = self.current_page * PAGE_SIZE
556
+ else:
557
+ self.selected_skill_idx = self.current_page * PAGE_SIZE
558
+ self.update_display()
559
+
560
+ @kb.add("enter")
561
+ def _(event):
562
+ """Select/enter the current item."""
563
+
564
+ if self.view_mode == "categories":
565
+ self._enter_category()
566
+ else:
567
+ self._select_current_skill()
568
+ event.app.exit()
569
+
570
+ @kb.add("escape")
571
+ def _(event):
572
+ """Go back."""
573
+
574
+ if self.view_mode == "skills":
575
+ self._go_back_to_categories()
576
+
577
+ @kb.add("backspace")
578
+ def _(event):
579
+ """Go back."""
580
+
581
+ if self.view_mode == "skills":
582
+ self._go_back_to_categories()
583
+
584
+ @kb.add("c-c")
585
+ def _(event):
586
+ """Quit the menu."""
587
+
588
+ event.app.exit()
589
+
590
+ layout = Layout(root_container)
591
+ app = Application(
592
+ layout=layout,
593
+ key_bindings=kb,
594
+ full_screen=False,
595
+ mouse_support=False,
596
+ style=on_prompt_toolkit_style(),
597
+ )
598
+
599
+ set_awaiting_user_input(True)
600
+
601
+ # Enter alternate screen buffer
602
+ sys.stdout.write("\033[?1049h")
603
+ sys.stdout.write("\033[2J\033[H")
604
+ sys.stdout.flush()
605
+ time.sleep(0.05)
606
+
607
+ try:
608
+ self.update_display()
609
+ sys.stdout.write("\033[2J\033[H")
610
+ sys.stdout.flush()
611
+
612
+ app.run(in_thread=True)
613
+
614
+ finally:
615
+ sys.stdout.write("\033[?1049l")
616
+ sys.stdout.flush()
617
+
618
+ # Flush any buffered input to prevent stale keypresses
619
+ try:
620
+ import termios
621
+
622
+ termios.tcflush(sys.stdin.fileno(), termios.TCIFLUSH)
623
+ except Exception:
624
+ pass # ImportError on Windows, termios.error, or not a tty
625
+
626
+ # Small delay to let terminal settle before any output
627
+ time.sleep(0.1)
628
+ set_awaiting_user_input(False)
629
+
630
+ # Handle install after TUI exits
631
+ if self.result == "pending_install" and self.pending_entry:
632
+ return _prompt_and_install(self.pending_entry)
633
+
634
+ emit_info("✓ Exited skills install browser")
635
+ return False
636
+
637
+
638
+ def _prompt_and_install(entry: SkillCatalogEntry) -> bool:
639
+ """Prompt for confirmation and install the given skill."""
640
+
641
+ installed = is_skill_installed(entry.id)
642
+ size_str = _format_bytes(entry.zip_size_bytes)
643
+
644
+ try:
645
+ if installed:
646
+ answer = safe_input(
647
+ f"Skill '{entry.display_name}' is already installed. Reinstall ({size_str})? [y/N] "
648
+ )
649
+ if answer.strip().lower() not in {"y", "yes"}:
650
+ emit_info("Installation cancelled")
651
+ return False
652
+ force = True
653
+ else:
654
+ answer = safe_input(
655
+ f"Install skill '{entry.display_name}' ({size_str})? [y/N] "
656
+ )
657
+ if answer.strip().lower() not in {"y", "yes"}:
658
+ emit_info("Installation cancelled")
659
+ return False
660
+ force = False
661
+
662
+ except (KeyboardInterrupt, EOFError):
663
+ emit_warning("Installation cancelled")
664
+ return False
665
+
666
+ emit_info(f"Downloading: {entry.display_name} ({size_str})")
667
+
668
+ result: InstallResult
669
+ try:
670
+ result = download_and_install_skill(
671
+ skill_name=entry.id,
672
+ download_url=entry.download_url,
673
+ force=force,
674
+ )
675
+ except Exception as e:
676
+ logger.exception(f"Unexpected error during skill install: {e}")
677
+ emit_error(f"Installation error: {e}")
678
+ return False
679
+
680
+ if result.success:
681
+ emit_success(result.message)
682
+ if result.installed_path:
683
+ emit_info(f"Installed to: {result.installed_path}")
684
+ return True
685
+
686
+ emit_error(result.message)
687
+ return False
688
+
689
+
690
+ def run_skills_install_menu() -> bool:
691
+ """Run the bundled skills install menu.
692
+
693
+ Returns:
694
+ True if a skill was installed, False otherwise.
695
+ """
696
+
697
+ menu = SkillsInstallMenu()
698
+ return menu.run()