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,348 @@
1
+ """Per-plugin contribution extraction for the ``/plugins`` preview.
2
+
3
+ For a given plugin name, return the concrete things it contributes per
4
+ category — tools, slash commands, agents, skills, model types/providers,
5
+ MCP catalog servers, browser types, and agent-advertised tool names.
6
+
7
+ Best-effort and display-only: we invoke only the target plugin's own
8
+ collection callbacks (filtered via ``get_callback_owner``), and every read
9
+ is wrapped in ``try/except`` so a raising callback or registry yields no
10
+ items rather than crashing the preview.
11
+
12
+ Some plugins register via a direct registry instead of firing the matching
13
+ callback, so two categories need extra attribution:
14
+
15
+ * commands: ``@register_command`` writes straight into the command registry
16
+ without firing ``custom_command_help``.
17
+ * tools: ``TOOL_REGISTRY`` is mutable, so a plugin can add a tool without
18
+ firing ``register_tools``.
19
+
20
+ For both, we read the registry and attribute each entry to the plugin whose
21
+ module defines the handler / register_func (see ``_plugin_owner_of_module``).
22
+ Core entries never match a real plugin name, so they fall out naturally.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ from typing import Any, Callable, Dict, List, Optional
28
+
29
+ from code_puppy.callbacks import get_callback_owner, get_callbacks
30
+
31
+ # Category keys (stable identifiers used by the preview renderer).
32
+ CATEGORY_TOOLS = "tools"
33
+ CATEGORY_COMMANDS = "commands"
34
+ CATEGORY_AGENTS = "agents"
35
+ CATEGORY_SKILLS = "skills"
36
+ CATEGORY_MODEL_TYPES = "model_types"
37
+ CATEGORY_MODEL_PROVIDERS = "model_providers"
38
+ CATEGORY_MCP_SERVERS = "mcp_servers"
39
+ CATEGORY_BROWSER_TYPES = "browser_types"
40
+ CATEGORY_AGENT_TOOLS = "agent_tools"
41
+
42
+
43
+ # Owner-filtered invocation primitives
44
+ def _owned_callbacks(plugin_name: str, phase: str) -> List[Callable[..., Any]]:
45
+ """Return *phase*'s callbacks attributed to *plugin_name*."""
46
+ return [
47
+ cb
48
+ for cb in get_callbacks(phase, include_disabled=True)
49
+ if get_callback_owner(cb) == plugin_name
50
+ ]
51
+
52
+
53
+ def _invoke_owned(plugin_name: str, phase: str, *args: Any) -> List[Any]:
54
+ """Invoke each owned callback for *phase*; a raising callback is skipped."""
55
+ results: List[Any] = []
56
+ for cb in _owned_callbacks(plugin_name, phase):
57
+ try:
58
+ results.append(cb(*args))
59
+ except Exception:
60
+ continue
61
+ return results
62
+
63
+
64
+ def _dedupe(items: List[str]) -> List[str]:
65
+ """Drop falsy/duplicate entries while preserving first-seen order."""
66
+ seen: set[str] = set()
67
+ out: List[str] = []
68
+ for item in items:
69
+ if not item or item in seen:
70
+ continue
71
+ seen.add(item)
72
+ out.append(item)
73
+ return out
74
+
75
+
76
+ def _as_list(result: Any) -> List[Any]:
77
+ """Normalize a callback result into a list (mirrors core's leniency)."""
78
+ if result is None:
79
+ return []
80
+ return result if isinstance(result, list) else [result]
81
+
82
+
83
+ def _dict_key(entry: Any, key: str) -> str:
84
+ """Return ``entry[key]`` as a non-empty str, else ``""``."""
85
+ if isinstance(entry, dict):
86
+ value = entry.get(key)
87
+ if isinstance(value, str) and value:
88
+ return value
89
+ return ""
90
+
91
+
92
+ # Direct-registry attribution (commands/tools bypassing callback hooks)
93
+ def _plugin_owner_of_module(module_name: Optional[str]) -> Optional[str]:
94
+ """Map a callable's ``__module__`` to the plugin name that defines it.
95
+
96
+ Mirrors the three plugin module-name layouts:
97
+
98
+ * builtin -> ``code_puppy_core_plugins.<name>.<...>``
99
+ * project -> ``project_plugins.<name>.<...>``
100
+ * user -> ``<name>.<...>``
101
+
102
+ Returns ``None`` when the module is empty/unparseable.
103
+ """
104
+ if not module_name:
105
+ return None
106
+ parts = module_name.split(".")
107
+ if module_name.startswith("code_puppy_core_plugins."):
108
+ return parts[1] if len(parts) >= 2 else None
109
+ # Retain attribution for third-party plugins using the host's legacy layout.
110
+ if module_name.startswith("code_puppy.plugins."):
111
+ return parts[2] if len(parts) >= 3 else None
112
+ if module_name.startswith("project_plugins."):
113
+ return parts[1] if len(parts) >= 2 else None
114
+ return parts[0] if parts and parts[0] else None
115
+
116
+
117
+ def _owns_callable(plugin_name: str, func: Any) -> bool:
118
+ """Whether *func* is defined inside *plugin_name*'s module tree."""
119
+ module_name = getattr(func, "__module__", None)
120
+ return _plugin_owner_of_module(module_name) == plugin_name
121
+
122
+
123
+ # Per-category extraction helpers
124
+ def _collect_dict_field(
125
+ plugin_name: str, phase: str, field: str, *args: Any
126
+ ) -> List[str]:
127
+ """Invoke *phase* (owned by *plugin_name*) and collect ``entry[field]``.
128
+
129
+ Each callback returns a dict or list of dicts; ``entry[field]`` from
130
+ each is appended. ``*args`` is forwarded (e.g. ``register_agent_tools(None)``).
131
+ """
132
+ names: List[str] = []
133
+ for result in _invoke_owned(plugin_name, phase, *args):
134
+ for entry in _as_list(result):
135
+ names.append(_dict_key(entry, field))
136
+ return names
137
+
138
+
139
+ def _collect_dict_keys(plugin_name: str, phase: str) -> List[str]:
140
+ """Invoke *phase* and collect the string keys of each dict result.
141
+
142
+ Used for hooks whose contract is ``() -> {name: handler}`` rather than
143
+ ``() -> [{"name": ...}]``.
144
+ """
145
+ keys: List[str] = []
146
+ for result in _invoke_owned(plugin_name, phase):
147
+ keys.extend(_dict_keys(result))
148
+ return keys
149
+
150
+
151
+ def get_tools(plugin_name: str) -> List[str]:
152
+ """Tool names contributed by *plugin_name*, deduped (first-seen order).
153
+
154
+ Combines the ``register_tools`` callback with direct ``TOOL_REGISTRY``
155
+ entries owned by this plugin's module.
156
+ """
157
+ names = _collect_dict_field(plugin_name, "register_tools", "name")
158
+ names.extend(_registry_tools(plugin_name))
159
+ return _dedupe(names)
160
+
161
+
162
+ def _registry_tools(plugin_name: str) -> List[str]:
163
+ """Tool names from ``TOOL_REGISTRY`` owned by *plugin_name* (read-only)."""
164
+ names: List[str] = []
165
+ try:
166
+ from code_puppy.tools import TOOL_REGISTRY
167
+
168
+ items = list(TOOL_REGISTRY.items())
169
+ except Exception:
170
+ return names
171
+ for tool_name, register_func in items:
172
+ try:
173
+ if isinstance(tool_name, str) and _owns_callable(
174
+ plugin_name, register_func
175
+ ):
176
+ names.append(tool_name)
177
+ except Exception:
178
+ continue
179
+ return names
180
+
181
+
182
+ def get_commands(plugin_name: str) -> List[str]:
183
+ """Slash commands contributed by *plugin_name*, deduped (first-seen order).
184
+
185
+ Combines the ``custom_command_help`` callback (which may return a single
186
+ ``(name, description)`` tuple, a list of such tuples, or a legacy list of
187
+ ``"/name - description"`` strings) with direct ``command_registry`` entries
188
+ whose handler is owned by this plugin. Each command renders as
189
+ ``"/name — description"`` (description omitted when absent).
190
+ """
191
+ commands: List[str] = []
192
+ for result in _invoke_owned(plugin_name, "custom_command_help"):
193
+ if not result:
194
+ continue
195
+ # Shape 1: a bare (name, description) tuple.
196
+ if isinstance(result, tuple) and len(result) == 2:
197
+ commands.append(_format_command(result[0], result[1]))
198
+ continue
199
+ if not isinstance(result, list):
200
+ continue
201
+ # Shape 2: list of (name, description) tuples.
202
+ if result and isinstance(result[0], tuple):
203
+ for item in result:
204
+ if isinstance(item, tuple) and len(item) == 2:
205
+ commands.append(_format_command(item[0], item[1]))
206
+ # Shape 3: legacy list of "/name - description" strings.
207
+ elif result and isinstance(result[0], str):
208
+ for item in result:
209
+ parsed = _parse_legacy_command(item)
210
+ if parsed:
211
+ commands.append(parsed)
212
+ commands.extend(_registry_commands(plugin_name))
213
+ return _dedupe(commands)
214
+
215
+
216
+ def _registry_commands(plugin_name: str) -> List[str]:
217
+ """Slash commands from ``command_registry`` owned by *plugin_name*.
218
+
219
+ Uses ``get_unique_commands`` (one entry per primary command, no aliases),
220
+ attributing each to the plugin that defines its handler.
221
+ """
222
+ commands: List[str] = []
223
+ try:
224
+ from code_puppy.command_line.command_registry import get_unique_commands
225
+
226
+ infos = get_unique_commands()
227
+ except Exception:
228
+ return commands
229
+ for info in infos:
230
+ try:
231
+ if not _owns_callable(plugin_name, getattr(info, "handler", None)):
232
+ continue
233
+ name = getattr(info, "name", "")
234
+ description = getattr(info, "description", "")
235
+ formatted = _format_command(name, description)
236
+ if formatted:
237
+ commands.append(formatted)
238
+ except Exception:
239
+ continue
240
+ return commands
241
+
242
+
243
+ def _format_command(name: Any, description: Any) -> str:
244
+ """Render a ``(name, description)`` pair as ``/name — description``."""
245
+ cmd = str(name).strip().lstrip("/").strip()
246
+ if not cmd:
247
+ return ""
248
+ desc = str(description).strip()
249
+ return f"/{cmd} — {desc}" if desc else f"/{cmd}"
250
+
251
+
252
+ def _parse_legacy_command(line: Any) -> str:
253
+ """Parse a legacy ``"/name - description"`` string."""
254
+ if not isinstance(line, str) or not line.startswith("/"):
255
+ return ""
256
+ if " - " in line:
257
+ name, desc = line.split(" - ", 1)
258
+ return _format_command(name, desc)
259
+ return _format_command(line, "")
260
+
261
+
262
+ def get_agents(plugin_name: str) -> List[str]:
263
+ """Agent names from ``register_agents`` -> ``[{"name", ...}]``."""
264
+ return _dedupe(_collect_dict_field(plugin_name, "register_agents", "name"))
265
+
266
+
267
+ def get_skills(plugin_name: str) -> List[str]:
268
+ """Skill names from ``register_skills`` -> ``[{"name", ...}]``."""
269
+ return _dedupe(_collect_dict_field(plugin_name, "register_skills", "name"))
270
+
271
+
272
+ def get_model_types(plugin_name: str) -> List[str]:
273
+ """Model type names from ``register_model_type`` -> ``[{"type", ...}]``."""
274
+ return _dedupe(_collect_dict_field(plugin_name, "register_model_type", "type"))
275
+
276
+
277
+ def get_model_providers(plugin_name: str) -> List[str]:
278
+ """Provider keys from ``register_model_providers`` -> ``{name: ModelClass}``."""
279
+ return _dedupe(_collect_dict_keys(plugin_name, "register_model_providers"))
280
+
281
+
282
+ def get_mcp_servers(plugin_name: str) -> List[str]:
283
+ """Server names from ``register_mcp_catalog_servers`` -> ``[MCPServerTemplate]``.
284
+
285
+ Accepts ``MCPServerTemplate``-like objects (``.name`` attribute) as well
286
+ as plain dicts with a ``"name"`` key, for maximum leniency.
287
+ """
288
+ names: List[str] = []
289
+ for result in _invoke_owned(plugin_name, "register_mcp_catalog_servers"):
290
+ for entry in _as_list(result):
291
+ names.append(_server_name(entry))
292
+ return _dedupe(names)
293
+
294
+
295
+ def _server_name(entry: Any) -> str:
296
+ """Extract a display name from an MCP server template or dict."""
297
+ name = getattr(entry, "name", None)
298
+ if isinstance(name, str) and name:
299
+ return name
300
+ return _dict_key(entry, "name")
301
+
302
+
303
+ def get_browser_types(plugin_name: str) -> List[str]:
304
+ """Browser type keys from ``register_browser_types`` -> ``{type_name: init}``."""
305
+ return _dedupe(_collect_dict_keys(plugin_name, "register_browser_types"))
306
+
307
+
308
+ def get_agent_tools(plugin_name: str) -> List[str]:
309
+ """Advertised tool names from ``register_agent_tools(None)`` -> ``[str]``.
310
+
311
+ ``None`` means "any agent" per the hook contract.
312
+ """
313
+ names: List[str] = []
314
+ for result in _invoke_owned(plugin_name, "register_agent_tools", None):
315
+ for item in _as_list(result):
316
+ if isinstance(item, str):
317
+ names.append(item)
318
+ return _dedupe(names)
319
+
320
+
321
+ def _dict_keys(result: Any) -> List[str]:
322
+ """Return the string keys of a dict result (provider/browser shape)."""
323
+ if not isinstance(result, dict):
324
+ return []
325
+ return [k for k in result.keys() if isinstance(k, str) and k]
326
+
327
+
328
+ # Public aggregate API
329
+ _EXTRACTORS: Dict[str, Callable[[str], List[str]]] = {
330
+ CATEGORY_TOOLS: get_tools,
331
+ CATEGORY_COMMANDS: get_commands,
332
+ CATEGORY_AGENTS: get_agents,
333
+ CATEGORY_SKILLS: get_skills,
334
+ CATEGORY_MODEL_TYPES: get_model_types,
335
+ CATEGORY_MODEL_PROVIDERS: get_model_providers,
336
+ CATEGORY_MCP_SERVERS: get_mcp_servers,
337
+ CATEGORY_BROWSER_TYPES: get_browser_types,
338
+ CATEGORY_AGENT_TOOLS: get_agent_tools,
339
+ }
340
+
341
+
342
+ def get_contributions(plugin_name: str) -> Dict[str, List[str]]:
343
+ """Return every contribution category for *plugin_name*.
344
+
345
+ Keyed by the ``CATEGORY_*`` constants; each value is a (possibly empty)
346
+ list of display strings, so the renderer always gets a complete dict.
347
+ """
348
+ return {category: extract(plugin_name) for category, extract in _EXTRACTORS.items()}
@@ -0,0 +1,67 @@
1
+ """Metadata resolution for loaded plugins.
2
+
3
+ Surfaces metadata the plugin system tracks but never displayed: the plugin's
4
+ module docstring (as a description), its on-disk path, and the lifecycle hook
5
+ phases it registers callbacks for. Kept separate from ``plugins_menu`` so the
6
+ TUI stays focused on rendering.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import sys
12
+ from typing import List, Optional, get_args
13
+
14
+ # Module-name templates per tier, mirroring how the plugin loader registers
15
+ # each tier in ``sys.modules`` (see code_puppy/plugins/__init__.py).
16
+ _MODULE_TEMPLATES = {
17
+ "builtin": "code_puppy.plugins.{name}.register_callbacks",
18
+ "user": "{name}.register_callbacks",
19
+ "project": "project_plugins.{name}.register_callbacks",
20
+ }
21
+
22
+
23
+ def _resolve_module(name: str, tier: str):
24
+ """Return the loaded ``register_callbacks`` module for *name*/*tier*.
25
+
26
+ Returns ``None`` if the module can't be found in ``sys.modules`` (e.g. a
27
+ plugin that loaded via a bare ``__init__.py`` fallback).
28
+ """
29
+ template = _MODULE_TEMPLATES.get(tier)
30
+ if not template:
31
+ return None
32
+ return sys.modules.get(template.format(name=name))
33
+
34
+
35
+ def get_description(name: str, tier: str) -> Optional[str]:
36
+ """Return the first paragraph of the plugin's module docstring, or ``None``."""
37
+ module = _resolve_module(name, tier)
38
+ doc = getattr(module, "__doc__", None) if module else None
39
+ if not doc:
40
+ return None
41
+ first_para = doc.strip().split("\n\n", 1)[0]
42
+ return " ".join(line.strip() for line in first_para.splitlines()).strip() or None
43
+
44
+
45
+ def get_file_path(name: str, tier: str) -> Optional[str]:
46
+ """Return the on-disk path of the plugin's ``register_callbacks`` module."""
47
+ module = _resolve_module(name, tier)
48
+ return getattr(module, "__file__", None) if module else None
49
+
50
+
51
+ def get_hooks(name: str) -> List[str]:
52
+ """Return the sorted lifecycle phases *name* registered callbacks for.
53
+
54
+ Disabled plugins still appear (``include_disabled=True``), so the preview
55
+ shows what a plugin would hook into once re-enabled.
56
+ """
57
+ from code_puppy.callbacks import PhaseType, get_callback_owner, get_callbacks
58
+
59
+ phases = [
60
+ phase
61
+ for phase in get_args(PhaseType)
62
+ if any(
63
+ get_callback_owner(func) == name
64
+ for func in get_callbacks(phase, include_disabled=True)
65
+ )
66
+ ]
67
+ return sorted(phases)
@@ -0,0 +1,173 @@
1
+ """Pure text-fragment helpers for the /plugins menu detail pane.
2
+
3
+ Extracted from ``plugins_menu.py`` to keep that file under the 600-line cap and
4
+ because these helpers are pure (no class state, no I/O) and independently
5
+ unit-testable. They all operate on prompt_toolkit's ``(style, text)`` fragment
6
+ tuples or raw strings.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import List, Tuple
12
+
13
+ from prompt_toolkit.utils import get_cwidth
14
+
15
+ Fragments = List[Tuple[str, str]]
16
+
17
+
18
+ def count_lines(fragments: Fragments) -> int:
19
+ """Number of logical (newline-delimited) lines in *fragments*."""
20
+ return sum(text.count("\n") for _, text in fragments)
21
+
22
+
23
+ def drop_leading_lines(fragments: Fragments, n: int) -> Fragments:
24
+ """Return *fragments* with the first *n* logical lines removed."""
25
+ if n <= 0:
26
+ return list(fragments)
27
+ out: Fragments = []
28
+ seen = 0
29
+ for style, text in fragments:
30
+ if seen >= n:
31
+ out.append((style, text))
32
+ continue
33
+ pos = 0
34
+ while seen < n:
35
+ nl = text.find("\n", pos)
36
+ if nl == -1:
37
+ pos = len(text)
38
+ break
39
+ seen += 1
40
+ pos = nl + 1
41
+ remainder = text[pos:]
42
+ if seen >= n and remainder:
43
+ out.append((style, remainder))
44
+ return out
45
+
46
+
47
+ # ``get_cwidth`` undercounts emoji that terminals render as two cells; force
48
+ # affected ranges to width 2 so padding cannot bleed into the divider.
49
+ _EMOJI_RANGES: tuple[tuple[int, int], ...] = (
50
+ (0x2600, 0x27BF), # Misc Symbols + Dingbats
51
+ (
52
+ 0x1F000,
53
+ 0x1FFFF,
54
+ ), # Emoji blocks (Misc Symbols & Pictographs through Symbols & Pictographs Ext-A)
55
+ )
56
+
57
+
58
+ def _is_emoji(cp: int) -> bool:
59
+ return any(lo <= cp <= hi for lo, hi in _EMOJI_RANGES)
60
+
61
+
62
+ # Codepoints stripped along with emojis: variation selectors (VS1-VS16) and
63
+ # the zero-width joiner. These are meaningless without the emoji they modify.
64
+ _EMOJI_MODIFIERS = frozenset(range(0xFE00, 0xFE10)) | {0x200D}
65
+
66
+
67
+ def strip_emojis(text: str) -> str:
68
+ """Remove emoji codepoints (and their variation selectors / ZWJ) from *text*.
69
+
70
+ Emojis are a known headache in prompt_toolkit TUIs: ``get_cwidth`` under-
71
+ reports many of them, terminal renderers disagree about cell width, and
72
+ variation selectors complicate cell math. Rather than play whack-a-mole
73
+ with each new glyph, display content goes through this strip so the menu
74
+ renders plain text only — the emoji-range override in ``cell_width`` stays
75
+ in place as a defensive backstop for any stragglers.
76
+ """
77
+ return "".join(
78
+ ch for ch in text if not _is_emoji(ord(ch)) and ord(ch) not in _EMOJI_MODIFIERS
79
+ )
80
+
81
+
82
+ def strip_emojis_from_fragments(fragments: Fragments) -> Fragments:
83
+ """Apply :func:`strip_emojis` to every fragment's text payload."""
84
+ return [(style, strip_emojis(text)) for style, text in fragments]
85
+
86
+
87
+ def cell_width(text: str) -> int:
88
+ """Sum of terminal cell widths for *text* (emojis count as 2, ASCII as 1).
89
+
90
+ Overrides ``get_cwidth`` for emoji codepoints it under-reports as width 1.
91
+ Variation selectors (VS16 U+FE0F, etc.) and zero-width joiners stay at 0.
92
+ """
93
+ total = 0
94
+ for ch in text:
95
+ w = get_cwidth(ch)
96
+ if w == 1 and _is_emoji(ord(ch)):
97
+ w = 2
98
+ total += w
99
+ return total
100
+
101
+
102
+ def pad_lines_to_cells(fragments: Fragments, width: int) -> Fragments:
103
+ """Pad each logical line in *fragments* to *width* terminal cells.
104
+
105
+ Prompt_toolkit's cell-diff renderer mishandles wide characters (emojis):
106
+ a line containing an emoji occupies one more cell than its Python length,
107
+ and when content shifts the diff can leave the emoji's "second half" cell
108
+ frozen with stale glyphs. By measuring width in real terminal cells and
109
+ explicitly writing trailing spaces to every column, we sidestep the
110
+ renderer's wide-char tracking entirely.
111
+ """
112
+ if width <= 0:
113
+ return list(fragments)
114
+ out: Fragments = []
115
+ cells = 0
116
+ for style, text in fragments:
117
+ i = 0
118
+ while True:
119
+ nl = text.find("\n", i)
120
+ if nl == -1:
121
+ segment = text[i:]
122
+ if segment:
123
+ out.append((style, segment))
124
+ cells += cell_width(segment)
125
+ break
126
+ segment = text[i:nl]
127
+ if segment:
128
+ out.append((style, segment))
129
+ cells += cell_width(segment)
130
+ if cells < width:
131
+ out.append(("", " " * (width - cells)))
132
+ out.append(("", "\n"))
133
+ cells = 0
134
+ i = nl + 1
135
+ if 0 < cells < width:
136
+ out.append(("", " " * (width - cells)))
137
+ return out
138
+
139
+
140
+ def wrap_text(body: str, width: int) -> List[str]:
141
+ """Wrap a single string to ``width`` columns without dropping characters.
142
+
143
+ Mirrors inspect_history's ``_wrap_one_line``: prefer breaking after the
144
+ last space in each window so words stay whole, hard-break when there is no
145
+ usable space (long paths, etc.). ``"".join(result) == body`` always holds.
146
+ """
147
+ if width <= 0 or len(body) <= width:
148
+ return [body]
149
+ pieces: List[str] = []
150
+ remaining = body
151
+ while len(remaining) > width:
152
+ window = remaining[:width]
153
+ cut = window.rfind(" ")
154
+ if cut <= 0:
155
+ cut = width
156
+ else:
157
+ cut += 1
158
+ pieces.append(remaining[:cut])
159
+ remaining = remaining[cut:]
160
+ pieces.append(remaining)
161
+ return pieces
162
+
163
+
164
+ __all__ = [
165
+ "Fragments",
166
+ "cell_width",
167
+ "count_lines",
168
+ "drop_leading_lines",
169
+ "pad_lines_to_cells",
170
+ "strip_emojis",
171
+ "strip_emojis_from_fragments",
172
+ "wrap_text",
173
+ ]