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,231 @@
|
|
|
1
|
+
"""Thread-safe registry of active sub-agents + status derivation.
|
|
2
|
+
|
|
3
|
+
The puppy spinner repaints from a background thread ~20x/second, so all reads
|
|
4
|
+
here must be cheap and lock-guarded.
|
|
5
|
+
|
|
6
|
+
Registration is driven by the sub-agent INVOCATION banner (which carries the
|
|
7
|
+
exact name, session_type, model AND session_id), so attribution is exact even
|
|
8
|
+
for parallel sub-agents -- no FIFO guessing, no session-id parsing. Status
|
|
9
|
+
updates (``record_event``) are UPDATE-ONLY: an event for an unregistered
|
|
10
|
+
session is ignored, which cleanly filters out the MAIN agent's own stream
|
|
11
|
+
events (the main agent is never registered).
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
import threading
|
|
18
|
+
import time
|
|
19
|
+
from typing import Any, Dict, List, Optional
|
|
20
|
+
|
|
21
|
+
# session_id -> {session_id, parent, name, model, status, start, last_seen}
|
|
22
|
+
_AGENTS: "Dict[str, Dict[str, Any]]" = {}
|
|
23
|
+
_LOCK = threading.RLock()
|
|
24
|
+
|
|
25
|
+
# --- tunables (env-overridable) -------------------------------------------
|
|
26
|
+
IDLE_PRUNE_S = float(os.environ.get("SUBAGENT_PANEL_IDLE_S", "600"))
|
|
27
|
+
|
|
28
|
+
# Single-char braille spinner frames (defined via escapes to keep the source
|
|
29
|
+
# emoji-free; braille isn't emoji but escapes dodge any filter ambiguity).
|
|
30
|
+
SPINNER_FRAMES = [
|
|
31
|
+
"\u280b",
|
|
32
|
+
"\u2819",
|
|
33
|
+
"\u2839",
|
|
34
|
+
"\u2838",
|
|
35
|
+
"\u283c",
|
|
36
|
+
"\u2834",
|
|
37
|
+
"\u2826",
|
|
38
|
+
"\u2827",
|
|
39
|
+
"\u2807",
|
|
40
|
+
"\u280f",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# Registration / teardown (invocation + response banners)
|
|
45
|
+
def register(
|
|
46
|
+
session_id: Optional[str],
|
|
47
|
+
name: str,
|
|
48
|
+
model: Optional[str] = None,
|
|
49
|
+
parent: Optional[str] = None,
|
|
50
|
+
) -> None:
|
|
51
|
+
if not session_id:
|
|
52
|
+
return
|
|
53
|
+
now = time.time()
|
|
54
|
+
with _LOCK:
|
|
55
|
+
# Re-invocation of an existing session: keep its original start time.
|
|
56
|
+
existing = _AGENTS.get(session_id)
|
|
57
|
+
_AGENTS[session_id] = {
|
|
58
|
+
"session_id": session_id,
|
|
59
|
+
"parent": parent,
|
|
60
|
+
"name": name,
|
|
61
|
+
"model": model,
|
|
62
|
+
"status": "starting",
|
|
63
|
+
"start": existing["start"] if existing else now,
|
|
64
|
+
"last_seen": now,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def finish(session_id: Optional[str]) -> None:
|
|
69
|
+
if not session_id:
|
|
70
|
+
return
|
|
71
|
+
with _LOCK:
|
|
72
|
+
_AGENTS.pop(session_id, None)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def mark_done(session_id: Optional[str]) -> None:
|
|
76
|
+
"""Mark a sub-agent completed but KEEP it in the live tree (frozen) until its
|
|
77
|
+
root flushes. This avoids the vanish-then-reappear gap that happens if a
|
|
78
|
+
nested child is popped the instant it finishes while its parent runs on."""
|
|
79
|
+
if not session_id:
|
|
80
|
+
return
|
|
81
|
+
now = time.time()
|
|
82
|
+
with _LOCK:
|
|
83
|
+
entry = _AGENTS.get(session_id)
|
|
84
|
+
if entry is None:
|
|
85
|
+
return
|
|
86
|
+
entry["done"] = True
|
|
87
|
+
entry["status"] = "completed"
|
|
88
|
+
entry["end"] = now
|
|
89
|
+
entry["last_seen"] = now
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def mark_failed(session_id: Optional[str]) -> None:
|
|
93
|
+
"""Mark a sub-agent FAILED (errored after exhausting retries) but KEEP it in
|
|
94
|
+
the live tree until its root flushes -- same lifecycle as mark_done, but the
|
|
95
|
+
renderer paints it red 'failed' with an X instead of green 'completed'."""
|
|
96
|
+
if not session_id:
|
|
97
|
+
return
|
|
98
|
+
now = time.time()
|
|
99
|
+
with _LOCK:
|
|
100
|
+
entry = _AGENTS.get(session_id)
|
|
101
|
+
if entry is None:
|
|
102
|
+
return
|
|
103
|
+
entry["done"] = True
|
|
104
|
+
entry["failed"] = True
|
|
105
|
+
entry["status"] = "failed"
|
|
106
|
+
entry["end"] = now
|
|
107
|
+
entry["last_seen"] = now
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def clear() -> None:
|
|
111
|
+
"""Drop ALL tracked sub-agents. Called when the top-level turn ends so a
|
|
112
|
+
root that errored/was cancelled (and thus never flushed) can't leak its
|
|
113
|
+
'completed' children into the next prompt's live block."""
|
|
114
|
+
with _LOCK:
|
|
115
|
+
_AGENTS.clear()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# Live status (stream_event updates only)
|
|
119
|
+
def record_event(session_id: Optional[str], event_type: str, event_data: Any) -> None:
|
|
120
|
+
if not session_id:
|
|
121
|
+
return
|
|
122
|
+
status = _derive_status(event_type, event_data)
|
|
123
|
+
now = time.time()
|
|
124
|
+
with _LOCK:
|
|
125
|
+
entry = _AGENTS.get(session_id)
|
|
126
|
+
if entry is None:
|
|
127
|
+
return # not a registered sub-agent (e.g. the main agent) -> ignore
|
|
128
|
+
entry["last_seen"] = now
|
|
129
|
+
if status:
|
|
130
|
+
entry["status"] = status
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# Reads
|
|
134
|
+
def snapshot() -> List[Dict[str, Any]]:
|
|
135
|
+
"""Return active sub-agents (oldest first), pruning idle/stale ones.
|
|
136
|
+
|
|
137
|
+
DONE entries are never pruned by idle -- they stay in the live tree (shown
|
|
138
|
+
as 'completed') until their root flushes them, so a finished child never
|
|
139
|
+
vanishes mid-run.
|
|
140
|
+
|
|
141
|
+
PARENTS are never idle-pruned either: a parent that has invoked children and
|
|
142
|
+
is merely AWAITING them emits no stream events of its own, so its last_seen
|
|
143
|
+
goes stale -- but it is busy, not idle. Pruning it would orphan its whole
|
|
144
|
+
subtree, making the children re-render as depth-0 roots.
|
|
145
|
+
|
|
146
|
+
ROOT rows (parent=None) are never idle-pruned either. A root is the visible
|
|
147
|
+
anchor of a live user-initiated ``invoke_agent`` call; silently deleting it
|
|
148
|
+
while its work is still in flight (e.g. a slow tool call that emits no
|
|
149
|
+
stream events for many minutes) would strand any completed sibling in state
|
|
150
|
+
with no flush trigger, and hide from the user that a real invocation is
|
|
151
|
+
still running. Roots exit via one of three legitimate paths only:
|
|
152
|
+
completion (``_handle_frozen`` -> ``mark_done`` -> ``_maybe_flush_group``),
|
|
153
|
+
cancel (``state.clear()`` from ``_on_agent_run_cancel``), or end-of-turn
|
|
154
|
+
(``state.clear()`` from ``_on_agent_run_end``).
|
|
155
|
+
|
|
156
|
+
So only childless, not-done, genuinely-stale ORPHAN LEAVES are eligible --
|
|
157
|
+
a non-root row whose parent is no longer in the tree AND has been silent
|
|
158
|
+
longer than IDLE_PRUNE_S. End-of-turn ``state.clear()`` remains the real
|
|
159
|
+
cleanup for anything that errors/cancels without flushing.
|
|
160
|
+
"""
|
|
161
|
+
now = time.time()
|
|
162
|
+
with _LOCK:
|
|
163
|
+
ids = set(_AGENTS)
|
|
164
|
+
# session_ids still referenced as someone's parent == busy (awaiting kids).
|
|
165
|
+
busy_parents = {e.get("parent") for e in _AGENTS.values() if e.get("parent")}
|
|
166
|
+
# Prune only non-done, childless, non-root orphan leaves whose parent is
|
|
167
|
+
# gone and whose silence exceeds IDLE_PRUNE_S; roots need explicit cleanup.
|
|
168
|
+
stale = [
|
|
169
|
+
s
|
|
170
|
+
for s, e in _AGENTS.items()
|
|
171
|
+
if not e.get("done")
|
|
172
|
+
and s not in busy_parents
|
|
173
|
+
and e.get("parent") is not None
|
|
174
|
+
and e.get("parent") not in ids
|
|
175
|
+
and now - e["last_seen"] > IDLE_PRUNE_S
|
|
176
|
+
]
|
|
177
|
+
for s in stale:
|
|
178
|
+
_AGENTS.pop(s, None)
|
|
179
|
+
return sorted(_AGENTS.values(), key=lambda e: e["start"])
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def has_active() -> bool:
|
|
183
|
+
with _LOCK:
|
|
184
|
+
return bool(_AGENTS)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# Helpers
|
|
188
|
+
def _derive_status(event_type: str, event_data: Any) -> Optional[str]:
|
|
189
|
+
"""Map a sub-agent stream event to a short status. None = no change."""
|
|
190
|
+
if not isinstance(event_data, dict):
|
|
191
|
+
return None
|
|
192
|
+
if event_type == "part_start":
|
|
193
|
+
tool = event_data.get("tool_name")
|
|
194
|
+
if tool:
|
|
195
|
+
return f"calling {tool}"
|
|
196
|
+
part_type = event_data.get("part_type", "") or ""
|
|
197
|
+
if "Thinking" in part_type:
|
|
198
|
+
return "thinking..."
|
|
199
|
+
if "Text" in part_type:
|
|
200
|
+
return "writing response"
|
|
201
|
+
return None
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def fmt_elapsed(start: float) -> str:
|
|
205
|
+
"""mm:ss elapsed (2-digit minutes), e.g. 00:19."""
|
|
206
|
+
elapsed = int(max(0.0, time.time() - start))
|
|
207
|
+
return f"{elapsed // 60:02d}:{elapsed % 60:02d}"
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def fmt_elapsed_entry(entry: Dict[str, Any]) -> str:
|
|
211
|
+
"""mm:ss for an entry, frozen at its 'end' time once done."""
|
|
212
|
+
end = entry.get("end")
|
|
213
|
+
start = entry["start"]
|
|
214
|
+
elapsed = int(max(0.0, (end if end else time.time()) - start))
|
|
215
|
+
return f"{elapsed // 60:02d}:{elapsed % 60:02d}"
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def spinner_frame() -> str:
|
|
219
|
+
"""Wall-clock-derived spinner char (~10fps), independent of caller."""
|
|
220
|
+
return SPINNER_FRAMES[int(time.time() * 10) % len(SPINNER_FRAMES)]
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def status_style(status: str) -> str:
|
|
224
|
+
"""Color-code the status text by activity type."""
|
|
225
|
+
if status.startswith("calling"):
|
|
226
|
+
return "yellow"
|
|
227
|
+
if status.startswith("thinking"):
|
|
228
|
+
return "magenta"
|
|
229
|
+
if status.startswith("writing"):
|
|
230
|
+
return "green"
|
|
231
|
+
return "dim"
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
"""Register /switch-agent and /sa custom commands.
|
|
2
|
+
|
|
3
|
+
/switch-agent <agent> — switch agent + auto-resume last terminal session
|
|
4
|
+
/switch-agent — show interactive agent picker, then switch + resume
|
|
5
|
+
/sa — alias for /switch-agent
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import datetime
|
|
9
|
+
import os
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from code_puppy.callbacks import register_callback
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _do_switch_and_resume(agent_name: str) -> bool:
|
|
16
|
+
"""Switch to agent_name and auto-resume this terminal's last session."""
|
|
17
|
+
import uuid
|
|
18
|
+
|
|
19
|
+
from rich.text import Text
|
|
20
|
+
|
|
21
|
+
from code_puppy.agents import get_current_agent, set_current_agent
|
|
22
|
+
from code_puppy.config import (
|
|
23
|
+
AUTOSAVE_DIR,
|
|
24
|
+
finalize_autosave_session,
|
|
25
|
+
get_current_session_name,
|
|
26
|
+
get_last_terminal_session,
|
|
27
|
+
pin_current_session_name,
|
|
28
|
+
record_terminal_session,
|
|
29
|
+
)
|
|
30
|
+
from code_puppy.messaging import emit_info, emit_success, emit_warning
|
|
31
|
+
from code_puppy.session_storage import (
|
|
32
|
+
build_session_paths,
|
|
33
|
+
load_session,
|
|
34
|
+
save_session,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
group_id = str(uuid.uuid4())
|
|
38
|
+
|
|
39
|
+
current_agent = get_current_agent()
|
|
40
|
+
if current_agent.name == agent_name:
|
|
41
|
+
emit_info(
|
|
42
|
+
f"Already using agent: {current_agent.display_name}",
|
|
43
|
+
message_group=group_id,
|
|
44
|
+
)
|
|
45
|
+
return True
|
|
46
|
+
|
|
47
|
+
# Capture the last saved session before finalize rotates to a fresh empty ID.
|
|
48
|
+
last_session_to_resume = get_last_terminal_session()
|
|
49
|
+
|
|
50
|
+
# Force-save before switching so users without auto-save keep context; resume the
|
|
51
|
+
# conversation just saved.
|
|
52
|
+
try:
|
|
53
|
+
history = current_agent.get_message_history()
|
|
54
|
+
if history: # Only save if there's something to save
|
|
55
|
+
session_name = get_current_session_name()
|
|
56
|
+
# Record the terminal session mapping before saving
|
|
57
|
+
record_terminal_session(session_name)
|
|
58
|
+
# Force-save the session to disk
|
|
59
|
+
save_session(
|
|
60
|
+
history=history,
|
|
61
|
+
session_name=session_name,
|
|
62
|
+
base_dir=Path(AUTOSAVE_DIR),
|
|
63
|
+
timestamp=datetime.datetime.now().isoformat(),
|
|
64
|
+
token_estimator=current_agent.estimate_tokens_for_message,
|
|
65
|
+
auto_saved=True,
|
|
66
|
+
)
|
|
67
|
+
last_session_to_resume = session_name
|
|
68
|
+
except Exception as e:
|
|
69
|
+
emit_warning(
|
|
70
|
+
f"Failed to save session before switching: {e}. "
|
|
71
|
+
"Your context may not be recoverable.",
|
|
72
|
+
message_group=group_id,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Finalize current session, switch agent
|
|
76
|
+
new_session_id = finalize_autosave_session()
|
|
77
|
+
if not set_current_agent(agent_name):
|
|
78
|
+
emit_warning(
|
|
79
|
+
"Agent switch failed after autosave rotation. Your context was preserved.",
|
|
80
|
+
message_group=group_id,
|
|
81
|
+
)
|
|
82
|
+
return True
|
|
83
|
+
|
|
84
|
+
new_agent = get_current_agent()
|
|
85
|
+
new_agent.reload_code_generation_agent()
|
|
86
|
+
|
|
87
|
+
# Auto-resume the session captured before finalize rotated/recorded a new id.
|
|
88
|
+
if last_session_to_resume:
|
|
89
|
+
try:
|
|
90
|
+
autosave_dir = Path(AUTOSAVE_DIR).resolve()
|
|
91
|
+
session_pickle = build_session_paths(
|
|
92
|
+
autosave_dir, last_session_to_resume
|
|
93
|
+
).pickle_path.resolve()
|
|
94
|
+
if autosave_dir not in session_pickle.parents:
|
|
95
|
+
raise FileNotFoundError(session_pickle)
|
|
96
|
+
history = load_session(last_session_to_resume, autosave_dir)
|
|
97
|
+
new_agent.set_message_history(history)
|
|
98
|
+
pin_current_session_name(last_session_to_resume)
|
|
99
|
+
record_terminal_session(last_session_to_resume)
|
|
100
|
+
total_tokens = sum(
|
|
101
|
+
new_agent.estimate_tokens_for_message(m) for m in history
|
|
102
|
+
)
|
|
103
|
+
emit_success(
|
|
104
|
+
Text.from_markup(
|
|
105
|
+
f"Switched to [bold]{new_agent.display_name}[/bold] "
|
|
106
|
+
f"and resumed last session "
|
|
107
|
+
f"([dim]{len(history)} messages, {total_tokens} tokens[/dim])"
|
|
108
|
+
),
|
|
109
|
+
message_group=group_id,
|
|
110
|
+
)
|
|
111
|
+
emit_info(f"{new_agent.description}", message_group=group_id)
|
|
112
|
+
emit_info(
|
|
113
|
+
Text.from_markup(
|
|
114
|
+
f"[dim]Auto-save session: {last_session_to_resume}[/dim]"
|
|
115
|
+
),
|
|
116
|
+
message_group=group_id,
|
|
117
|
+
)
|
|
118
|
+
# Show resumed history summary
|
|
119
|
+
try:
|
|
120
|
+
from code_puppy.command_line.autosave_menu import (
|
|
121
|
+
display_resumed_history,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
display_resumed_history(history)
|
|
125
|
+
except Exception:
|
|
126
|
+
pass
|
|
127
|
+
except Exception:
|
|
128
|
+
# Session file gone or any other load error — clean switch, don't crash
|
|
129
|
+
emit_success(
|
|
130
|
+
f"Switched to agent: {new_agent.display_name}",
|
|
131
|
+
message_group=group_id,
|
|
132
|
+
)
|
|
133
|
+
emit_info(f"{new_agent.description}", message_group=group_id)
|
|
134
|
+
else:
|
|
135
|
+
# No previous session for this terminal — fresh start
|
|
136
|
+
emit_success(
|
|
137
|
+
f"Switched to agent: {new_agent.display_name}",
|
|
138
|
+
message_group=group_id,
|
|
139
|
+
)
|
|
140
|
+
emit_info(f"{new_agent.description}", message_group=group_id)
|
|
141
|
+
emit_info(
|
|
142
|
+
Text.from_markup(
|
|
143
|
+
f"[dim]Auto-save session rotated to: {new_session_id}[/dim]"
|
|
144
|
+
),
|
|
145
|
+
message_group=group_id,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
return True
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _handle_switch_agent(command: str, name: str) -> object:
|
|
152
|
+
"""Handle /switch-agent and /sa commands."""
|
|
153
|
+
if name not in ("switch-agent", "sa"):
|
|
154
|
+
return None # Not our command
|
|
155
|
+
|
|
156
|
+
from code_puppy.agents import get_available_agents
|
|
157
|
+
from code_puppy.command_line.agent_menu import interactive_agent_picker
|
|
158
|
+
|
|
159
|
+
tokens = command.split()
|
|
160
|
+
|
|
161
|
+
# No arg OR unrecognized agent name → interactive picker (same as /agent)
|
|
162
|
+
if len(tokens) == 1:
|
|
163
|
+
agent_name = None
|
|
164
|
+
else:
|
|
165
|
+
candidate = tokens[1].lower()
|
|
166
|
+
available = get_available_agents()
|
|
167
|
+
agent_name = candidate if candidate in available else None
|
|
168
|
+
|
|
169
|
+
if agent_name is None:
|
|
170
|
+
# Show interactive picker
|
|
171
|
+
import asyncio
|
|
172
|
+
import concurrent.futures
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
176
|
+
future = executor.submit(
|
|
177
|
+
lambda: asyncio.run(interactive_agent_picker())
|
|
178
|
+
)
|
|
179
|
+
agent_name = future.result(timeout=300)
|
|
180
|
+
except Exception:
|
|
181
|
+
agent_name = None
|
|
182
|
+
|
|
183
|
+
# Apply picker-deferred pin reloads on the main loop; worker-thread reloads
|
|
184
|
+
# can deadlock MCP autostart during loop shutdown.
|
|
185
|
+
try:
|
|
186
|
+
from code_puppy.command_line.agent_menu import (
|
|
187
|
+
apply_pending_pin_reload,
|
|
188
|
+
consume_pending_pin_reloads,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
for pin_agent, pin_model in consume_pending_pin_reloads():
|
|
192
|
+
apply_pending_pin_reload(pin_agent, pin_model)
|
|
193
|
+
except Exception:
|
|
194
|
+
pass
|
|
195
|
+
|
|
196
|
+
if not agent_name:
|
|
197
|
+
from code_puppy.messaging import emit_warning
|
|
198
|
+
|
|
199
|
+
emit_warning("Agent selection cancelled")
|
|
200
|
+
return True
|
|
201
|
+
|
|
202
|
+
return _do_switch_and_resume(agent_name)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _handle_help() -> list:
|
|
206
|
+
return [
|
|
207
|
+
(
|
|
208
|
+
"switch-agent",
|
|
209
|
+
"Switch agent + auto-resume last terminal session (alias: /sa)",
|
|
210
|
+
),
|
|
211
|
+
]
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _cleanup_orphaned_tty_sessions() -> None:
|
|
215
|
+
"""Clean up orphaned TTY session files on startup.
|
|
216
|
+
|
|
217
|
+
Removes session files for:
|
|
218
|
+
1. TTY devices that no longer exist (terminal closed)
|
|
219
|
+
2. Files older than 7 days (stale sessions)
|
|
220
|
+
|
|
221
|
+
Never crashes — all errors are silently caught.
|
|
222
|
+
"""
|
|
223
|
+
from code_puppy.config import CACHE_DIR, get_terminal_tty
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
# Skip cleanup if no TTY available
|
|
227
|
+
current_tty = get_terminal_tty()
|
|
228
|
+
if not current_tty:
|
|
229
|
+
return
|
|
230
|
+
|
|
231
|
+
# Get the tty_sessions directory
|
|
232
|
+
tty_sessions_dir = Path(CACHE_DIR) / "tty_sessions"
|
|
233
|
+
if not tty_sessions_dir.exists():
|
|
234
|
+
return
|
|
235
|
+
|
|
236
|
+
# Build current terminal's filename to skip it
|
|
237
|
+
current_tty_key = current_tty.replace("/", "_").lstrip("_")
|
|
238
|
+
current_filename = f"{current_tty_key}.txt"
|
|
239
|
+
|
|
240
|
+
# Calculate cutoff time (7 days ago)
|
|
241
|
+
cutoff_time = datetime.datetime.now() - datetime.timedelta(days=7)
|
|
242
|
+
|
|
243
|
+
# Iterate through all .txt files in tty_sessions directory
|
|
244
|
+
for session_file in tty_sessions_dir.glob("*.txt"):
|
|
245
|
+
try:
|
|
246
|
+
# Skip current terminal's file
|
|
247
|
+
if session_file.name == current_filename:
|
|
248
|
+
continue
|
|
249
|
+
|
|
250
|
+
# Rebuild the TTY path from the session filename (e.g. dev_ttys057 ->
|
|
251
|
+
# /dev/ttys057).
|
|
252
|
+
tty_key = session_file.stem # Remove .txt extension
|
|
253
|
+
tty_path = "/" + tty_key.replace("_", "/")
|
|
254
|
+
|
|
255
|
+
# Delete if TTY device no longer exists
|
|
256
|
+
if not os.path.exists(tty_path):
|
|
257
|
+
session_file.unlink()
|
|
258
|
+
continue
|
|
259
|
+
|
|
260
|
+
# Delete if file is older than 7 days
|
|
261
|
+
file_mtime = datetime.datetime.fromtimestamp(
|
|
262
|
+
session_file.stat().st_mtime
|
|
263
|
+
)
|
|
264
|
+
if file_mtime < cutoff_time:
|
|
265
|
+
session_file.unlink()
|
|
266
|
+
|
|
267
|
+
except Exception:
|
|
268
|
+
# Skip this file on any error, never crash
|
|
269
|
+
continue
|
|
270
|
+
|
|
271
|
+
except Exception:
|
|
272
|
+
# Never crash the app over cleanup
|
|
273
|
+
pass
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
async def _cleanup_orphaned_tty_sessions_async() -> None:
|
|
277
|
+
"""Non-blocking wrapper for TTY session cleanup on startup.
|
|
278
|
+
|
|
279
|
+
Fires off the cleanup task in a background thread and immediately returns,
|
|
280
|
+
ensuring zero impact on Code Puppy launch time.
|
|
281
|
+
"""
|
|
282
|
+
import asyncio
|
|
283
|
+
|
|
284
|
+
# Fire-and-forget: run cleanup in background thread without blocking startup
|
|
285
|
+
asyncio.create_task(asyncio.to_thread(_cleanup_orphaned_tty_sessions))
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def _has_user_override() -> bool:
|
|
289
|
+
"""Return True if a user-level switch_agent_resume plugin exists.
|
|
290
|
+
|
|
291
|
+
Built-in plugins load before user plugins. The callback wrapper below
|
|
292
|
+
returns ``None`` when a user plugin exists so user code can take ownership
|
|
293
|
+
of /switch-agent and /sa without racing the built-in handler.
|
|
294
|
+
"""
|
|
295
|
+
user_plugin_file = (
|
|
296
|
+
Path.home()
|
|
297
|
+
/ ".code_puppy"
|
|
298
|
+
/ "plugins"
|
|
299
|
+
/ "switch_agent_resume"
|
|
300
|
+
/ "register_callbacks.py"
|
|
301
|
+
)
|
|
302
|
+
return user_plugin_file.exists()
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _handle_switch_agent_callback(command: str, name: str) -> object:
|
|
306
|
+
"""Callback wrapper that yields ownership to a user override plugin."""
|
|
307
|
+
if _has_user_override():
|
|
308
|
+
return None
|
|
309
|
+
return _handle_switch_agent(command, name)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
register_callback("startup", _cleanup_orphaned_tty_sessions_async)
|
|
313
|
+
register_callback("custom_command", _handle_switch_agent_callback)
|
|
314
|
+
register_callback("custom_command_help", _handle_help)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# theme — `/theme` for Code Puppy
|
|
2
|
+
|
|
3
|
+
A friendlier `/theme` command with an **interactive picker**, **live preview**,
|
|
4
|
+
and **four layers of theming** — banner headers, body text, inline markup,
|
|
5
|
+
AND the whole terminal window background/foreground/ANSI palette via OSC
|
|
6
|
+
escape sequences (mure-style).
|
|
7
|
+
|
|
8
|
+
## What you get
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
/theme
|
|
12
|
+
```
|
|
13
|
+
Launches a paginated split-panel TUI with 16 themes (7 neon/dark themes, 7 palette-first/light themes, surprise, reset). Five themes are shown per page; use Up/Down for item navigation and PgUp/PgDn to change pages.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## What gets themed
|
|
17
|
+
|
|
18
|
+
| Level | Surface | Themed? |
|
|
19
|
+
|-------|---------|---------|
|
|
20
|
+
| 0 | Banner headers (`[ THINKING ]`, etc.) | ✅ |
|
|
21
|
+
| 1 | Body text (`emit_info`/`warning`/`success`/`error`/`debug` + diffs) | ✅ |
|
|
22
|
+
| 2 | Inline Rich markup (`[bold cyan]`, `[dim cyan]`, `[magenta]`...) | ✅ |
|
|
23
|
+
| 3 | **Entire terminal window** (bg/fg/16-color ANSI palette via OSC 10/11/4) | ✅ |
|
|
24
|
+
|
|
25
|
+
**Semantic colors are preserved** at Level 2 (red errors stay angry, yellow warnings warn). Level 3 OSC remap is broader — it changes how *your terminal itself* interprets every ANSI color, so e.g. an `ls` ran after `/theme tokyo-night` will also look Tokyo Night. Pure terminal-level magic.
|
|
26
|
+
|
|
27
|
+
Tokyo Night is applied by default on first run. Themes persist to your Code Puppy config and survive restarts; existing or explicitly restored choices are never overwritten by the default. The OSC palette auto-resets on Code Puppy exit (via `atexit`) so you never get a stuck-pink terminal.
|
|
28
|
+
|
|
29
|
+
## Bundled themes
|
|
30
|
+
|
|
31
|
+
| # | Theme | Vibe |
|
|
32
|
+
|---|-------|------|
|
|
33
|
+
| 1 | 🌊 Ocean | cool blues, cyans & teals |
|
|
34
|
+
| 2 | 🌲 Forest | earthy greens & olives |
|
|
35
|
+
| 3 | 🌅 Sunset | warm reds, oranges & gold |
|
|
36
|
+
| 4 | 🪩 Vaporwave | neon pinks & purples |
|
|
37
|
+
| 5 | 🫧 Bubblegum Pink | sweet pinks, candy neon & glow |
|
|
38
|
+
| 6 | 🐶 Purple Puppy | vivid violet fur, white muzzle & pink tongue |
|
|
39
|
+
| 7 | 🐱 Catppuccin Mocha | soothing pastel dark (mure default) |
|
|
40
|
+
| 8 | ☕ Catppuccin Latte | soothing pastel light |
|
|
41
|
+
| 9 | 🌃 Tokyo Night | neon-on-navy night |
|
|
42
|
+
| 10 | [CRT] Green Screen | llxprt's radioactive green phosphor CRT |
|
|
43
|
+
| 11 | Deep Black | inky noir with subtle neon edge |
|
|
44
|
+
| 12 | Solarized Light | classic warm beige with calm accents |
|
|
45
|
+
| 13 | GitHub Light | crisp white, familiar code colors |
|
|
46
|
+
| 14 | Rose Pine Dawn | soft pastel rose light |
|
|
47
|
+
| 15 | Surprise Me | a fresh random remix every time |
|
|
48
|
+
| 16 | Restore Defaults | back to Code Puppy + terminal factory |
|
|
49
|
+
|
|
50
|
+
Green Screen ports the canonical phosphor colors from [vybestack/llxprt-code](https://github.com/vybestack/llxprt-code), whose theme is licensed under Apache-2.0.
|
|
51
|
+
|
|
52
|
+
## Power-user shortcuts
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
/theme 5 apply theme #5 (Bubblegum Pink)
|
|
56
|
+
/theme bubblegum apply by alias (also: pink, puppy, purple, mocha,
|
|
57
|
+
latte, tokyo, green, crt, solarized, github, rose-pine)
|
|
58
|
+
/theme tokyo-night apply by canonical name
|
|
59
|
+
/theme surprise re-roll a random palette
|
|
60
|
+
/theme default restore Code Puppy + terminal factory colors
|
|
61
|
+
/theme reset alias of /theme default
|
|
62
|
+
/theme show dump current banner + content style mappings
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Architecture (Zen-compliant, one job per file)
|
|
66
|
+
|
|
67
|
+
| File | Responsibility |
|
|
68
|
+
|------|----------------|
|
|
69
|
+
| `themes.py` | Theme catalog + banner/content/remap/palette construction |
|
|
70
|
+
| `bundled_palettes.py` | Hex palette data (Catppuccin, Tokyo Night, Deep Black, +5 originals) |
|
|
71
|
+
| `content_styles.py` | Mutates `RichConsoleRenderer` style maps (Level 1) |
|
|
72
|
+
| `rich_themes.py` | Monkey-patches `Console.get_style()` for inline remap (Level 2) |
|
|
73
|
+
| `osc_palette.py` | OSC escape sequences for terminal-wide bg/fg/ANSI palette (Level 3) |
|
|
74
|
+
| `picker.py` | prompt_toolkit split-panel TUI with live preview |
|
|
75
|
+
| `register_callbacks.py` | `/theme` command handler, glue only |
|
|
76
|
+
| `__init__.py` | Re-applies persisted (L1 + L2 + L3) on plugin load |
|
|
77
|
+
|
|
78
|
+
## How each level works (the spicy bits)
|
|
79
|
+
|
|
80
|
+
**Level 1** — mutates `code_puppy.messaging.rich_renderer.DEFAULT_STYLES` and walks live `RichConsoleRenderer._styles` via `gc.get_objects()`.
|
|
81
|
+
|
|
82
|
+
**Level 2** — Rich's stock `Theme` only intercepts named styles, not base colors inside `[bold cyan]`. So we monkey-patch `Console.get_style()` on every live `Console`: every resolved `Style` flows throughd if `.color`/`.bgcolor` matches our remap, we swap. Tracked in a `WeakKeyDictionary` for clean swap-without-leak.
|
|
83
|
+
|
|
84
|
+
**Level 3** — fires xterm OSC sequences (`\\033]10;#fg\\007`, `\\033]11;#bg\\007`, `\\033]4;N;#hex\\007`) which modern terminals honor terminal-wide. Auto-resets on exit via `atexit`. Persists across restarts via JSON in `puppy.cfg`. Supported in iTerm2, Terminal.app, Alacritty, kitty, ode, GNOME Terminal, Windows Terminal. Unsupported terminals silently ignore.
|
|
85
|
+
|
|
86
|
+
Plays nice with `/colors` — same color pool, same config keys for banners.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Theme picker plugin for Code Puppy - banner + content + inline + terminal theming."""
|
|
2
|
+
|
|
3
|
+
# Reapply persisted overrides at load: banner colors persist in puppy.cfg, while
|
|
4
|
+
# content styles, Rich remaps, and OSC palettes reset each process.
|
|
5
|
+
from . import content_styles as _cs
|
|
6
|
+
from . import osc_palette as _osc
|
|
7
|
+
from . import rich_themes as _rt
|
|
8
|
+
|
|
9
|
+
for _mod in (_cs, _rt, _osc):
|
|
10
|
+
try:
|
|
11
|
+
_mod.reapply_from_config()
|
|
12
|
+
except Exception:
|
|
13
|
+
# Never let theme persistence break Code Puppy startup.
|
|
14
|
+
pass
|