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.
- code_puppy_core_plugins/__init__.py +5 -0
- code_puppy_core_plugins/acp/README.md +266 -0
- code_puppy_core_plugins/acp/__init__.py +8 -0
- code_puppy_core_plugins/acp/agent.py +494 -0
- code_puppy_core_plugins/acp/bridge.py +309 -0
- code_puppy_core_plugins/acp/capabilities.py +73 -0
- code_puppy_core_plugins/acp/commands.py +116 -0
- code_puppy_core_plugins/acp/content.py +115 -0
- code_puppy_core_plugins/acp/io_delegation.py +231 -0
- code_puppy_core_plugins/acp/mcp_config.py +66 -0
- code_puppy_core_plugins/acp/permissions.py +159 -0
- code_puppy_core_plugins/acp/persistence.py +197 -0
- code_puppy_core_plugins/acp/register_callbacks.py +110 -0
- code_puppy_core_plugins/acp/replay.py +114 -0
- code_puppy_core_plugins/acp/session.py +329 -0
- code_puppy_core_plugins/acp/session_config.py +179 -0
- code_puppy_core_plugins/acp/state.py +135 -0
- code_puppy_core_plugins/agent_skills/__init__.py +22 -0
- code_puppy_core_plugins/agent_skills/config.py +208 -0
- code_puppy_core_plugins/agent_skills/discovery.py +337 -0
- code_puppy_core_plugins/agent_skills/downloader.py +392 -0
- code_puppy_core_plugins/agent_skills/enabled_skills.py +82 -0
- code_puppy_core_plugins/agent_skills/installer.py +22 -0
- code_puppy_core_plugins/agent_skills/metadata.py +219 -0
- code_puppy_core_plugins/agent_skills/prompt_builder.py +49 -0
- code_puppy_core_plugins/agent_skills/provider.py +52 -0
- code_puppy_core_plugins/agent_skills/register_callbacks.py +309 -0
- code_puppy_core_plugins/agent_skills/remote_catalog.py +322 -0
- code_puppy_core_plugins/agent_skills/skill_catalog.py +257 -0
- code_puppy_core_plugins/agent_skills/skill_commands.py +102 -0
- code_puppy_core_plugins/agent_skills/skills_install_menu.py +698 -0
- code_puppy_core_plugins/agent_skills/skills_menu.py +794 -0
- code_puppy_core_plugins/aws_bedrock/__init__.py +14 -0
- code_puppy_core_plugins/aws_bedrock/config.py +101 -0
- code_puppy_core_plugins/aws_bedrock/register_callbacks.py +243 -0
- code_puppy_core_plugins/aws_bedrock/utils.py +186 -0
- code_puppy_core_plugins/azure_foundry/README.md +238 -0
- code_puppy_core_plugins/azure_foundry/__init__.py +15 -0
- code_puppy_core_plugins/azure_foundry/config.py +126 -0
- code_puppy_core_plugins/azure_foundry/discovery.py +189 -0
- code_puppy_core_plugins/azure_foundry/register_callbacks.py +506 -0
- code_puppy_core_plugins/azure_foundry/token.py +182 -0
- code_puppy_core_plugins/azure_foundry/utils.py +375 -0
- code_puppy_core_plugins/btw/__init__.py +1 -0
- code_puppy_core_plugins/btw/inline_view.py +135 -0
- code_puppy_core_plugins/btw/register_callbacks.py +106 -0
- code_puppy_core_plugins/btw/side_query.py +91 -0
- code_puppy_core_plugins/chatgpt_oauth/IMAGEGEN_SKILL.md +83 -0
- code_puppy_core_plugins/chatgpt_oauth/__init__.py +8 -0
- code_puppy_core_plugins/chatgpt_oauth/config.py +52 -0
- code_puppy_core_plugins/chatgpt_oauth/image_generation.py +253 -0
- code_puppy_core_plugins/chatgpt_oauth/image_tool.py +70 -0
- code_puppy_core_plugins/chatgpt_oauth/oauth_flow.py +461 -0
- code_puppy_core_plugins/chatgpt_oauth/register_callbacks.py +288 -0
- code_puppy_core_plugins/chatgpt_oauth/test_plugin.py +663 -0
- code_puppy_core_plugins/chatgpt_oauth/usage.py +113 -0
- code_puppy_core_plugins/chatgpt_oauth/utils.py +534 -0
- code_puppy_core_plugins/claude_code_hooks/__init__.py +1 -0
- code_puppy_core_plugins/claude_code_hooks/config.py +137 -0
- code_puppy_core_plugins/claude_code_hooks/register_callbacks.py +383 -0
- code_puppy_core_plugins/claude_code_oauth/README.md +167 -0
- code_puppy_core_plugins/claude_code_oauth/SETUP.md +93 -0
- code_puppy_core_plugins/claude_code_oauth/__init__.py +25 -0
- code_puppy_core_plugins/claude_code_oauth/config.py +59 -0
- code_puppy_core_plugins/claude_code_oauth/fast_mode.py +122 -0
- code_puppy_core_plugins/claude_code_oauth/prompt_handler.py +64 -0
- code_puppy_core_plugins/claude_code_oauth/register_callbacks.py +719 -0
- code_puppy_core_plugins/claude_code_oauth/test_plugin.py +285 -0
- code_puppy_core_plugins/claude_code_oauth/token_refresh_heartbeat.py +240 -0
- code_puppy_core_plugins/claude_code_oauth/utils.py +663 -0
- code_puppy_core_plugins/code_puppy_agent/AGENTS_AND_TOOLS.md +155 -0
- code_puppy_core_plugins/code_puppy_agent/MODELS_AND_MCP.md +104 -0
- code_puppy_core_plugins/code_puppy_agent/PLUGINS_AND_CALLBACKS.md +147 -0
- code_puppy_core_plugins/code_puppy_agent/SESSIONS_AND_HISTORY.md +39 -0
- code_puppy_core_plugins/code_puppy_agent/SKILL.md +180 -0
- code_puppy_core_plugins/code_puppy_agent/SKILLS_SYSTEM.md +136 -0
- code_puppy_core_plugins/code_puppy_agent/SYSTEM_PROMPT_CONFIG_AND_I18N.md +160 -0
- code_puppy_core_plugins/code_puppy_agent/__init__.py +5 -0
- code_puppy_core_plugins/code_puppy_agent/register_callbacks.py +46 -0
- code_puppy_core_plugins/computer_use/NativeCapture.swift +123 -0
- code_puppy_core_plugins/computer_use/README.md +109 -0
- code_puppy_core_plugins/computer_use/__init__.py +1 -0
- code_puppy_core_plugins/computer_use/accessibility.py +163 -0
- code_puppy_core_plugins/computer_use/activation.py +75 -0
- code_puppy_core_plugins/computer_use/backend.py +584 -0
- code_puppy_core_plugins/computer_use/backend_types.py +5 -0
- code_puppy_core_plugins/computer_use/capture.py +110 -0
- code_puppy_core_plugins/computer_use/commands.py +62 -0
- code_puppy_core_plugins/computer_use/geometry.py +81 -0
- code_puppy_core_plugins/computer_use/inline_image.py +56 -0
- code_puppy_core_plugins/computer_use/keycodes.py +47 -0
- code_puppy_core_plugins/computer_use/policy.py +144 -0
- code_puppy_core_plugins/computer_use/register_callbacks.py +96 -0
- code_puppy_core_plugins/computer_use/safety.py +18 -0
- code_puppy_core_plugins/computer_use/settle.py +64 -0
- code_puppy_core_plugins/computer_use/state.py +86 -0
- code_puppy_core_plugins/computer_use/tools.py +469 -0
- code_puppy_core_plugins/context_indicator/__init__.py +14 -0
- code_puppy_core_plugins/context_indicator/register_callbacks.py +249 -0
- code_puppy_core_plugins/context_indicator/usage.py +40 -0
- code_puppy_core_plugins/copilot_auth/__init__.py +11 -0
- code_puppy_core_plugins/copilot_auth/config.py +93 -0
- code_puppy_core_plugins/copilot_auth/reasoning_client.py +409 -0
- code_puppy_core_plugins/copilot_auth/register_callbacks.py +460 -0
- code_puppy_core_plugins/copilot_auth/utils.py +587 -0
- code_puppy_core_plugins/customizable_commands/__init__.py +0 -0
- code_puppy_core_plugins/customizable_commands/register_callbacks.py +464 -0
- code_puppy_core_plugins/dbos_durable_exec/__init__.py +1 -0
- code_puppy_core_plugins/dbos_durable_exec/cancel.py +15 -0
- code_puppy_core_plugins/dbos_durable_exec/commands.py +31 -0
- code_puppy_core_plugins/dbos_durable_exec/config.py +31 -0
- code_puppy_core_plugins/dbos_durable_exec/lifecycle.py +83 -0
- code_puppy_core_plugins/dbos_durable_exec/register_callbacks.py +45 -0
- code_puppy_core_plugins/dbos_durable_exec/runtime.py +65 -0
- code_puppy_core_plugins/dbos_durable_exec/startup_lock.py +155 -0
- code_puppy_core_plugins/dbos_durable_exec/workflow_ids.py +16 -0
- code_puppy_core_plugins/dbos_durable_exec/wrapper.py +58 -0
- code_puppy_core_plugins/destructive_command_guard/AGENTS.md +79 -0
- code_puppy_core_plugins/destructive_command_guard/__init__.py +14 -0
- code_puppy_core_plugins/destructive_command_guard/detector.py +374 -0
- code_puppy_core_plugins/destructive_command_guard/register_callbacks.py +165 -0
- code_puppy_core_plugins/emoji_filter/__init__.py +12 -0
- code_puppy_core_plugins/emoji_filter/config.py +26 -0
- code_puppy_core_plugins/emoji_filter/register_callbacks.py +238 -0
- code_puppy_core_plugins/emoji_filter/stripper.py +45 -0
- code_puppy_core_plugins/empty_html_comment_filter/register_callbacks.py +116 -0
- code_puppy_core_plugins/example_custom_command/README.md +328 -0
- code_puppy_core_plugins/example_custom_command/register_callbacks.py +66 -0
- code_puppy_core_plugins/file_permission_handler/__init__.py +4 -0
- code_puppy_core_plugins/file_permission_handler/register_callbacks.py +575 -0
- code_puppy_core_plugins/flux_bootstrap/__init__.py +5 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/_docs/README.md +173 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/_docs/cheatsheet.md +86 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/_docs/pipeline.md +167 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/_docs/synopsis.md +392 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/about.md +51 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/address-feedback.md +129 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/ask.md +211 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/aug.md +181 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/auto-pilot.md +71 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/cheatsheet.md +16 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/commit.md +103 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/config.md +108 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/create-pr.md +195 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/exec.md +221 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/new.md +151 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/qa.md +208 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/rebase.md +180 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/review.md +304 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/split.md +120 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/squash-commits.md +179 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/status.md +46 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/tests.md +155 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/scripts/flux_about.py +154 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/scripts/flux_cheatsheet.py +248 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/scripts/flux_status.py +345 -0
- code_puppy_core_plugins/flux_bootstrap/installer.py +256 -0
- code_puppy_core_plugins/flux_bootstrap/register_callbacks.py +71 -0
- code_puppy_core_plugins/force_push_guard/__init__.py +5 -0
- code_puppy_core_plugins/force_push_guard/detector.py +96 -0
- code_puppy_core_plugins/force_push_guard/register_callbacks.py +161 -0
- code_puppy_core_plugins/fork/__init__.py +7 -0
- code_puppy_core_plugins/fork/register_callbacks.py +445 -0
- code_puppy_core_plugins/frontend_emitter/__init__.py +25 -0
- code_puppy_core_plugins/frontend_emitter/emitter.py +249 -0
- code_puppy_core_plugins/frontend_emitter/register_callbacks.py +412 -0
- code_puppy_core_plugins/frontend_emitter/session_context.py +50 -0
- code_puppy_core_plugins/grok_oauth/__init__.py +6 -0
- code_puppy_core_plugins/grok_oauth/config.py +49 -0
- code_puppy_core_plugins/grok_oauth/oauth_flow.py +213 -0
- code_puppy_core_plugins/grok_oauth/register_callbacks.py +132 -0
- code_puppy_core_plugins/grok_oauth/utils.py +195 -0
- code_puppy_core_plugins/herdr/README.md +168 -0
- code_puppy_core_plugins/herdr/__init__.py +9 -0
- code_puppy_core_plugins/herdr/client.py +339 -0
- code_puppy_core_plugins/herdr/register_callbacks.py +140 -0
- code_puppy_core_plugins/herdr/reporter.py +212 -0
- code_puppy_core_plugins/herdr/smoke.py +143 -0
- code_puppy_core_plugins/herdr/sources.py +133 -0
- code_puppy_core_plugins/hook_creator/__init__.py +1 -0
- code_puppy_core_plugins/hook_creator/register_callbacks.py +33 -0
- code_puppy_core_plugins/hook_manager/__init__.py +1 -0
- code_puppy_core_plugins/hook_manager/config.py +290 -0
- code_puppy_core_plugins/hook_manager/hooks_menu.py +571 -0
- code_puppy_core_plugins/hook_manager/register_callbacks.py +227 -0
- code_puppy_core_plugins/meta_oauth/__init__.py +1 -0
- code_puppy_core_plugins/meta_oauth/config.py +55 -0
- code_puppy_core_plugins/meta_oauth/oauth_flow.py +109 -0
- code_puppy_core_plugins/meta_oauth/register_callbacks.py +154 -0
- code_puppy_core_plugins/meta_oauth/utils.py +252 -0
- code_puppy_core_plugins/namespace_skill_search/README.md +118 -0
- code_puppy_core_plugins/namespace_skill_search/__init__.py +15 -0
- code_puppy_core_plugins/namespace_skill_search/namespaces.py +125 -0
- code_puppy_core_plugins/namespace_skill_search/register_callbacks.py +99 -0
- code_puppy_core_plugins/namespace_skill_search/search_tool.py +158 -0
- code_puppy_core_plugins/no_tools/__init__.py +0 -0
- code_puppy_core_plugins/no_tools/register_callbacks.py +42 -0
- code_puppy_core_plugins/oauth_pasteback.py +110 -0
- code_puppy_core_plugins/oauth_puppy_html.py +194 -0
- code_puppy_core_plugins/obsidian_agent/README.md +52 -0
- code_puppy_core_plugins/obsidian_agent/__init__.py +8 -0
- code_puppy_core_plugins/obsidian_agent/agent_obsidian.py +147 -0
- code_puppy_core_plugins/obsidian_agent/register_callbacks.py +13 -0
- code_puppy_core_plugins/ollama/__init__.py +1 -0
- code_puppy_core_plugins/ollama/register_callbacks.py +127 -0
- code_puppy_core_plugins/ollama_setup/__init__.py +5 -0
- code_puppy_core_plugins/ollama_setup/completer.py +38 -0
- code_puppy_core_plugins/ollama_setup/register_callbacks.py +419 -0
- code_puppy_core_plugins/plugin_list/__init__.py +0 -0
- code_puppy_core_plugins/plugin_list/plugin_contributions.py +348 -0
- code_puppy_core_plugins/plugin_list/plugin_meta.py +67 -0
- code_puppy_core_plugins/plugin_list/plugin_text_utils.py +173 -0
- code_puppy_core_plugins/plugin_list/plugins_menu.py +455 -0
- code_puppy_core_plugins/plugin_list/plugins_menu_layout.py +194 -0
- code_puppy_core_plugins/plugin_list/plugins_menu_render.py +431 -0
- code_puppy_core_plugins/plugin_list/project_trust_flow.py +162 -0
- code_puppy_core_plugins/plugin_list/register_callbacks.py +281 -0
- code_puppy_core_plugins/pop_command/__init__.py +1 -0
- code_puppy_core_plugins/pop_command/register_callbacks.py +191 -0
- code_puppy_core_plugins/prompt_newline/__init__.py +13 -0
- code_puppy_core_plugins/prompt_newline/config.py +21 -0
- code_puppy_core_plugins/prompt_newline/register_callbacks.py +163 -0
- code_puppy_core_plugins/prune/__init__.py +1 -0
- code_puppy_core_plugins/prune/prune_menu.py +308 -0
- code_puppy_core_plugins/prune/prune_model.py +517 -0
- code_puppy_core_plugins/prune/prune_render.py +354 -0
- code_puppy_core_plugins/prune/register_callbacks.py +388 -0
- code_puppy_core_plugins/puppy_kennel/README.md +203 -0
- code_puppy_core_plugins/puppy_kennel/__init__.py +6 -0
- code_puppy_core_plugins/puppy_kennel/commands.py +206 -0
- code_puppy_core_plugins/puppy_kennel/config.py +36 -0
- code_puppy_core_plugins/puppy_kennel/kennel.py +368 -0
- code_puppy_core_plugins/puppy_kennel/packer.py +152 -0
- code_puppy_core_plugins/puppy_kennel/recorder.py +83 -0
- code_puppy_core_plugins/puppy_kennel/register_callbacks.py +109 -0
- code_puppy_core_plugins/puppy_kennel/retriever.py +28 -0
- code_puppy_core_plugins/puppy_kennel/schema.py +78 -0
- code_puppy_core_plugins/puppy_kennel/state.py +68 -0
- code_puppy_core_plugins/puppy_kennel/tools.py +449 -0
- code_puppy_core_plugins/puppy_kennel/wings.py +110 -0
- code_puppy_core_plugins/puppy_spinner/README.md +93 -0
- code_puppy_core_plugins/puppy_spinner/__init__.py +11 -0
- code_puppy_core_plugins/puppy_spinner/builtin_frames.py +172 -0
- code_puppy_core_plugins/puppy_spinner/commands.py +116 -0
- code_puppy_core_plugins/puppy_spinner/picker.py +379 -0
- code_puppy_core_plugins/puppy_spinner/register_callbacks.py +193 -0
- code_puppy_core_plugins/puppy_spinner/spinners.py +444 -0
- code_puppy_core_plugins/quick_resume/__init__.py +0 -0
- code_puppy_core_plugins/quick_resume/register_callbacks.py +77 -0
- code_puppy_core_plugins/review_pr/__init__.py +1 -0
- code_puppy_core_plugins/review_pr/register_callbacks.py +162 -0
- code_puppy_core_plugins/shell_safety/__init__.py +6 -0
- code_puppy_core_plugins/shell_safety/agent_shell_safety.py +69 -0
- code_puppy_core_plugins/shell_safety/command_cache.py +156 -0
- code_puppy_core_plugins/shell_safety/register_callbacks.py +203 -0
- code_puppy_core_plugins/stack_dump/__init__.py +1 -0
- code_puppy_core_plugins/stack_dump/register_callbacks.py +79 -0
- code_puppy_core_plugins/statusline/__init__.py +14 -0
- code_puppy_core_plugins/statusline/config.py +68 -0
- code_puppy_core_plugins/statusline/payload.py +138 -0
- code_puppy_core_plugins/statusline/prompt_patch.py +76 -0
- code_puppy_core_plugins/statusline/register_callbacks.py +45 -0
- code_puppy_core_plugins/statusline/runner.py +122 -0
- code_puppy_core_plugins/statusline/statusline_command.py +197 -0
- code_puppy_core_plugins/steer_queue/__init__.py +10 -0
- code_puppy_core_plugins/steer_queue/queue_menu.py +444 -0
- code_puppy_core_plugins/steer_queue/register_callbacks.py +138 -0
- code_puppy_core_plugins/subagent_panel/README.md +102 -0
- code_puppy_core_plugins/subagent_panel/__init__.py +24 -0
- code_puppy_core_plugins/subagent_panel/coalesce_patch.py +242 -0
- code_puppy_core_plugins/subagent_panel/panel_render.py +145 -0
- code_puppy_core_plugins/subagent_panel/register_callbacks.py +535 -0
- code_puppy_core_plugins/subagent_panel/resume_repaint.py +112 -0
- code_puppy_core_plugins/subagent_panel/state.py +231 -0
- code_puppy_core_plugins/switch_agent_resume/__init__.py +5 -0
- code_puppy_core_plugins/switch_agent_resume/register_callbacks.py +314 -0
- code_puppy_core_plugins/theme/README.md +86 -0
- code_puppy_core_plugins/theme/__init__.py +14 -0
- code_puppy_core_plugins/theme/bundled_palettes.py +346 -0
- code_puppy_core_plugins/theme/content_styles.py +146 -0
- code_puppy_core_plugins/theme/osc_palette.py +178 -0
- code_puppy_core_plugins/theme/picker.py +358 -0
- code_puppy_core_plugins/theme/prompt_toolkit_theme.py +113 -0
- code_puppy_core_plugins/theme/register_callbacks.py +348 -0
- code_puppy_core_plugins/theme/rich_themes.py +202 -0
- code_puppy_core_plugins/theme/themes.py +720 -0
- code_puppy_core_plugins/timestamp_heartbeat/__init__.py +1 -0
- code_puppy_core_plugins/timestamp_heartbeat/register_callbacks.py +114 -0
- code_puppy_core_plugins/token_ratio_learner/__init__.py +5 -0
- code_puppy_core_plugins/token_ratio_learner/ratios.py +208 -0
- code_puppy_core_plugins/token_ratio_learner/register_callbacks.py +213 -0
- code_puppy_core_plugins/universal_constructor/__init__.py +13 -0
- code_puppy_core_plugins/universal_constructor/models.py +138 -0
- code_puppy_core_plugins/universal_constructor/provider.py +62 -0
- code_puppy_core_plugins/universal_constructor/register_callbacks.py +75 -0
- code_puppy_core_plugins/universal_constructor/registry.py +302 -0
- code_puppy_core_plugins/universal_constructor/sandbox.py +584 -0
- code_puppy_core_plugins/wide_completion_menu/__init__.py +6 -0
- code_puppy_core_plugins/wide_completion_menu/register_callbacks.py +165 -0
- code_puppy_core_plugins/wiggum/__init__.py +1 -0
- code_puppy_core_plugins/wiggum/goal_runs.py +91 -0
- code_puppy_core_plugins/wiggum/judge.py +299 -0
- code_puppy_core_plugins/wiggum/judge_config.py +249 -0
- code_puppy_core_plugins/wiggum/judges_menu.py +814 -0
- code_puppy_core_plugins/wiggum/register_callbacks.py +569 -0
- code_puppy_core_plugins/wiggum/state.py +89 -0
- code_puppy_core_plugins/yolo_cli/__init__.py +1 -0
- code_puppy_core_plugins/yolo_cli/register_callbacks.py +41 -0
- code_puppy_core_plugins-0.0.2.dist-info/METADATA +64 -0
- code_puppy_core_plugins-0.0.2.dist-info/RECORD +313 -0
- code_puppy_core_plugins-0.0.2.dist-info/WHEEL +4 -0
- code_puppy_core_plugins-0.0.2.dist-info/entry_points.txt +55 -0
- 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
|
+
]
|