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,252 @@
1
+ """Credential, device-flow, and model-catalog helpers for Meta Muse OAuth."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import logging
7
+ import os
8
+ import tempfile
9
+ from pathlib import Path
10
+ from typing import Any
11
+ from urllib.parse import urlparse
12
+
13
+ import requests
14
+
15
+ from .config import (
16
+ FALLBACK_MODELS,
17
+ META_OAUTH_CONFIG,
18
+ get_muse_auth_path,
19
+ get_token_storage_path,
20
+ )
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ def _read_json(path: Path) -> dict[str, Any] | None:
26
+ try:
27
+ data = json.loads(path.read_text(encoding="utf-8"))
28
+ return data if isinstance(data, dict) else None
29
+ except FileNotFoundError:
30
+ return None
31
+ except Exception as exc: # noqa: BLE001
32
+ logger.warning("Could not read Meta credentials from %s: %s", path, exc)
33
+ return None
34
+
35
+
36
+ def _validate_url(url: str, allowed_host: str) -> str:
37
+ parsed = urlparse(url)
38
+ host = (parsed.hostname or "").lower()
39
+ if parsed.scheme != "https" or host != allowed_host:
40
+ raise ValueError(f"Unexpected Meta endpoint: {url}")
41
+ return url.rstrip("/")
42
+
43
+
44
+ def validate_api_base_url(url: str) -> str:
45
+ """Allow only Meta's HTTPS Model API front door."""
46
+ return _validate_url(url, "api.meta.ai")
47
+
48
+
49
+ def validate_verification_url(url: str) -> str:
50
+ """Allow HTTPS verification pages hosted by Meta."""
51
+ parsed = urlparse(url)
52
+ host = (parsed.hostname or "").lower()
53
+ if parsed.scheme != "https" or not (
54
+ host == "meta.com" or host.endswith(".meta.com")
55
+ ):
56
+ raise ValueError(f"Unexpected Meta verification URL: {url}")
57
+ return url
58
+
59
+
60
+ def _normalize_credentials(data: dict[str, Any], source: str) -> dict[str, Any] | None:
61
+ api_key = data.get("api_key")
62
+ if not isinstance(api_key, str) or not api_key:
63
+ return None
64
+ try:
65
+ base_url = validate_api_base_url(
66
+ str(data.get("api_base_url") or META_OAUTH_CONFIG["api_base_url"])
67
+ )
68
+ except ValueError as exc:
69
+ logger.warning("Ignoring invalid Meta credential endpoint: %s", exc)
70
+ return None
71
+ normalized = dict(data)
72
+ normalized.update({"api_key": api_key, "api_base_url": base_url, "_source": source})
73
+ return normalized
74
+
75
+
76
+ def load_credentials() -> dict[str, Any] | None:
77
+ """Load Meta credentials using env, Code Puppy, then Muse CLI precedence."""
78
+ env_key = os.getenv("META_API_KEY")
79
+ if env_key:
80
+ return {
81
+ "api_key": env_key,
82
+ "api_base_url": META_OAUTH_CONFIG["api_base_url"],
83
+ "_source": "environment",
84
+ }
85
+
86
+ own = _read_json(get_token_storage_path())
87
+ if own:
88
+ normalized = _normalize_credentials(own, "code-puppy")
89
+ if normalized:
90
+ return normalized
91
+
92
+ muse = _read_json(get_muse_auth_path())
93
+ providers = muse.get("providers") if muse else None
94
+ provider = providers.get("meta") if isinstance(providers, dict) else None
95
+ if isinstance(provider, dict):
96
+ return _normalize_credentials(provider, "muse")
97
+ return None
98
+
99
+
100
+ def save_credentials(credentials: dict[str, Any]) -> bool:
101
+ """Atomically save private Meta credentials with mode 0600."""
102
+ if not credentials.get("api_key"):
103
+ raise ValueError("Meta credentials require an api_key")
104
+ destination = get_token_storage_path()
105
+ payload = {key: value for key, value in credentials.items() if key != "_source"}
106
+ temporary_path: str | None = None
107
+ try:
108
+ fd, temporary_path = tempfile.mkstemp(
109
+ prefix=".meta_oauth.", dir=destination.parent
110
+ )
111
+ os.fchmod(fd, 0o600)
112
+ with os.fdopen(fd, "w", encoding="utf-8") as handle:
113
+ json.dump(payload, handle, indent=2)
114
+ handle.write("\n")
115
+ os.replace(temporary_path, destination)
116
+ destination.chmod(0o600)
117
+ return True
118
+ except Exception as exc: # noqa: BLE001
119
+ logger.error("Failed to save Meta OAuth credentials: %s", exc)
120
+ if temporary_path:
121
+ Path(temporary_path).unlink(missing_ok=True)
122
+ return False
123
+
124
+
125
+ def _problem(response: requests.Response, fallback: str) -> str:
126
+ try:
127
+ payload = response.json()
128
+ except ValueError:
129
+ return fallback
130
+ if not isinstance(payload, dict):
131
+ return fallback
132
+ return str(
133
+ payload.get("detail")
134
+ or payload.get("title")
135
+ or payload.get("error")
136
+ or fallback
137
+ )
138
+
139
+
140
+ def request_device_authorization() -> dict[str, Any]:
141
+ """Start Meta's RFC 8628 device authorization flow."""
142
+ base = _validate_url(str(META_OAUTH_CONFIG["auth_base_url"]), "auth.meta.com")
143
+ response = requests.post(
144
+ base + str(META_OAUTH_CONFIG["device_authorization_path"]),
145
+ data={"client_id": META_OAUTH_CONFIG["client_id"]},
146
+ headers={"Accept": "application/json", "User-Agent": "code-puppy/meta-oauth"},
147
+ timeout=META_OAUTH_CONFIG["request_timeout"],
148
+ allow_redirects=False,
149
+ )
150
+ if not response.ok:
151
+ raise RuntimeError(
152
+ _problem(response, f"Device authorization failed ({response.status_code})")
153
+ )
154
+ payload = response.json()
155
+ if not isinstance(payload, dict):
156
+ raise RuntimeError("Meta device authorization returned invalid JSON")
157
+ verification_url = payload.get("verification_uri_complete") or payload.get(
158
+ "verification_uri"
159
+ )
160
+ if verification_url:
161
+ validate_verification_url(str(verification_url))
162
+ return payload
163
+
164
+
165
+ def poll_device_token(device_code: str) -> tuple[str, dict[str, Any]]:
166
+ """Perform one device-token poll and return its OAuth state and payload."""
167
+ base = _validate_url(str(META_OAUTH_CONFIG["auth_base_url"]), "auth.meta.com")
168
+ response = requests.post(
169
+ base + str(META_OAUTH_CONFIG["device_token_path"]),
170
+ data={
171
+ "grant_type": META_OAUTH_CONFIG["device_code_grant"],
172
+ "device_code": device_code,
173
+ "client_id": META_OAUTH_CONFIG["client_id"],
174
+ },
175
+ headers={"Accept": "application/json", "User-Agent": "code-puppy/meta-oauth"},
176
+ timeout=META_OAUTH_CONFIG["request_timeout"],
177
+ allow_redirects=False,
178
+ )
179
+ try:
180
+ payload = response.json()
181
+ except ValueError as exc:
182
+ raise RuntimeError("Meta token endpoint returned invalid JSON") from exc
183
+ if response.ok and isinstance(payload, dict) and payload.get("access_token"):
184
+ return "authorized", payload
185
+ if isinstance(payload, dict):
186
+ return str(payload.get("error") or f"http_{response.status_code}"), payload
187
+ return f"http_{response.status_code}", {}
188
+
189
+
190
+ def mint_api_credentials(access_token: str) -> dict[str, Any]:
191
+ """Exchange a Meta account token for a Model API credential."""
192
+ response = requests.post(
193
+ _validate_url(str(META_OAUTH_CONFIG["mint_url"]), "api.meta.ai"),
194
+ headers={
195
+ "Accept": "application/json",
196
+ "Authorization": f"Bearer {access_token}",
197
+ "User-Agent": "code-puppy/meta-oauth",
198
+ "x-api-version": META_OAUTH_CONFIG["api_version"],
199
+ },
200
+ timeout=META_OAUTH_CONFIG["request_timeout"],
201
+ allow_redirects=False,
202
+ )
203
+ if not response.ok:
204
+ raise RuntimeError(
205
+ _problem(response, f"Meta key mint failed ({response.status_code})")
206
+ )
207
+ payload = response.json()
208
+ if not isinstance(payload, dict) or not payload.get("api_key"):
209
+ raise RuntimeError("Meta key mint returned no API key")
210
+ payload["api_base_url"] = validate_api_base_url(
211
+ str(payload.get("base_url") or payload.get("api_base_url") or "")
212
+ )
213
+ payload.pop("base_url", None)
214
+ return payload
215
+
216
+
217
+ def discover_models(credentials: dict[str, Any]) -> dict[str, dict[str, Any]]:
218
+ """Fetch Meta's Muse catalogue and convert it to Code Puppy model specs."""
219
+ response = requests.get(
220
+ _validate_url(str(META_OAUTH_CONFIG["catalog_url"]), "api.meta.ai"),
221
+ headers={
222
+ "Accept": "application/json",
223
+ "Authorization": f"Bearer {credentials['api_key']}",
224
+ "User-Agent": "code-puppy/meta-oauth",
225
+ "x-api-version": META_OAUTH_CONFIG["api_version"],
226
+ },
227
+ timeout=META_OAUTH_CONFIG["request_timeout"],
228
+ )
229
+ if not response.ok:
230
+ raise RuntimeError(
231
+ _problem(response, f"Meta model discovery failed ({response.status_code})")
232
+ )
233
+
234
+ payload = response.json()
235
+ rows = payload.get("data", []) if isinstance(payload, dict) else []
236
+ models: dict[str, dict[str, Any]] = {}
237
+ for row in rows:
238
+ if not isinstance(row, dict) or not isinstance(row.get("id"), str):
239
+ continue
240
+ metadata = row.get("metadata")
241
+ details = metadata.get("muse-code") if isinstance(metadata, dict) else None
242
+ if not isinstance(details, dict) or details.get("is_hidden") is True:
243
+ continue
244
+ raw_limits = details.get("limit")
245
+ limits = raw_limits if isinstance(raw_limits, dict) else {}
246
+ models[row["id"]] = {
247
+ "context_length": int(limits.get("context") or 128_000),
248
+ "supported_settings": ["reasoning_effort"]
249
+ if details.get("reasoning")
250
+ else [],
251
+ }
252
+ return models or dict(FALLBACK_MODELS)
@@ -0,0 +1,118 @@
1
+ # namespace_skill_search
2
+
3
+ Model-agnostic reimplementation of OpenAI's namespace + `tool_search`
4
+ pattern, applied to Code Puppy's skill catalog.
5
+
6
+ ## Why this exists
7
+
8
+ Frontier labs converge on one shape for large tool/skill catalogs:
9
+ **one grouping layer + on-demand search**, instead of a flat list of
10
+ every item injected into the system prompt upfront.
11
+
12
+ - Anthropic's shipped Tool Search Tool: flat on-demand search,
13
+ `defer_loading: true`, results injected at end of context to preserve
14
+ prompt caching. ~85% token reduction, measurable accuracy gains on
15
+ internal MCP evals ([Anthropic engineering blog, Nov 2025](https://www.anthropic.com/engineering/advanced-tool-use)).
16
+ Note: our `load_prompt` fragment lands earlier in the assembled prompt
17
+ (layer 2 of `base_agent.py::get_full_system_prompt`) than the built-in
18
+ flat skill list it replaces did (layer 6, dead last). We prioritized
19
+ additive-safety (`load_prompt` can't collide with another plugin's
20
+ fragment; `get_model_system_prompt` can) over exactly matching
21
+ Anthropic's end-of-context caching position. If cache-hit rate on the
22
+ namespace block ever matters in practice, moving it later is a
23
+ follow-up, not a redesign.
24
+ - OpenAI's shipped `tool_search`: same on-demand mechanic, PLUS an
25
+ explicit **namespace** grouping layer above it
26
+ (`{"type": "namespace", "name": "crm", "tools": [...]}`), because
27
+ "our models have primarily been trained to search those surfaces" —
28
+ namespaces outperform bare deferred functions in their own docs
29
+ ([OpenAI tool-search guide](https://developers.openai.com/api/docs/guides/tools-tool-search)).
30
+ They recommend keeping each namespace under 10 functions.
31
+ - Neither ships a deep multi-level tree. One grouping layer is the norm;
32
+ true hierarchical (server -> tool) routing exists mainly in academic
33
+ work (e.g. MCP-Zero, arXiv:2506.01056), not shipped production
34
+ features.
35
+
36
+ ## What we copied, and why
37
+
38
+ We copied OpenAI's shape (namespace + search) rather than Anthropic's
39
+ (flat search only), because the namespace layer is a genuine win once a
40
+ catalog gets into the hundreds of items. But OpenAI's mechanism depends
41
+ on a model-specific API flag (`defer_loading`) that only exists for
42
+ their own models. This plugin gets the same effect through Code Puppy's
43
+ model-agnostic plugin hooks instead, so it behaves identically on
44
+ Claude, GPT, Gemini, or any custom endpoint wired through
45
+ `ModelFactory` — no provider-specific request fields required.
46
+
47
+ ## How it works
48
+
49
+ 1. **Namespace derivation** (`namespaces.py`) — a skill's namespace is
50
+ its first `tags:` entry (untagged skills land in `General`). No new
51
+ frontmatter field, no migration of existing `SKILL.md` files required.
52
+ 2. **Compact directory injection** (`load_prompt` hook) — instead of one
53
+ line per skill (the built-in behavior when
54
+ `frontmatter_in_system_prompt` is on), the system prompt gets one
55
+ line per *namespace* with a 3-item preview and a count. `load_prompt`
56
+ fragments from every plugin are newline-joined by `base_agent.py`, so
57
+ this is safely additive — it cannot collide with or overwrite another
58
+ plugin's fragment the way `get_model_system_prompt` callbacks can
59
+ (see "What we deliberately avoided" below).
60
+ 3. **Drill-down tool** (`browse_skill_namespace`) — one tool, three modes:
61
+ - no args -> namespace directory (same content as the prompt block)
62
+ - `namespace="X"` -> every skill in that namespace
63
+ - `query="..."` -> keyword search across all namespaces
64
+ 4. **Flat list turned off** — the first time this plugin ever starts up
65
+ (via a `startup` callback, not an import-time side effect — see
66
+ "What we deliberately avoided" below), it calls
67
+ `set_frontmatter_in_system_prompt(False)` via the public config API
68
+ (`code_puppy_core_plugins.agent_skills.config`) so the built-in per-skill
69
+ flat list doesn't also render alongside the namespace directory.
70
+
71
+ **This is a real, persisted config write, not an in-memory toggle.**
72
+ Enabling this plugin once permanently sets
73
+ `frontmatter_in_system_prompt=false` in `puppy.cfg`. A dedicated
74
+ marker key (`namespace_skill_search_frontmatter_migrated`) records
75
+ that the flip already happened, so if you re-enable it afterwards
76
+ with `/skills frontmatter on`, we never touch the flag again — your
77
+ choice sticks across restarts. But if you disable or remove this
78
+ plugin entirely, the flag stays `false`; nothing restores the flat
79
+ list automatically. Run `/skills frontmatter on` yourself if you want
80
+ it back.
81
+
82
+ ## What we deliberately avoided
83
+
84
+ - **No config mutation at import time.** The `frontmatter_in_system_prompt`
85
+ flip runs inside a `register_callback("startup", ...)` handler (matching
86
+ the same pattern as `code_puppy/plugins/theme`'s
87
+ `_apply_default_theme_on_first_run`), not as top-level code in
88
+ `register_callbacks.py`. Import-time disk writes are a landmine: they
89
+ fire on every import (including test collection), can't be unit-tested
90
+ without `importlib.reload` gymnastics, and race with plugin load order
91
+ in ways a callback doesn't. A dedicated migration marker (see above)
92
+ makes the one-time-ness explicit and testable instead of relying on
93
+ "only flip it when the shared flag is currently True" as the sole
94
+ signal.
95
+ - **No `get_model_system_prompt` callback.** That phase is
96
+ last-write-wins across plugins — `model_utils.prepare_prompt_for_model`
97
+ threads augmenter results sequentially and the *last* callback
98
+ processed wins on any key both callbacks set. A second plugin
99
+ registering on that phase risks silently clobbering the built-in
100
+ `agent_skills` plugin's contribution. `load_prompt` avoids that
101
+ failure mode entirely by design (simple concatenation, no merge
102
+ conflicts possible).
103
+ - **No vector DB / embeddings.** Namespace + substring search is enough
104
+ at this scale; see Anthropic's own `tool_search_with_embeddings.ipynb`
105
+ cookbook, which only recommends embeddings above ~100 tools with high
106
+ semantic overlap between items.
107
+ - **No deep multi-level tree** (namespace -> subnamespace -> skill). No
108
+ frontier lab ships that in production; one grouping layer is the norm.
109
+ - **No new tool duplicating `list_or_search_skills`.** That tool still
110
+ exists and still works for flat queries; `browse_skill_namespace` is
111
+ additive, not a replacement.
112
+
113
+ ## Sizing signal
114
+
115
+ OpenAI's own docs recommend keeping each namespace under 10 functions
116
+ for best token efficiency. We don't enforce this (skills aren't ours to
117
+ re-tag), but the directory block flags any namespace over that
118
+ threshold as `oversized namespace` so it's visible instead of silent.
@@ -0,0 +1,15 @@
1
+ """namespace_skill_search — OpenAI-style namespace + on-demand search for skills.
2
+
3
+ Model-agnostic reimplementation of the pattern OpenAI ships as its
4
+ `tool_search` + namespaces feature. OpenAI's version relies on a
5
+ model-provider-specific `defer_loading` API flag; this plugin gets the same
6
+ effect — one grouping layer + on-demand search instead of a flat, unranked
7
+ skill list baked into every system prompt — using only Code Puppy's
8
+ model-agnostic plugin hooks, so it behaves identically on Claude, GPT,
9
+ Gemini, or any custom endpoint wired through `ModelFactory`.
10
+
11
+ See README.md in this directory for the full design rationale, including
12
+ what frontier labs (Anthropic, OpenAI, Google) actually ship in production
13
+ vs. what's academic-only, and why this plugin copies OpenAI's shape
14
+ specifically.
15
+ """
@@ -0,0 +1,125 @@
1
+ """Namespace derivation and summary-block rendering.
2
+
3
+ A "namespace" is a skill's first tag (falling back to "General" for
4
+ untagged skills). No new frontmatter field, no schema migration — every
5
+ skill that already has a `tags:` entry in its SKILL.md slots in for free.
6
+
7
+ This mirrors OpenAI's namespace grouping (`{"type": "namespace", "name":
8
+ "crm", "tools": [...]}`) as a plain-data structure instead of a
9
+ model-provider-specific request field.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from collections import Counter
15
+ from typing import Dict, List, Optional
16
+
17
+ from code_puppy_core_plugins.agent_skills.enabled_skills import (
18
+ list_enabled_skill_metadata,
19
+ )
20
+ from code_puppy_core_plugins.agent_skills.metadata import SkillMetadata
21
+
22
+ _DEFAULT_NAMESPACE = "General"
23
+
24
+ # OpenAI recommends fewer than 10 functions per namespace; flag oversized
25
+ # namespaces without re-tagging skills we don't own.
26
+ _RECOMMENDED_MAX_PER_NAMESPACE = 10
27
+
28
+
29
+ def _namespace_for(skill: SkillMetadata) -> str:
30
+ """First tag wins; untagged skills land in the General bucket."""
31
+ if skill.tags:
32
+ return skill.tags[0].strip() or _DEFAULT_NAMESPACE
33
+ return _DEFAULT_NAMESPACE
34
+
35
+
36
+ def build_namespaces() -> Dict[str, List[SkillMetadata]]:
37
+ """Group every enabled skill by namespace.
38
+
39
+ Grouping is case-insensitive: skills tagged "finance" and "Finance"
40
+ land in the same bucket. Without this, the directory would silently
41
+ fragment into two separate namespace entries that look distinct to
42
+ the model but are semantically the same category, and a
43
+ case-sensitive drill-down would only ever surface one of them (see
44
+ `search_tool.py`'s case-insensitive `namespace=` lookup, which
45
+ depends on this function never producing two keys that only differ
46
+ by case). The *first* casing encountered wins for display purposes,
47
+ which keeps behavior deterministic for a given skill-discovery order.
48
+
49
+ Returns an empty dict if skills are globally disabled or none exist —
50
+ callers should treat that as "nothing to show", not an error.
51
+ """
52
+ namespaces: Dict[str, List[SkillMetadata]] = {}
53
+ display_names: Dict[str, str] = {}
54
+ for skill in list_enabled_skill_metadata():
55
+ raw_ns = _namespace_for(skill)
56
+ key = raw_ns.lower()
57
+ display = display_names.setdefault(key, raw_ns)
58
+ namespaces.setdefault(display, []).append(skill)
59
+ return namespaces
60
+
61
+
62
+ def _duplicate_skill_names(namespaces: Dict[str, List[SkillMetadata]]) -> List[str]:
63
+ """Names shared by 2+ skills, regardless of namespace.
64
+
65
+ Nothing in `agent_skills` enforces globally-unique skill names, and
66
+ grouping by namespace makes a collision more visible (two entries
67
+ with the same name can now show up side-by-side across namespace
68
+ listings), not less. We can't resolve the ambiguity here -- that's an
69
+ `agent_skills`-level concern -- but we can at least flag it instead
70
+ of silently presenting the model with two indistinguishable
71
+ `activate_skill(name)` targets.
72
+ """
73
+ counts = Counter(s.name for skills in namespaces.values() for s in skills)
74
+ return sorted(name for name, count in counts.items() if count > 1)
75
+
76
+
77
+ def build_namespace_summary() -> Optional[str]:
78
+ """Render the compact `load_prompt` block replacing the flat skill list.
79
+
80
+ Format is intentionally terse — this is the ONE thing that always
81
+ lands in the system prompt, so every line here is context budget spent
82
+ on every single turn.
83
+ """
84
+ namespaces = build_namespaces()
85
+ if not namespaces:
86
+ return None
87
+
88
+ total = sum(len(v) for v in namespaces.values())
89
+ lines = [
90
+ "## Skill Namespaces",
91
+ f"{total} skills available across {len(namespaces)} namespaces. "
92
+ "Individual skills are NOT listed here — browse or search instead.",
93
+ "",
94
+ ]
95
+
96
+ for ns, skills in sorted(namespaces.items(), key=lambda kv: -len(kv[1])):
97
+ preview = ", ".join(s.name for s in skills[:3])
98
+ remainder = len(skills) - 3
99
+ if remainder > 0:
100
+ preview += f", +{remainder} more"
101
+ flag = (
102
+ " oversized namespace"
103
+ if len(skills) > _RECOMMENDED_MAX_PER_NAMESPACE
104
+ else ""
105
+ )
106
+ lines.append(f"- **{ns}** ({len(skills)}){flag}: {preview}")
107
+
108
+ lines.append("")
109
+ lines.append(
110
+ "Call `browse_skill_namespace()` with no arguments to see this same "
111
+ "directory again, `browse_skill_namespace(namespace=...)` to list "
112
+ "every skill in one namespace, or `browse_skill_namespace(query=...)` "
113
+ "to keyword-search across all namespaces. Then `activate_skill(name)` "
114
+ "to load full instructions."
115
+ )
116
+
117
+ duplicates = _duplicate_skill_names(namespaces)
118
+ if duplicates:
119
+ lines.append(
120
+ "Note: these skill names appear more than once across "
121
+ f"namespaces, so `activate_skill(name)` may be ambiguous: "
122
+ f"{', '.join(duplicates)}."
123
+ )
124
+
125
+ return "\n".join(lines)
@@ -0,0 +1,99 @@
1
+ """Entry point: registers all hooks for the namespace_skill_search plugin.
2
+
3
+ Three hooks:
4
+
5
+ 1. `startup` -> one-time migration that turns off the built-in flat
6
+ skill list (see `_maybe_disable_frontmatter` below).
7
+ 2. `load_prompt` -> injects the compact namespace directory (a few lines
8
+ per namespace instead of one line per skill).
9
+ Fragments from every plugin are simply
10
+ newline-joined by base_agent.py, so this coexists
11
+ safely with any other plugin's fragment.
12
+ 3. `register_tools` -> adds `browse_skill_namespace` as a real tool, wired
13
+ to every agent automatically via
14
+ `register_agent_tools`.
15
+
16
+ We deliberately do NOT touch `get_model_system_prompt` (that's where the
17
+ built-in flat skill list gets injected, and where a *second* callback on
18
+ that same phase would silently clobber the first one's output — see
19
+ `model_utils.prepare_prompt_for_model`: augmenter results are threaded
20
+ through sequentially and the *last* one processed wins on any key they
21
+ both set). Instead we turn the flat list off via the public config API
22
+ and let our `load_prompt` fragment be the only skills-summary content in
23
+ the prompt.
24
+ """
25
+
26
+ import logging
27
+
28
+ from code_puppy.callbacks import register_callback
29
+ from code_puppy.config import get_value, set_config_value
30
+ from code_puppy_core_plugins.agent_skills.config import (
31
+ get_frontmatter_in_system_prompt,
32
+ set_frontmatter_in_system_prompt,
33
+ )
34
+
35
+ from .namespaces import build_namespace_summary
36
+ from .search_tool import register_browse_skill_namespace
37
+
38
+ logger = logging.getLogger(__name__)
39
+
40
+ # Own marker distinguishes this plugin's one-time migration from a user's later
41
+ # `/skills frontmatter on`; flip the shared flag once, then never override it.
42
+ _MIGRATION_MARKER_KEY = "namespace_skill_search_frontmatter_migrated"
43
+
44
+
45
+ def _maybe_disable_frontmatter() -> None:
46
+ """One-time migration: turn off the built-in flat per-skill list.
47
+
48
+ Runs on the `startup` callback (not at import time) so it is:
49
+ - testable in isolation (call this function directly with mocked
50
+ config, same pattern as `theme._apply_default_theme_on_first_run`);
51
+ - not repeated on every module import (`startup` callbacks fire once
52
+ per process, same as any other plugin);
53
+ - a single, auditable point of config mutation rather than an
54
+ import-time side effect.
55
+
56
+ Only ever flips the shared `frontmatter_in_system_prompt` flag from
57
+ True -> False, and only the first time this plugin ever starts up. A
58
+ user who runs `/skills frontmatter on` afterwards keeps that choice
59
+ forever — the marker means we never re-evaluate `get_value` for this
60
+ decision again.
61
+ """
62
+ if get_value(_MIGRATION_MARKER_KEY):
63
+ return
64
+
65
+ if get_frontmatter_in_system_prompt():
66
+ set_frontmatter_in_system_prompt(False)
67
+ logger.info(
68
+ "namespace_skill_search: disabled flat skill-list frontmatter "
69
+ "(one-time migration); namespace directory + "
70
+ "browse_skill_namespace tool take over. Run `/skills "
71
+ "frontmatter on` to restore the flat list."
72
+ )
73
+
74
+ set_config_value(_MIGRATION_MARKER_KEY, "true")
75
+
76
+
77
+ def _on_load_prompt():
78
+ return build_namespace_summary()
79
+
80
+
81
+ def _register_tools():
82
+ return [
83
+ {
84
+ "name": "browse_skill_namespace",
85
+ "register_func": register_browse_skill_namespace,
86
+ }
87
+ ]
88
+
89
+
90
+ def _advertise_to_all_agents(agent_name=None):
91
+ return ["browse_skill_namespace"]
92
+
93
+
94
+ register_callback("startup", _maybe_disable_frontmatter)
95
+ register_callback("load_prompt", _on_load_prompt)
96
+ register_callback("register_tools", _register_tools)
97
+ register_callback("register_agent_tools", _advertise_to_all_agents)
98
+
99
+ logger.info("namespace_skill_search plugin loaded")