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,455 @@
|
|
|
1
|
+
"""Interactive TUI for managing plugins.
|
|
2
|
+
|
|
3
|
+
Launch with ``/plugins`` to browse and toggle plugins on/off.
|
|
4
|
+
Built with prompt_toolkit, following the same pattern as the skills menu.
|
|
5
|
+
|
|
6
|
+
This module is the *controller*: terminal sizing, key bindings, app lifecycle,
|
|
7
|
+
and plugin state mutation. All rendering (fragment construction, padding,
|
|
8
|
+
emoji stripping) lives in :mod:`plugins_menu_render` so each module has one
|
|
9
|
+
reason to change.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import shutil
|
|
15
|
+
import sys
|
|
16
|
+
import time
|
|
17
|
+
from typing import List, Optional, Tuple
|
|
18
|
+
|
|
19
|
+
from prompt_toolkit.application import Application
|
|
20
|
+
from prompt_toolkit.application.current import get_app
|
|
21
|
+
from prompt_toolkit.filters import Condition
|
|
22
|
+
from prompt_toolkit.layout import Window
|
|
23
|
+
from prompt_toolkit.layout.controls import FormattedTextControl
|
|
24
|
+
from prompt_toolkit.widgets import TextArea
|
|
25
|
+
|
|
26
|
+
from code_puppy_core_plugins.plugin_list.plugins_menu_layout import (
|
|
27
|
+
build_key_bindings,
|
|
28
|
+
build_layout,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
from code_puppy.command_line.pagination import (
|
|
32
|
+
ensure_visible_page,
|
|
33
|
+
get_total_pages,
|
|
34
|
+
)
|
|
35
|
+
from code_puppy_core_plugins.plugin_list.plugin_text_utils import (
|
|
36
|
+
Fragments,
|
|
37
|
+
count_lines,
|
|
38
|
+
drop_leading_lines,
|
|
39
|
+
)
|
|
40
|
+
from code_puppy_core_plugins.plugin_list.plugins_menu_render import (
|
|
41
|
+
fill_pane,
|
|
42
|
+
render_detail,
|
|
43
|
+
render_list,
|
|
44
|
+
)
|
|
45
|
+
from code_puppy.tools.command_runner import set_awaiting_user_input
|
|
46
|
+
from code_puppy.callbacks import on_prompt_toolkit_style
|
|
47
|
+
|
|
48
|
+
PAGE_SIZE = 20
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class _PluginEntry:
|
|
52
|
+
"""Lightweight struct for a plugin row.
|
|
53
|
+
|
|
54
|
+
``status`` is "loaded" for imported plugins; project plugins held back
|
|
55
|
+
by the trust gate carry their gate status instead ("untrusted",
|
|
56
|
+
"changed", "disabled", "error") so the TUI can show them without
|
|
57
|
+
pretending they're active.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
__slots__ = ("name", "tier", "status")
|
|
61
|
+
|
|
62
|
+
def __init__(self, name: str, tier: str, status: str = "loaded") -> None:
|
|
63
|
+
self.name = name
|
|
64
|
+
self.tier = tier
|
|
65
|
+
self.status = status
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class PluginsMenu:
|
|
69
|
+
"""Interactive TUI for enabling/disabling plugins.
|
|
70
|
+
|
|
71
|
+
The view (``plugins_menu_render``) reads the following attributes on this
|
|
72
|
+
object — keep them stable to avoid breaking the render contract:
|
|
73
|
+
|
|
74
|
+
* ``plugins``, ``disabled``, ``selected_idx``, ``current_page``, ``page_size``
|
|
75
|
+
* ``project_dir`` (posix string or None)
|
|
76
|
+
* ``trust_target``, ``trust_error``, ``trust_feedback`` (popup state)
|
|
77
|
+
* ``_detail_cols``, ``_pane_rows``
|
|
78
|
+
* ``_changed``
|
|
79
|
+
* ``_current()``
|
|
80
|
+
|
|
81
|
+
Entries carry a ``status`` ("loaded" or a trust-gate status) — the
|
|
82
|
+
renderer branches on it, so keep it stable too.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
def __init__(self, focus_plugin: Optional[str] = None) -> None:
|
|
86
|
+
"""*focus_plugin* preselects that plugin; if it's trust-gated, the
|
|
87
|
+
risk-acceptance popup opens immediately (used by ``/plugins enable``
|
|
88
|
+
to bring the user straight to the ceremony)."""
|
|
89
|
+
self.plugins: List[_PluginEntry] = []
|
|
90
|
+
self.disabled: set[str] = set()
|
|
91
|
+
self.project_dir: Optional[str] = None
|
|
92
|
+
self.lock_builtin: bool = False
|
|
93
|
+
self.hidden_builtin_count: int = 0
|
|
94
|
+
|
|
95
|
+
# The trust modal disables list bindings while open, keeping accept-word
|
|
96
|
+
# typing from triggering shortcuts; focus remains on its input.
|
|
97
|
+
self.trust_target: Optional[_PluginEntry] = None
|
|
98
|
+
self.trust_error: str = ""
|
|
99
|
+
self.trust_feedback: str = ""
|
|
100
|
+
self.trust_input: TextArea = TextArea(
|
|
101
|
+
multiline=False, height=1, accept_handler=self._accept_trust
|
|
102
|
+
)
|
|
103
|
+
self._modal_open = Condition(lambda: self.trust_target is not None)
|
|
104
|
+
|
|
105
|
+
self.selected_idx = 0
|
|
106
|
+
self.current_page = 0
|
|
107
|
+
# Mirrors PAGE_SIZE so the renderer's pagination math and the
|
|
108
|
+
# keybindings (which use the module constant) can't drift apart.
|
|
109
|
+
self.page_size = PAGE_SIZE
|
|
110
|
+
self.result: Optional[str] = None
|
|
111
|
+
self._changed = False
|
|
112
|
+
|
|
113
|
+
self.detail_scroll = 0
|
|
114
|
+
|
|
115
|
+
# Track pane height so cell-diff redraws overwrite stale glyphs below
|
|
116
|
+
# short content.
|
|
117
|
+
self._menu_cols = 30
|
|
118
|
+
self._detail_cols = 60
|
|
119
|
+
self._pane_rows = 20
|
|
120
|
+
self._last_size: Tuple[int, int] = (0, 0)
|
|
121
|
+
|
|
122
|
+
self.menu_control: Optional[FormattedTextControl] = None
|
|
123
|
+
self.detail_control: Optional[FormattedTextControl] = None
|
|
124
|
+
self.detail_window: Optional[Window] = None
|
|
125
|
+
|
|
126
|
+
self._refresh_data()
|
|
127
|
+
|
|
128
|
+
if focus_plugin:
|
|
129
|
+
for i, entry in enumerate(self.plugins):
|
|
130
|
+
if entry.name == focus_plugin:
|
|
131
|
+
self.selected_idx = i
|
|
132
|
+
self.current_page = ensure_visible_page(
|
|
133
|
+
i, self.current_page, len(self.plugins), PAGE_SIZE
|
|
134
|
+
)
|
|
135
|
+
if entry.status in ("untrusted", "changed"):
|
|
136
|
+
self._open_trust_modal(entry)
|
|
137
|
+
break
|
|
138
|
+
|
|
139
|
+
# -- data helpers ------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
def _refresh_data(self) -> None:
|
|
142
|
+
from code_puppy.plugins import (
|
|
143
|
+
get_loaded_plugins,
|
|
144
|
+
get_project_plugin_status,
|
|
145
|
+
get_project_plugins_directory,
|
|
146
|
+
)
|
|
147
|
+
from code_puppy.plugins.config import get_disabled_plugins
|
|
148
|
+
from code_puppy.plugins.config import (
|
|
149
|
+
get_lock_builtin_plugins,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
loaded = get_loaded_plugins()
|
|
153
|
+
self.disabled = get_disabled_plugins()
|
|
154
|
+
self.lock_builtin = get_lock_builtin_plugins()
|
|
155
|
+
|
|
156
|
+
project_dir = get_project_plugins_directory()
|
|
157
|
+
self.project_dir = project_dir.as_posix() if project_dir else None
|
|
158
|
+
|
|
159
|
+
entries: List[_PluginEntry] = []
|
|
160
|
+
self.hidden_builtin_count = 0
|
|
161
|
+
for tier in ("builtin", "user", "project"):
|
|
162
|
+
for name in sorted(loaded.get(tier, [])):
|
|
163
|
+
# When locked, builtins are managed/protected — hide them so
|
|
164
|
+
# they can't be toggled (the config layer refuses anyway).
|
|
165
|
+
if self.lock_builtin and tier == "builtin":
|
|
166
|
+
self.hidden_builtin_count += 1
|
|
167
|
+
continue
|
|
168
|
+
entries.append(_PluginEntry(name, tier))
|
|
169
|
+
|
|
170
|
+
# Project plugins held back by the trust gate — shown so Enter can
|
|
171
|
+
# open the ceremony popup on them.
|
|
172
|
+
statuses = get_project_plugin_status()
|
|
173
|
+
shown = {e.name for e in entries if e.tier == "project"}
|
|
174
|
+
for name in sorted(statuses):
|
|
175
|
+
if statuses[name] != "loaded" and name not in shown:
|
|
176
|
+
entries.append(_PluginEntry(name, "project", statuses[name]))
|
|
177
|
+
|
|
178
|
+
self.plugins = entries
|
|
179
|
+
|
|
180
|
+
# Keep selection in range if the list shrank.
|
|
181
|
+
if self.selected_idx >= len(self.plugins):
|
|
182
|
+
self.selected_idx = max(0, len(self.plugins) - 1)
|
|
183
|
+
|
|
184
|
+
def _current(self) -> Optional[_PluginEntry]:
|
|
185
|
+
if 0 <= self.selected_idx < len(self.plugins):
|
|
186
|
+
return self.plugins[self.selected_idx]
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
def _toggle_current(self) -> None:
|
|
190
|
+
entry = self._current()
|
|
191
|
+
if not entry:
|
|
192
|
+
return
|
|
193
|
+
self.trust_feedback = ""
|
|
194
|
+
|
|
195
|
+
if entry.status in ("untrusted", "changed"):
|
|
196
|
+
# Ceremony required — open the risk-acceptance popup.
|
|
197
|
+
self._open_trust_modal(entry)
|
|
198
|
+
return
|
|
199
|
+
if entry.status in ("disabled", "error"):
|
|
200
|
+
# Already trusted; just (re)activate — no ceremony needed.
|
|
201
|
+
from code_puppy_core_plugins.plugin_list.project_trust_flow import (
|
|
202
|
+
activate_project_plugin,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
_ok, message = activate_project_plugin(entry.name)
|
|
206
|
+
self.trust_feedback = message
|
|
207
|
+
self.detail_scroll = 0
|
|
208
|
+
self._refresh_data()
|
|
209
|
+
self.update_display()
|
|
210
|
+
return
|
|
211
|
+
|
|
212
|
+
from code_puppy.plugins.config import set_plugin_disabled
|
|
213
|
+
|
|
214
|
+
is_disabled = entry.name in self.disabled
|
|
215
|
+
changed = set_plugin_disabled(entry.name, not is_disabled)
|
|
216
|
+
if changed:
|
|
217
|
+
self._changed = True
|
|
218
|
+
self.detail_scroll = 0
|
|
219
|
+
self._refresh_data()
|
|
220
|
+
self.update_display()
|
|
221
|
+
|
|
222
|
+
# -- trust popup ---------------------------------------------------------
|
|
223
|
+
|
|
224
|
+
def _open_trust_modal(self, entry: _PluginEntry) -> None:
|
|
225
|
+
self.trust_target = entry
|
|
226
|
+
self.trust_error = ""
|
|
227
|
+
self.trust_input.text = ""
|
|
228
|
+
try:
|
|
229
|
+
get_app().layout.focus(self.trust_input)
|
|
230
|
+
except Exception:
|
|
231
|
+
pass # no running app (tests) — state alone drives the logic
|
|
232
|
+
self.update_display()
|
|
233
|
+
|
|
234
|
+
def _close_trust_modal(self) -> None:
|
|
235
|
+
self.trust_target = None
|
|
236
|
+
self.trust_error = ""
|
|
237
|
+
self.trust_input.text = ""
|
|
238
|
+
try:
|
|
239
|
+
get_app().layout.focus(self.menu_control)
|
|
240
|
+
except Exception:
|
|
241
|
+
pass
|
|
242
|
+
self.update_display()
|
|
243
|
+
|
|
244
|
+
def _accept_trust(self, buff) -> bool:
|
|
245
|
+
"""Accept-handler for the popup input. Returns False to clear the box."""
|
|
246
|
+
entry = self.trust_target
|
|
247
|
+
if entry is None:
|
|
248
|
+
return False
|
|
249
|
+
from code_puppy_core_plugins.plugin_list.project_trust_flow import (
|
|
250
|
+
ACCEPT_WORD,
|
|
251
|
+
grant_trust_and_load,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
if buff.text.strip().lower() != ACCEPT_WORD:
|
|
255
|
+
self.trust_error = (
|
|
256
|
+
f"That isn't '{ACCEPT_WORD}' — type it exactly, or press Esc to cancel."
|
|
257
|
+
)
|
|
258
|
+
self.update_display()
|
|
259
|
+
return False
|
|
260
|
+
|
|
261
|
+
_ok, message = grant_trust_and_load(entry.name)
|
|
262
|
+
self.trust_feedback = message
|
|
263
|
+
self._refresh_data()
|
|
264
|
+
self._close_trust_modal()
|
|
265
|
+
return False
|
|
266
|
+
|
|
267
|
+
# -- render compatibility ---------------------------------------------
|
|
268
|
+
|
|
269
|
+
def _render_list(self) -> Fragments:
|
|
270
|
+
"""Return rendered list fragments for existing tests/callers."""
|
|
271
|
+
return render_list(self)
|
|
272
|
+
|
|
273
|
+
def _render_detail(self) -> Fragments:
|
|
274
|
+
"""Return rendered detail fragments for existing tests/callers."""
|
|
275
|
+
return render_detail(self)
|
|
276
|
+
|
|
277
|
+
# -- display update ----------------------------------------------------
|
|
278
|
+
|
|
279
|
+
def update_display(self) -> None:
|
|
280
|
+
# fill_pane writes every cell of every row — see its docstring for
|
|
281
|
+
# the stale-glyph rationale.
|
|
282
|
+
if self.menu_control:
|
|
283
|
+
self.menu_control.text = fill_pane(
|
|
284
|
+
render_list(self), self._menu_cols, self._pane_rows
|
|
285
|
+
)
|
|
286
|
+
if self.detail_control:
|
|
287
|
+
sliced = drop_leading_lines(render_detail(self), self.detail_scroll)
|
|
288
|
+
self.detail_control.text = fill_pane(
|
|
289
|
+
sliced, self._detail_cols, self._pane_rows
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
def _max_detail_scroll(self) -> int:
|
|
293
|
+
"""Topmost line we may scroll to, keeping a screenful visible."""
|
|
294
|
+
total = count_lines(render_detail(self))
|
|
295
|
+
visible = 1
|
|
296
|
+
if self.detail_window is not None and self.detail_window.render_info:
|
|
297
|
+
visible = max(1, self.detail_window.render_info.window_height)
|
|
298
|
+
return max(0, total - visible)
|
|
299
|
+
|
|
300
|
+
def _scroll_detail(self, delta: int) -> None:
|
|
301
|
+
new = max(0, min(self.detail_scroll + delta, self._max_detail_scroll()))
|
|
302
|
+
if new != self.detail_scroll:
|
|
303
|
+
self.detail_scroll = new
|
|
304
|
+
self.update_display()
|
|
305
|
+
|
|
306
|
+
# -- application -------------------------------------------------------
|
|
307
|
+
|
|
308
|
+
def _measure_terminal(self) -> Tuple[int, int]:
|
|
309
|
+
"""Return (cols, rows) of the current terminal, with sane fallbacks."""
|
|
310
|
+
try:
|
|
311
|
+
size = shutil.get_terminal_size(fallback=(120, 40))
|
|
312
|
+
return max(60, size.columns), max(15, size.lines)
|
|
313
|
+
except Exception:
|
|
314
|
+
return 120, 40
|
|
315
|
+
|
|
316
|
+
def _recompute_dimensions(self) -> bool:
|
|
317
|
+
"""Re-measure the terminal and recompute pane widths.
|
|
318
|
+
|
|
319
|
+
Returns True when the size actually changed. The width-callable
|
|
320
|
+
closures in ``run`` read ``self._menu_cols`` / ``self._detail_cols``
|
|
321
|
+
on every render, so updating these here automatically reflows the
|
|
322
|
+
layout on terminal resize.
|
|
323
|
+
"""
|
|
324
|
+
cols, rows = self._measure_terminal()
|
|
325
|
+
if self._last_size == (cols, rows):
|
|
326
|
+
return False
|
|
327
|
+
self._last_size = (cols, rows)
|
|
328
|
+
# Two side-by-side Frames cost 4 columns of border (1 per side, per
|
|
329
|
+
# frame). Anything more leaves dead space on the right edge.
|
|
330
|
+
usable_cols = max(40, cols - 4)
|
|
331
|
+
# 35% / 65% split, with a minimum so the menu pane is always usable.
|
|
332
|
+
self._menu_cols = max(20, min(40, int(usable_cols * 0.35)))
|
|
333
|
+
self._detail_cols = max(20, usable_cols - self._menu_cols)
|
|
334
|
+
# Reserve 2 rows for the Frame's top + bottom borders.
|
|
335
|
+
self._pane_rows = max(5, rows - 2)
|
|
336
|
+
return True
|
|
337
|
+
|
|
338
|
+
def _set_selection(self, new_idx: int) -> None:
|
|
339
|
+
"""Move selection to *new_idx* (clamped), resetting detail scroll.
|
|
340
|
+
|
|
341
|
+
Single chokepoint for every selection mutation -- ``_move_selection``,
|
|
342
|
+
the jump-to-first/last actions, and the page jumps all funnel through
|
|
343
|
+
here so the "reset detail scroll + keep selection's page visible"
|
|
344
|
+
contract can't drift between callers.
|
|
345
|
+
"""
|
|
346
|
+
if not self.plugins:
|
|
347
|
+
return
|
|
348
|
+
new_idx = max(0, min(new_idx, len(self.plugins) - 1))
|
|
349
|
+
if new_idx == self.selected_idx:
|
|
350
|
+
return
|
|
351
|
+
self.selected_idx = new_idx
|
|
352
|
+
self.detail_scroll = 0
|
|
353
|
+
self.current_page = ensure_visible_page(
|
|
354
|
+
self.selected_idx,
|
|
355
|
+
self.current_page,
|
|
356
|
+
len(self.plugins),
|
|
357
|
+
PAGE_SIZE,
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
def _move_selection(self, delta: int) -> None:
|
|
361
|
+
"""Shift the selection by *delta*, clamped, and keep the page in view."""
|
|
362
|
+
self._set_selection(self.selected_idx + delta)
|
|
363
|
+
|
|
364
|
+
def _change_page(self, delta: int) -> None:
|
|
365
|
+
"""Move the page by *delta* (clamped) and jump selection to its head."""
|
|
366
|
+
total_pages = get_total_pages(len(self.plugins), PAGE_SIZE)
|
|
367
|
+
new_page = max(0, min(self.current_page + delta, total_pages - 1))
|
|
368
|
+
if new_page == self.current_page:
|
|
369
|
+
return
|
|
370
|
+
self.current_page = new_page
|
|
371
|
+
self._set_selection(self.current_page * PAGE_SIZE)
|
|
372
|
+
|
|
373
|
+
def run(self) -> Optional[str]:
|
|
374
|
+
self.menu_control = FormattedTextControl(text="", focusable=True)
|
|
375
|
+
self.detail_control = FormattedTextControl(text="")
|
|
376
|
+
|
|
377
|
+
self._recompute_dimensions()
|
|
378
|
+
|
|
379
|
+
layout = build_layout(self)
|
|
380
|
+
if self.trust_target is not None:
|
|
381
|
+
# Popup pre-opened (focus_plugin ceremony): focus its input now
|
|
382
|
+
# that the layout exists — _open_trust_modal ran before any app.
|
|
383
|
+
try:
|
|
384
|
+
layout.focus(self.trust_input)
|
|
385
|
+
except Exception:
|
|
386
|
+
pass
|
|
387
|
+
kb = build_key_bindings(self)
|
|
388
|
+
|
|
389
|
+
app = Application(
|
|
390
|
+
layout=layout,
|
|
391
|
+
key_bindings=kb,
|
|
392
|
+
full_screen=False,
|
|
393
|
+
mouse_support=False,
|
|
394
|
+
style=on_prompt_toolkit_style(),
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
# Recompute fixed-width wrapping before each render so SIGWINCH updates
|
|
398
|
+
# dimensions and content; the helper is a no-op when size is unchanged.
|
|
399
|
+
def _on_before_render(_app: Application) -> None:
|
|
400
|
+
if self._recompute_dimensions():
|
|
401
|
+
self.update_display()
|
|
402
|
+
|
|
403
|
+
app.before_render += _on_before_render
|
|
404
|
+
|
|
405
|
+
set_awaiting_user_input(True)
|
|
406
|
+
|
|
407
|
+
sys.stdout.write("\033[?1049h") # Enter alternate buffer
|
|
408
|
+
sys.stdout.write("\033[2J\033[H") # Clear and home
|
|
409
|
+
sys.stdout.flush()
|
|
410
|
+
time.sleep(0.05)
|
|
411
|
+
|
|
412
|
+
try:
|
|
413
|
+
self.update_display()
|
|
414
|
+
|
|
415
|
+
sys.stdout.write("\033[2J\033[H")
|
|
416
|
+
sys.stdout.flush()
|
|
417
|
+
|
|
418
|
+
app.run(in_thread=True)
|
|
419
|
+
|
|
420
|
+
finally:
|
|
421
|
+
sys.stdout.write("\033[?1049l") # Exit alternate buffer
|
|
422
|
+
sys.stdout.flush()
|
|
423
|
+
|
|
424
|
+
try:
|
|
425
|
+
import termios
|
|
426
|
+
|
|
427
|
+
termios.tcflush(sys.stdin.fileno(), termios.TCIFLUSH)
|
|
428
|
+
except Exception:
|
|
429
|
+
pass
|
|
430
|
+
|
|
431
|
+
time.sleep(0.1)
|
|
432
|
+
set_awaiting_user_input(False)
|
|
433
|
+
|
|
434
|
+
return self.result
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def run_plugins_menu(focus_plugin: Optional[str] = None) -> Optional[str]:
|
|
438
|
+
"""Entry point: create and run the plugins TUI, return the result.
|
|
439
|
+
|
|
440
|
+
*focus_plugin* preselects a plugin and, when it's trust-gated, opens
|
|
441
|
+
the risk-acceptance popup straight away.
|
|
442
|
+
"""
|
|
443
|
+
from code_puppy.messaging import emit_warning
|
|
444
|
+
|
|
445
|
+
menu = PluginsMenu(focus_plugin=focus_plugin)
|
|
446
|
+
result = menu.run()
|
|
447
|
+
|
|
448
|
+
if menu._changed:
|
|
449
|
+
emit_warning("Restart Code Puppy for plugin changes to take effect.")
|
|
450
|
+
|
|
451
|
+
return result
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
# Re-export for callers that don't want to know about the render split.
|
|
455
|
+
__all__ = ["PluginsMenu", "Fragments", "run_plugins_menu"]
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""Layout + keybinding construction for the /plugins TUI.
|
|
2
|
+
|
|
3
|
+
Split from ``plugins_menu`` the same way ``plugins_menu_render`` was: these
|
|
4
|
+
are pure *construction* helpers that read the menu's state and hand back
|
|
5
|
+
prompt_toolkit objects. No app state lives here.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
from prompt_toolkit.key_binding import KeyBindings
|
|
13
|
+
from prompt_toolkit.layout import (
|
|
14
|
+
ConditionalContainer,
|
|
15
|
+
Dimension,
|
|
16
|
+
Float,
|
|
17
|
+
FloatContainer,
|
|
18
|
+
HSplit,
|
|
19
|
+
Layout,
|
|
20
|
+
VSplit,
|
|
21
|
+
Window,
|
|
22
|
+
)
|
|
23
|
+
from prompt_toolkit.layout.controls import FormattedTextControl
|
|
24
|
+
from prompt_toolkit.widgets import Frame
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING: # pragma: no cover - import-cycle guard
|
|
27
|
+
from code_puppy_core_plugins.plugin_list.plugins_menu import PluginsMenu
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def build_key_bindings(menu: "PluginsMenu") -> KeyBindings:
|
|
31
|
+
"""Wire keys to match the ``inspect_history`` plugin's mental model.
|
|
32
|
+
|
|
33
|
+
Both plugins share the same split-pane shape (list + scrollable
|
|
34
|
+
detail). Keeping the bindings aligned means muscle memory carries
|
|
35
|
+
over. The mental model (lifted from inspect_history):
|
|
36
|
+
|
|
37
|
+
* Left-hand keys (``h``, ``j``) move things UP.
|
|
38
|
+
* Right-hand keys (``k``, ``l``) move things DOWN.
|
|
39
|
+
* ``h``/``l`` (and ``left``/``right``) scroll the *detail* pane.
|
|
40
|
+
* ``j``/``k`` (and ``up``/``down``, ``c-p``/``c-n``) move the
|
|
41
|
+
*selection* in the list.
|
|
42
|
+
* ``pageup``/``pagedown`` page through the list.
|
|
43
|
+
* ``g``/``home`` and ``G``/``end`` jump to first/last.
|
|
44
|
+
"""
|
|
45
|
+
kb = KeyBindings()
|
|
46
|
+
|
|
47
|
+
# Disable list bindings while the trust popup is open so accept-word typing
|
|
48
|
+
# cannot trigger shortcuts.
|
|
49
|
+
no_modal = ~menu._modal_open
|
|
50
|
+
|
|
51
|
+
# -- Selection (j = up, k = down -- inspect_history convention) ---
|
|
52
|
+
@kb.add("up", filter=no_modal)
|
|
53
|
+
@kb.add("c-p", filter=no_modal)
|
|
54
|
+
@kb.add("j", filter=no_modal)
|
|
55
|
+
def _(event):
|
|
56
|
+
menu._move_selection(-1)
|
|
57
|
+
menu.update_display()
|
|
58
|
+
|
|
59
|
+
@kb.add("down", filter=no_modal)
|
|
60
|
+
@kb.add("c-n", filter=no_modal)
|
|
61
|
+
@kb.add("k", filter=no_modal)
|
|
62
|
+
def _(event):
|
|
63
|
+
menu._move_selection(+1)
|
|
64
|
+
menu.update_display()
|
|
65
|
+
|
|
66
|
+
# -- Page through the list -----------------------------------------
|
|
67
|
+
@kb.add("pageup", filter=no_modal)
|
|
68
|
+
def _(event):
|
|
69
|
+
menu._change_page(-1)
|
|
70
|
+
menu.update_display()
|
|
71
|
+
|
|
72
|
+
@kb.add("pagedown", filter=no_modal)
|
|
73
|
+
def _(event):
|
|
74
|
+
menu._change_page(+1)
|
|
75
|
+
menu.update_display()
|
|
76
|
+
|
|
77
|
+
# -- Jump to first / last ------------------------------------------
|
|
78
|
+
@kb.add("home", filter=no_modal)
|
|
79
|
+
@kb.add("g", filter=no_modal)
|
|
80
|
+
def _(event):
|
|
81
|
+
menu._set_selection(0)
|
|
82
|
+
menu.update_display()
|
|
83
|
+
|
|
84
|
+
@kb.add("end", filter=no_modal)
|
|
85
|
+
@kb.add("G", filter=no_modal)
|
|
86
|
+
def _(event):
|
|
87
|
+
menu._set_selection(len(menu.plugins) - 1)
|
|
88
|
+
menu.update_display()
|
|
89
|
+
|
|
90
|
+
# -- Detail pane scroll (h/left = up, l/right = down) --------------
|
|
91
|
+
@kb.add("h", filter=no_modal)
|
|
92
|
+
@kb.add("left", filter=no_modal)
|
|
93
|
+
def _(event):
|
|
94
|
+
menu._scroll_detail(-1)
|
|
95
|
+
|
|
96
|
+
@kb.add("l", filter=no_modal)
|
|
97
|
+
@kb.add("right", filter=no_modal)
|
|
98
|
+
def _(event):
|
|
99
|
+
menu._scroll_detail(+1)
|
|
100
|
+
|
|
101
|
+
# -- Actions / exit ------------------------------------------------
|
|
102
|
+
@kb.add("enter", filter=no_modal)
|
|
103
|
+
def _(event):
|
|
104
|
+
menu._toggle_current()
|
|
105
|
+
menu.result = "changed"
|
|
106
|
+
|
|
107
|
+
@kb.add("q", filter=no_modal)
|
|
108
|
+
@kb.add("escape", filter=no_modal)
|
|
109
|
+
@kb.add("c-c", filter=no_modal)
|
|
110
|
+
def _(event):
|
|
111
|
+
menu.result = "quit"
|
|
112
|
+
event.app.exit()
|
|
113
|
+
|
|
114
|
+
# -- Trust popup: Esc / Ctrl+C cancel (Enter is handled by the
|
|
115
|
+
# focused TextArea's accept_handler) ----------------------------
|
|
116
|
+
@kb.add("escape", filter=menu._modal_open)
|
|
117
|
+
@kb.add("c-c", filter=menu._modal_open)
|
|
118
|
+
def _(event):
|
|
119
|
+
menu._close_trust_modal()
|
|
120
|
+
|
|
121
|
+
return kb
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def build_layout(menu: "PluginsMenu") -> Layout:
|
|
125
|
+
"""Build the side-by-side Plugins / Details layout plus the trust float.
|
|
126
|
+
|
|
127
|
+
Pane widths are *callables* so they track the live terminal: when the
|
|
128
|
+
window is resized, ``_recompute_dimensions`` updates the cached cols
|
|
129
|
+
and these closures hand prompt_toolkit the fresh numbers on the very
|
|
130
|
+
next render. ``wrap_lines=False`` is critical: auto-wrap bleeds
|
|
131
|
+
characters into the divider/border column and leaves stale glyphs on
|
|
132
|
+
redraw. Long lines (description, path) are pre-wrapped by the renderer.
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
def menu_width() -> Dimension:
|
|
136
|
+
return Dimension(min=20, max=menu._menu_cols, preferred=menu._menu_cols)
|
|
137
|
+
|
|
138
|
+
def detail_width() -> Dimension:
|
|
139
|
+
return Dimension(min=20, max=menu._detail_cols, preferred=menu._detail_cols)
|
|
140
|
+
|
|
141
|
+
def pane_height() -> Dimension:
|
|
142
|
+
return Dimension(min=5, max=menu._pane_rows, preferred=menu._pane_rows)
|
|
143
|
+
|
|
144
|
+
menu_window = Window(
|
|
145
|
+
content=menu.menu_control,
|
|
146
|
+
wrap_lines=False,
|
|
147
|
+
width=menu_width,
|
|
148
|
+
height=pane_height,
|
|
149
|
+
)
|
|
150
|
+
detail_window = Window(
|
|
151
|
+
content=menu.detail_control,
|
|
152
|
+
wrap_lines=False,
|
|
153
|
+
width=detail_width,
|
|
154
|
+
height=pane_height,
|
|
155
|
+
)
|
|
156
|
+
menu.detail_window = detail_window
|
|
157
|
+
|
|
158
|
+
menu_frame = Frame(menu_window, title="Plugins")
|
|
159
|
+
detail_frame = Frame(detail_window, title="Details")
|
|
160
|
+
|
|
161
|
+
# Render the centered popup only during the ceremony; its focused TextArea
|
|
162
|
+
# owns printable keys while list shortcuts stay disabled.
|
|
163
|
+
from code_puppy_core_plugins.plugin_list.plugins_menu_render import (
|
|
164
|
+
render_trust_modal,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
def modal_width() -> Dimension:
|
|
168
|
+
cols = min(64, max(40, menu._menu_cols + menu._detail_cols - 8))
|
|
169
|
+
return Dimension(min=40, max=cols, preferred=cols)
|
|
170
|
+
|
|
171
|
+
modal_body = HSplit(
|
|
172
|
+
[
|
|
173
|
+
Window(
|
|
174
|
+
content=FormattedTextControl(lambda: render_trust_modal(menu)),
|
|
175
|
+
wrap_lines=False,
|
|
176
|
+
dont_extend_height=True,
|
|
177
|
+
),
|
|
178
|
+
menu.trust_input,
|
|
179
|
+
],
|
|
180
|
+
width=modal_width,
|
|
181
|
+
)
|
|
182
|
+
modal_float = Float(
|
|
183
|
+
content=ConditionalContainer(
|
|
184
|
+
Frame(modal_body, title="Enable project plugin?"),
|
|
185
|
+
filter=menu._modal_open,
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
return Layout(
|
|
190
|
+
FloatContainer(
|
|
191
|
+
content=VSplit([menu_frame, detail_frame]),
|
|
192
|
+
floats=[modal_float],
|
|
193
|
+
)
|
|
194
|
+
)
|