code-muse 0.0.1__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_muse/__init__.py +26 -0
- code_muse/__main__.py +10 -0
- code_muse/agents/__init__.py +31 -0
- code_muse/agents/_builder.py +214 -0
- code_muse/agents/_compaction.py +506 -0
- code_muse/agents/_diagnostics.py +171 -0
- code_muse/agents/_history.py +382 -0
- code_muse/agents/_key_listeners.py +148 -0
- code_muse/agents/_non_streaming_render.py +148 -0
- code_muse/agents/_runtime.py +596 -0
- code_muse/agents/agent_creator_agent.py +603 -0
- code_muse/agents/agent_helios.py +47 -0
- code_muse/agents/agent_manager.py +740 -0
- code_muse/agents/agent_muse.py +78 -0
- code_muse/agents/agent_planning.py +44 -0
- code_muse/agents/agent_qa_melpomene.py +207 -0
- code_muse/agents/base_agent.py +194 -0
- code_muse/agents/event_stream_handler.py +361 -0
- code_muse/agents/json_agent.py +201 -0
- code_muse/agents/prompt_v3.py +521 -0
- code_muse/agents/subagent_stream_handler.py +273 -0
- code_muse/callbacks.py +941 -0
- code_muse/chatgpt_codex_client.py +333 -0
- code_muse/claude_cache_client.py +853 -0
- code_muse/cli_runner/__init__.py +319 -0
- code_muse/cli_runner/args.py +63 -0
- code_muse/cli_runner/loop.py +510 -0
- code_muse/cli_runner/resume.py +72 -0
- code_muse/cli_runner/runner.py +161 -0
- code_muse/command_line/__init__.py +1 -0
- code_muse/command_line/add_model_menu.py +1331 -0
- code_muse/command_line/agent_menu.py +674 -0
- code_muse/command_line/attachments.py +397 -0
- code_muse/command_line/autosave_menu.py +709 -0
- code_muse/command_line/clipboard.py +528 -0
- code_muse/command_line/colors_menu.py +530 -0
- code_muse/command_line/command_handler.py +262 -0
- code_muse/command_line/command_registry.py +150 -0
- code_muse/command_line/config_commands.py +711 -0
- code_muse/command_line/core_commands.py +740 -0
- code_muse/command_line/diff_menu.py +865 -0
- code_muse/command_line/file_path_completion.py +73 -0
- code_muse/command_line/load_context_completion.py +57 -0
- code_muse/command_line/model_picker_completion.py +512 -0
- code_muse/command_line/model_settings_menu.py +983 -0
- code_muse/command_line/onboarding_slides.py +162 -0
- code_muse/command_line/onboarding_wizard.py +337 -0
- code_muse/command_line/pagination.py +41 -0
- code_muse/command_line/pin_command_completion.py +329 -0
- code_muse/command_line/prompt_toolkit_completion.py +886 -0
- code_muse/command_line/session_commands.py +304 -0
- code_muse/command_line/shell_passthrough.py +145 -0
- code_muse/command_line/skills_completion.py +158 -0
- code_muse/command_line/types.py +18 -0
- code_muse/command_line/uc_menu.py +908 -0
- code_muse/command_line/utils.py +105 -0
- code_muse/command_line/wiggum_state.py +77 -0
- code_muse/config.py +1138 -0
- code_muse/config_agent.py +168 -0
- code_muse/config_appearance.py +241 -0
- code_muse/config_model.py +357 -0
- code_muse/config_security.py +73 -0
- code_muse/error_logging.py +132 -0
- code_muse/evals/__init__.py +35 -0
- code_muse/evals/eval_helpers.py +81 -0
- code_muse/evals/eval_runner.py +299 -0
- code_muse/evals/sample_evals/__init__.py +1 -0
- code_muse/evals/sample_evals/eval_frugal_reads.py +59 -0
- code_muse/evals/sample_evals/eval_memory_planning.py +31 -0
- code_muse/evals/sample_evals/eval_shell_efficiency.py +39 -0
- code_muse/evals/sample_evals/eval_tool_masking.py +33 -0
- code_muse/fs_scan_cache/__init__.py +31 -0
- code_muse/fs_scan_cache/invalidation_hooks.py +89 -0
- code_muse/fs_scan_cache/scan_cache_core.cpython-314-darwin.so +0 -0
- code_muse/fs_scan_cache/scan_cache_core.pyx +203 -0
- code_muse/fs_scan_cache/tool_integration.py +309 -0
- code_muse/fs_scan_cache/ttl_policy.py +44 -0
- code_muse/gemini_code_assist.py +383 -0
- code_muse/gemini_model.py +838 -0
- code_muse/hook_engine/README.md +105 -0
- code_muse/hook_engine/__init__.py +21 -0
- code_muse/hook_engine/aliases.py +153 -0
- code_muse/hook_engine/engine.py +221 -0
- code_muse/hook_engine/executor.py +347 -0
- code_muse/hook_engine/matcher.py +154 -0
- code_muse/hook_engine/models.py +245 -0
- code_muse/hook_engine/registry.py +114 -0
- code_muse/hook_engine/trust.py +268 -0
- code_muse/hook_engine/validator.py +144 -0
- code_muse/http_utils.py +360 -0
- code_muse/keymap.py +128 -0
- code_muse/list_filtering.py +26 -0
- code_muse/main.py +10 -0
- code_muse/messaging/__init__.py +259 -0
- code_muse/messaging/bus.py +621 -0
- code_muse/messaging/commands.py +166 -0
- code_muse/messaging/markdown_patches.py +57 -0
- code_muse/messaging/message_queue.py +397 -0
- code_muse/messaging/messages.py +591 -0
- code_muse/messaging/queue_console.py +269 -0
- code_muse/messaging/renderers.py +308 -0
- code_muse/messaging/rich_renderer.py +1158 -0
- code_muse/messaging/shimmer.py +154 -0
- code_muse/messaging/spinner/__init__.py +87 -0
- code_muse/messaging/spinner/console_spinner.py +250 -0
- code_muse/messaging/spinner/spinner_base.py +82 -0
- code_muse/messaging/subagent_console.py +458 -0
- code_muse/model_factory.py +1203 -0
- code_muse/model_switching.py +59 -0
- code_muse/model_utils.py +156 -0
- code_muse/models.json +66 -0
- code_muse/models_cache/__init__.py +26 -0
- code_muse/models_cache/blocking_lru_cache.py +98 -0
- code_muse/models_cache/cache_writer.py +86 -0
- code_muse/models_cache/sha256_hash.cpython-314-darwin.so +0 -0
- code_muse/models_cache/sha256_hash.pyx +34 -0
- code_muse/models_cache/startup_integration.py +75 -0
- code_muse/models_dev_api.json +1 -0
- code_muse/models_dev_parser.py +590 -0
- code_muse/motion.py +126 -0
- code_muse/plugins/__init__.py +471 -0
- code_muse/plugins/agent_skills/__init__.py +32 -0
- code_muse/plugins/agent_skills/config.py +176 -0
- code_muse/plugins/agent_skills/discovery.py +309 -0
- code_muse/plugins/agent_skills/downloader.py +389 -0
- code_muse/plugins/agent_skills/installer.py +19 -0
- code_muse/plugins/agent_skills/metadata.py +293 -0
- code_muse/plugins/agent_skills/prompt_builder.py +66 -0
- code_muse/plugins/agent_skills/register_callbacks.py +298 -0
- code_muse/plugins/agent_skills/remote_catalog.py +320 -0
- code_muse/plugins/agent_skills/skill_catalog.py +254 -0
- code_muse/plugins/agent_skills/skills_install_menu.py +690 -0
- code_muse/plugins/agent_skills/skills_menu.py +791 -0
- code_muse/plugins/autonomous_memory/__init__.py +39 -0
- code_muse/plugins/autonomous_memory/bm25_scorer.cpython-314-darwin.so +0 -0
- code_muse/plugins/autonomous_memory/bm25_scorer.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/plugins/autonomous_memory/bm25_scorer.pyx +291 -0
- code_muse/plugins/autonomous_memory/consolidation.py +82 -0
- code_muse/plugins/autonomous_memory/extraction.py +382 -0
- code_muse/plugins/autonomous_memory/lease_lock.py +105 -0
- code_muse/plugins/autonomous_memory/memory_injection.py +59 -0
- code_muse/plugins/autonomous_memory/register_callbacks.py +268 -0
- code_muse/plugins/autonomous_memory/secret_scanner.py +62 -0
- code_muse/plugins/autonomous_memory/session_scanner.py +163 -0
- code_muse/plugins/aws_bedrock/__init__.py +14 -0
- code_muse/plugins/aws_bedrock/config.py +99 -0
- code_muse/plugins/aws_bedrock/register_callbacks.py +241 -0
- code_muse/plugins/aws_bedrock/utils.py +153 -0
- code_muse/plugins/azure_foundry/README.md +238 -0
- code_muse/plugins/azure_foundry/__init__.py +15 -0
- code_muse/plugins/azure_foundry/config.py +125 -0
- code_muse/plugins/azure_foundry/discovery.py +187 -0
- code_muse/plugins/azure_foundry/register_callbacks.py +495 -0
- code_muse/plugins/azure_foundry/token.py +180 -0
- code_muse/plugins/azure_foundry/utils.py +345 -0
- code_muse/plugins/build_filter/__init__.py +1 -0
- code_muse/plugins/build_filter/register_callbacks.py +201 -0
- code_muse/plugins/build_filter/strategies/__init__.py +1 -0
- code_muse/plugins/build_filter/strategies/build.py +397 -0
- code_muse/plugins/chatgpt_oauth/__init__.py +6 -0
- code_muse/plugins/chatgpt_oauth/config.py +52 -0
- code_muse/plugins/chatgpt_oauth/oauth_flow.py +338 -0
- code_muse/plugins/chatgpt_oauth/register_callbacks.py +172 -0
- code_muse/plugins/chatgpt_oauth/test_plugin.py +301 -0
- code_muse/plugins/chatgpt_oauth/utils.py +538 -0
- code_muse/plugins/checkpointing/__init__.py +29 -0
- code_muse/plugins/checkpointing/checkpoint_hook.py +51 -0
- code_muse/plugins/checkpointing/conversation_snapshots.py +117 -0
- code_muse/plugins/checkpointing/register_callbacks.py +51 -0
- code_muse/plugins/checkpointing/restore_command.py +263 -0
- code_muse/plugins/checkpointing/rewind_shortcut.py +88 -0
- code_muse/plugins/checkpointing/shadow_git.py +90 -0
- code_muse/plugins/claude_code_hooks/__init__.py +1 -0
- code_muse/plugins/claude_code_hooks/config.py +188 -0
- code_muse/plugins/claude_code_hooks/register_callbacks.py +208 -0
- code_muse/plugins/claude_code_oauth/README.md +167 -0
- code_muse/plugins/claude_code_oauth/SETUP.md +93 -0
- code_muse/plugins/claude_code_oauth/__init__.py +25 -0
- code_muse/plugins/claude_code_oauth/config.py +52 -0
- code_muse/plugins/claude_code_oauth/fast_mode.py +124 -0
- code_muse/plugins/claude_code_oauth/prompt_handler.py +63 -0
- code_muse/plugins/claude_code_oauth/register_callbacks.py +547 -0
- code_muse/plugins/claude_code_oauth/test_fast_mode.py +165 -0
- code_muse/plugins/claude_code_oauth/test_plugin.py +283 -0
- code_muse/plugins/claude_code_oauth/token_refresh_heartbeat.py +237 -0
- code_muse/plugins/claude_code_oauth/utils.py +664 -0
- code_muse/plugins/copilot_auth/__init__.py +11 -0
- code_muse/plugins/copilot_auth/config.py +91 -0
- code_muse/plugins/copilot_auth/reasoning_client.py +409 -0
- code_muse/plugins/copilot_auth/register_callbacks.py +461 -0
- code_muse/plugins/copilot_auth/utils.py +584 -0
- code_muse/plugins/custom_commands/__init__.py +14 -0
- code_muse/plugins/custom_commands/args_injection.py +82 -0
- code_muse/plugins/custom_commands/command_discovery.py +89 -0
- code_muse/plugins/custom_commands/command_toml_schema.py +71 -0
- code_muse/plugins/custom_commands/register_callbacks.py +176 -0
- code_muse/plugins/customizable_commands/__init__.py +0 -0
- code_muse/plugins/customizable_commands/register_callbacks.py +136 -0
- code_muse/plugins/destructive_command_guard/__init__.py +14 -0
- code_muse/plugins/destructive_command_guard/detector.py +375 -0
- code_muse/plugins/destructive_command_guard/register_callbacks.py +148 -0
- code_muse/plugins/example_custom_command/README.md +280 -0
- code_muse/plugins/example_custom_command/register_callbacks.py +51 -0
- code_muse/plugins/file_permission_handler/__init__.py +4 -0
- code_muse/plugins/file_permission_handler/register_callbacks.py +441 -0
- code_muse/plugins/filter_engine/__init__.py +30 -0
- code_muse/plugins/filter_engine/classifier.py +153 -0
- code_muse/plugins/filter_engine/content_detector.py +184 -0
- code_muse/plugins/filter_engine/dispatcher.py +244 -0
- code_muse/plugins/filter_engine/register_callbacks.py +188 -0
- code_muse/plugins/filter_engine/registry.py +279 -0
- code_muse/plugins/filter_engine/strategies/__init__.py +8 -0
- code_muse/plugins/filter_engine/strategies/ast_compressor.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/ast_compressor.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/plugins/filter_engine/strategies/ast_compressor.pyx +348 -0
- code_muse/plugins/filter_engine/strategies/ast_parser.py +167 -0
- code_muse/plugins/filter_engine/strategies/code.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/code.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/plugins/filter_engine/strategies/code.pyx +584 -0
- code_muse/plugins/filter_engine/strategies/git.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/git.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/plugins/filter_engine/strategies/git.pyx +438 -0
- code_muse/plugins/filter_engine/strategies/json_compressor.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/json_compressor.pyx +253 -0
- code_muse/plugins/filter_engine/strategies/json_patterns.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/json_patterns.pyx +178 -0
- code_muse/plugins/filter_engine/strategies/lint.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/lint.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/plugins/filter_engine/strategies/lint.pyx +626 -0
- code_muse/plugins/filter_engine/strategies/test.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/test.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/plugins/filter_engine/strategies/test.pyx +431 -0
- code_muse/plugins/filter_engine/verbosity.py +63 -0
- code_muse/plugins/force_push_guard/__init__.py +5 -0
- code_muse/plugins/force_push_guard/detector.py +96 -0
- code_muse/plugins/force_push_guard/register_callbacks.py +144 -0
- code_muse/plugins/force_push_guard/test_detector.py +143 -0
- code_muse/plugins/frontend_emitter/__init__.py +25 -0
- code_muse/plugins/frontend_emitter/emitter.py +121 -0
- code_muse/plugins/frontend_emitter/register_callbacks.py +259 -0
- code_muse/plugins/gac/__init__.py +4 -0
- code_muse/plugins/gac/git_ops.py +136 -0
- code_muse/plugins/gac/prompt.py +191 -0
- code_muse/plugins/gac/register_callbacks.py +82 -0
- code_muse/plugins/hook_creator/__init__.py +1 -0
- code_muse/plugins/hook_creator/register_callbacks.py +34 -0
- code_muse/plugins/hook_manager/__init__.py +1 -0
- code_muse/plugins/hook_manager/config.py +289 -0
- code_muse/plugins/hook_manager/hooks_menu.py +563 -0
- code_muse/plugins/hook_manager/register_callbacks.py +227 -0
- code_muse/plugins/hook_monitor/register_callbacks.py +36 -0
- code_muse/plugins/mindpack/__init__.py +0 -0
- code_muse/plugins/mindpack/factory.py +930 -0
- code_muse/plugins/mindpack/judge.py +573 -0
- code_muse/plugins/mindpack/memory.py +100 -0
- code_muse/plugins/mindpack/mindpack_menu.py +1552 -0
- code_muse/plugins/mindpack/orchestration.py +605 -0
- code_muse/plugins/mindpack/register_callbacks.py +175 -0
- code_muse/plugins/mindpack/schemas.py +358 -0
- code_muse/plugins/mindpack/tools.py +387 -0
- code_muse/plugins/oauth_muse_html.py +226 -0
- code_muse/plugins/ollama_setup/__init__.py +5 -0
- code_muse/plugins/ollama_setup/completer.py +36 -0
- code_muse/plugins/ollama_setup/register_callbacks.py +410 -0
- code_muse/plugins/plan_command/__init__.py +0 -0
- code_muse/plugins/plan_command/register_callbacks.py +206 -0
- code_muse/plugins/plan_mode/__init__.py +37 -0
- code_muse/plugins/plan_mode/mode_cycling.py +40 -0
- code_muse/plugins/plan_mode/plan_generation.py +68 -0
- code_muse/plugins/plan_mode/plan_hooks.py +74 -0
- code_muse/plugins/plan_mode/plan_mode_tools.py +138 -0
- code_muse/plugins/plan_mode/register_callbacks.py +121 -0
- code_muse/plugins/plugin_trust/register_callbacks.py +140 -0
- code_muse/plugins/policy_engine/__init__.py +46 -0
- code_muse/plugins/policy_engine/approval_flow_integration.py +59 -0
- code_muse/plugins/policy_engine/policy_evaluator.py +75 -0
- code_muse/plugins/policy_engine/policy_file_discovery.py +90 -0
- code_muse/plugins/policy_engine/policy_toml_schema.py +115 -0
- code_muse/plugins/policy_engine/register_callbacks.py +112 -0
- code_muse/plugins/pop_command/__init__.py +1 -0
- code_muse/plugins/pop_command/register_callbacks.py +189 -0
- code_muse/plugins/prompt_newline/__init__.py +13 -0
- code_muse/plugins/prompt_newline/config.py +19 -0
- code_muse/plugins/prompt_newline/register_callbacks.py +159 -0
- code_muse/plugins/safety_status/__init__.py +0 -0
- code_muse/plugins/safety_status/register_callbacks.py +113 -0
- code_muse/plugins/semantic_compression/__init__.py +6 -0
- code_muse/plugins/semantic_compression/compressor.py +295 -0
- code_muse/plugins/semantic_compression/config.py +123 -0
- code_muse/plugins/semantic_compression/register_callbacks.py +320 -0
- code_muse/plugins/shell_minimizer/__init__.py +50 -0
- code_muse/plugins/shell_minimizer/builtin_filters.toml +393 -0
- code_muse/plugins/shell_minimizer/pipeline.py +556 -0
- code_muse/plugins/shell_minimizer/primitives.py +482 -0
- code_muse/plugins/shell_minimizer/register_callbacks.py +276 -0
- code_muse/plugins/shell_safety/__init__.py +6 -0
- code_muse/plugins/shell_safety/agent_shell_safety.py +69 -0
- code_muse/plugins/shell_safety/command_cache.py +149 -0
- code_muse/plugins/shell_safety/register_callbacks.py +202 -0
- code_muse/plugins/synthetic_status/__init__.py +1 -0
- code_muse/plugins/synthetic_status/register_callbacks.py +128 -0
- code_muse/plugins/synthetic_status/status_api.py +145 -0
- code_muse/plugins/token_caching/__init__.py +21 -0
- code_muse/plugins/token_caching/cache_hit_tracking.py +128 -0
- code_muse/plugins/token_caching/cacheable_prefix_detection.py +28 -0
- code_muse/plugins/token_caching/register_callbacks.py +54 -0
- code_muse/plugins/token_caching/stats_display.py +35 -0
- code_muse/plugins/token_tracking/__init__.py +26 -0
- code_muse/plugins/token_tracking/database.py +381 -0
- code_muse/plugins/token_tracking/edit_analyzer.py +97 -0
- code_muse/plugins/token_tracking/record.py +55 -0
- code_muse/plugins/token_tracking/register_callbacks.py +277 -0
- code_muse/plugins/token_tracking/reports.py +329 -0
- code_muse/plugins/universal_constructor/__init__.py +13 -0
- code_muse/plugins/universal_constructor/models.py +136 -0
- code_muse/plugins/universal_constructor/register_callbacks.py +47 -0
- code_muse/plugins/universal_constructor/registry.py +390 -0
- code_muse/plugins/universal_constructor/runner.py +474 -0
- code_muse/plugins/universal_constructor/safety.py +440 -0
- code_muse/plugins/universal_constructor/sandbox.py +584 -0
- code_muse/provider_identity.py +105 -0
- code_muse/pydantic_patches.py +410 -0
- code_muse/reopenable_async_client.py +233 -0
- code_muse/round_robin_model.py +151 -0
- code_muse/secret_storage.py +74 -0
- code_muse/security/__init__.py +1 -0
- code_muse/security/redaction.cpython-314-darwin.so +0 -0
- code_muse/security/redaction.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/security/redaction.pyx +135 -0
- code_muse/session_storage.py +565 -0
- code_muse/status_display.py +261 -0
- code_muse/stream_parser/__init__.py +76 -0
- code_muse/stream_parser/assistant_text_parser.py +90 -0
- code_muse/stream_parser/citation_parser.py +76 -0
- code_muse/stream_parser/inline_hidden_tag_parser.py +236 -0
- code_muse/stream_parser/proposed_plan_parser.py +158 -0
- code_muse/stream_parser/stream_text_chunk.py +23 -0
- code_muse/stream_parser/stream_text_parser.py +27 -0
- code_muse/stream_parser/tagged_line_parser.cpython-314-darwin.so +0 -0
- code_muse/stream_parser/tagged_line_parser.pyx +251 -0
- code_muse/stream_parser/utf8_stream_parser.cpython-314-darwin.so +0 -0
- code_muse/stream_parser/utf8_stream_parser.pyx +206 -0
- code_muse/summarization_agent.py +308 -0
- code_muse/terminal_utils.cpython-314-darwin.so +0 -0
- code_muse/terminal_utils.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/terminal_utils.pyx +483 -0
- code_muse/tools/__init__.py +459 -0
- code_muse/tools/agent_tools.py +613 -0
- code_muse/tools/ask_user_question/__init__.py +26 -0
- code_muse/tools/ask_user_question/constants.py +73 -0
- code_muse/tools/ask_user_question/demo_tui.py +55 -0
- code_muse/tools/ask_user_question/handler.py +232 -0
- code_muse/tools/ask_user_question/models.py +302 -0
- code_muse/tools/ask_user_question/registration.py +37 -0
- code_muse/tools/ask_user_question/renderers.py +336 -0
- code_muse/tools/ask_user_question/terminal_ui.py +327 -0
- code_muse/tools/ask_user_question/theme.py +156 -0
- code_muse/tools/ask_user_question/tui_loop.py +422 -0
- code_muse/tools/background_jobs.py +99 -0
- code_muse/tools/browser/__init__.py +37 -0
- code_muse/tools/browser/browser_control.py +289 -0
- code_muse/tools/browser/browser_interactions.py +545 -0
- code_muse/tools/browser/browser_locators.py +640 -0
- code_muse/tools/browser/browser_manager.py +376 -0
- code_muse/tools/browser/browser_navigation.py +251 -0
- code_muse/tools/browser/browser_screenshot.py +180 -0
- code_muse/tools/browser/browser_scripts.py +462 -0
- code_muse/tools/browser/browser_workflows.py +222 -0
- code_muse/tools/chrome_cdp/__init__.py +1070 -0
- code_muse/tools/chrome_cdp/register_callbacks.py +61 -0
- code_muse/tools/command_runner.py +1401 -0
- code_muse/tools/common.py +1407 -0
- code_muse/tools/display.py +87 -0
- code_muse/tools/file_modifications.py +1099 -0
- code_muse/tools/file_operations.py +860 -0
- code_muse/tools/image_tools.py +185 -0
- code_muse/tools/meetin_proxy/__init__.py +243 -0
- code_muse/tools/meetin_proxy/capture_addon.py +82 -0
- code_muse/tools/meetin_proxy/proxy_manager.py +326 -0
- code_muse/tools/meetin_proxy/register_callbacks.py +45 -0
- code_muse/tools/path_policy.py +219 -0
- code_muse/tools/skills_tools.py +586 -0
- code_muse/tools/subagent_context.py +158 -0
- code_muse/tools/tools_content.py +50 -0
- code_muse/tools/universal_constructor.py +965 -0
- code_muse/uvx_detection.py +241 -0
- code_muse/version_checker.py +86 -0
- code_muse-0.0.1.data/data/code_muse/models.json +66 -0
- code_muse-0.0.1.data/data/code_muse/models_dev_api.json +1 -0
- code_muse-0.0.1.dist-info/METADATA +845 -0
- code_muse-0.0.1.dist-info/RECORD +394 -0
- code_muse-0.0.1.dist-info/WHEEL +4 -0
- code_muse-0.0.1.dist-info/entry_points.txt +2 -0
- code_muse-0.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command execution engine for hooks.
|
|
3
|
+
|
|
4
|
+
Handles async command execution with timeout, variable substitution,
|
|
5
|
+
and comprehensive error handling.
|
|
6
|
+
|
|
7
|
+
Claude Code Hook Compatibility:
|
|
8
|
+
- Input is passed via STDIN as JSON (primary method, Claude Code standard)
|
|
9
|
+
- Input is also available via CLAUDE_TOOL_INPUT env var (legacy/convenience)
|
|
10
|
+
- Exit code 0 => success, stdout shown in transcript
|
|
11
|
+
- Exit code 1 => block the operation (stderr used as reason)
|
|
12
|
+
- Exit code 2 => error feedback to Claude (stderr fed back as tool error)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
import json
|
|
17
|
+
import logging
|
|
18
|
+
import os
|
|
19
|
+
import re
|
|
20
|
+
import time
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
from .matcher import _extract_file_path
|
|
24
|
+
from .models import EventData, ExecutionResult, HookConfig
|
|
25
|
+
from .trust import build_minimal_hook_env, cap_hook_output
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
# Max output from hooks to feed into model context
|
|
30
|
+
_MAX_HOOK_STDOUT = 4096
|
|
31
|
+
_MAX_HOOK_STDERR = 2048
|
|
32
|
+
_MAX_HOOK_STDOUT_LINES = 256
|
|
33
|
+
_MAX_HOOK_STDERR_LINES = 128
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _build_stdin_payload(event_data: EventData) -> bytes:
|
|
37
|
+
"""
|
|
38
|
+
Build the JSON payload sent to hook scripts via stdin.
|
|
39
|
+
|
|
40
|
+
Matches the Claude Code hook input format:
|
|
41
|
+
{
|
|
42
|
+
"session_id": "...",
|
|
43
|
+
"hook_event_name": "PreToolUse",
|
|
44
|
+
"tool_name": "Bash",
|
|
45
|
+
"tool_input": { ... },
|
|
46
|
+
"cwd": "/path/to/project",
|
|
47
|
+
"permission_mode": "default"
|
|
48
|
+
}
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def _make_serializable(obj: Any) -> Any:
|
|
52
|
+
if isinstance(obj, dict):
|
|
53
|
+
return {k: _make_serializable(v) for k, v in obj.items()}
|
|
54
|
+
if isinstance(obj, (list, tuple)):
|
|
55
|
+
return [_make_serializable(v) for v in obj]
|
|
56
|
+
if isinstance(obj, (str, int, float, bool, type(None))):
|
|
57
|
+
return obj
|
|
58
|
+
try:
|
|
59
|
+
return str(obj)
|
|
60
|
+
except Exception:
|
|
61
|
+
return "<unserializable>"
|
|
62
|
+
|
|
63
|
+
payload = {
|
|
64
|
+
"session_id": event_data.context.get("session_id", "muse-session"),
|
|
65
|
+
"hook_event_name": event_data.event_type,
|
|
66
|
+
"tool_name": event_data.tool_name,
|
|
67
|
+
"tool_input": _make_serializable(event_data.tool_args),
|
|
68
|
+
"cwd": os.getcwd(),
|
|
69
|
+
"permission_mode": "default",
|
|
70
|
+
}
|
|
71
|
+
if "result" in event_data.context:
|
|
72
|
+
payload["tool_result"] = _make_serializable(event_data.context["result"])
|
|
73
|
+
if "duration_ms" in event_data.context:
|
|
74
|
+
payload["tool_duration_ms"] = event_data.context["duration_ms"]
|
|
75
|
+
|
|
76
|
+
return json.dumps(payload, ensure_ascii=False).encode("utf-8")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
async def execute_hook(
|
|
80
|
+
hook: HookConfig,
|
|
81
|
+
event_data: EventData,
|
|
82
|
+
env_vars: dict[str, str | None] = None,
|
|
83
|
+
) -> ExecutionResult:
|
|
84
|
+
"""
|
|
85
|
+
Execute a hook command with timeout and variable substitution.
|
|
86
|
+
|
|
87
|
+
Trust enforcement:
|
|
88
|
+
- Project hooks (source="project") that are not trusted are blocked
|
|
89
|
+
with a clear skipped/blocked result.
|
|
90
|
+
|
|
91
|
+
Input to the hook script:
|
|
92
|
+
- stdin: JSON object (Claude Code compatible format)
|
|
93
|
+
- env CLAUDE_TOOL_INPUT: JSON string of tool_args (legacy)
|
|
94
|
+
- env CLAUDE_PROJECT_DIR: current working directory
|
|
95
|
+
|
|
96
|
+
Exit code semantics:
|
|
97
|
+
- 0: success (stdout shown in transcript)
|
|
98
|
+
- 1: block operation (stderr becomes block reason)
|
|
99
|
+
- 2: error feedback to Claude without blocking
|
|
100
|
+
"""
|
|
101
|
+
# Trust check: project hooks must be explicitly trusted
|
|
102
|
+
if hook.source == "project" and not hook.trusted:
|
|
103
|
+
return ExecutionResult(
|
|
104
|
+
blocked=True,
|
|
105
|
+
hook_command=hook.command,
|
|
106
|
+
stdout="",
|
|
107
|
+
stderr="Hook blocked: project hook not trusted. Run /trust-hooks or approve in settings.",
|
|
108
|
+
exit_code=1,
|
|
109
|
+
duration_ms=0.0,
|
|
110
|
+
error="Project hook blocked: not explicitly trusted",
|
|
111
|
+
hook_id=hook.id,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
if hook.type == "prompt":
|
|
115
|
+
return ExecutionResult(
|
|
116
|
+
blocked=False,
|
|
117
|
+
hook_command=hook.command,
|
|
118
|
+
stdout=hook.command,
|
|
119
|
+
exit_code=0,
|
|
120
|
+
duration_ms=0.0,
|
|
121
|
+
hook_id=hook.id,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
command = _substitute_variables(hook.command, event_data, env_vars or {})
|
|
125
|
+
stdin_payload = _build_stdin_payload(event_data)
|
|
126
|
+
start_time = time.perf_counter()
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
env = _build_environment(event_data, env_vars, hook_source=hook.source)
|
|
130
|
+
|
|
131
|
+
proc = await asyncio.create_subprocess_shell(
|
|
132
|
+
command,
|
|
133
|
+
stdin=asyncio.subprocess.PIPE,
|
|
134
|
+
stdout=asyncio.subprocess.PIPE,
|
|
135
|
+
stderr=asyncio.subprocess.PIPE,
|
|
136
|
+
cwd=os.getcwd(),
|
|
137
|
+
env=env,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
stdout, stderr = await asyncio.wait_for(
|
|
142
|
+
proc.communicate(input=stdin_payload),
|
|
143
|
+
timeout=hook.timeout / 1000.0,
|
|
144
|
+
)
|
|
145
|
+
except TimeoutError:
|
|
146
|
+
try:
|
|
147
|
+
proc.kill()
|
|
148
|
+
await proc.wait()
|
|
149
|
+
except Exception:
|
|
150
|
+
pass
|
|
151
|
+
|
|
152
|
+
duration_ms = (time.perf_counter() - start_time) * 1000
|
|
153
|
+
return ExecutionResult(
|
|
154
|
+
blocked=True,
|
|
155
|
+
hook_command=command,
|
|
156
|
+
stdout="",
|
|
157
|
+
stderr=f"Command timed out after {hook.timeout}ms",
|
|
158
|
+
exit_code=-1,
|
|
159
|
+
duration_ms=duration_ms,
|
|
160
|
+
error=f"Hook execution timed out after {hook.timeout}ms",
|
|
161
|
+
hook_id=hook.id,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
duration_ms = (time.perf_counter() - start_time) * 1000
|
|
165
|
+
stdout_str = stdout.decode("utf-8", errors="replace") if stdout else ""
|
|
166
|
+
stderr_str = stderr.decode("utf-8", errors="replace") if stderr else ""
|
|
167
|
+
|
|
168
|
+
# Cap output to prevent unbounded model context growth
|
|
169
|
+
stdout_str = cap_hook_output(
|
|
170
|
+
stdout_str, _MAX_HOOK_STDOUT, _MAX_HOOK_STDOUT_LINES
|
|
171
|
+
)
|
|
172
|
+
stderr_str = cap_hook_output(
|
|
173
|
+
stderr_str, _MAX_HOOK_STDERR, _MAX_HOOK_STDERR_LINES
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
exit_code = proc.returncode or 0
|
|
177
|
+
|
|
178
|
+
blocked = exit_code == 1
|
|
179
|
+
error = stderr_str if exit_code != 0 and stderr_str else None
|
|
180
|
+
|
|
181
|
+
return ExecutionResult(
|
|
182
|
+
blocked=blocked,
|
|
183
|
+
hook_command=command,
|
|
184
|
+
stdout=stdout_str,
|
|
185
|
+
stderr=stderr_str,
|
|
186
|
+
exit_code=exit_code,
|
|
187
|
+
duration_ms=duration_ms,
|
|
188
|
+
error=error,
|
|
189
|
+
hook_id=hook.id,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
except Exception as e:
|
|
193
|
+
duration_ms = (time.perf_counter() - start_time) * 1000
|
|
194
|
+
logger.error(f"Hook execution failed: {e}", exc_info=True)
|
|
195
|
+
return ExecutionResult(
|
|
196
|
+
blocked=False,
|
|
197
|
+
hook_command=command,
|
|
198
|
+
stdout="",
|
|
199
|
+
stderr=str(e),
|
|
200
|
+
exit_code=-1,
|
|
201
|
+
duration_ms=duration_ms,
|
|
202
|
+
error=f"Hook execution error: {e}",
|
|
203
|
+
hook_id=hook.id,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _substitute_variables(
|
|
208
|
+
command: str,
|
|
209
|
+
event_data: EventData,
|
|
210
|
+
env_vars: dict[str, str],
|
|
211
|
+
) -> str:
|
|
212
|
+
substitutions = {
|
|
213
|
+
"CLAUDE_PROJECT_DIR": os.getcwd(),
|
|
214
|
+
"tool_name": event_data.tool_name,
|
|
215
|
+
"event_type": event_data.event_type,
|
|
216
|
+
"file": _extract_file_path(event_data.tool_args) or "",
|
|
217
|
+
"CLAUDE_TOOL_INPUT": json.dumps(event_data.tool_args),
|
|
218
|
+
}
|
|
219
|
+
if event_data.context:
|
|
220
|
+
if "result" in event_data.context:
|
|
221
|
+
substitutions["result"] = str(event_data.context["result"])
|
|
222
|
+
if "duration_ms" in event_data.context:
|
|
223
|
+
substitutions["duration_ms"] = str(event_data.context["duration_ms"])
|
|
224
|
+
substitutions.update(env_vars)
|
|
225
|
+
|
|
226
|
+
result = command
|
|
227
|
+
for var, value in substitutions.items():
|
|
228
|
+
result = result.replace(f"${{{var}}}", str(value))
|
|
229
|
+
result = re.sub(
|
|
230
|
+
rf"\${re.escape(var)}(?=\W|$)", lambda m, value=value: str(value), result
|
|
231
|
+
)
|
|
232
|
+
return result
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _build_environment(
|
|
236
|
+
event_data: EventData,
|
|
237
|
+
env_vars: dict[str, str | None] = None,
|
|
238
|
+
hook_source: str = "global",
|
|
239
|
+
) -> dict[str, str]:
|
|
240
|
+
"""Build environment for hook execution.
|
|
241
|
+
|
|
242
|
+
Project hooks receive a stripped-down environment with secret-like
|
|
243
|
+
variables removed. Global hooks receive the full environment.
|
|
244
|
+
"""
|
|
245
|
+
env = build_minimal_hook_env() if hook_source == "project" else os.environ.copy()
|
|
246
|
+
|
|
247
|
+
env["CLAUDE_PROJECT_DIR"] = os.getcwd()
|
|
248
|
+
env["CLAUDE_TOOL_INPUT"] = json.dumps(event_data.tool_args)
|
|
249
|
+
env["CLAUDE_TOOL_NAME"] = event_data.tool_name
|
|
250
|
+
env["CLAUDE_HOOK_EVENT"] = event_data.event_type
|
|
251
|
+
env["CLAUDE_CODE_HOOK"] = "1"
|
|
252
|
+
|
|
253
|
+
file_path = _extract_file_path(event_data.tool_args)
|
|
254
|
+
if file_path:
|
|
255
|
+
env["CLAUDE_FILE_PATH"] = file_path
|
|
256
|
+
|
|
257
|
+
if env_vars:
|
|
258
|
+
env.update(env_vars)
|
|
259
|
+
return env
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
async def execute_hooks_parallel(
|
|
263
|
+
hooks: list[HookConfig],
|
|
264
|
+
event_data: EventData,
|
|
265
|
+
env_vars: dict[str, str | None] = None,
|
|
266
|
+
) -> list[ExecutionResult]:
|
|
267
|
+
if not hooks:
|
|
268
|
+
return []
|
|
269
|
+
tg_tasks: list[asyncio.Task[ExecutionResult]] = []
|
|
270
|
+
try:
|
|
271
|
+
async with asyncio.TaskGroup() as tg:
|
|
272
|
+
for hook in hooks:
|
|
273
|
+
tg_tasks.append(
|
|
274
|
+
tg.create_task(execute_hook(hook, event_data, env_vars))
|
|
275
|
+
)
|
|
276
|
+
except* Exception:
|
|
277
|
+
pass # exceptions captured per-task below
|
|
278
|
+
|
|
279
|
+
final_results = []
|
|
280
|
+
for i, task in enumerate(tg_tasks):
|
|
281
|
+
exc = task.exception()
|
|
282
|
+
if exc is not None:
|
|
283
|
+
final_results.append(
|
|
284
|
+
ExecutionResult(
|
|
285
|
+
blocked=False,
|
|
286
|
+
hook_command=hooks[i].command,
|
|
287
|
+
stdout="",
|
|
288
|
+
stderr=str(exc),
|
|
289
|
+
exit_code=-1,
|
|
290
|
+
duration_ms=0.0,
|
|
291
|
+
error=f"Hook execution failed: {exc}",
|
|
292
|
+
hook_id=hooks[i].id,
|
|
293
|
+
)
|
|
294
|
+
)
|
|
295
|
+
else:
|
|
296
|
+
final_results.append(task.result())
|
|
297
|
+
return final_results
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
async def execute_hooks_sequential(
|
|
301
|
+
hooks: list[HookConfig],
|
|
302
|
+
event_data: EventData,
|
|
303
|
+
env_vars: dict[str, str | None] = None,
|
|
304
|
+
stop_on_block: bool = True,
|
|
305
|
+
) -> list[ExecutionResult]:
|
|
306
|
+
results = []
|
|
307
|
+
for hook in hooks:
|
|
308
|
+
result = await execute_hook(hook, event_data, env_vars)
|
|
309
|
+
results.append(result)
|
|
310
|
+
if stop_on_block and result.blocked:
|
|
311
|
+
logger.debug(f"Hook blocked operation, stopping: {hook.command}")
|
|
312
|
+
break
|
|
313
|
+
return results
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def get_blocking_result(results: list[ExecutionResult]) -> ExecutionResult | None:
|
|
317
|
+
for result in results:
|
|
318
|
+
if result.blocked:
|
|
319
|
+
return result
|
|
320
|
+
return None
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def get_failed_results(results: list[ExecutionResult]) -> list[ExecutionResult]:
|
|
324
|
+
return [result for result in results if not result.success]
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def format_execution_summary(results: list[ExecutionResult]) -> str:
|
|
328
|
+
if not results:
|
|
329
|
+
return "No hooks executed"
|
|
330
|
+
total = len(results)
|
|
331
|
+
successful = sum(1 for r in results if r.success)
|
|
332
|
+
blocked = sum(1 for r in results if r.blocked)
|
|
333
|
+
total_duration = sum(r.duration_ms for r in results)
|
|
334
|
+
summary = [
|
|
335
|
+
f"Executed {total} hook(s)",
|
|
336
|
+
f"Successful: {successful}",
|
|
337
|
+
f"Blocked: {blocked}",
|
|
338
|
+
f"Total duration: {total_duration:.2f}ms",
|
|
339
|
+
]
|
|
340
|
+
if blocked > 0:
|
|
341
|
+
blocking_hooks = [r for r in results if r.blocked]
|
|
342
|
+
summary.append("\nBlocking hooks:")
|
|
343
|
+
for result in blocking_hooks:
|
|
344
|
+
summary.append(f" - {result.hook_command}")
|
|
345
|
+
if result.error:
|
|
346
|
+
summary.append(f" Error: {result.error}")
|
|
347
|
+
return "\n".join(summary)
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pattern matching engine for hook filters.
|
|
3
|
+
|
|
4
|
+
Provides flexible pattern matching to determine if a hook should execute
|
|
5
|
+
based on tool name, arguments, and other event data.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from .aliases import get_aliases
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def matches(matcher: str, tool_name: str, tool_args: dict[str, Any]) -> bool:
|
|
15
|
+
"""
|
|
16
|
+
Evaluate if a matcher pattern matches the tool call.
|
|
17
|
+
|
|
18
|
+
Matcher Syntax:
|
|
19
|
+
- "*" - Matches all tools
|
|
20
|
+
- "ToolName" - Exact tool name match
|
|
21
|
+
- ".ext" - File extension match (e.g., ".py", ".ts")
|
|
22
|
+
- "Pattern1 && Pattern2" - AND condition (all must match)
|
|
23
|
+
- "Pattern1 || Pattern2" - OR condition (any must match)
|
|
24
|
+
"""
|
|
25
|
+
if not matcher:
|
|
26
|
+
return False
|
|
27
|
+
|
|
28
|
+
if matcher.strip() == "*":
|
|
29
|
+
return True
|
|
30
|
+
|
|
31
|
+
if "||" in matcher:
|
|
32
|
+
parts = [p.strip() for p in matcher.split("||")]
|
|
33
|
+
return any(matches(part, tool_name, tool_args) for part in parts)
|
|
34
|
+
|
|
35
|
+
if "&&" in matcher:
|
|
36
|
+
parts = [p.strip() for p in matcher.split("&&")]
|
|
37
|
+
return all(matches(part, tool_name, tool_args) for part in parts)
|
|
38
|
+
|
|
39
|
+
return _match_single(matcher.strip(), tool_name, tool_args)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _match_single(pattern: str, tool_name: str, tool_args: dict[str, Any]) -> bool:
|
|
43
|
+
if pattern == tool_name:
|
|
44
|
+
return True
|
|
45
|
+
|
|
46
|
+
if pattern.lower() == tool_name.lower():
|
|
47
|
+
return True
|
|
48
|
+
|
|
49
|
+
# Check cross-provider aliases: a hook written for "Bash" (Claude Code) should
|
|
50
|
+
# fire when code_muse calls "agent_run_shell_command", and vice-versa.
|
|
51
|
+
tool_aliases = get_aliases(tool_name)
|
|
52
|
+
pattern_aliases = get_aliases(pattern)
|
|
53
|
+
if tool_aliases & pattern_aliases: # non-empty intersection → same logical tool
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
if pattern.startswith("."):
|
|
57
|
+
file_path = _extract_file_path(tool_args)
|
|
58
|
+
if file_path:
|
|
59
|
+
return file_path.endswith(pattern)
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
if "*" in pattern:
|
|
63
|
+
parts = pattern.split("*")
|
|
64
|
+
regex_pattern = ".*".join(re.escape(part) for part in parts)
|
|
65
|
+
if re.match(f"^{regex_pattern}$", tool_name, re.IGNORECASE):
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
if _is_regex_pattern(pattern):
|
|
69
|
+
try:
|
|
70
|
+
if re.search(pattern, tool_name, re.IGNORECASE):
|
|
71
|
+
return True
|
|
72
|
+
file_path = _extract_file_path(tool_args)
|
|
73
|
+
if file_path and re.search(pattern, file_path, re.IGNORECASE):
|
|
74
|
+
return True
|
|
75
|
+
except re.error:
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _extract_file_path(tool_args: dict[str, Any]) -> str | None:
|
|
82
|
+
file_keys = [
|
|
83
|
+
"file_path",
|
|
84
|
+
"file",
|
|
85
|
+
"path",
|
|
86
|
+
"target",
|
|
87
|
+
"input_file",
|
|
88
|
+
"output_file",
|
|
89
|
+
"source",
|
|
90
|
+
"destination",
|
|
91
|
+
"src",
|
|
92
|
+
"dest",
|
|
93
|
+
"filename",
|
|
94
|
+
]
|
|
95
|
+
for key in file_keys:
|
|
96
|
+
if key in tool_args:
|
|
97
|
+
value = tool_args[key]
|
|
98
|
+
if isinstance(value, str):
|
|
99
|
+
return value
|
|
100
|
+
if hasattr(value, "__fspath__"):
|
|
101
|
+
return str(value)
|
|
102
|
+
for value in tool_args.values():
|
|
103
|
+
if isinstance(value, str) and _looks_like_file_path(value):
|
|
104
|
+
return value
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _looks_like_file_path(value: str) -> bool:
|
|
109
|
+
if not value:
|
|
110
|
+
return False
|
|
111
|
+
if "." in value and not value.startswith("."):
|
|
112
|
+
parts = value.rsplit(".", 1)
|
|
113
|
+
if len(parts) == 2 and len(parts[1]) <= 10 and parts[1].isalnum():
|
|
114
|
+
return True
|
|
115
|
+
return bool("/" in value or "\\" in value)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _is_regex_pattern(pattern: str) -> bool:
|
|
119
|
+
regex_chars = ["^", "$", ".", "+", "?", "[", "]", "(", ")", "{", "}", "|", "\\"]
|
|
120
|
+
return any(char in pattern for char in regex_chars)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def extract_file_extension(file_path: str) -> str | None:
|
|
124
|
+
if not file_path or "." not in file_path:
|
|
125
|
+
return None
|
|
126
|
+
if "/" in file_path:
|
|
127
|
+
file_path = file_path.rsplit("/", 1)[-1]
|
|
128
|
+
if "\\" in file_path:
|
|
129
|
+
file_path = file_path.rsplit("\\", 1)[-1]
|
|
130
|
+
if "." in file_path:
|
|
131
|
+
return "." + file_path.rsplit(".", 1)[-1]
|
|
132
|
+
return None
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def matches_tool(tool_name: str, *names: str) -> bool:
|
|
136
|
+
return tool_name.lower() in [name.lower() for name in names]
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def matches_file_extension(tool_args: dict[str, Any], *extensions: str) -> bool:
|
|
140
|
+
file_path = _extract_file_path(tool_args)
|
|
141
|
+
if not file_path:
|
|
142
|
+
return False
|
|
143
|
+
ext = extract_file_extension(file_path)
|
|
144
|
+
return ext in extensions
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def matches_file_pattern(tool_args: dict[str, Any], pattern: str) -> bool:
|
|
148
|
+
file_path = _extract_file_path(tool_args)
|
|
149
|
+
if not file_path:
|
|
150
|
+
return False
|
|
151
|
+
try:
|
|
152
|
+
return bool(re.search(pattern, file_path, re.IGNORECASE))
|
|
153
|
+
except re.error:
|
|
154
|
+
return False
|