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,814 @@
|
|
|
1
|
+
"""Interactive terminal UI for configuring goal-mode LLM judges.
|
|
2
|
+
|
|
3
|
+
A split-panel list (left = judges, right = preview), with an in-TUI
|
|
4
|
+
form for adding/editing — no $EDITOR popout. Everything happens in
|
|
5
|
+
prompt_toolkit, so the UX stays inside the terminal session.
|
|
6
|
+
|
|
7
|
+
List view keys:
|
|
8
|
+
N add new judge (opens form)
|
|
9
|
+
Enter / E edit selected judge (opens form)
|
|
10
|
+
T toggle enabled
|
|
11
|
+
D delete selected
|
|
12
|
+
Esc / Ctrl+C close menu
|
|
13
|
+
|
|
14
|
+
Form view keys:
|
|
15
|
+
Tab / Shift+Tab cycle between Name ↔ Model ↔ Prompt
|
|
16
|
+
↑ / ↓ (when Model is focused) select model
|
|
17
|
+
←→ / PgUp PgDn (when Model is focused) page through models
|
|
18
|
+
Home / End (when Model is focused) jump to first / last model
|
|
19
|
+
Ctrl+S save
|
|
20
|
+
Esc / Ctrl+C cancel
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import asyncio
|
|
26
|
+
import sys
|
|
27
|
+
import unicodedata
|
|
28
|
+
from typing import Optional
|
|
29
|
+
|
|
30
|
+
from prompt_toolkit.application import Application
|
|
31
|
+
from prompt_toolkit.key_binding import KeyBindings
|
|
32
|
+
from prompt_toolkit.layout import Dimension, HSplit, Layout, VSplit, Window
|
|
33
|
+
from prompt_toolkit.layout.controls import FormattedTextControl
|
|
34
|
+
from prompt_toolkit.widgets import Frame, TextArea
|
|
35
|
+
|
|
36
|
+
from code_puppy.command_line.model_picker_completion import load_model_names
|
|
37
|
+
from code_puppy.command_line.pagination import (
|
|
38
|
+
ensure_visible_page,
|
|
39
|
+
get_page_bounds,
|
|
40
|
+
get_page_for_index,
|
|
41
|
+
get_total_pages,
|
|
42
|
+
)
|
|
43
|
+
from code_puppy.messaging import emit_info, emit_success, emit_warning
|
|
44
|
+
from .judge_config import (
|
|
45
|
+
DEFAULT_JUDGE_PROMPT,
|
|
46
|
+
JudgeConfig,
|
|
47
|
+
add_judge,
|
|
48
|
+
delete_judge,
|
|
49
|
+
load_judges,
|
|
50
|
+
toggle_judge,
|
|
51
|
+
update_judge,
|
|
52
|
+
validate_name,
|
|
53
|
+
)
|
|
54
|
+
from code_puppy.tools.command_runner import set_awaiting_user_input
|
|
55
|
+
from code_puppy.callbacks import on_prompt_toolkit_style
|
|
56
|
+
|
|
57
|
+
PAGE_SIZE = 10
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# ---------------------------------------------------------------------------
|
|
61
|
+
# Display helpers
|
|
62
|
+
# ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _sanitize(text: str) -> str:
|
|
66
|
+
"""Strip characters that mess with prompt_toolkit width calculations."""
|
|
67
|
+
safe = (
|
|
68
|
+
"Lu",
|
|
69
|
+
"Ll",
|
|
70
|
+
"Lt",
|
|
71
|
+
"Lm",
|
|
72
|
+
"Lo",
|
|
73
|
+
"Nd",
|
|
74
|
+
"Nl",
|
|
75
|
+
"No",
|
|
76
|
+
"Pc",
|
|
77
|
+
"Pd",
|
|
78
|
+
"Ps",
|
|
79
|
+
"Pe",
|
|
80
|
+
"Pi",
|
|
81
|
+
"Pf",
|
|
82
|
+
"Po",
|
|
83
|
+
"Zs",
|
|
84
|
+
"Sm",
|
|
85
|
+
"Sc",
|
|
86
|
+
"Sk",
|
|
87
|
+
)
|
|
88
|
+
cleaned = "".join(c for c in text if unicodedata.category(c) in safe)
|
|
89
|
+
return " ".join(cleaned.split())
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _wrap(text: str, width: int) -> list[str]:
|
|
93
|
+
"""Crude word-wrap for the preview panel."""
|
|
94
|
+
out: list[str] = []
|
|
95
|
+
for line in text.split("\n"):
|
|
96
|
+
if not line.strip():
|
|
97
|
+
out.append("")
|
|
98
|
+
continue
|
|
99
|
+
words = line.split()
|
|
100
|
+
current = ""
|
|
101
|
+
for word in words:
|
|
102
|
+
if not current:
|
|
103
|
+
current = word
|
|
104
|
+
elif len(current) + 1 + len(word) > width:
|
|
105
|
+
out.append(current)
|
|
106
|
+
current = word
|
|
107
|
+
else:
|
|
108
|
+
current += " " + word
|
|
109
|
+
if current:
|
|
110
|
+
out.append(current)
|
|
111
|
+
return out
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# ---------------------------------------------------------------------------
|
|
115
|
+
# Model list (inline paginated picker rendered as a tabbable form section)
|
|
116
|
+
# ---------------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
MODEL_PAGE_SIZE = 8 # rows of models visible at once in the form section
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _load_available_models() -> list[str]:
|
|
122
|
+
"""Return the list of model names, or [] if loading fails."""
|
|
123
|
+
try:
|
|
124
|
+
return load_model_names() or []
|
|
125
|
+
except Exception as exc:
|
|
126
|
+
emit_warning(f"Failed to load models: {exc}")
|
|
127
|
+
return []
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _render_model_list(
|
|
131
|
+
models: list[str],
|
|
132
|
+
selected_idx: int,
|
|
133
|
+
page: int,
|
|
134
|
+
*,
|
|
135
|
+
focused: bool,
|
|
136
|
+
) -> list:
|
|
137
|
+
"""Render the inline paginated model list with a selection marker."""
|
|
138
|
+
lines: list = []
|
|
139
|
+
|
|
140
|
+
if not models:
|
|
141
|
+
lines.append(("class:tui.warning", " No models available."))
|
|
142
|
+
lines.append(("", "\n"))
|
|
143
|
+
lines.append(
|
|
144
|
+
(
|
|
145
|
+
"class:tui.muted",
|
|
146
|
+
" Configure models first — see /model in the main CLI.",
|
|
147
|
+
)
|
|
148
|
+
)
|
|
149
|
+
return lines
|
|
150
|
+
|
|
151
|
+
total_pages = get_total_pages(len(models), MODEL_PAGE_SIZE)
|
|
152
|
+
start, end = get_page_bounds(page, len(models), MODEL_PAGE_SIZE)
|
|
153
|
+
|
|
154
|
+
# Header: (Page x/y, focused indicator)
|
|
155
|
+
if focused:
|
|
156
|
+
lines.append(("class:tui.selected", "▼ "))
|
|
157
|
+
else:
|
|
158
|
+
lines.append(("class:tui.muted", " "))
|
|
159
|
+
lines.append(
|
|
160
|
+
(
|
|
161
|
+
"class:tui.muted",
|
|
162
|
+
f"Page {page + 1}/{max(total_pages, 1)} "
|
|
163
|
+
f"(↑↓ to move, ←→ / PgUp PgDn to page)\n",
|
|
164
|
+
)
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
for i in range(start, end):
|
|
168
|
+
is_sel = i == selected_idx
|
|
169
|
+
name = _sanitize(models[i])
|
|
170
|
+
if is_sel and focused:
|
|
171
|
+
lines.append(("class:tui.selected", " ▶ "))
|
|
172
|
+
lines.append(("class:tui.selected", name))
|
|
173
|
+
elif is_sel:
|
|
174
|
+
lines.append(("class:tui.warning", " · "))
|
|
175
|
+
lines.append(("class:tui.warning", name))
|
|
176
|
+
else:
|
|
177
|
+
lines.append(("", " "))
|
|
178
|
+
lines.append(("", name))
|
|
179
|
+
lines.append(("", "\n"))
|
|
180
|
+
|
|
181
|
+
return lines
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# ---------------------------------------------------------------------------
|
|
185
|
+
# In-TUI form for add/edit
|
|
186
|
+
# ---------------------------------------------------------------------------
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class _FormResult:
|
|
190
|
+
"""Mutable struct so closures in key bindings can mutate."""
|
|
191
|
+
|
|
192
|
+
def __init__(self) -> None:
|
|
193
|
+
self.saved: bool = False
|
|
194
|
+
self.cancelled: bool = False
|
|
195
|
+
self.name: str = ""
|
|
196
|
+
self.model: str = ""
|
|
197
|
+
self.prompt: str = ""
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
async def _run_judge_form(
|
|
201
|
+
*,
|
|
202
|
+
title: str,
|
|
203
|
+
initial_name: str = "",
|
|
204
|
+
initial_model: str = "",
|
|
205
|
+
initial_prompt: str = DEFAULT_JUDGE_PROMPT,
|
|
206
|
+
) -> _FormResult:
|
|
207
|
+
"""Render the add/edit form with three tabbable sections:
|
|
208
|
+
|
|
209
|
+
1. Name — single-line TextArea
|
|
210
|
+
2. Model — inline paginated list (focusable Window)
|
|
211
|
+
3. Prompt — multiline TextArea
|
|
212
|
+
|
|
213
|
+
Tab / Shift+Tab cycles between sections. When Model is focused, the
|
|
214
|
+
arrow keys move the selection; ←→ / PgUp PgDn page-jump. Whatever's
|
|
215
|
+
highlighted IS the selected model — no separate "confirm" gesture.
|
|
216
|
+
"""
|
|
217
|
+
from prompt_toolkit.filters import Condition
|
|
218
|
+
|
|
219
|
+
result = _FormResult()
|
|
220
|
+
status_line = [""]
|
|
221
|
+
|
|
222
|
+
# ---- Model list state ----
|
|
223
|
+
models = _load_available_models()
|
|
224
|
+
# Find the index of the initial model, or default to 0.
|
|
225
|
+
try:
|
|
226
|
+
model_idx = [models.index(initial_model)] if initial_model in models else [0]
|
|
227
|
+
except ValueError:
|
|
228
|
+
model_idx = [0]
|
|
229
|
+
model_page = [get_page_for_index(model_idx[0], MODEL_PAGE_SIZE) if models else 0]
|
|
230
|
+
|
|
231
|
+
def current_model() -> str:
|
|
232
|
+
if not models:
|
|
233
|
+
return ""
|
|
234
|
+
return models[max(0, min(model_idx[0], len(models) - 1))]
|
|
235
|
+
|
|
236
|
+
# ---- Widgets ----
|
|
237
|
+
name_area = TextArea(
|
|
238
|
+
text=initial_name,
|
|
239
|
+
multiline=False,
|
|
240
|
+
wrap_lines=False,
|
|
241
|
+
focusable=True,
|
|
242
|
+
height=1,
|
|
243
|
+
)
|
|
244
|
+
prompt_area = TextArea(
|
|
245
|
+
text=initial_prompt,
|
|
246
|
+
multiline=True,
|
|
247
|
+
wrap_lines=True,
|
|
248
|
+
focusable=True,
|
|
249
|
+
scrollbar=True,
|
|
250
|
+
height=Dimension(min=6, weight=55),
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
model_control = FormattedTextControl(
|
|
254
|
+
text="",
|
|
255
|
+
focusable=True,
|
|
256
|
+
show_cursor=False,
|
|
257
|
+
)
|
|
258
|
+
model_window = Window(
|
|
259
|
+
content=model_control,
|
|
260
|
+
wrap_lines=False,
|
|
261
|
+
# +2 rows for the header line and padding.
|
|
262
|
+
height=Dimension(min=MODEL_PAGE_SIZE + 2, max=MODEL_PAGE_SIZE + 2),
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
status_control = FormattedTextControl(text="")
|
|
266
|
+
help_control = FormattedTextControl(text="")
|
|
267
|
+
status_window = Window(content=status_control, height=1)
|
|
268
|
+
help_window = Window(content=help_control, height=1)
|
|
269
|
+
|
|
270
|
+
# Layout: stacked frames, Name fixed-height, Model fixed-height,
|
|
271
|
+
# Prompt takes the rest of the column.
|
|
272
|
+
model_frame = Frame(model_window, title="Model")
|
|
273
|
+
root = HSplit(
|
|
274
|
+
[
|
|
275
|
+
Frame(name_area, title="Name", height=3),
|
|
276
|
+
model_frame,
|
|
277
|
+
Frame(prompt_area, title="Prompt (multiline)"),
|
|
278
|
+
status_window,
|
|
279
|
+
help_window,
|
|
280
|
+
]
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
# ---- Renderers ----
|
|
284
|
+
def is_model_focused() -> bool:
|
|
285
|
+
try:
|
|
286
|
+
return app.layout.current_window is model_window
|
|
287
|
+
except Exception:
|
|
288
|
+
return False
|
|
289
|
+
|
|
290
|
+
def refresh() -> None:
|
|
291
|
+
model_control.text = _render_model_list(
|
|
292
|
+
models,
|
|
293
|
+
model_idx[0],
|
|
294
|
+
model_page[0],
|
|
295
|
+
focused=is_model_focused(),
|
|
296
|
+
)
|
|
297
|
+
if status_line[0]:
|
|
298
|
+
status_control.text = [("class:tui.error", status_line[0])]
|
|
299
|
+
else:
|
|
300
|
+
# Hint at the bottom of the form: show current model selection.
|
|
301
|
+
current = current_model() or "(no models available)"
|
|
302
|
+
status_control.text = [
|
|
303
|
+
("class:tui.muted", "Selected model: "),
|
|
304
|
+
("class:tui.warning", _sanitize(current)),
|
|
305
|
+
]
|
|
306
|
+
help_control.text = [
|
|
307
|
+
("class:tui.help-key", " Tab "),
|
|
308
|
+
("", "next field "),
|
|
309
|
+
("class:tui.help-key", " ↑↓ "),
|
|
310
|
+
("", "select model "),
|
|
311
|
+
("class:tui.help-key", " Ctrl+S "),
|
|
312
|
+
("", "save "),
|
|
313
|
+
("class:tui.error", " Esc/Ctrl+C "),
|
|
314
|
+
("", "cancel"),
|
|
315
|
+
]
|
|
316
|
+
|
|
317
|
+
# ---- Keybindings ----
|
|
318
|
+
kb = KeyBindings()
|
|
319
|
+
|
|
320
|
+
# Cycle order: name → model → prompt → name ...
|
|
321
|
+
_focus_cycle = [name_area, model_window, prompt_area]
|
|
322
|
+
|
|
323
|
+
def _focus_index() -> int:
|
|
324
|
+
try:
|
|
325
|
+
cur = app.layout.current_window
|
|
326
|
+
except Exception:
|
|
327
|
+
return 0
|
|
328
|
+
for i, item in enumerate(_focus_cycle):
|
|
329
|
+
target = item.window if hasattr(item, "window") else item
|
|
330
|
+
if cur is target:
|
|
331
|
+
return i
|
|
332
|
+
return 0
|
|
333
|
+
|
|
334
|
+
def _focus(item) -> None:
|
|
335
|
+
# TextArea.focus works on the TextArea; Window is focused directly.
|
|
336
|
+
app.layout.focus(item)
|
|
337
|
+
|
|
338
|
+
@kb.add("tab")
|
|
339
|
+
def _(event):
|
|
340
|
+
nxt = (_focus_index() + 1) % len(_focus_cycle)
|
|
341
|
+
_focus(_focus_cycle[nxt])
|
|
342
|
+
refresh()
|
|
343
|
+
|
|
344
|
+
@kb.add("s-tab")
|
|
345
|
+
def _(event):
|
|
346
|
+
prev = (_focus_index() - 1) % len(_focus_cycle)
|
|
347
|
+
_focus(_focus_cycle[prev])
|
|
348
|
+
refresh()
|
|
349
|
+
|
|
350
|
+
@kb.add("escape")
|
|
351
|
+
@kb.add("c-c")
|
|
352
|
+
def _(event):
|
|
353
|
+
result.cancelled = True
|
|
354
|
+
event.app.exit()
|
|
355
|
+
|
|
356
|
+
@kb.add("c-s")
|
|
357
|
+
def _(event):
|
|
358
|
+
name = name_area.text.strip()
|
|
359
|
+
prompt_text = prompt_area.text
|
|
360
|
+
err = validate_name(name)
|
|
361
|
+
if err:
|
|
362
|
+
status_line[0] = err
|
|
363
|
+
refresh()
|
|
364
|
+
return
|
|
365
|
+
chosen_model = current_model()
|
|
366
|
+
if not chosen_model:
|
|
367
|
+
status_line[0] = "No models available — cannot save."
|
|
368
|
+
refresh()
|
|
369
|
+
return
|
|
370
|
+
if not prompt_text.strip():
|
|
371
|
+
status_line[0] = "Prompt cannot be empty."
|
|
372
|
+
refresh()
|
|
373
|
+
return
|
|
374
|
+
result.saved = True
|
|
375
|
+
result.name = name
|
|
376
|
+
result.model = chosen_model
|
|
377
|
+
result.prompt = prompt_text
|
|
378
|
+
event.app.exit()
|
|
379
|
+
|
|
380
|
+
# ---- Model-list navigation (only fires when the model window is focused) ----
|
|
381
|
+
model_focused = Condition(is_model_focused)
|
|
382
|
+
|
|
383
|
+
@kb.add("up", filter=model_focused)
|
|
384
|
+
def _(event):
|
|
385
|
+
if not models:
|
|
386
|
+
return
|
|
387
|
+
if model_idx[0] > 0:
|
|
388
|
+
model_idx[0] -= 1
|
|
389
|
+
model_page[0] = ensure_visible_page(
|
|
390
|
+
model_idx[0], model_page[0], len(models), MODEL_PAGE_SIZE
|
|
391
|
+
)
|
|
392
|
+
status_line[0] = ""
|
|
393
|
+
refresh()
|
|
394
|
+
|
|
395
|
+
@kb.add("down", filter=model_focused)
|
|
396
|
+
def _(event):
|
|
397
|
+
if not models:
|
|
398
|
+
return
|
|
399
|
+
if model_idx[0] < len(models) - 1:
|
|
400
|
+
model_idx[0] += 1
|
|
401
|
+
model_page[0] = ensure_visible_page(
|
|
402
|
+
model_idx[0], model_page[0], len(models), MODEL_PAGE_SIZE
|
|
403
|
+
)
|
|
404
|
+
status_line[0] = ""
|
|
405
|
+
refresh()
|
|
406
|
+
|
|
407
|
+
def _page_jump(delta: int) -> None:
|
|
408
|
+
if not models:
|
|
409
|
+
return
|
|
410
|
+
total = get_total_pages(len(models), MODEL_PAGE_SIZE)
|
|
411
|
+
new_page = max(0, min(model_page[0] + delta, total - 1))
|
|
412
|
+
if new_page == model_page[0]:
|
|
413
|
+
return
|
|
414
|
+
model_page[0] = new_page
|
|
415
|
+
# Snap selection to the first item on the new page.
|
|
416
|
+
model_idx[0] = new_page * MODEL_PAGE_SIZE
|
|
417
|
+
status_line[0] = ""
|
|
418
|
+
refresh()
|
|
419
|
+
|
|
420
|
+
@kb.add("left", filter=model_focused)
|
|
421
|
+
@kb.add("pageup", filter=model_focused)
|
|
422
|
+
def _(event):
|
|
423
|
+
_page_jump(-1)
|
|
424
|
+
|
|
425
|
+
@kb.add("right", filter=model_focused)
|
|
426
|
+
@kb.add("pagedown", filter=model_focused)
|
|
427
|
+
def _(event):
|
|
428
|
+
_page_jump(1)
|
|
429
|
+
|
|
430
|
+
@kb.add("home", filter=model_focused)
|
|
431
|
+
def _(event):
|
|
432
|
+
if not models:
|
|
433
|
+
return
|
|
434
|
+
model_idx[0] = 0
|
|
435
|
+
model_page[0] = 0
|
|
436
|
+
status_line[0] = ""
|
|
437
|
+
refresh()
|
|
438
|
+
|
|
439
|
+
@kb.add("end", filter=model_focused)
|
|
440
|
+
def _(event):
|
|
441
|
+
if not models:
|
|
442
|
+
return
|
|
443
|
+
model_idx[0] = len(models) - 1
|
|
444
|
+
model_page[0] = get_page_for_index(model_idx[0], MODEL_PAGE_SIZE)
|
|
445
|
+
status_line[0] = ""
|
|
446
|
+
refresh()
|
|
447
|
+
|
|
448
|
+
# ---- App ----
|
|
449
|
+
layout = Layout(root)
|
|
450
|
+
app = Application(
|
|
451
|
+
layout=layout,
|
|
452
|
+
key_bindings=kb,
|
|
453
|
+
full_screen=False,
|
|
454
|
+
mouse_support=False,
|
|
455
|
+
style=on_prompt_toolkit_style(),
|
|
456
|
+
)
|
|
457
|
+
layout.focus(name_area)
|
|
458
|
+
|
|
459
|
+
sys.stdout.write("\033[2J\033[H")
|
|
460
|
+
sys.stdout.flush()
|
|
461
|
+
refresh()
|
|
462
|
+
await app.run_async()
|
|
463
|
+
|
|
464
|
+
return result
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
# ---------------------------------------------------------------------------
|
|
468
|
+
# Panel rendering for the list view
|
|
469
|
+
# ---------------------------------------------------------------------------
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def _render_menu(
|
|
473
|
+
judges: list[JudgeConfig],
|
|
474
|
+
page: int,
|
|
475
|
+
selected_idx: int,
|
|
476
|
+
) -> list:
|
|
477
|
+
lines = []
|
|
478
|
+
total_pages = get_total_pages(len(judges), PAGE_SIZE)
|
|
479
|
+
start, end = get_page_bounds(page, len(judges), PAGE_SIZE)
|
|
480
|
+
|
|
481
|
+
lines.append(("class:tui.header", "Goal Judges"))
|
|
482
|
+
lines.append(("class:tui.muted", f" (Page {page + 1}/{max(total_pages, 1)})"))
|
|
483
|
+
lines.append(("", "\n\n"))
|
|
484
|
+
|
|
485
|
+
if not judges:
|
|
486
|
+
lines.append(("class:tui.warning", " No judges configured."))
|
|
487
|
+
lines.append(("", "\n"))
|
|
488
|
+
lines.append(("class:tui.muted", " Press "))
|
|
489
|
+
lines.append(("class:tui.help-key", "N"))
|
|
490
|
+
lines.append(("class:tui.muted", " to add one."))
|
|
491
|
+
lines.append(("", "\n\n"))
|
|
492
|
+
else:
|
|
493
|
+
for i in range(start, end):
|
|
494
|
+
judge = judges[i]
|
|
495
|
+
is_selected = i == selected_idx
|
|
496
|
+
marker = "▶ " if is_selected else " "
|
|
497
|
+
row_style = "class:tui.selected" if is_selected else ""
|
|
498
|
+
enabled_glyph = "[on] " if judge.enabled else "[off]"
|
|
499
|
+
enabled_style = "class:tui.success" if judge.enabled else "class:tui.muted"
|
|
500
|
+
|
|
501
|
+
lines.append((row_style or "class:tui.success", marker))
|
|
502
|
+
lines.append((enabled_style, enabled_glyph + " "))
|
|
503
|
+
lines.append((row_style, _sanitize(judge.name)))
|
|
504
|
+
lines.append(("class:tui.muted", " "))
|
|
505
|
+
lines.append(("class:tui.warning", _sanitize(judge.model)))
|
|
506
|
+
lines.append(("", "\n"))
|
|
507
|
+
|
|
508
|
+
lines.append(("", "\n"))
|
|
509
|
+
lines.append(("class:tui.help-key", " ↑↓ "))
|
|
510
|
+
lines.append(("", "Navigate\n"))
|
|
511
|
+
lines.append(("class:tui.help-key", " ←→ "))
|
|
512
|
+
lines.append(("", "Page\n"))
|
|
513
|
+
lines.append(("class:tui.help-key", " N "))
|
|
514
|
+
lines.append(("", "New judge\n"))
|
|
515
|
+
lines.append(("class:tui.help-key", " Enter "))
|
|
516
|
+
lines.append(("", "Edit (or E)\n"))
|
|
517
|
+
lines.append(("class:tui.help-key", " T "))
|
|
518
|
+
lines.append(("", "Toggle enabled\n"))
|
|
519
|
+
lines.append(("class:tui.error", " D "))
|
|
520
|
+
lines.append(("", "Delete\n"))
|
|
521
|
+
lines.append(("class:tui.help-key", " Esc "))
|
|
522
|
+
lines.append(("", "Close (or Ctrl+C)"))
|
|
523
|
+
return lines
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def _render_preview(judge: Optional[JudgeConfig]) -> list:
|
|
527
|
+
lines = []
|
|
528
|
+
lines.append(("class:tui.title", " JUDGE DETAILS"))
|
|
529
|
+
lines.append(("", "\n\n"))
|
|
530
|
+
|
|
531
|
+
if judge is None:
|
|
532
|
+
lines.append(("class:tui.warning", " No judge selected."))
|
|
533
|
+
lines.append(("", "\n"))
|
|
534
|
+
return lines
|
|
535
|
+
|
|
536
|
+
lines.append(("class:tui.label", "Name: "))
|
|
537
|
+
lines.append(("", _sanitize(judge.name)))
|
|
538
|
+
lines.append(("", "\n\n"))
|
|
539
|
+
|
|
540
|
+
lines.append(("class:tui.label", "Model: "))
|
|
541
|
+
lines.append(("class:tui.warning", _sanitize(judge.model)))
|
|
542
|
+
lines.append(("", "\n\n"))
|
|
543
|
+
|
|
544
|
+
lines.append(("class:tui.label", "Enabled: "))
|
|
545
|
+
if judge.enabled:
|
|
546
|
+
lines.append(("class:tui.success", "yes"))
|
|
547
|
+
else:
|
|
548
|
+
lines.append(("class:tui.muted", "no"))
|
|
549
|
+
lines.append(("", "\n\n"))
|
|
550
|
+
|
|
551
|
+
lines.append(("class:tui.label", "Prompt:"))
|
|
552
|
+
lines.append(("", "\n"))
|
|
553
|
+
for wrapped in _wrap(judge.prompt or "", width=58):
|
|
554
|
+
lines.append(("class:tui.muted", wrapped or " "))
|
|
555
|
+
lines.append(("", "\n"))
|
|
556
|
+
|
|
557
|
+
return lines
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
# ---------------------------------------------------------------------------
|
|
561
|
+
# Add / edit handlers (invoked between TUI sessions)
|
|
562
|
+
# ---------------------------------------------------------------------------
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
async def _add_judge_flow() -> Optional[str]:
|
|
566
|
+
form = await _run_judge_form(title="New Judge")
|
|
567
|
+
if not form.saved:
|
|
568
|
+
emit_info("Cancelled.")
|
|
569
|
+
return None
|
|
570
|
+
try:
|
|
571
|
+
add_judge(
|
|
572
|
+
JudgeConfig(
|
|
573
|
+
name=form.name,
|
|
574
|
+
model=form.model,
|
|
575
|
+
prompt=form.prompt,
|
|
576
|
+
enabled=True,
|
|
577
|
+
)
|
|
578
|
+
)
|
|
579
|
+
except ValueError as exc:
|
|
580
|
+
emit_warning(str(exc))
|
|
581
|
+
return None
|
|
582
|
+
emit_success(f"Added judge {form.name!r} → {form.model}")
|
|
583
|
+
return form.name
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
async def _edit_judge_flow(current: JudgeConfig) -> Optional[str]:
|
|
587
|
+
form = await _run_judge_form(
|
|
588
|
+
title=f"Edit Judge — {current.name}",
|
|
589
|
+
initial_name=current.name,
|
|
590
|
+
initial_model=current.model,
|
|
591
|
+
initial_prompt=current.prompt,
|
|
592
|
+
)
|
|
593
|
+
if not form.saved:
|
|
594
|
+
emit_info("Cancelled.")
|
|
595
|
+
return current.name
|
|
596
|
+
|
|
597
|
+
try:
|
|
598
|
+
update_judge(
|
|
599
|
+
current.name,
|
|
600
|
+
new_name=form.name if form.name != current.name else None,
|
|
601
|
+
model=form.model if form.model != current.model else None,
|
|
602
|
+
prompt=form.prompt if form.prompt != current.prompt else None,
|
|
603
|
+
)
|
|
604
|
+
except ValueError as exc:
|
|
605
|
+
emit_warning(str(exc))
|
|
606
|
+
return current.name
|
|
607
|
+
emit_success(f"Updated judge {form.name!r}")
|
|
608
|
+
return form.name
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
# ---------------------------------------------------------------------------
|
|
612
|
+
# Main TUI loop
|
|
613
|
+
# ---------------------------------------------------------------------------
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
async def interactive_judges_menu() -> None:
|
|
617
|
+
"""Open the goal-judges TUI. Returns when the user closes the menu."""
|
|
618
|
+
registry = load_judges()
|
|
619
|
+
judges = list(registry.judges)
|
|
620
|
+
|
|
621
|
+
selected_idx = [0]
|
|
622
|
+
current_page = [0]
|
|
623
|
+
pending_action: list[Optional[str]] = [None]
|
|
624
|
+
pending_target: list[Optional[str]] = [None]
|
|
625
|
+
|
|
626
|
+
def refresh(select_name: Optional[str] = None) -> None:
|
|
627
|
+
nonlocal judges
|
|
628
|
+
registry = load_judges()
|
|
629
|
+
judges = list(registry.judges)
|
|
630
|
+
if not judges:
|
|
631
|
+
selected_idx[0] = 0
|
|
632
|
+
current_page[0] = 0
|
|
633
|
+
return
|
|
634
|
+
if select_name:
|
|
635
|
+
for i, j in enumerate(judges):
|
|
636
|
+
if j.name == select_name:
|
|
637
|
+
selected_idx[0] = i
|
|
638
|
+
break
|
|
639
|
+
else:
|
|
640
|
+
selected_idx[0] = min(selected_idx[0], len(judges) - 1)
|
|
641
|
+
else:
|
|
642
|
+
selected_idx[0] = min(selected_idx[0], len(judges) - 1)
|
|
643
|
+
current_page[0] = get_page_for_index(selected_idx[0], PAGE_SIZE)
|
|
644
|
+
|
|
645
|
+
def current_judge() -> Optional[JudgeConfig]:
|
|
646
|
+
if 0 <= selected_idx[0] < len(judges):
|
|
647
|
+
return judges[selected_idx[0]]
|
|
648
|
+
return None
|
|
649
|
+
|
|
650
|
+
menu_control = FormattedTextControl(text="")
|
|
651
|
+
preview_control = FormattedTextControl(text="")
|
|
652
|
+
|
|
653
|
+
def update_display() -> None:
|
|
654
|
+
menu_control.text = _render_menu(judges, current_page[0], selected_idx[0])
|
|
655
|
+
preview_control.text = _render_preview(current_judge())
|
|
656
|
+
|
|
657
|
+
menu_window = Window(
|
|
658
|
+
content=menu_control, wrap_lines=False, width=Dimension(weight=40)
|
|
659
|
+
)
|
|
660
|
+
preview_window = Window(
|
|
661
|
+
content=preview_control, wrap_lines=True, width=Dimension(weight=60)
|
|
662
|
+
)
|
|
663
|
+
menu_frame = Frame(menu_window, width=Dimension(weight=40), title="Judges")
|
|
664
|
+
preview_frame = Frame(preview_window, width=Dimension(weight=60), title="Preview")
|
|
665
|
+
root = VSplit([menu_frame, preview_frame])
|
|
666
|
+
|
|
667
|
+
kb = KeyBindings()
|
|
668
|
+
|
|
669
|
+
@kb.add("up")
|
|
670
|
+
def _(event):
|
|
671
|
+
if selected_idx[0] > 0:
|
|
672
|
+
selected_idx[0] -= 1
|
|
673
|
+
current_page[0] = ensure_visible_page(
|
|
674
|
+
selected_idx[0], current_page[0], len(judges), PAGE_SIZE
|
|
675
|
+
)
|
|
676
|
+
update_display()
|
|
677
|
+
|
|
678
|
+
@kb.add("down")
|
|
679
|
+
def _(event):
|
|
680
|
+
if selected_idx[0] < len(judges) - 1:
|
|
681
|
+
selected_idx[0] += 1
|
|
682
|
+
current_page[0] = ensure_visible_page(
|
|
683
|
+
selected_idx[0], current_page[0], len(judges), PAGE_SIZE
|
|
684
|
+
)
|
|
685
|
+
update_display()
|
|
686
|
+
|
|
687
|
+
@kb.add("left")
|
|
688
|
+
def _(event):
|
|
689
|
+
if current_page[0] > 0:
|
|
690
|
+
current_page[0] -= 1
|
|
691
|
+
selected_idx[0] = current_page[0] * PAGE_SIZE
|
|
692
|
+
update_display()
|
|
693
|
+
|
|
694
|
+
@kb.add("right")
|
|
695
|
+
def _(event):
|
|
696
|
+
total = get_total_pages(len(judges), PAGE_SIZE)
|
|
697
|
+
if current_page[0] < total - 1:
|
|
698
|
+
current_page[0] += 1
|
|
699
|
+
selected_idx[0] = current_page[0] * PAGE_SIZE
|
|
700
|
+
update_display()
|
|
701
|
+
|
|
702
|
+
@kb.add("n")
|
|
703
|
+
def _(event):
|
|
704
|
+
pending_action[0] = "add"
|
|
705
|
+
event.app.exit()
|
|
706
|
+
|
|
707
|
+
# Enter edits the highlighted judge — it's the obvious "act on this row"
|
|
708
|
+
# gesture in a list view. 'E' is kept as an alias for muscle memory.
|
|
709
|
+
@kb.add("enter")
|
|
710
|
+
@kb.add("e")
|
|
711
|
+
def _(event):
|
|
712
|
+
judge = current_judge()
|
|
713
|
+
if judge:
|
|
714
|
+
pending_action[0] = "edit"
|
|
715
|
+
pending_target[0] = judge.name
|
|
716
|
+
event.app.exit()
|
|
717
|
+
|
|
718
|
+
@kb.add("t")
|
|
719
|
+
def _(event):
|
|
720
|
+
judge = current_judge()
|
|
721
|
+
if judge:
|
|
722
|
+
pending_action[0] = "toggle"
|
|
723
|
+
pending_target[0] = judge.name
|
|
724
|
+
event.app.exit()
|
|
725
|
+
|
|
726
|
+
@kb.add("d")
|
|
727
|
+
def _(event):
|
|
728
|
+
judge = current_judge()
|
|
729
|
+
if judge:
|
|
730
|
+
pending_action[0] = "delete"
|
|
731
|
+
pending_target[0] = judge.name
|
|
732
|
+
event.app.exit()
|
|
733
|
+
|
|
734
|
+
# Esc and Ctrl+C both close (Esc = natural "I'm done"; Ctrl+C = escape
|
|
735
|
+
# hatch). eager=False is fine: no Esc-chord in the list view, so the
|
|
736
|
+
# handler fires as soon as the chord timeout expires (immediate in
|
|
737
|
+
# practice).
|
|
738
|
+
@kb.add("escape")
|
|
739
|
+
@kb.add("c-c")
|
|
740
|
+
def _(event):
|
|
741
|
+
pending_action[0] = "close"
|
|
742
|
+
event.app.exit()
|
|
743
|
+
|
|
744
|
+
layout = Layout(root)
|
|
745
|
+
app = Application(
|
|
746
|
+
layout=layout,
|
|
747
|
+
key_bindings=kb,
|
|
748
|
+
full_screen=False,
|
|
749
|
+
mouse_support=False,
|
|
750
|
+
style=on_prompt_toolkit_style(),
|
|
751
|
+
)
|
|
752
|
+
|
|
753
|
+
set_awaiting_user_input(True)
|
|
754
|
+
sys.stdout.write("\033[?1049h")
|
|
755
|
+
sys.stdout.write("\033[2J\033[H")
|
|
756
|
+
sys.stdout.flush()
|
|
757
|
+
await asyncio.sleep(0.05)
|
|
758
|
+
|
|
759
|
+
try:
|
|
760
|
+
while True:
|
|
761
|
+
pending_action[0] = None
|
|
762
|
+
pending_target[0] = None
|
|
763
|
+
update_display()
|
|
764
|
+
sys.stdout.write("\033[2J\033[H")
|
|
765
|
+
sys.stdout.flush()
|
|
766
|
+
|
|
767
|
+
await app.run_async()
|
|
768
|
+
|
|
769
|
+
action = pending_action[0]
|
|
770
|
+
target = pending_target[0]
|
|
771
|
+
|
|
772
|
+
if action in (None, "close", "cancel"):
|
|
773
|
+
break
|
|
774
|
+
|
|
775
|
+
if action == "add":
|
|
776
|
+
new_name = await _add_judge_flow()
|
|
777
|
+
refresh(select_name=new_name)
|
|
778
|
+
continue
|
|
779
|
+
|
|
780
|
+
if not target:
|
|
781
|
+
continue
|
|
782
|
+
|
|
783
|
+
if action == "edit":
|
|
784
|
+
judge = next((j for j in judges if j.name == target), None)
|
|
785
|
+
if judge:
|
|
786
|
+
new_name = await _edit_judge_flow(judge)
|
|
787
|
+
refresh(select_name=new_name or target)
|
|
788
|
+
continue
|
|
789
|
+
|
|
790
|
+
if action == "toggle":
|
|
791
|
+
new_state = toggle_judge(target)
|
|
792
|
+
if new_state is None:
|
|
793
|
+
emit_warning(f"No judge named {target!r}.")
|
|
794
|
+
else:
|
|
795
|
+
emit_info(
|
|
796
|
+
f"{target!r} is now {'enabled' if new_state else 'disabled'}"
|
|
797
|
+
)
|
|
798
|
+
refresh(select_name=target)
|
|
799
|
+
continue
|
|
800
|
+
|
|
801
|
+
if action == "delete":
|
|
802
|
+
if delete_judge(target):
|
|
803
|
+
emit_success(f"Deleted judge {target!r}")
|
|
804
|
+
else:
|
|
805
|
+
emit_warning(f"No judge named {target!r}.")
|
|
806
|
+
refresh()
|
|
807
|
+
continue
|
|
808
|
+
|
|
809
|
+
finally:
|
|
810
|
+
sys.stdout.write("\033[?1049l")
|
|
811
|
+
sys.stdout.flush()
|
|
812
|
+
set_awaiting_user_input(False)
|
|
813
|
+
|
|
814
|
+
emit_info("✓ Exited judges menu")
|