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,172 @@
|
|
|
1
|
+
"""Frame data for the catalogue's cli-spinners-style builtins.
|
|
2
|
+
|
|
3
|
+
GENERATED data module -- every non-ASCII char is escape-spelled (repo
|
|
4
|
+
emoji filter), which is also why this file is data-only: hand-editing
|
|
5
|
+
escaped braille is nobody's idea of fun. Intervals are intentionally
|
|
6
|
+
absent; ``spinners.py`` applies one normalized speed to all of these.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
# Frame rows are packed several-per-line; one-per-line would blow the
|
|
10
|
+
# 600-line cap. The bare marker below must stay bare -- ruff ignores
|
|
11
|
+
# 'fmt: off' comments that have trailing prose.
|
|
12
|
+
# fmt: off
|
|
13
|
+
# name -> (frames, description)
|
|
14
|
+
EXTRA_SPECS = {
|
|
15
|
+
"dotsWide": (
|
|
16
|
+
(
|
|
17
|
+
"\u2809\u2809", "\u2808\u2819", "\u2800\u2839", "\u2800\u28b8", "\u2800\u28f0",
|
|
18
|
+
"\u2880\u28e0", "\u28c0\u28c0", "\u28c4\u2840", "\u28c6\u2800", "\u2847\u2800",
|
|
19
|
+
"\u280f\u2800", "\u280b\u2801",
|
|
20
|
+
),
|
|
21
|
+
"a two-cell braille orbit",
|
|
22
|
+
),
|
|
23
|
+
"dots8Bit": (
|
|
24
|
+
(
|
|
25
|
+
"\u2800", "\u2801", "\u2802", "\u2803", "\u2804", "\u2805", "\u2806", "\u2807",
|
|
26
|
+
"\u2840", "\u2841", "\u2842", "\u2843", "\u2844", "\u2845", "\u2846", "\u2847",
|
|
27
|
+
"\u2808", "\u2809", "\u280a", "\u280b", "\u280c", "\u280d", "\u280e", "\u280f",
|
|
28
|
+
"\u2848", "\u2849", "\u284a", "\u284b", "\u284c", "\u284d", "\u284e", "\u284f",
|
|
29
|
+
"\u2810", "\u2811", "\u2812", "\u2813", "\u2814", "\u2815", "\u2816", "\u2817",
|
|
30
|
+
"\u2850", "\u2851", "\u2852", "\u2853", "\u2854", "\u2855", "\u2856", "\u2857",
|
|
31
|
+
"\u2818", "\u2819", "\u281a", "\u281b", "\u281c", "\u281d", "\u281e", "\u281f",
|
|
32
|
+
"\u2858", "\u2859", "\u285a", "\u285b", "\u285c", "\u285d", "\u285e", "\u285f",
|
|
33
|
+
"\u2820", "\u2821", "\u2822", "\u2823", "\u2824", "\u2825", "\u2826", "\u2827",
|
|
34
|
+
"\u2860", "\u2861", "\u2862", "\u2863", "\u2864", "\u2865", "\u2866", "\u2867",
|
|
35
|
+
"\u2828", "\u2829", "\u282a", "\u282b", "\u282c", "\u282d", "\u282e", "\u282f",
|
|
36
|
+
"\u2868", "\u2869", "\u286a", "\u286b", "\u286c", "\u286d", "\u286e", "\u286f",
|
|
37
|
+
"\u2830", "\u2831", "\u2832", "\u2833", "\u2834", "\u2835", "\u2836", "\u2837",
|
|
38
|
+
"\u2870", "\u2871", "\u2872", "\u2873", "\u2874", "\u2875", "\u2876", "\u2877",
|
|
39
|
+
"\u2838", "\u2839", "\u283a", "\u283b", "\u283c", "\u283d", "\u283e", "\u283f",
|
|
40
|
+
"\u2878", "\u2879", "\u287a", "\u287b", "\u287c", "\u287d", "\u287e", "\u287f",
|
|
41
|
+
"\u2880", "\u2881", "\u2882", "\u2883", "\u2884", "\u2885", "\u2886", "\u2887",
|
|
42
|
+
"\u28c0", "\u28c1", "\u28c2", "\u28c3", "\u28c4", "\u28c5", "\u28c6", "\u28c7",
|
|
43
|
+
"\u2888", "\u2889", "\u288a", "\u288b", "\u288c", "\u288d", "\u288e", "\u288f",
|
|
44
|
+
"\u28c8", "\u28c9", "\u28ca", "\u28cb", "\u28cc", "\u28cd", "\u28ce", "\u28cf",
|
|
45
|
+
"\u2890", "\u2891", "\u2892", "\u2893", "\u2894", "\u2895", "\u2896", "\u2897",
|
|
46
|
+
"\u28d0", "\u28d1", "\u28d2", "\u28d3", "\u28d4", "\u28d5", "\u28d6", "\u28d7",
|
|
47
|
+
"\u2898", "\u2899", "\u289a", "\u289b", "\u289c", "\u289d", "\u289e", "\u289f",
|
|
48
|
+
"\u28d8", "\u28d9", "\u28da", "\u28db", "\u28dc", "\u28dd", "\u28de", "\u28df",
|
|
49
|
+
"\u28a0", "\u28a1", "\u28a2", "\u28a3", "\u28a4", "\u28a5", "\u28a6", "\u28a7",
|
|
50
|
+
"\u28e0", "\u28e1", "\u28e2", "\u28e3", "\u28e4", "\u28e5", "\u28e6", "\u28e7",
|
|
51
|
+
"\u28a8", "\u28a9", "\u28aa", "\u28ab", "\u28ac", "\u28ad", "\u28ae", "\u28af",
|
|
52
|
+
"\u28e8", "\u28e9", "\u28ea", "\u28eb", "\u28ec", "\u28ed", "\u28ee", "\u28ef",
|
|
53
|
+
"\u28b0", "\u28b1", "\u28b2", "\u28b3", "\u28b4", "\u28b5", "\u28b6", "\u28b7",
|
|
54
|
+
"\u28f0", "\u28f1", "\u28f2", "\u28f3", "\u28f4", "\u28f5", "\u28f6", "\u28f7",
|
|
55
|
+
"\u28b8", "\u28b9", "\u28ba", "\u28bb", "\u28bc", "\u28bd", "\u28be", "\u28bf",
|
|
56
|
+
"\u28f8", "\u28f9", "\u28fa", "\u28fb", "\u28fc", "\u28fd", "\u28fe", "\u28ff",
|
|
57
|
+
),
|
|
58
|
+
"all 256 braille patterns",
|
|
59
|
+
),
|
|
60
|
+
"dotsCircle": (
|
|
61
|
+
(
|
|
62
|
+
"\u288e ", "\u280e\u2801", "\u280a\u2811", "\u2808\u2831", " \u2871",
|
|
63
|
+
"\u2880\u2870", "\u2884\u2860", "\u2886\u2840",
|
|
64
|
+
),
|
|
65
|
+
"a braille ring chase",
|
|
66
|
+
),
|
|
67
|
+
"sand": (
|
|
68
|
+
(
|
|
69
|
+
"\u2801", "\u2802", "\u2804", "\u2840", "\u2848", "\u2850", "\u2860", "\u28c0",
|
|
70
|
+
"\u28c1", "\u28c2", "\u28c4", "\u28cc", "\u28d4", "\u28e4", "\u28e5", "\u28e6",
|
|
71
|
+
"\u28ee", "\u28f6", "\u28f7", "\u28ff", "\u287f", "\u283f", "\u289f", "\u281f",
|
|
72
|
+
"\u285b", "\u281b", "\u282b", "\u288b", "\u280b", "\u280d", "\u2849", "\u2809",
|
|
73
|
+
"\u2811", "\u2821", "\u2881",
|
|
74
|
+
),
|
|
75
|
+
"grains trickling through",
|
|
76
|
+
),
|
|
77
|
+
"growVertical": (
|
|
78
|
+
(
|
|
79
|
+
"\u2581", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2586", "\u2585",
|
|
80
|
+
"\u2584", "\u2583",
|
|
81
|
+
),
|
|
82
|
+
"a bar breathing up and down",
|
|
83
|
+
),
|
|
84
|
+
"growHorizontal": (
|
|
85
|
+
(
|
|
86
|
+
"\u258f", "\u258e", "\u258d", "\u258c", "\u258b", "\u258a", "\u2589", "\u258a",
|
|
87
|
+
"\u258b", "\u258c", "\u258d", "\u258e",
|
|
88
|
+
),
|
|
89
|
+
"a bar breathing side to side",
|
|
90
|
+
),
|
|
91
|
+
"noise": (
|
|
92
|
+
(
|
|
93
|
+
"\u2593", "\u2592", "\u2591",
|
|
94
|
+
),
|
|
95
|
+
"static from an old TV",
|
|
96
|
+
),
|
|
97
|
+
"binary": (
|
|
98
|
+
(
|
|
99
|
+
"010010", "001100", "100101", "111010", "111101", "010111", "101011", "111000",
|
|
100
|
+
"110011", "110101",
|
|
101
|
+
),
|
|
102
|
+
"streaming ones and zeros",
|
|
103
|
+
),
|
|
104
|
+
"chevrons": (
|
|
105
|
+
(
|
|
106
|
+
"\u25b9\u25b9\u25b9\u25b9\u25b9", "\u25b8\u25b9\u25b9\u25b9\u25b9",
|
|
107
|
+
"\u25b9\u25b8\u25b9\u25b9\u25b9", "\u25b9\u25b9\u25b8\u25b9\u25b9",
|
|
108
|
+
"\u25b9\u25b9\u25b9\u25b8\u25b9", "\u25b9\u25b9\u25b9\u25b9\u25b8",
|
|
109
|
+
),
|
|
110
|
+
"a pulse of chevrons",
|
|
111
|
+
),
|
|
112
|
+
"bouncingBar": (
|
|
113
|
+
(
|
|
114
|
+
"[ ]", "[= ]", "[== ]", "[=== ]", "[====]", "[ ===]", "[ ==]", "[ =]",
|
|
115
|
+
"[ ]", "[ =]", "[ ==]", "[ ===]", "[====]", "[=== ]", "[== ]", "[= ]",
|
|
116
|
+
),
|
|
117
|
+
"old-school loading-bar ping-pong",
|
|
118
|
+
),
|
|
119
|
+
"bouncingBall": (
|
|
120
|
+
(
|
|
121
|
+
"( \u25cf )", "( \u25cf )", "( \u25cf )", "( \u25cf )", "( \u25cf)",
|
|
122
|
+
"( \u25cf )", "( \u25cf )", "( \u25cf )", "( \u25cf )", "(\u25cf )",
|
|
123
|
+
),
|
|
124
|
+
"a ball on kennel patrol",
|
|
125
|
+
),
|
|
126
|
+
"pong": (
|
|
127
|
+
(
|
|
128
|
+
"\u2590\u2802 \u258c", "\u2590\u2808 \u258c",
|
|
129
|
+
"\u2590 \u2802 \u258c", "\u2590 \u2820 \u258c",
|
|
130
|
+
"\u2590 \u2840 \u258c", "\u2590 \u2820 \u258c",
|
|
131
|
+
"\u2590 \u2802 \u258c", "\u2590 \u2808 \u258c",
|
|
132
|
+
"\u2590 \u2802 \u258c", "\u2590 \u2820 \u258c",
|
|
133
|
+
"\u2590 \u2840 \u258c", "\u2590 \u2820 \u258c",
|
|
134
|
+
"\u2590 \u2802 \u258c", "\u2590 \u2808 \u258c",
|
|
135
|
+
"\u2590 \u2802\u258c", "\u2590 \u2820\u258c",
|
|
136
|
+
"\u2590 \u2840\u258c", "\u2590 \u2820 \u258c",
|
|
137
|
+
"\u2590 \u2802 \u258c", "\u2590 \u2808 \u258c",
|
|
138
|
+
"\u2590 \u2802 \u258c", "\u2590 \u2820 \u258c",
|
|
139
|
+
"\u2590 \u2840 \u258c", "\u2590 \u2820 \u258c",
|
|
140
|
+
"\u2590 \u2802 \u258c", "\u2590 \u2808 \u258c",
|
|
141
|
+
"\u2590 \u2802 \u258c", "\u2590 \u2820 \u258c",
|
|
142
|
+
"\u2590 \u2840 \u258c", "\u2590\u2820 \u258c",
|
|
143
|
+
),
|
|
144
|
+
"two paddles, one braille ball",
|
|
145
|
+
),
|
|
146
|
+
"fistBump": (
|
|
147
|
+
(
|
|
148
|
+
"\U0001f91c\u3000\u3000\u3000\u3000\U0001f91b ",
|
|
149
|
+
"\U0001f91c\u3000\u3000\u3000\u3000\U0001f91b ",
|
|
150
|
+
"\U0001f91c\u3000\u3000\u3000\u3000\U0001f91b ",
|
|
151
|
+
"\u3000\U0001f91c\u3000\u3000\U0001f91b\u3000 ",
|
|
152
|
+
"\u3000\u3000\U0001f91c\U0001f91b\u3000\u3000 ",
|
|
153
|
+
"\u3000\U0001f91c\u2728\U0001f91b\u3000\u3000 ",
|
|
154
|
+
"\U0001f91c\u3000\u2728\u3000\U0001f91b\u3000 ",
|
|
155
|
+
),
|
|
156
|
+
"when the work deserves respect",
|
|
157
|
+
),
|
|
158
|
+
"aesthetic": (
|
|
159
|
+
(
|
|
160
|
+
"\u25b0\u25b1\u25b1\u25b1\u25b1\u25b1\u25b1",
|
|
161
|
+
"\u25b0\u25b0\u25b1\u25b1\u25b1\u25b1\u25b1",
|
|
162
|
+
"\u25b0\u25b0\u25b0\u25b1\u25b1\u25b1\u25b1",
|
|
163
|
+
"\u25b0\u25b0\u25b0\u25b0\u25b1\u25b1\u25b1",
|
|
164
|
+
"\u25b0\u25b0\u25b0\u25b0\u25b0\u25b1\u25b1",
|
|
165
|
+
"\u25b0\u25b0\u25b0\u25b0\u25b0\u25b0\u25b1",
|
|
166
|
+
"\u25b0\u25b0\u25b0\u25b0\u25b0\u25b0\u25b0",
|
|
167
|
+
"\u25b1\u25b1\u25b1\u25b1\u25b1\u25b1\u25b1",
|
|
168
|
+
),
|
|
169
|
+
"vaporwave progress vibes",
|
|
170
|
+
),
|
|
171
|
+
}
|
|
172
|
+
# fmt: on
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""/spinner -- pick a status-bar spinner style, interactively or by name.
|
|
2
|
+
|
|
3
|
+
UX:
|
|
4
|
+
/spinner -> interactive picker with live animated preview
|
|
5
|
+
(-/+ adjusts speed, i writes a starter
|
|
6
|
+
spinners.json, Enter applies)
|
|
7
|
+
/spinner <name> [seconds] -> apply by name, optionally at a custom speed
|
|
8
|
+
/spinner init -> write a starter spinners.json for custom spinners
|
|
9
|
+
|
|
10
|
+
Custom spinners live in ``spinners.json`` next to ``puppy.cfg``; user
|
|
11
|
+
entries override builtins on name collision. The choice persists in
|
|
12
|
+
``puppy.cfg``, custom speeds save into spinners.json itself, and both
|
|
13
|
+
take effect on the very next tick -- no restart. Edits to spinners.json
|
|
14
|
+
are picked up automatically (mtime watch), so there's no reload command
|
|
15
|
+
to remember.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import asyncio
|
|
21
|
+
import concurrent.futures
|
|
22
|
+
|
|
23
|
+
from code_puppy.messaging import emit_error, emit_info, emit_warning
|
|
24
|
+
|
|
25
|
+
from . import spinners
|
|
26
|
+
from .picker import interactive_spinner_picker
|
|
27
|
+
|
|
28
|
+
_INTERACTIVE_TIMEOUT_SECONDS = 300 # 5 min -- generous; user is browsing
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def help_entries():
|
|
32
|
+
return [
|
|
33
|
+
("spinner", "Pick a status-bar spinner style (interactive: /spinner)"),
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _run_interactive_picker() -> tuple[str, float | None] | None:
|
|
38
|
+
"""Run the async TUI from a sync command handler (theme-plugin pattern)."""
|
|
39
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
40
|
+
future = executor.submit(lambda: asyncio.run(interactive_spinner_picker()))
|
|
41
|
+
return future.result(timeout=_INTERACTIVE_TIMEOUT_SECONDS)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _apply(name: str, interval: float | None = None) -> None:
|
|
45
|
+
# Match case-insensitively; built-in names use camelCase.
|
|
46
|
+
catalogue = spinners.get_catalogue()
|
|
47
|
+
name = next((n for n in catalogue if n.lower() == name.lower()), name)
|
|
48
|
+
try:
|
|
49
|
+
spinner = spinners.set_active(name, interval)
|
|
50
|
+
except KeyError:
|
|
51
|
+
known = ", ".join(sorted(spinners.get_catalogue()))
|
|
52
|
+
emit_warning(
|
|
53
|
+
f"Unknown spinner '{name}'. Try /spinner for the picker, "
|
|
54
|
+
f"or pick one of: {known}."
|
|
55
|
+
)
|
|
56
|
+
return
|
|
57
|
+
speed_note = " -- speed saved to spinners.json" if interval is not None else ""
|
|
58
|
+
emit_info(
|
|
59
|
+
f"Spinner set to '{spinner.name}' "
|
|
60
|
+
f"({len(spinner.frames)} frames @ {spinner.interval:.2f}s{speed_note}). "
|
|
61
|
+
f"Sample: {spinner.frames[0].strip()}"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _init() -> None:
|
|
66
|
+
try:
|
|
67
|
+
created = spinners.write_template()
|
|
68
|
+
except OSError as exc:
|
|
69
|
+
emit_error(f"Could not write {spinners.USER_SPINNERS_FILE}: {exc}")
|
|
70
|
+
return
|
|
71
|
+
if created:
|
|
72
|
+
emit_info(
|
|
73
|
+
f"Starter file written: {spinners.USER_SPINNERS_FILE}\n"
|
|
74
|
+
"Edit it freely -- changes are picked up automatically."
|
|
75
|
+
)
|
|
76
|
+
else:
|
|
77
|
+
emit_info(
|
|
78
|
+
f"{spinners.USER_SPINNERS_FILE} already exists -- not touching it. "
|
|
79
|
+
"Edit it directly; changes are picked up automatically."
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def handle_spinner(command: str, name: str):
|
|
84
|
+
"""``custom_command`` callback -- owns ``/spinner`` only."""
|
|
85
|
+
if name != "spinner":
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
parts = command.split()
|
|
89
|
+
sub = parts[1].lower() if len(parts) > 1 else ""
|
|
90
|
+
|
|
91
|
+
if sub == "":
|
|
92
|
+
try:
|
|
93
|
+
chosen = _run_interactive_picker()
|
|
94
|
+
except Exception as exc: # pragma: no cover -- defensive UX
|
|
95
|
+
emit_error(f"Spinner picker failed: {exc}")
|
|
96
|
+
return True
|
|
97
|
+
if chosen is None:
|
|
98
|
+
emit_info("Spinner unchanged.")
|
|
99
|
+
return True
|
|
100
|
+
name, interval = chosen
|
|
101
|
+
_apply(name, interval)
|
|
102
|
+
elif sub == "init":
|
|
103
|
+
_init()
|
|
104
|
+
else:
|
|
105
|
+
interval = None
|
|
106
|
+
if len(parts) > 2:
|
|
107
|
+
try:
|
|
108
|
+
interval = float(parts[2])
|
|
109
|
+
except ValueError:
|
|
110
|
+
emit_warning(
|
|
111
|
+
f"'{parts[2]}' is not a number of seconds. "
|
|
112
|
+
"Usage: /spinner <name> [seconds]"
|
|
113
|
+
)
|
|
114
|
+
return True
|
|
115
|
+
_apply(sub, interval)
|
|
116
|
+
return True
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
"""Interactive spinner picker TUI.
|
|
2
|
+
|
|
3
|
+
Same full-screen prompt_toolkit pattern as the theme plugin's picker,
|
|
4
|
+
but with a preview pane that actually *animates*: a background asyncio
|
|
5
|
+
task invalidates the app ~20x/sec, and the preview control derives the
|
|
6
|
+
current frame from wall-clock time, so every spinner runs at its own
|
|
7
|
+
configured interval.
|
|
8
|
+
|
|
9
|
+
Navigation follows the plugins-menu convention (same split-pane shape,
|
|
10
|
+
same muscle memory): ``j``/``k`` + arrows move the selection (clamped,
|
|
11
|
+
no wraparound), ``pageup``/``pagedown`` page through the list via the
|
|
12
|
+
shared pagination helpers, ``g``/``G``/``home``/``end`` jump to the
|
|
13
|
+
first/last entry, ``enter`` applies, and ``q``/``esc``/``ctrl-c`` bail.
|
|
14
|
+
``i`` writes the starter spinners.json without leaving; the animator
|
|
15
|
+
task also watches the file's mtime, so external edits reload the menu
|
|
16
|
+
live while the picker is open.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import asyncio
|
|
22
|
+
import sys
|
|
23
|
+
import time
|
|
24
|
+
from typing import List, Optional, Tuple
|
|
25
|
+
|
|
26
|
+
from prompt_toolkit import Application
|
|
27
|
+
from prompt_toolkit.formatted_text import FormattedText
|
|
28
|
+
from prompt_toolkit.key_binding import KeyBindings
|
|
29
|
+
from prompt_toolkit.layout import Layout, VSplit, Window
|
|
30
|
+
from prompt_toolkit.layout.controls import FormattedTextControl
|
|
31
|
+
from prompt_toolkit.widgets import Frame
|
|
32
|
+
|
|
33
|
+
from code_puppy.command_line.pagination import (
|
|
34
|
+
ensure_visible_page,
|
|
35
|
+
get_page_bounds,
|
|
36
|
+
get_page_for_index,
|
|
37
|
+
get_total_pages,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
from . import spinners as sp
|
|
41
|
+
from code_puppy.callbacks import on_prompt_toolkit_style
|
|
42
|
+
|
|
43
|
+
#: Preview invalidation cadence, pinned to MIN_INTERVAL so slowest spinners run
|
|
44
|
+
#: at their true speed.
|
|
45
|
+
_REFRESH_INTERVAL_S = sp.MIN_INTERVAL
|
|
46
|
+
PAGE_SIZE = 20 # one line per entry, same as the plugins menu
|
|
47
|
+
#: Speed-key grid step; matching the clamp floor keeps 0.02..1.0 on-grid.
|
|
48
|
+
_SPEED_STEP_S = 0.02
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _step_interval(current: float, delta: float) -> float:
|
|
52
|
+
"""One speed-key step from *current*: snap to the step grid, then clamp.
|
|
53
|
+
|
|
54
|
+
Snapping matters at the edges: clamping alone strands you off-grid
|
|
55
|
+
(0.05 -> floor 0.02 -> 0.07 -> 0.12 ...). Rounding to the nearest
|
|
56
|
+
multiple of the step first means the floor is just a stop -- the
|
|
57
|
+
next nudge up lands back on 0.05. Off-grid starting values (from a
|
|
58
|
+
spinners.json tweak) get folded onto the grid the same way.
|
|
59
|
+
"""
|
|
60
|
+
stepped = round((current + delta) / _SPEED_STEP_S) * _SPEED_STEP_S
|
|
61
|
+
return sp.clamp_interval(stepped)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _format_menu(
|
|
65
|
+
entries: List[sp.Spinner], selected: int, page: int, active: str
|
|
66
|
+
) -> FormattedText:
|
|
67
|
+
"""Left-hand menu, styled to match the plugins menu's list pane.
|
|
68
|
+
|
|
69
|
+
One line per entry: ``" > "`` marks the selection (bold), a green
|
|
70
|
+
``*`` glyph marks the active spinner (the plugins menu's ``+``/``x``
|
|
71
|
+
slot), names only -- descriptions live in the preview pane, exactly
|
|
72
|
+
like plugin descriptions live in the detail pane.
|
|
73
|
+
"""
|
|
74
|
+
total_pages = get_total_pages(len(entries), PAGE_SIZE)
|
|
75
|
+
start, end = get_page_bounds(page, len(entries), PAGE_SIZE)
|
|
76
|
+
|
|
77
|
+
lines: list[tuple[str, str]] = [
|
|
78
|
+
("class:tui.header", " Spinners"),
|
|
79
|
+
("", "\n\n"),
|
|
80
|
+
]
|
|
81
|
+
for i in range(start, end):
|
|
82
|
+
spinner = entries[i]
|
|
83
|
+
is_selected = i == selected
|
|
84
|
+
icon = "*" if spinner.name == active else " "
|
|
85
|
+
prefix = " > " if is_selected else " "
|
|
86
|
+
|
|
87
|
+
if is_selected:
|
|
88
|
+
lines.append(("class:tui.selected", prefix))
|
|
89
|
+
lines.append(("class:tui.selected", icon))
|
|
90
|
+
lines.append(("class:tui.selected", f" {spinner.name}"))
|
|
91
|
+
else:
|
|
92
|
+
lines.append(("class:tui.body", prefix))
|
|
93
|
+
lines.append(("class:tui.success", icon))
|
|
94
|
+
lines.append(("class:tui.muted", f" {spinner.name}"))
|
|
95
|
+
lines.append(("", "\n"))
|
|
96
|
+
|
|
97
|
+
lines.append(("", "\n"))
|
|
98
|
+
lines.append(("class:tui.muted", f" Page {page + 1}/{total_pages}"))
|
|
99
|
+
lines.append(("", "\n"))
|
|
100
|
+
|
|
101
|
+
_render_hints(lines)
|
|
102
|
+
return FormattedText(lines)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
#: Hint rows; ``_render_hints`` pads keys to keep action columns aligned.
|
|
106
|
+
_HINTS = [
|
|
107
|
+
("class:tui.help-key", "up/down or j/k", "Navigate"),
|
|
108
|
+
("class:tui.help-key", "PgUp/PgDn", "Page"),
|
|
109
|
+
("class:tui.help-key", "g / G", "First / Last"),
|
|
110
|
+
("class:tui.help-key", "-/+ or \u2190/\u2192", "Slower / Faster"),
|
|
111
|
+
("class:tui.help-key", "i", "Init spinners.json"),
|
|
112
|
+
("class:tui.success", "Enter", "Apply"),
|
|
113
|
+
("class:tui.error", "q / Esc", "Exit"),
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _render_hints(lines: list[tuple[str, str]]) -> None:
|
|
118
|
+
"""The plugins menu's hint block, with a speed row where their
|
|
119
|
+
detail-scroll row sits. The key column is computed, not hand-padded.
|
|
120
|
+
"""
|
|
121
|
+
key_col = max(len(keys) for _, keys, _ in _HINTS) + 1
|
|
122
|
+
lines.append(("", "\n"))
|
|
123
|
+
for i, (style, keys, label) in enumerate(_HINTS):
|
|
124
|
+
lines.append((style, f" {keys.ljust(key_col)}"))
|
|
125
|
+
lines.append(
|
|
126
|
+
("class:tui.help", label if i == len(_HINTS) - 1 else f"{label}\n")
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _format_preview(
|
|
131
|
+
spinner: sp.Spinner,
|
|
132
|
+
started_at: float,
|
|
133
|
+
interval: Optional[float] = None,
|
|
134
|
+
notice: str = "",
|
|
135
|
+
) -> FormattedText:
|
|
136
|
+
"""Right-hand pane, styled like the plugins menu's detail pane.
|
|
137
|
+
|
|
138
|
+
*interval* is the speed the user dialed with the speed keys; None
|
|
139
|
+
means the spinner's own speed (the usual case). *notice* is a
|
|
140
|
+
one-line status message (e.g. the outcome of pressing ``i``).
|
|
141
|
+
"""
|
|
142
|
+
effective = interval if interval is not None else spinner.interval
|
|
143
|
+
elapsed = time.monotonic() - started_at
|
|
144
|
+
frame = spinner.frames[int(elapsed / effective) % len(spinner.frames)]
|
|
145
|
+
lines: list[tuple[str, str]] = [
|
|
146
|
+
("class:tui.title", " LIVE PREVIEW"),
|
|
147
|
+
("", "\n\n"),
|
|
148
|
+
("class:tui.label", f" {spinner.name}"),
|
|
149
|
+
("", "\n\n"),
|
|
150
|
+
]
|
|
151
|
+
if spinner.description:
|
|
152
|
+
lines.append(("", f" {spinner.description}"))
|
|
153
|
+
lines.append(("", "\n\n"))
|
|
154
|
+
lines.extend(
|
|
155
|
+
[
|
|
156
|
+
("class:tui.label", f" {frame}"),
|
|
157
|
+
("", "\n\n"),
|
|
158
|
+
("class:tui.label", " Source: "),
|
|
159
|
+
("class:tui.body", spinner.source),
|
|
160
|
+
("", "\n"),
|
|
161
|
+
("class:tui.label", " Frames: "),
|
|
162
|
+
("class:tui.body", str(len(spinner.frames))),
|
|
163
|
+
("", "\n"),
|
|
164
|
+
("class:tui.label", " Interval: "),
|
|
165
|
+
("class:tui.body", f"{effective:.2f}s"),
|
|
166
|
+
(
|
|
167
|
+
"class:tui.warning",
|
|
168
|
+
" (custom -- Enter saves it)" if interval is not None else "",
|
|
169
|
+
),
|
|
170
|
+
("", "\n\n"),
|
|
171
|
+
("class:tui.label", " Custom spinners:"),
|
|
172
|
+
("", "\n"),
|
|
173
|
+
("class:tui.muted", f" {sp.USER_SPINNERS_FILE}"),
|
|
174
|
+
("", "\n"),
|
|
175
|
+
("class:tui.muted", " (press i to write a starter file)"),
|
|
176
|
+
("", "\n"),
|
|
177
|
+
]
|
|
178
|
+
)
|
|
179
|
+
if notice:
|
|
180
|
+
lines.append(("", "\n"))
|
|
181
|
+
lines.append(("class:tui.warning", f" {notice}"))
|
|
182
|
+
lines.append(("", "\n"))
|
|
183
|
+
return FormattedText(lines)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
async def interactive_spinner_picker() -> Optional[Tuple[str, Optional[float]]]:
|
|
187
|
+
"""Show the full-screen spinner picker.
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
``(name, interval)`` -- *interval* is the dialed custom speed or
|
|
191
|
+
None for the spinner's own speed -- or ``None`` if cancelled.
|
|
192
|
+
"""
|
|
193
|
+
from code_puppy.tools.command_runner import set_awaiting_user_input
|
|
194
|
+
|
|
195
|
+
entries = list(sp.get_catalogue().values())
|
|
196
|
+
active = sp.get_active_spinner().name
|
|
197
|
+
selected = [next((i for i, s in enumerate(entries) if s.name == active), 0)]
|
|
198
|
+
page = [get_page_for_index(selected[0], PAGE_SIZE)]
|
|
199
|
+
custom_interval: list[Optional[float]] = [None]
|
|
200
|
+
notice = [""]
|
|
201
|
+
result: list[Optional[Tuple[str, Optional[float]]]] = [None]
|
|
202
|
+
started_at = time.monotonic()
|
|
203
|
+
|
|
204
|
+
set_awaiting_user_input(True)
|
|
205
|
+
sys.stdout.write("\033[?1049h\033[2J\033[H")
|
|
206
|
+
sys.stdout.flush()
|
|
207
|
+
await asyncio.sleep(0.05)
|
|
208
|
+
|
|
209
|
+
def _set_selection(new_idx: int) -> None:
|
|
210
|
+
"""Clamp selection and keep its page visible (plugins-menu contract).
|
|
211
|
+
|
|
212
|
+
Moving resets the dialed speed -- each entry previews at its own
|
|
213
|
+
speed until you nudge it.
|
|
214
|
+
"""
|
|
215
|
+
selected[0] = max(0, min(new_idx, len(entries) - 1))
|
|
216
|
+
page[0] = ensure_visible_page(selected[0], page[0], len(entries), PAGE_SIZE)
|
|
217
|
+
custom_interval[0] = None
|
|
218
|
+
|
|
219
|
+
def _nudge_speed(delta: float) -> None:
|
|
220
|
+
current = (
|
|
221
|
+
custom_interval[0]
|
|
222
|
+
if custom_interval[0] is not None
|
|
223
|
+
else entries[selected[0]].interval
|
|
224
|
+
)
|
|
225
|
+
custom_interval[0] = _step_interval(current, delta)
|
|
226
|
+
|
|
227
|
+
def _refresh_entries() -> None:
|
|
228
|
+
"""Re-read the catalogue in place, keeping the selection pinned by
|
|
229
|
+
name. Shared by the ``i`` key and the live file watcher."""
|
|
230
|
+
current_name = entries[selected[0]].name
|
|
231
|
+
entries[:] = list(sp.get_catalogue().values())
|
|
232
|
+
_set_selection(
|
|
233
|
+
next((i for i, s in enumerate(entries) if s.name == current_name), 0)
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
def _change_page(delta: int) -> None:
|
|
237
|
+
"""Move the page by *delta* (clamped) and jump selection to its head."""
|
|
238
|
+
total_pages = get_total_pages(len(entries), PAGE_SIZE)
|
|
239
|
+
new_page = max(0, min(page[0] + delta, total_pages - 1))
|
|
240
|
+
if new_page == page[0]:
|
|
241
|
+
return
|
|
242
|
+
page[0] = new_page
|
|
243
|
+
_set_selection(new_page * PAGE_SIZE)
|
|
244
|
+
|
|
245
|
+
kb = KeyBindings()
|
|
246
|
+
|
|
247
|
+
# -- Selection (j = up, k = down -- plugins-menu convention) ------------
|
|
248
|
+
@kb.add("up")
|
|
249
|
+
@kb.add("c-p")
|
|
250
|
+
@kb.add("j")
|
|
251
|
+
def _(event):
|
|
252
|
+
_set_selection(selected[0] - 1)
|
|
253
|
+
event.app.invalidate()
|
|
254
|
+
|
|
255
|
+
@kb.add("down")
|
|
256
|
+
@kb.add("c-n")
|
|
257
|
+
@kb.add("k")
|
|
258
|
+
def _(event):
|
|
259
|
+
_set_selection(selected[0] + 1)
|
|
260
|
+
event.app.invalidate()
|
|
261
|
+
|
|
262
|
+
# -- Page through the list ----------------------------------------------
|
|
263
|
+
@kb.add("pageup")
|
|
264
|
+
def _(event):
|
|
265
|
+
_change_page(-1)
|
|
266
|
+
event.app.invalidate()
|
|
267
|
+
|
|
268
|
+
@kb.add("pagedown")
|
|
269
|
+
def _(event):
|
|
270
|
+
_change_page(+1)
|
|
271
|
+
event.app.invalidate()
|
|
272
|
+
|
|
273
|
+
# -- Jump to first / last -------------------------------------------------
|
|
274
|
+
@kb.add("home")
|
|
275
|
+
@kb.add("g")
|
|
276
|
+
def _(event):
|
|
277
|
+
_set_selection(0)
|
|
278
|
+
event.app.invalidate()
|
|
279
|
+
|
|
280
|
+
@kb.add("end")
|
|
281
|
+
@kb.add("G")
|
|
282
|
+
def _(event):
|
|
283
|
+
_set_selection(len(entries) - 1)
|
|
284
|
+
event.app.invalidate()
|
|
285
|
+
|
|
286
|
+
# -- Speed (left/- slower, right/+ faster: interval moves inversely) ----
|
|
287
|
+
@kb.add("left")
|
|
288
|
+
@kb.add("-")
|
|
289
|
+
def _(event):
|
|
290
|
+
_nudge_speed(+_SPEED_STEP_S)
|
|
291
|
+
event.app.invalidate()
|
|
292
|
+
|
|
293
|
+
@kb.add("right")
|
|
294
|
+
@kb.add("+")
|
|
295
|
+
@kb.add("=") # unshifted + on most layouts
|
|
296
|
+
def _(event):
|
|
297
|
+
_nudge_speed(-_SPEED_STEP_S)
|
|
298
|
+
event.app.invalidate()
|
|
299
|
+
|
|
300
|
+
# -- Init the user spinners file (no need to leave the menu) -----------
|
|
301
|
+
@kb.add("i")
|
|
302
|
+
def _(event):
|
|
303
|
+
try:
|
|
304
|
+
created = sp.write_template()
|
|
305
|
+
notice[0] = (
|
|
306
|
+
"Starter file written -- edit it freely, changes apply live."
|
|
307
|
+
if created
|
|
308
|
+
else "spinners.json already exists -- edit it directly."
|
|
309
|
+
)
|
|
310
|
+
except OSError as exc:
|
|
311
|
+
notice[0] = f"Could not write starter file: {exc}"
|
|
312
|
+
_refresh_entries()
|
|
313
|
+
event.app.invalidate()
|
|
314
|
+
|
|
315
|
+
# -- Actions / exit --------------------------------------------------------
|
|
316
|
+
@kb.add("enter")
|
|
317
|
+
def _(event):
|
|
318
|
+
result[0] = (entries[selected[0]].name, custom_interval[0])
|
|
319
|
+
event.app.exit()
|
|
320
|
+
|
|
321
|
+
@kb.add("q")
|
|
322
|
+
@kb.add("escape")
|
|
323
|
+
@kb.add("c-c")
|
|
324
|
+
def _(event):
|
|
325
|
+
result[0] = None
|
|
326
|
+
event.app.exit()
|
|
327
|
+
|
|
328
|
+
left = Window(
|
|
329
|
+
content=FormattedTextControl(
|
|
330
|
+
lambda: _format_menu(entries, selected[0], page[0], active)
|
|
331
|
+
),
|
|
332
|
+
width=36, # widest hint row is 35 cells; don't guillotine the json
|
|
333
|
+
)
|
|
334
|
+
right = Window(
|
|
335
|
+
content=FormattedTextControl(
|
|
336
|
+
lambda: _format_preview(
|
|
337
|
+
entries[selected[0]], started_at, custom_interval[0], notice[0]
|
|
338
|
+
)
|
|
339
|
+
),
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
layout = Layout(
|
|
343
|
+
VSplit([Frame(left, title="Spinners"), Frame(right, title="Live Preview")])
|
|
344
|
+
)
|
|
345
|
+
app = Application(
|
|
346
|
+
layout=layout,
|
|
347
|
+
key_bindings=kb,
|
|
348
|
+
full_screen=False,
|
|
349
|
+
mouse_support=False,
|
|
350
|
+
style=on_prompt_toolkit_style(),
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
async def _animate() -> None:
|
|
354
|
+
"""Poke the app awake so the preview frame advances -- and watch
|
|
355
|
+
spinners.json so external edits reload the menu while it's open
|
|
356
|
+
(the same mtime signal the tick loop uses).
|
|
357
|
+
"""
|
|
358
|
+
file_stamp = sp.user_file_stamp()
|
|
359
|
+
try:
|
|
360
|
+
while True:
|
|
361
|
+
await asyncio.sleep(_REFRESH_INTERVAL_S)
|
|
362
|
+
stamp = sp.user_file_stamp()
|
|
363
|
+
if stamp != file_stamp:
|
|
364
|
+
file_stamp = stamp
|
|
365
|
+
_refresh_entries()
|
|
366
|
+
app.invalidate()
|
|
367
|
+
except asyncio.CancelledError:
|
|
368
|
+
pass
|
|
369
|
+
|
|
370
|
+
animator = asyncio.get_running_loop().create_task(_animate())
|
|
371
|
+
try:
|
|
372
|
+
await app.run_async()
|
|
373
|
+
finally:
|
|
374
|
+
animator.cancel()
|
|
375
|
+
set_awaiting_user_input(False)
|
|
376
|
+
sys.stdout.write("\033[?1049l")
|
|
377
|
+
sys.stdout.flush()
|
|
378
|
+
|
|
379
|
+
return result[0]
|