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