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,431 @@
1
+ """Rendering for the /plugins TUI — split off so ``plugins_menu`` stays small.
2
+
3
+ These are pure-ish view functions: they take a :class:`PluginsMenu` (or a
4
+ plain plugin entry) and return ``(style, text)`` fragment tuples for
5
+ prompt_toolkit. No I/O, no key handling, no app state mutation. Keeping
6
+ them out of the menu class also makes the file split sustainable as more
7
+ detail sections get added.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import TYPE_CHECKING, List, Tuple
13
+
14
+ from code_puppy.command_line.pagination import get_page_bounds, get_total_pages
15
+ from code_puppy_core_plugins.plugin_list.plugin_text_utils import (
16
+ Fragments,
17
+ count_lines,
18
+ pad_lines_to_cells,
19
+ strip_emojis_from_fragments,
20
+ wrap_text,
21
+ )
22
+
23
+ if TYPE_CHECKING: # pragma: no cover - import-cycle guard
24
+ from code_puppy_core_plugins.plugin_list.plugins_menu import (
25
+ PluginsMenu,
26
+ _PluginEntry,
27
+ )
28
+
29
+ # Display order + labels for the "Contributes" section, keyed by the
30
+ # ``CATEGORY_*`` constants from ``plugin_contributions``.
31
+ _CONTRIB_LABELS: List[Tuple[str, str]] = [
32
+ ("tools", "Tools"),
33
+ ("commands", "Slash Commands"),
34
+ ("agents", "Agents"),
35
+ ("skills", "Skills"),
36
+ ("model_types", "Model Types"),
37
+ ("model_providers", "Model Providers"),
38
+ ("mcp_servers", "MCP Servers"),
39
+ ("browser_types", "Browser Types"),
40
+ ("agent_tools", "Agent Tools"),
41
+ ]
42
+
43
+
44
+ # Helpers
45
+ def _registers_command_handler(name: str) -> bool:
46
+ """Whether *name* registered a ``custom_command`` hook.
47
+
48
+ Such a plugin has a command handler but may lack ``custom_command_help``,
49
+ so we know one exists without its name (hence the placeholder we surface).
50
+ """
51
+ from code_puppy_core_plugins.plugin_list import plugin_meta
52
+
53
+ try:
54
+ return "custom_command" in plugin_meta.get_hooks(name)
55
+ except Exception:
56
+ return False
57
+
58
+
59
+ def _append_wrapped(
60
+ lines: Fragments, style: str, prefix: str, body: str, inner_width: int
61
+ ) -> None:
62
+ """Append *body* (wrapped to *inner_width*) under *prefix* with *style*."""
63
+ for piece in wrap_text(body, inner_width):
64
+ lines.append((style, f"{prefix}{piece}"))
65
+ lines.append(("", "\n"))
66
+
67
+
68
+ # List pane (left)
69
+ def render_list(menu: "PluginsMenu") -> Fragments:
70
+ lines: Fragments = []
71
+
72
+ lines.append(("bold", " Plugins"))
73
+ lines.append(("", "\n\n"))
74
+
75
+ if menu.lock_builtin and menu.hidden_builtin_count:
76
+ lines.append(
77
+ (
78
+ "class:tui.muted",
79
+ f" {menu.hidden_builtin_count} builtin plugins are "
80
+ f"managed and hidden.",
81
+ )
82
+ )
83
+ lines.append(("", "\n\n"))
84
+
85
+ if not menu.plugins:
86
+ if menu.lock_builtin and menu.hidden_builtin_count:
87
+ lines.append(("class:tui.warning", " No user or project plugins loaded."))
88
+ else:
89
+ lines.append(("class:tui.warning", " No plugins loaded."))
90
+ lines.append(("", "\n"))
91
+ _render_hints(lines)
92
+ return lines
93
+
94
+ total_pages = get_total_pages(len(menu.plugins), menu.page_size)
95
+ start_idx, end_idx = get_page_bounds(
96
+ menu.current_page, len(menu.plugins), menu.page_size
97
+ )
98
+
99
+ for i in range(start_idx, end_idx):
100
+ entry = menu.plugins[i]
101
+ is_selected = i == menu.selected_idx
102
+ is_disabled = entry.name in menu.disabled
103
+
104
+ if entry.status == "loaded":
105
+ icon = "x" if is_disabled else "+"
106
+ icon_style = "class:tui.error" if is_disabled else "class:tui.success"
107
+ else:
108
+ # Trust-gated project plugin: discovered but never imported.
109
+ icon = "!"
110
+ icon_style = (
111
+ "class:tui.error" if entry.status == "error" else "class:tui.warning"
112
+ )
113
+ prefix = " > " if is_selected else " "
114
+
115
+ if is_selected:
116
+ lines.append(("class:tui.selected", prefix))
117
+ lines.append(("class:tui.selected", icon))
118
+ lines.append(("class:tui.selected", f" {entry.name}"))
119
+ else:
120
+ lines.append(("", prefix))
121
+ lines.append((icon_style, icon))
122
+ lines.append(("class:tui.muted", f" {entry.name}"))
123
+
124
+ lines.append(("", "\n"))
125
+
126
+ lines.append(("", "\n"))
127
+ lines.append(("class:tui.muted", f" Page {menu.current_page + 1}/{total_pages}"))
128
+ lines.append(("", "\n"))
129
+
130
+ _render_hints(lines)
131
+ return lines
132
+
133
+
134
+ def _render_hints(lines: Fragments) -> None:
135
+ # Match inspect_history bindings so muscle memory carries across split-pane
136
+ # inspectors; see _build_key_bindings for the full mapping.
137
+ hints: List[Tuple[str, str, str]] = [
138
+ ("class:tui.help-key", " up/down or j/k ", "Navigate"),
139
+ ("class:tui.help-key", " PgUp/PgDn ", "Page"),
140
+ ("class:tui.help-key", " g / G ", "First / Last"),
141
+ ("class:tui.help-key", " h/l or \u2190/\u2192 ", "Scroll details"),
142
+ ("class:tui.help-key", " Enter ", "Toggle / Enable"),
143
+ ("class:tui.help-key", " q / Esc ", "Exit"),
144
+ ]
145
+ lines.append(("", "\n"))
146
+ for i, (style, key_text, label) in enumerate(hints):
147
+ lines.append((style, key_text))
148
+ lines.append(("", label if i == len(hints) - 1 else f"{label}\n"))
149
+
150
+
151
+ # Detail pane (right)
152
+
153
+ # Status line + explanation for project plugins the trust gate held back.
154
+ # ``{name}`` is substituted with the plugin name in the hint text.
155
+ _GATE_STATUS_DETAILS = {
156
+ "untrusted": (
157
+ "class:tui.warning",
158
+ "Not enabled (project plugins are disabled by default)",
159
+ "Press Enter to review its files and enable it.",
160
+ ),
161
+ "changed": (
162
+ "class:tui.warning",
163
+ "Changed since you accepted it",
164
+ "Its files were modified after you trusted it, so trust was "
165
+ "revoked. Press Enter to re-review and re-enable.",
166
+ ),
167
+ "disabled": (
168
+ "class:tui.error",
169
+ "Disabled (trusted, but not loaded)",
170
+ "Press Enter to load it.",
171
+ ),
172
+ "error": (
173
+ "class:tui.error",
174
+ "Failed to load",
175
+ "Check the logs, then press Enter to retry.",
176
+ ),
177
+ }
178
+
179
+
180
+ def _render_gate_status(lines: Fragments, menu: "PluginsMenu", entry) -> None:
181
+ """Status + hint for a project plugin that was never imported."""
182
+ style, label, hint = _GATE_STATUS_DETAILS.get(
183
+ entry.status,
184
+ ("class:tui.warning", entry.status, "See '/plugins list' for details."),
185
+ )
186
+ lines.append(("bold", " Status: "))
187
+ lines.append((style, label))
188
+ lines.append(("", "\n"))
189
+ inner = max(20, menu._detail_cols - 4)
190
+ _append_wrapped(lines, "class:tui.muted", " ", hint.format(name=entry.name), inner)
191
+ lines.append(("", "\n"))
192
+
193
+
194
+ def render_trust_modal(menu: "PluginsMenu") -> Fragments:
195
+ """Body of the trust popup (the input box is a separate widget below).
196
+
197
+ Kept deliberately stark: this is the security decision point, so it
198
+ restates the risk and lists the files about to be executed.
199
+ """
200
+ from code_puppy_core_plugins.plugin_list.project_trust_flow import (
201
+ ACCEPT_WORD,
202
+ plugin_file_listing,
203
+ )
204
+
205
+ entry = menu.trust_target
206
+ lines: Fragments = []
207
+ if entry is None:
208
+ return lines
209
+ inner = 58 # popup is width-capped at 64; leave room for the frame
210
+
211
+ reason = (
212
+ "has CHANGED since you last accepted it"
213
+ if entry.status == "changed"
214
+ else "has never been enabled for this project"
215
+ )
216
+ _append_wrapped(lines, "class:tui.warning", " ", f"'{entry.name}' {reason}.", inner)
217
+ lines.append(("", "\n"))
218
+ _append_wrapped(
219
+ lines,
220
+ "",
221
+ " ",
222
+ "Project plugins run arbitrary code with YOUR permissions the "
223
+ "moment they load. Only enable plugins you have reviewed.",
224
+ inner,
225
+ )
226
+ lines.append(("", "\n"))
227
+
228
+ if menu.project_dir:
229
+ lines.append(("bold", " Files:"))
230
+ lines.append(("", "\n"))
231
+ from pathlib import Path
232
+
233
+ listing = plugin_file_listing(Path(menu.project_dir) / entry.name, limit=8)
234
+ for row in listing.splitlines():
235
+ for piece in wrap_text(row.strip(), inner - 2):
236
+ lines.append(("class:tui.header", f" {piece}"))
237
+ lines.append(("", "\n"))
238
+ lines.append(("", "\n"))
239
+
240
+ if menu.trust_error:
241
+ _append_wrapped(lines, "class:tui.error", " ", menu.trust_error, inner)
242
+ lines.append(("", "\n"))
243
+
244
+ _append_wrapped(
245
+ lines,
246
+ "bold",
247
+ " ",
248
+ f"Type '{ACCEPT_WORD}' and press Enter to accept the risk — Esc cancels:",
249
+ inner,
250
+ )
251
+ return lines
252
+
253
+
254
+ def render_detail(menu: "PluginsMenu") -> Fragments:
255
+ lines: Fragments = []
256
+
257
+ lines.append(("class:tui.title dim", " PLUGIN DETAILS"))
258
+ lines.append(("", "\n\n"))
259
+
260
+ # Outcome of the most recent enable/activate action, if any.
261
+ if menu.trust_feedback:
262
+ inner = max(20, menu._detail_cols - 4)
263
+ _append_wrapped(lines, "class:tui.success", " ", menu.trust_feedback, inner)
264
+ lines.append(("", "\n"))
265
+
266
+ entry = menu._current()
267
+ if not entry:
268
+ lines.append(("class:tui.warning", " No plugin selected."))
269
+ return lines
270
+
271
+ from code_puppy_core_plugins.plugin_list import plugin_meta
272
+
273
+ is_disabled = entry.name in menu.disabled
274
+
275
+ lines.append(("bold", f" {entry.name}"))
276
+ lines.append(("", "\n\n"))
277
+
278
+ # Pre-wrap to pane width: the Window runs wrap_lines=False (auto-wrap
279
+ # bled chars into the divider column).
280
+ description = plugin_meta.get_description(entry.name, entry.tier)
281
+ if description:
282
+ inner = max(20, menu._detail_cols - 4) # -2 indent, -2 frame border
283
+ _append_wrapped(lines, "", " ", description, inner)
284
+ lines.append(("", "\n"))
285
+
286
+ lines.append(("bold", " Tier: "))
287
+ lines.append(("", entry.tier))
288
+ lines.append(("", "\n\n"))
289
+
290
+ # Trust is scoped per project path — always show which project this is.
291
+ if entry.tier == "project" and menu.project_dir:
292
+ lines.append(("bold", " Project:"))
293
+ lines.append(("", "\n"))
294
+ inner = max(20, menu._detail_cols - 6)
295
+ _append_wrapped(lines, "class:tui.muted", " ", menu.project_dir, inner)
296
+ lines.append(("", "\n"))
297
+
298
+ if entry.status != "loaded":
299
+ # Trust-gated: never imported, so hooks/contributions don't exist.
300
+ _render_gate_status(lines, menu, entry)
301
+ path = f"{menu.project_dir}/{entry.name}/" if menu.project_dir else None
302
+ if path:
303
+ lines.append(("bold", " Path:"))
304
+ lines.append(("", "\n"))
305
+ inner = max(20, menu._detail_cols - 6)
306
+ _append_wrapped(lines, "class:tui.muted", " ", path, inner)
307
+ return lines
308
+
309
+ lines.append(("bold", " Status: "))
310
+ if is_disabled:
311
+ lines.append(("class:tui.error", "Disabled"))
312
+ lines.append(("", "\n"))
313
+ lines.append(("class:tui.muted", " Callbacks are skipped at dispatch time."))
314
+ else:
315
+ lines.append(("class:tui.success", "Enabled"))
316
+ lines.append(("", "\n"))
317
+ lines.append(("class:tui.muted", " All callbacks are active."))
318
+ lines.append(("", "\n\n"))
319
+
320
+ _render_contributions(lines, menu, entry)
321
+
322
+ hooks = plugin_meta.get_hooks(entry.name)
323
+ lines.append(("bold", f" Lifecycle hooks used ({len(hooks)}):"))
324
+ lines.append(("", "\n"))
325
+ if hooks:
326
+ for hook in hooks:
327
+ lines.append(("class:tui.header", f" • {hook}"))
328
+ lines.append(("", "\n"))
329
+ else:
330
+ lines.append(("class:tui.muted", " (none registered)"))
331
+ lines.append(("", "\n"))
332
+ lines.append(("", "\n"))
333
+
334
+ # Pre-wrap: paths can be very long.
335
+ path = plugin_meta.get_file_path(entry.name, entry.tier)
336
+ if path:
337
+ lines.append(("bold", " Path:"))
338
+ lines.append(("", "\n"))
339
+ inner = max(20, menu._detail_cols - 6) # -4 indent, -2 frame border
340
+ _append_wrapped(lines, "class:tui.muted", " ", path, inner)
341
+ lines.append(("", "\n"))
342
+
343
+ if menu._changed:
344
+ lines.append(("", "\n\n"))
345
+ # Let wrap_text choose the split; hard-coding one broke wider panes.
346
+ inner = max(20, menu._detail_cols - 4) # -2 indent, -2 frame border
347
+ _append_wrapped(
348
+ lines,
349
+ "class:tui.warning",
350
+ " ",
351
+ "Restart Code Puppy for changes to take effect.",
352
+ inner,
353
+ )
354
+
355
+ return lines
356
+
357
+
358
+ def _render_contributions(
359
+ lines: Fragments, menu: "PluginsMenu", entry: "_PluginEntry"
360
+ ) -> None:
361
+ """Render the 'Contributes' section, grouped by non-empty category.
362
+
363
+ Best-effort and display-only: extraction failures degrade to showing
364
+ nothing extra rather than crashing the preview. Empty categories are
365
+ skipped, and a command handler with no ``custom_command_help`` is
366
+ surfaced as 'command handler (name unknown)'.
367
+ """
368
+ from code_puppy_core_plugins.plugin_list import plugin_contributions
369
+
370
+ try:
371
+ contributions = plugin_contributions.get_contributions(entry.name)
372
+ except Exception:
373
+ contributions = {}
374
+
375
+ # Synthesize a placeholder only when custom_command_help gave us nothing
376
+ # but a command handler exists.
377
+ if not contributions.get(plugin_contributions.CATEGORY_COMMANDS):
378
+ if _registers_command_handler(entry.name):
379
+ contributions[plugin_contributions.CATEGORY_COMMANDS] = [
380
+ "command handler (name unknown)"
381
+ ]
382
+
383
+ if not any(contributions.get(key) for key, _ in _CONTRIB_LABELS):
384
+ return
385
+
386
+ lines.append(("bold", " Contributes:"))
387
+ lines.append(("", "\n"))
388
+ # Pre-wrap long contributions so stale tail glyphs cannot bleed into the
389
+ # divider when switching to a shorter plugin.
390
+ bullet_prefix = " • "
391
+ cont_prefix = " " # aligns continuation lines under the text
392
+ inner = max(20, menu._detail_cols - 2 - len(bullet_prefix))
393
+ for key, label in _CONTRIB_LABELS:
394
+ items = contributions.get(key)
395
+ if not items:
396
+ continue
397
+ lines.append(("class:tui.title", f" {label}:"))
398
+ lines.append(("", "\n"))
399
+ for item in items:
400
+ pieces = wrap_text(str(item), inner)
401
+ for idx, piece in enumerate(pieces):
402
+ prefix = bullet_prefix if idx == 0 else cont_prefix
403
+ lines.append(("class:tui.header", f"{prefix}{piece}"))
404
+ lines.append(("", "\n"))
405
+ lines.append(("", "\n"))
406
+
407
+
408
+ # Pane post-processing (cell padding + blank-row fill)
409
+ def fill_pane(fragments: Fragments, pane_cols: int, pane_rows: int) -> Fragments:
410
+ """Strip emojis, pad to pane width, fill to *pane_rows* rows.
411
+
412
+ Emojis are stripped first (cell-width accounting in TUIs is fragile —
413
+ cleaner to just not draw them), then each line is cell-padded to the
414
+ pane's inner width and blank rows fill any unused height. The combo
415
+ guarantees every visible cell is explicitly written every render.
416
+
417
+ Takes scalar dimensions instead of the whole menu so this stays a pure
418
+ function over its inputs (Law of Demeter — the renderer doesn't need to
419
+ know about ``PluginsMenu``'s shape).
420
+ """
421
+ fragments = strip_emojis_from_fragments(fragments)
422
+ current_lines = count_lines(fragments)
423
+ out = list(fragments)
424
+ # Ensure the final line ends with a newline so blank padding lines up.
425
+ if out and not out[-1][1].endswith("\n"):
426
+ out.append(("", "\n"))
427
+ current_lines += 1
428
+ blanks_needed = max(0, pane_rows - current_lines)
429
+ if blanks_needed:
430
+ out.append(("", "\n" * blanks_needed))
431
+ return pad_lines_to_cells(out, max(0, pane_cols - 2))
@@ -0,0 +1,162 @@
1
+ """Risk-acceptance flow for enabling project-level plugins.
2
+
3
+ Project plugins (``<CWD>/.code_puppy/plugins/``) are **disabled by default**
4
+ because they execute arbitrary repo-supplied code at import time.
5
+
6
+ The risk-acceptance ceremony lives in the ``/plugins`` TUI (select the
7
+ plugin, press Enter, type ``trust`` in the popup). This module provides the
8
+ non-interactive primitives the TUI calls — ``grant_trust_and_load`` and
9
+ ``activate_project_plugin`` — plus the slash-command handlers, which for
10
+ untrusted plugins LAUNCH the TUI (preselected, popup open) rather than
11
+ prompting inline.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import logging
17
+ from pathlib import Path
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+ # The word the user must type in the TUI popup to accept the risk. A y/n
22
+ # would be too easy to fat-finger; typing the word is a deliberate speed bump.
23
+ ACCEPT_WORD = "trust"
24
+
25
+
26
+ def plugin_file_listing(plugin_dir: Path, limit: int = 20) -> str:
27
+ """Bullet list of the files the user is about to trust."""
28
+ try:
29
+ files = sorted(
30
+ p.relative_to(plugin_dir).as_posix()
31
+ for p in plugin_dir.rglob("*")
32
+ if p.is_file() and "__pycache__" not in p.relative_to(plugin_dir).parts
33
+ )
34
+ except OSError as exc:
35
+ logger.warning("Could not list plugin dir %s: %s", plugin_dir, exc)
36
+ return " (unreadable)"
37
+ lines = [f" • {name}" for name in files[:limit]]
38
+ if len(files) > limit:
39
+ lines.append(f" … and {len(files) - limit} more")
40
+ return "\n".join(lines) if lines else " (no files)"
41
+
42
+
43
+ def activate_project_plugin(plugin_name: str) -> tuple[bool, str]:
44
+ """Clear the disabled flag and hot-load an ALREADY TRUSTED project plugin.
45
+
46
+ Fail closed: ``load_project_plugin_now`` re-checks the trust store, so
47
+ calling this for an untrusted plugin loads nothing.
48
+
49
+ Returns ``(ok, human_message)``.
50
+ """
51
+ from code_puppy.plugins import get_loaded_plugins, load_project_plugin_now
52
+ from code_puppy.plugins.config import set_plugin_disabled
53
+
54
+ set_plugin_disabled(plugin_name, False)
55
+
56
+ if plugin_name in get_loaded_plugins()["project"]:
57
+ return True, f"Project plugin '{plugin_name}' enabled."
58
+ if load_project_plugin_now(plugin_name):
59
+ return True, (
60
+ f"Project plugin '{plugin_name}' trusted and loaded — no restart needed."
61
+ )
62
+ return False, f"Loading '{plugin_name}' failed — check the logs."
63
+
64
+
65
+ def grant_trust_and_load(plugin_name: str) -> tuple[bool, str]:
66
+ """Record trust at the current content hash, then activate.
67
+
68
+ ONLY call this after the user completed the TUI risk-acceptance popup
69
+ (typed the accept word). This function does no prompting itself.
70
+
71
+ Returns ``(ok, human_message)``.
72
+ """
73
+ from code_puppy.plugins import get_project_plugins_directory
74
+ from code_puppy.plugins import trust as plugin_trust
75
+
76
+ project_dir = get_project_plugins_directory()
77
+ if project_dir is None:
78
+ return False, "No project plugins directory in this project."
79
+ plugin_dir = project_dir / plugin_name
80
+ if not plugin_dir.is_dir():
81
+ return False, f"'{plugin_name}' is not a project plugin here."
82
+
83
+ project_root = project_dir.parent.parent
84
+ if not plugin_trust.trust_plugin(project_root, plugin_name, plugin_dir):
85
+ return False, f"Could not record trust for '{plugin_name}' — see logs."
86
+ return activate_project_plugin(plugin_name)
87
+
88
+
89
+ def try_enable_project_plugin(plugin_name: str) -> bool:
90
+ """Handle ``/plugins enable`` for project plugins.
91
+
92
+ Trusted plugins (e.g. previously disabled) are activated directly.
93
+ Untrusted/changed plugins are NOT prompted for inline — the ceremony
94
+ lives in the TUI, so we open it with the plugin preselected and the
95
+ popup already up. Returns True if *plugin_name* is a project plugin
96
+ (fully handled either way); False lets the generic enable path run.
97
+ """
98
+ from code_puppy.messaging import emit_error, emit_info, emit_success
99
+ from code_puppy.plugins import get_project_plugins_directory
100
+ from code_puppy.plugins import trust as plugin_trust
101
+
102
+ project_dir = get_project_plugins_directory()
103
+ if project_dir is None:
104
+ return False
105
+ plugin_dir = project_dir / plugin_name
106
+ if not plugin_dir.is_dir():
107
+ return False # not a project plugin — let the generic toggle handle it
108
+
109
+ project_root = project_dir.parent.parent
110
+ status = plugin_trust.get_trust_status(project_root, plugin_name, plugin_dir)
111
+
112
+ if status != plugin_trust.TRUSTED:
113
+ # Open the preselected TUI; use text fallback only when headless/non-TTY.
114
+ try:
115
+ from code_puppy_core_plugins.plugin_list.plugins_menu import (
116
+ run_plugins_menu,
117
+ )
118
+
119
+ run_plugins_menu(focus_plugin=plugin_name)
120
+ except Exception as exc:
121
+ logger.warning("Plugins TUI failed for enable ceremony: %s", exc)
122
+ emit_info(
123
+ f"Project plugin '{plugin_name}' requires the review "
124
+ "ceremony: run /plugins, select it, press Enter, and type "
125
+ f"'{ACCEPT_WORD}'."
126
+ )
127
+ return True
128
+
129
+ ok, message = activate_project_plugin(plugin_name)
130
+ (emit_success if ok else emit_error)(message)
131
+ return True
132
+
133
+
134
+ def revoke_project_plugin(plugin_name: str) -> bool:
135
+ """Handle ``/plugins revoke <name>`` — remove trust for a project plugin.
136
+
137
+ Always returns True (the command is fully handled, with messaging).
138
+ """
139
+ from code_puppy.messaging import emit_info, emit_success, emit_warning
140
+ from code_puppy.plugins import get_loaded_plugins, get_project_plugins_directory
141
+ from code_puppy.plugins import trust as plugin_trust
142
+ from code_puppy.plugins.config import set_plugin_disabled
143
+
144
+ project_dir = get_project_plugins_directory()
145
+ if project_dir is None:
146
+ emit_info("No project plugins directory in this project.")
147
+ return True
148
+
149
+ project_root = project_dir.parent.parent
150
+ if plugin_trust.revoke_plugin(project_root, plugin_name):
151
+ emit_success(f"Trust revoked for project plugin '{plugin_name}'.")
152
+ if plugin_name in get_loaded_plugins()["project"]:
153
+ # Already imported this session — best we can do is stop its
154
+ # callbacks now; a restart fully unloads it.
155
+ set_plugin_disabled(plugin_name, True)
156
+ emit_warning(
157
+ "It was already loaded this session — callbacks are now "
158
+ "skipped; restart Code Puppy to fully unload it."
159
+ )
160
+ else:
161
+ emit_info(f"'{plugin_name}' was not trusted — nothing to revoke.")
162
+ return True