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,62 @@
|
|
|
1
|
+
"""Slash commands for explicit computer-use authorization."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import shlex
|
|
6
|
+
|
|
7
|
+
from code_puppy.messaging import emit_error, emit_info, emit_success, emit_warning
|
|
8
|
+
|
|
9
|
+
from .policy import policy_store
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def command_help() -> list[tuple[str, str]]:
|
|
13
|
+
return [
|
|
14
|
+
(
|
|
15
|
+
"computer-use",
|
|
16
|
+
"Enable, disable, pause, or configure macOS Computer Use",
|
|
17
|
+
)
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def handle_command(command: str, name: str) -> bool | None:
|
|
22
|
+
if name != "computer-use":
|
|
23
|
+
return None
|
|
24
|
+
try:
|
|
25
|
+
tokens = shlex.split(command)
|
|
26
|
+
except ValueError as exc:
|
|
27
|
+
emit_error(f"Invalid /computer-use command: {exc}")
|
|
28
|
+
return True
|
|
29
|
+
subcommand = tokens[1].casefold() if len(tokens) > 1 else "status"
|
|
30
|
+
if subcommand == "status":
|
|
31
|
+
emit_info(f"Computer Use policy: {policy_store.status()}")
|
|
32
|
+
elif subcommand in {"enable", "disable"}:
|
|
33
|
+
enabled = subcommand == "enable"
|
|
34
|
+
policy_store.set_enabled(enabled)
|
|
35
|
+
if enabled:
|
|
36
|
+
emit_success(
|
|
37
|
+
"Computer Use enabled. It can now view and control allowed apps."
|
|
38
|
+
)
|
|
39
|
+
else:
|
|
40
|
+
emit_warning("Computer Use disabled in settings.")
|
|
41
|
+
elif subcommand == "pause":
|
|
42
|
+
policy_store.set_paused(True)
|
|
43
|
+
emit_warning("Computer Use paused immediately.")
|
|
44
|
+
elif subcommand == "resume":
|
|
45
|
+
policy_store.set_paused(False)
|
|
46
|
+
emit_success("Computer Use resumed.")
|
|
47
|
+
elif subcommand in {"allow", "deny"}:
|
|
48
|
+
if len(tokens) < 3:
|
|
49
|
+
emit_error("Usage: /computer-use allow|deny BUNDLE_ID")
|
|
50
|
+
return True
|
|
51
|
+
bundle_id = tokens[2]
|
|
52
|
+
if subcommand == "deny":
|
|
53
|
+
policy_store.deny(bundle_id)
|
|
54
|
+
emit_warning(f"Computer Use denied for {bundle_id}.")
|
|
55
|
+
else:
|
|
56
|
+
policy_store.allow(bundle_id)
|
|
57
|
+
emit_success(f"Computer Use allowed for {bundle_id}.")
|
|
58
|
+
else:
|
|
59
|
+
emit_error(
|
|
60
|
+
"Usage: /computer-use [status|enable|disable|pause|resume|allow|deny]"
|
|
61
|
+
)
|
|
62
|
+
return True
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Coordinate spaces used by macOS computer use."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from .backend_types import ComputerUseError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class Rect:
|
|
12
|
+
x: float
|
|
13
|
+
y: float
|
|
14
|
+
width: float
|
|
15
|
+
height: float
|
|
16
|
+
|
|
17
|
+
def as_dict(self) -> dict[str, float]:
|
|
18
|
+
return {
|
|
19
|
+
"x": self.x,
|
|
20
|
+
"y": self.y,
|
|
21
|
+
"width": self.width,
|
|
22
|
+
"height": self.height,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class CaptureGeometry:
|
|
28
|
+
"""Mapping between screenshot pixels and global Quartz screen points.
|
|
29
|
+
|
|
30
|
+
ScreenCaptureKit and CGWindow bounds use a top-left global coordinate
|
|
31
|
+
system. AppKit uses bottom-left coordinates; callers that provide AppKit
|
|
32
|
+
coordinates must opt into the explicit conversion helper below.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
window_points: Rect
|
|
36
|
+
image_width_pixels: int
|
|
37
|
+
image_height_pixels: int
|
|
38
|
+
backing_scale: float
|
|
39
|
+
|
|
40
|
+
def __post_init__(self) -> None:
|
|
41
|
+
if self.window_points.width <= 0 or self.window_points.height <= 0:
|
|
42
|
+
raise ComputerUseError("Window bounds must have positive dimensions.")
|
|
43
|
+
if self.image_width_pixels <= 0 or self.image_height_pixels <= 0:
|
|
44
|
+
raise ComputerUseError("Screenshot dimensions must be positive.")
|
|
45
|
+
if self.backing_scale <= 0:
|
|
46
|
+
raise ComputerUseError("Backing scale must be positive.")
|
|
47
|
+
|
|
48
|
+
def screenshot_to_quartz(self, x: float, y: float) -> tuple[float, float]:
|
|
49
|
+
if not (0 <= x < self.image_width_pixels):
|
|
50
|
+
raise ComputerUseError(
|
|
51
|
+
f"Screenshot x={x} is outside 0..{self.image_width_pixels - 1}."
|
|
52
|
+
)
|
|
53
|
+
if not (0 <= y < self.image_height_pixels):
|
|
54
|
+
raise ComputerUseError(
|
|
55
|
+
f"Screenshot y={y} is outside 0..{self.image_height_pixels - 1}."
|
|
56
|
+
)
|
|
57
|
+
return (
|
|
58
|
+
self.window_points.x
|
|
59
|
+
+ (float(x) / self.image_width_pixels) * self.window_points.width,
|
|
60
|
+
self.window_points.y
|
|
61
|
+
+ (float(y) / self.image_height_pixels) * self.window_points.height,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
def appkit_to_quartz(
|
|
66
|
+
x: float, y: float, display_points: Rect
|
|
67
|
+
) -> tuple[float, float]:
|
|
68
|
+
"""Convert bottom-left AppKit coordinates to top-left Quartz points."""
|
|
69
|
+
return x, display_points.y + display_points.height - (y - display_points.y)
|
|
70
|
+
|
|
71
|
+
def as_dict(self) -> dict[str, object]:
|
|
72
|
+
return {
|
|
73
|
+
"window_bounds_points": self.window_points.as_dict(),
|
|
74
|
+
"screenshot_size_pixels": {
|
|
75
|
+
"width": self.image_width_pixels,
|
|
76
|
+
"height": self.image_height_pixels,
|
|
77
|
+
},
|
|
78
|
+
"backing_scale": self.backing_scale,
|
|
79
|
+
"screenshot_coordinate_system": "top-left, window-local pixels",
|
|
80
|
+
"action_coordinate_system": "top-left, global Quartz points",
|
|
81
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Best-effort inline image display for modern terminals."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import base64
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _terminal_kind() -> str | None:
|
|
12
|
+
program = os.environ.get("TERM_PROGRAM", "").casefold()
|
|
13
|
+
term = os.environ.get("TERM", "").casefold()
|
|
14
|
+
if os.environ.get("ITERM_SESSION_ID") or program == "iterm.app":
|
|
15
|
+
return "iterm"
|
|
16
|
+
if (
|
|
17
|
+
program in {"ghostty", "wezterm"}
|
|
18
|
+
or os.environ.get("KITTY_WINDOW_ID")
|
|
19
|
+
or "ghostty" in term
|
|
20
|
+
or "kitty" in term
|
|
21
|
+
):
|
|
22
|
+
return "kitty"
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def emit_inline_image(path: str | Path) -> bool:
|
|
27
|
+
"""Render a PNG using iTerm2 or Kitty graphics escape sequences."""
|
|
28
|
+
image_path = Path(path)
|
|
29
|
+
kind = _terminal_kind()
|
|
30
|
+
if kind is None or not sys.stdout.isatty() or not image_path.is_file():
|
|
31
|
+
return False
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
if kind == "iterm":
|
|
35
|
+
data = base64.b64encode(image_path.read_bytes()).decode("ascii")
|
|
36
|
+
name = base64.b64encode(image_path.name.encode()).decode("ascii")
|
|
37
|
+
sequence = (
|
|
38
|
+
f"\033]1337;File=name={name};inline=1;width=50%;"
|
|
39
|
+
f"preserveAspectRatio=1:{data}\a\n"
|
|
40
|
+
)
|
|
41
|
+
else:
|
|
42
|
+
encoded_path = base64.b64encode(str(image_path.resolve()).encode()).decode(
|
|
43
|
+
"ascii"
|
|
44
|
+
)
|
|
45
|
+
# Direct file transmission keeps the full screenshot out of the terminal
|
|
46
|
+
# stream; q=2 suppresses replies, c/r bound it to a compact PiP preview.
|
|
47
|
+
sequence = f"\033_Ga=T,t=f,f=100,q=2,c=64,r=22;{encoded_path}\033\\\n"
|
|
48
|
+
|
|
49
|
+
from code_puppy.messaging.run_ui import suspended_run_ui
|
|
50
|
+
|
|
51
|
+
with suspended_run_ui():
|
|
52
|
+
sys.stdout.write(sequence)
|
|
53
|
+
sys.stdout.flush()
|
|
54
|
+
except Exception:
|
|
55
|
+
return False
|
|
56
|
+
return True
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""macOS virtual key codes for keys exposed by the computer-use tool."""
|
|
2
|
+
|
|
3
|
+
KEY_CODES = {
|
|
4
|
+
"return": 36,
|
|
5
|
+
"enter": 36,
|
|
6
|
+
"tab": 48,
|
|
7
|
+
"space": 49,
|
|
8
|
+
"delete": 51,
|
|
9
|
+
"escape": 53,
|
|
10
|
+
"left": 123,
|
|
11
|
+
"right": 124,
|
|
12
|
+
"down": 125,
|
|
13
|
+
"up": 126,
|
|
14
|
+
**dict(
|
|
15
|
+
zip(
|
|
16
|
+
"abcdefghijklmnopqrstuvwxyz",
|
|
17
|
+
(
|
|
18
|
+
0,
|
|
19
|
+
11,
|
|
20
|
+
8,
|
|
21
|
+
2,
|
|
22
|
+
14,
|
|
23
|
+
3,
|
|
24
|
+
5,
|
|
25
|
+
4,
|
|
26
|
+
34,
|
|
27
|
+
38,
|
|
28
|
+
40,
|
|
29
|
+
37,
|
|
30
|
+
46,
|
|
31
|
+
45,
|
|
32
|
+
31,
|
|
33
|
+
35,
|
|
34
|
+
12,
|
|
35
|
+
15,
|
|
36
|
+
1,
|
|
37
|
+
17,
|
|
38
|
+
32,
|
|
39
|
+
9,
|
|
40
|
+
13,
|
|
41
|
+
7,
|
|
42
|
+
16,
|
|
43
|
+
6,
|
|
44
|
+
),
|
|
45
|
+
)
|
|
46
|
+
),
|
|
47
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""Persisted computer-use consent, explicit denials, and hard stops."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import threading
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from code_puppy.config import CONFIG_DIR
|
|
12
|
+
|
|
13
|
+
from .backend_types import ComputerUseError
|
|
14
|
+
|
|
15
|
+
POLICY_PATH = Path(CONFIG_DIR) / "computer_use_policy.json"
|
|
16
|
+
SYSTEM_DENYLIST = {
|
|
17
|
+
"com.apple.loginwindow",
|
|
18
|
+
"com.apple.securityagent",
|
|
19
|
+
"com.apple.systempreferences",
|
|
20
|
+
"com.apple.systemsettings",
|
|
21
|
+
"com.apple.keychainaccess",
|
|
22
|
+
"com.apple.authorizationhost",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class PolicyStore:
|
|
27
|
+
def __init__(self, path: Path = POLICY_PATH) -> None:
|
|
28
|
+
self.path = path
|
|
29
|
+
self._paused = False
|
|
30
|
+
self._lock = threading.RLock()
|
|
31
|
+
|
|
32
|
+
def _load(self) -> dict[str, Any]:
|
|
33
|
+
if not self.path.is_file():
|
|
34
|
+
return {"enabled": None, "denied": []}
|
|
35
|
+
try:
|
|
36
|
+
payload = json.loads(self.path.read_text())
|
|
37
|
+
except (OSError, json.JSONDecodeError):
|
|
38
|
+
return {"enabled": None, "denied": []}
|
|
39
|
+
enabled = payload.get("enabled")
|
|
40
|
+
return {
|
|
41
|
+
"enabled": enabled if isinstance(enabled, bool) else None,
|
|
42
|
+
"denied": list(payload.get("denied", [])),
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
def _save(self, payload: dict[str, Any]) -> None:
|
|
46
|
+
self.path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
47
|
+
temporary = self.path.with_suffix(".tmp")
|
|
48
|
+
temporary.write_text(json.dumps(payload, indent=2, sort_keys=True))
|
|
49
|
+
os.chmod(temporary, 0o600)
|
|
50
|
+
temporary.replace(self.path)
|
|
51
|
+
|
|
52
|
+
def allow(self, bundle_id: str) -> None:
|
|
53
|
+
"""Remove an explicit denial; all other applications are allowed."""
|
|
54
|
+
normalized = bundle_id.casefold()
|
|
55
|
+
if normalized in SYSTEM_DENYLIST:
|
|
56
|
+
raise ComputerUseError(
|
|
57
|
+
f"Computer Use is never allowed for security process {bundle_id}."
|
|
58
|
+
)
|
|
59
|
+
with self._lock:
|
|
60
|
+
payload = self._load()
|
|
61
|
+
denied = {str(item).casefold() for item in payload["denied"]}
|
|
62
|
+
denied.discard(normalized)
|
|
63
|
+
payload["denied"] = sorted(denied)
|
|
64
|
+
self._save(payload)
|
|
65
|
+
|
|
66
|
+
def deny(self, bundle_id: str) -> None:
|
|
67
|
+
normalized = bundle_id.casefold()
|
|
68
|
+
with self._lock:
|
|
69
|
+
payload = self._load()
|
|
70
|
+
denied = {str(item).casefold() for item in payload["denied"]}
|
|
71
|
+
denied.add(normalized)
|
|
72
|
+
payload["denied"] = sorted(denied)
|
|
73
|
+
self._save(payload)
|
|
74
|
+
|
|
75
|
+
def set_enabled(self, enabled: bool) -> None:
|
|
76
|
+
with self._lock:
|
|
77
|
+
payload = self._load()
|
|
78
|
+
payload["enabled"] = enabled
|
|
79
|
+
self._save(payload)
|
|
80
|
+
|
|
81
|
+
def set_paused(self, paused: bool) -> None:
|
|
82
|
+
with self._lock:
|
|
83
|
+
self._paused = paused
|
|
84
|
+
|
|
85
|
+
def is_enabled(self) -> bool:
|
|
86
|
+
"""Return whether the user explicitly opted in to Computer Use."""
|
|
87
|
+
with self._lock:
|
|
88
|
+
return self._load()["enabled"] is True
|
|
89
|
+
|
|
90
|
+
def require_enabled(self) -> None:
|
|
91
|
+
with self._lock:
|
|
92
|
+
if self._paused:
|
|
93
|
+
raise ComputerUseError(
|
|
94
|
+
"Computer Use is paused by the emergency stop. Run "
|
|
95
|
+
"`/computer-use resume` after confirming it is safe."
|
|
96
|
+
)
|
|
97
|
+
payload = self._load()
|
|
98
|
+
if payload["enabled"] is None:
|
|
99
|
+
raise ComputerUseError(
|
|
100
|
+
"Computer Use needs your one-time permission before its first "
|
|
101
|
+
"use. It can view screenshots and control apps on this Mac. "
|
|
102
|
+
"Run `/computer-use enable` to allow it, or "
|
|
103
|
+
"`/computer-use disable` to keep it off. You can change this "
|
|
104
|
+
"setting later with the same commands."
|
|
105
|
+
)
|
|
106
|
+
if payload["enabled"] is False:
|
|
107
|
+
raise ComputerUseError(
|
|
108
|
+
"Computer Use is disabled in settings. Run "
|
|
109
|
+
"`/computer-use enable` to turn it on."
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
def require(self, bundle_id: str) -> None:
|
|
113
|
+
self.require_enabled()
|
|
114
|
+
normalized = bundle_id.casefold()
|
|
115
|
+
with self._lock:
|
|
116
|
+
if normalized in SYSTEM_DENYLIST:
|
|
117
|
+
raise ComputerUseError(
|
|
118
|
+
f"Computer Use is blocked for security process {bundle_id}."
|
|
119
|
+
)
|
|
120
|
+
payload = self._load()
|
|
121
|
+
denied = {str(item).casefold() for item in payload["denied"]}
|
|
122
|
+
if normalized in denied:
|
|
123
|
+
raise ComputerUseError(
|
|
124
|
+
f"Computer Use is denied for application {bundle_id}."
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
def status(self) -> dict[str, Any]:
|
|
128
|
+
with self._lock:
|
|
129
|
+
payload = self._load()
|
|
130
|
+
return {
|
|
131
|
+
"mode": (
|
|
132
|
+
"enabled"
|
|
133
|
+
if payload["enabled"] is True
|
|
134
|
+
else "disabled"
|
|
135
|
+
if payload["enabled"] is False
|
|
136
|
+
else "needs-first-use-consent"
|
|
137
|
+
),
|
|
138
|
+
"paused": self._paused,
|
|
139
|
+
"denied": payload["denied"],
|
|
140
|
+
"system_denied": sorted(SYSTEM_DENYLIST),
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
policy_store = PolicyStore()
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Register the macOS computer-use tools as an optional builtin plugin."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from code_puppy.callbacks import register_callback
|
|
9
|
+
from code_puppy.i18n import t
|
|
10
|
+
from code_puppy.messaging import emit_info
|
|
11
|
+
|
|
12
|
+
from .policy import policy_store
|
|
13
|
+
|
|
14
|
+
_TOOL_NAMES = (
|
|
15
|
+
"computer_get_app_state",
|
|
16
|
+
"computer_snapshot",
|
|
17
|
+
"computer_click",
|
|
18
|
+
"computer_set_value",
|
|
19
|
+
"computer_perform_action",
|
|
20
|
+
"computer_select_text",
|
|
21
|
+
"computer_press_key",
|
|
22
|
+
"computer_type_text",
|
|
23
|
+
"computer_scroll",
|
|
24
|
+
"computer_drag",
|
|
25
|
+
"computer_screenshot",
|
|
26
|
+
"computer_use_batch",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _available() -> bool:
|
|
31
|
+
return sys.platform == "darwin"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _enabled() -> bool:
|
|
35
|
+
return _available() and policy_store.is_enabled()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _register_tools() -> list[dict[str, Any]]:
|
|
39
|
+
if not _enabled():
|
|
40
|
+
return []
|
|
41
|
+
|
|
42
|
+
from .tools import REGISTRARS
|
|
43
|
+
|
|
44
|
+
return [{"name": name, "register_func": REGISTRARS[name]} for name in _TOOL_NAMES]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _register_agent_tools(agent_name: str | None = None) -> list[str]:
|
|
48
|
+
del agent_name
|
|
49
|
+
return list(_TOOL_NAMES) if _enabled() else []
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _load_prompt() -> str | None:
|
|
53
|
+
if not _enabled():
|
|
54
|
+
return None
|
|
55
|
+
return (
|
|
56
|
+
"macOS Computer Use requires persisted one-time user consent. If a tool "
|
|
57
|
+
"reports that consent is unset or disabled, show its exact slash command "
|
|
58
|
+
"to the user and do not work around it. Slash commands are not visible in "
|
|
59
|
+
"conversation history, so never repeat an old consent error from memory. "
|
|
60
|
+
"When the user repeats the request, always call the appropriate Computer "
|
|
61
|
+
"Use state tool again; the tool's current result is authoritative. Once "
|
|
62
|
+
"enabled, do not ask for per-application approval. Prefer "
|
|
63
|
+
"computer_get_app_state and element IDs over screen coordinates. Every "
|
|
64
|
+
"standalone mutation needs its current state_revision; guarded batches "
|
|
65
|
+
"return fresh state after deterministic UI settling. For pixel-based "
|
|
66
|
+
"focus-and-type workflows, use one guarded batch instead of guessing across "
|
|
67
|
+
"separate revisions. Verify the returned state and recover until the "
|
|
68
|
+
"requested outcome is visibly complete. Do not stop merely because the user "
|
|
69
|
+
"moves the pointer or types. The emergency stop and hard macOS "
|
|
70
|
+
"security-process denylist remain enforced."
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _startup() -> None:
|
|
75
|
+
if _available() and not policy_store.is_enabled():
|
|
76
|
+
emit_info(t("computer_use.startup.opt_in"))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _custom_help():
|
|
80
|
+
from .commands import command_help
|
|
81
|
+
|
|
82
|
+
return command_help()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _custom_command(command: str, name: str):
|
|
86
|
+
from .commands import handle_command
|
|
87
|
+
|
|
88
|
+
return handle_command(command, name)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
register_callback("startup", _startup)
|
|
92
|
+
register_callback("register_tools", _register_tools)
|
|
93
|
+
register_callback("register_agent_tools", _register_agent_tools)
|
|
94
|
+
register_callback("load_prompt", _load_prompt)
|
|
95
|
+
register_callback("custom_command_help", _custom_help)
|
|
96
|
+
register_callback("custom_command", _custom_command)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Shared policy and stale-state enforcement."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .policy import policy_store
|
|
6
|
+
from .state import AppState, state_store
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def require_safe_state(
|
|
10
|
+
revision: str,
|
|
11
|
+
*,
|
|
12
|
+
consume: bool = False,
|
|
13
|
+
) -> AppState:
|
|
14
|
+
state = state_store.require(revision)
|
|
15
|
+
policy_store.require(state.bundle_id)
|
|
16
|
+
if consume:
|
|
17
|
+
return state_store.require(revision, consume=True)
|
|
18
|
+
return state
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Deterministic UI settling based on accessibility-state stability."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
import time
|
|
8
|
+
from typing import Any, Callable
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _fingerprint(snapshot: dict[str, Any]) -> str:
|
|
12
|
+
normalized = [
|
|
13
|
+
{
|
|
14
|
+
key: node.get(key)
|
|
15
|
+
for key in (
|
|
16
|
+
"role",
|
|
17
|
+
"title",
|
|
18
|
+
"description",
|
|
19
|
+
"value",
|
|
20
|
+
"enabled",
|
|
21
|
+
"focused",
|
|
22
|
+
"actions",
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
for node in snapshot.get("nodes", [])
|
|
26
|
+
]
|
|
27
|
+
encoded = json.dumps(normalized, sort_keys=True, ensure_ascii=False).encode()
|
|
28
|
+
return hashlib.sha256(encoded).hexdigest()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def wait_for_ui_settle(
|
|
32
|
+
snapshotter: Callable[[str, int], dict[str, Any]],
|
|
33
|
+
app_name: str,
|
|
34
|
+
timeout: float = 3.0,
|
|
35
|
+
interval: float = 0.1,
|
|
36
|
+
stable_observations: int = 3,
|
|
37
|
+
) -> dict[str, Any]:
|
|
38
|
+
"""Return after the focused accessibility state repeats consecutively."""
|
|
39
|
+
deadline = time.monotonic() + max(0.1, timeout)
|
|
40
|
+
previous = None
|
|
41
|
+
stable = 0
|
|
42
|
+
observations = 0
|
|
43
|
+
while time.monotonic() < deadline:
|
|
44
|
+
snapshot = snapshotter(app_name, 120)
|
|
45
|
+
observations += 1
|
|
46
|
+
fingerprint = _fingerprint(snapshot)
|
|
47
|
+
if fingerprint == previous:
|
|
48
|
+
stable += 1
|
|
49
|
+
if stable >= stable_observations:
|
|
50
|
+
return {
|
|
51
|
+
"settled": True,
|
|
52
|
+
"observations": observations,
|
|
53
|
+
"fingerprint": fingerprint,
|
|
54
|
+
}
|
|
55
|
+
else:
|
|
56
|
+
previous = fingerprint
|
|
57
|
+
stable = 0
|
|
58
|
+
time.sleep(max(0.01, interval))
|
|
59
|
+
return {
|
|
60
|
+
"settled": False,
|
|
61
|
+
"observations": observations,
|
|
62
|
+
"fingerprint": previous,
|
|
63
|
+
"reason": "timeout",
|
|
64
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Revisioned computer-use state shared by snapshots and actions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
7
|
+
import uuid
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from .backend_types import ComputerUseError
|
|
12
|
+
from .geometry import CaptureGeometry
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class AppState:
|
|
17
|
+
revision: str
|
|
18
|
+
application: str
|
|
19
|
+
bundle_id: str
|
|
20
|
+
pid: int
|
|
21
|
+
window_id: int
|
|
22
|
+
window_title: str
|
|
23
|
+
geometry: CaptureGeometry
|
|
24
|
+
screenshot_path: str
|
|
25
|
+
elements: dict[int, Any] = field(repr=False)
|
|
26
|
+
created_at: float = field(default_factory=time.monotonic)
|
|
27
|
+
consumed: bool = False
|
|
28
|
+
|
|
29
|
+
def public_metadata(self) -> dict[str, Any]:
|
|
30
|
+
return {
|
|
31
|
+
"state_revision": self.revision,
|
|
32
|
+
"application": self.application,
|
|
33
|
+
"bundle_id": self.bundle_id,
|
|
34
|
+
"pid": self.pid,
|
|
35
|
+
"window_id": self.window_id,
|
|
36
|
+
"window_title": self.window_title,
|
|
37
|
+
"screenshot_path": self.screenshot_path,
|
|
38
|
+
**self.geometry.as_dict(),
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class StateStore:
|
|
43
|
+
def __init__(self, max_age_seconds: float = 120.0) -> None:
|
|
44
|
+
self._state: AppState | None = None
|
|
45
|
+
self._lock = threading.RLock()
|
|
46
|
+
self.max_age_seconds = max_age_seconds
|
|
47
|
+
|
|
48
|
+
def create(self, **kwargs: Any) -> AppState:
|
|
49
|
+
state = AppState(revision=uuid.uuid4().hex, **kwargs)
|
|
50
|
+
with self._lock:
|
|
51
|
+
self._state = state
|
|
52
|
+
return state
|
|
53
|
+
|
|
54
|
+
def require(self, revision: str, *, consume: bool = False) -> AppState:
|
|
55
|
+
if not revision:
|
|
56
|
+
raise ComputerUseError(
|
|
57
|
+
"state_revision is required. Call computer_get_app_state first."
|
|
58
|
+
)
|
|
59
|
+
with self._lock:
|
|
60
|
+
state = self._state
|
|
61
|
+
if state is None or state.revision != revision:
|
|
62
|
+
raise ComputerUseError(
|
|
63
|
+
"Stale or unknown state_revision. Call computer_get_app_state again."
|
|
64
|
+
)
|
|
65
|
+
if time.monotonic() - state.created_at > self.max_age_seconds:
|
|
66
|
+
raise ComputerUseError(
|
|
67
|
+
"The app state expired. Call computer_get_app_state again."
|
|
68
|
+
)
|
|
69
|
+
if state.consumed:
|
|
70
|
+
raise ComputerUseError(
|
|
71
|
+
"The app state changed after an action. Fetch fresh app state."
|
|
72
|
+
)
|
|
73
|
+
if consume:
|
|
74
|
+
state.consumed = True
|
|
75
|
+
return state
|
|
76
|
+
|
|
77
|
+
def current(self) -> AppState | None:
|
|
78
|
+
with self._lock:
|
|
79
|
+
return self._state
|
|
80
|
+
|
|
81
|
+
def clear(self) -> None:
|
|
82
|
+
with self._lock:
|
|
83
|
+
self._state = None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
state_store = StateStore()
|