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,144 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration validation for hooks.
|
|
3
|
+
|
|
4
|
+
Validates hook configuration dictionaries and provides actionable error messages.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
VALID_EVENT_TYPES = [
|
|
13
|
+
"PreToolUse",
|
|
14
|
+
"PostToolUse",
|
|
15
|
+
"SessionStart",
|
|
16
|
+
"SessionEnd",
|
|
17
|
+
"PreCompact",
|
|
18
|
+
"UserPromptSubmit",
|
|
19
|
+
"Notification",
|
|
20
|
+
"Stop",
|
|
21
|
+
"SubagentStop",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
VALID_HOOK_TYPES = ["command", "prompt"]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def validate_hooks_config(config: dict[str, Any]) -> tuple[bool, list[str]]:
|
|
28
|
+
"""
|
|
29
|
+
Validate a hooks configuration dictionary.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Tuple of (is_valid, list_of_error_messages)
|
|
33
|
+
"""
|
|
34
|
+
errors: list[str] = []
|
|
35
|
+
|
|
36
|
+
if not isinstance(config, dict):
|
|
37
|
+
return False, ["Configuration must be a dictionary"]
|
|
38
|
+
|
|
39
|
+
for event_type, hook_groups in config.items():
|
|
40
|
+
if event_type.startswith("_"):
|
|
41
|
+
continue # skip comment keys
|
|
42
|
+
|
|
43
|
+
if event_type not in VALID_EVENT_TYPES:
|
|
44
|
+
errors.append(
|
|
45
|
+
f"Unknown event type '{event_type}'. "
|
|
46
|
+
f"Valid types: {', '.join(VALID_EVENT_TYPES)}"
|
|
47
|
+
)
|
|
48
|
+
continue
|
|
49
|
+
|
|
50
|
+
if not isinstance(hook_groups, list):
|
|
51
|
+
errors.append(f"'{event_type}' must be a list of hook groups")
|
|
52
|
+
continue
|
|
53
|
+
|
|
54
|
+
for i, group in enumerate(hook_groups):
|
|
55
|
+
if not isinstance(group, dict):
|
|
56
|
+
errors.append(
|
|
57
|
+
f"'{event_type}[{i}]' must be a dict with 'matcher' and 'hooks'"
|
|
58
|
+
)
|
|
59
|
+
continue
|
|
60
|
+
|
|
61
|
+
if "matcher" not in group:
|
|
62
|
+
errors.append(f"'{event_type}[{i}]' missing required field 'matcher'")
|
|
63
|
+
|
|
64
|
+
if "hooks" not in group:
|
|
65
|
+
errors.append(f"'{event_type}[{i}]' missing required field 'hooks'")
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
if not isinstance(group["hooks"], list):
|
|
69
|
+
errors.append(f"'{event_type}[{i}].hooks' must be a list")
|
|
70
|
+
continue
|
|
71
|
+
|
|
72
|
+
for j, hook in enumerate(group["hooks"]):
|
|
73
|
+
hook_errors = _validate_hook(event_type, i, j, hook)
|
|
74
|
+
errors.extend(hook_errors)
|
|
75
|
+
|
|
76
|
+
return len(errors) == 0, errors
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _validate_hook(
|
|
80
|
+
event_type: str, group_idx: int, hook_idx: int, hook: Any
|
|
81
|
+
) -> list[str]:
|
|
82
|
+
errors: list[str] = []
|
|
83
|
+
prefix = f"'{event_type}[{group_idx}].hooks[{hook_idx}]'"
|
|
84
|
+
|
|
85
|
+
if not isinstance(hook, dict):
|
|
86
|
+
return [f"{prefix} must be a dict"]
|
|
87
|
+
|
|
88
|
+
hook_type = hook.get("type")
|
|
89
|
+
if not hook_type:
|
|
90
|
+
errors.append(f"{prefix} missing required field 'type'")
|
|
91
|
+
elif hook_type not in VALID_HOOK_TYPES:
|
|
92
|
+
errors.append(
|
|
93
|
+
f"{prefix} invalid type '{hook_type}'. Must be one of: {', '.join(VALID_HOOK_TYPES)}"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if hook_type == "command" and not hook.get("command"):
|
|
97
|
+
errors.append(f"{prefix} missing required field 'command' for type 'command'")
|
|
98
|
+
elif hook_type == "prompt" and not hook.get("prompt") and not hook.get("command"):
|
|
99
|
+
errors.append(
|
|
100
|
+
f"{prefix} missing required field 'prompt' (or 'command') for type 'prompt'"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
timeout = hook.get("timeout")
|
|
104
|
+
if timeout is not None:
|
|
105
|
+
if not isinstance(timeout, (int, float)) or timeout < 100:
|
|
106
|
+
errors.append(f"{prefix} 'timeout' must be >= 100ms, got: {timeout}")
|
|
107
|
+
|
|
108
|
+
return errors
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def format_validation_report(
|
|
112
|
+
is_valid: bool, errors: list[str], suggestions: list[str | None] = None
|
|
113
|
+
) -> str:
|
|
114
|
+
lines = []
|
|
115
|
+
if is_valid:
|
|
116
|
+
lines.append("✓ Configuration is valid")
|
|
117
|
+
else:
|
|
118
|
+
lines.append(f"✗ Configuration has {len(errors)} error(s):")
|
|
119
|
+
for error in errors:
|
|
120
|
+
lines.append(f" • {error}")
|
|
121
|
+
|
|
122
|
+
if suggestions:
|
|
123
|
+
lines.append("\nSuggestions:")
|
|
124
|
+
for suggestion in suggestions:
|
|
125
|
+
lines.append(f" → {suggestion}")
|
|
126
|
+
|
|
127
|
+
return "\n".join(lines)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def get_config_suggestions(config: dict[str, Any], errors: list[str]) -> list[str]:
|
|
131
|
+
suggestions: list[str] = []
|
|
132
|
+
|
|
133
|
+
for error in errors:
|
|
134
|
+
if "Unknown event type" in error:
|
|
135
|
+
suggestions.append("Valid event types are: " + ", ".join(VALID_EVENT_TYPES))
|
|
136
|
+
break
|
|
137
|
+
|
|
138
|
+
if any("missing required field 'command'" in e for e in errors):
|
|
139
|
+
suggestions.append(
|
|
140
|
+
"Hook commands should be shell commands like: "
|
|
141
|
+
"'bash .claude/hooks/my-hook.sh'"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
return suggestions
|
code_muse/http_utils.py
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
"""
|
|
2
|
+
HTTP utilities module for Muse.
|
|
3
|
+
|
|
4
|
+
This module provides functions for creating properly configured HTTP clients.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import os
|
|
9
|
+
import socket
|
|
10
|
+
import time
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
import httpx
|
|
16
|
+
|
|
17
|
+
from code_muse.config import get_http2
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class ProxyConfig:
|
|
22
|
+
"""Configuration for proxy and SSL settings."""
|
|
23
|
+
|
|
24
|
+
verify: bool | str | None
|
|
25
|
+
trust_env: bool
|
|
26
|
+
proxy_url: str | None
|
|
27
|
+
disable_retry: bool
|
|
28
|
+
http2_enabled: bool
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _detect_proxy_url() -> str | None:
|
|
32
|
+
"""Return the first proxy URL found in environment variables."""
|
|
33
|
+
return (
|
|
34
|
+
os.environ.get("HTTPS_PROXY")
|
|
35
|
+
or os.environ.get("https_proxy")
|
|
36
|
+
or os.environ.get("HTTP_PROXY")
|
|
37
|
+
or os.environ.get("http_proxy")
|
|
38
|
+
or None
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _resolve_proxy_config(verify: bool | str | None = None) -> ProxyConfig:
|
|
43
|
+
"""Resolve proxy, SSL, and retry settings from environment.
|
|
44
|
+
|
|
45
|
+
This centralizes the logic for detecting proxies, determining SSL verification,
|
|
46
|
+
and checking if retry transport should be disabled.
|
|
47
|
+
"""
|
|
48
|
+
if verify is None:
|
|
49
|
+
verify = get_cert_bundle_path()
|
|
50
|
+
|
|
51
|
+
http2_enabled = get_http2()
|
|
52
|
+
|
|
53
|
+
disable_retry = os.environ.get(
|
|
54
|
+
"MUSE_DISABLE_RETRY_TRANSPORT", ""
|
|
55
|
+
).lower() in ("1", "true", "yes")
|
|
56
|
+
|
|
57
|
+
# When retry transport is disabled and no cert bundle is configured,
|
|
58
|
+
# also disable TLS verification (legacy compatibility). If a cert
|
|
59
|
+
# bundle is available, keep it — only bare disable_retry sets False.
|
|
60
|
+
if disable_retry and verify is None:
|
|
61
|
+
verify = False
|
|
62
|
+
|
|
63
|
+
# Explicit TLS disable — independent from retry logic
|
|
64
|
+
disable_tls_verify = os.environ.get(
|
|
65
|
+
"MUSE_DISABLE_TLS_VERIFY", ""
|
|
66
|
+
).lower() in ("1", "true", "yes")
|
|
67
|
+
if disable_tls_verify:
|
|
68
|
+
verify = False
|
|
69
|
+
|
|
70
|
+
proxy_url = _detect_proxy_url()
|
|
71
|
+
has_proxy = proxy_url is not None
|
|
72
|
+
|
|
73
|
+
# trust_env lets httpx read proxy settings from the environment.
|
|
74
|
+
# It should not affect SSL verification.
|
|
75
|
+
trust_env = has_proxy
|
|
76
|
+
|
|
77
|
+
return ProxyConfig(
|
|
78
|
+
verify=verify,
|
|
79
|
+
trust_env=trust_env,
|
|
80
|
+
proxy_url=proxy_url,
|
|
81
|
+
disable_retry=disable_retry,
|
|
82
|
+
http2_enabled=http2_enabled,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
from .reopenable_async_client import ReopenableAsyncClient
|
|
88
|
+
except ImportError:
|
|
89
|
+
ReopenableAsyncClient = None
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
from .messaging import emit_info, emit_warning
|
|
93
|
+
except ImportError:
|
|
94
|
+
# Fallback if messaging system is not available
|
|
95
|
+
def emit_info(content: str, **metadata):
|
|
96
|
+
pass # No-op if messaging system is not available
|
|
97
|
+
|
|
98
|
+
def emit_warning(content: str, **metadata):
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class RetryingAsyncClient(httpx.AsyncClient):
|
|
103
|
+
"""AsyncClient with built-in rate limit handling (429) and retries.
|
|
104
|
+
|
|
105
|
+
This replaces the Tenacity transport with a more direct subclass implementation,
|
|
106
|
+
which plays nicer with proxies and custom transports.
|
|
107
|
+
|
|
108
|
+
Special handling for Cerebras: Their Retry-After headers are absurdly aggressive
|
|
109
|
+
(often 60s), so we ignore them and use a 3s base backoff instead.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
def __init__(
|
|
113
|
+
self,
|
|
114
|
+
retry_status_codes: tuple = (429, 502, 503, 504),
|
|
115
|
+
max_retries: int = 5,
|
|
116
|
+
model_name: str = "",
|
|
117
|
+
**kwargs,
|
|
118
|
+
):
|
|
119
|
+
super().__init__(**kwargs)
|
|
120
|
+
self.retry_status_codes = retry_status_codes
|
|
121
|
+
self.max_retries = max_retries
|
|
122
|
+
self.model_name = model_name.lower() if model_name else ""
|
|
123
|
+
# Cerebras sends crazy aggressive Retry-After headers (60s), ignore them
|
|
124
|
+
self._ignore_retry_headers = "cerebras" in self.model_name
|
|
125
|
+
|
|
126
|
+
async def send(self, request: httpx.Request, **kwargs: Any) -> httpx.Response:
|
|
127
|
+
"""Send request with automatic retries for rate limits and server errors."""
|
|
128
|
+
last_response = None
|
|
129
|
+
last_exception = None
|
|
130
|
+
|
|
131
|
+
for attempt in range(self.max_retries + 1):
|
|
132
|
+
try:
|
|
133
|
+
response = await super().send(request, **kwargs)
|
|
134
|
+
last_response = response
|
|
135
|
+
|
|
136
|
+
# Check for retryable status
|
|
137
|
+
if response.status_code not in self.retry_status_codes:
|
|
138
|
+
return response
|
|
139
|
+
|
|
140
|
+
# Close response if we're going to retry
|
|
141
|
+
await response.aclose()
|
|
142
|
+
|
|
143
|
+
# Determine wait time - Cerebras gets special treatment
|
|
144
|
+
if self._ignore_retry_headers:
|
|
145
|
+
# Cerebras: 3s base with exponential backoff (3s, 6s, 12s...)
|
|
146
|
+
wait_time = 3.0 * (2**attempt)
|
|
147
|
+
else:
|
|
148
|
+
# Default exponential backoff: 1s, 2s, 4s...
|
|
149
|
+
wait_time = 1.0 * (2**attempt)
|
|
150
|
+
|
|
151
|
+
# Check Retry-After header (only for non-Cerebras)
|
|
152
|
+
retry_after = response.headers.get("Retry-After")
|
|
153
|
+
if retry_after:
|
|
154
|
+
try:
|
|
155
|
+
wait_time = float(retry_after)
|
|
156
|
+
except ValueError:
|
|
157
|
+
# Try parsing http-date
|
|
158
|
+
from email.utils import parsedate_to_datetime
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
date = parsedate_to_datetime(retry_after)
|
|
162
|
+
wait_time = date.timestamp() - time.time()
|
|
163
|
+
except Exception:
|
|
164
|
+
pass
|
|
165
|
+
|
|
166
|
+
# Cap wait time
|
|
167
|
+
wait_time = max(0.5, min(wait_time, 60.0))
|
|
168
|
+
|
|
169
|
+
if attempt < self.max_retries:
|
|
170
|
+
provider_note = (
|
|
171
|
+
" (ignoring header)" if self._ignore_retry_headers else ""
|
|
172
|
+
)
|
|
173
|
+
emit_info(
|
|
174
|
+
f"HTTP retry: {response.status_code} received{provider_note}. "
|
|
175
|
+
f"Waiting {wait_time:.1f}s (attempt {attempt + 1}/{self.max_retries})"
|
|
176
|
+
)
|
|
177
|
+
await asyncio.sleep(wait_time)
|
|
178
|
+
|
|
179
|
+
except (httpx.ConnectError, httpx.ReadTimeout, httpx.PoolTimeout) as e:
|
|
180
|
+
last_exception = e
|
|
181
|
+
wait_time = 1.0 * (2**attempt)
|
|
182
|
+
if attempt < self.max_retries:
|
|
183
|
+
emit_warning(
|
|
184
|
+
f"HTTP connection error: {e}. Retrying in {wait_time}s..."
|
|
185
|
+
)
|
|
186
|
+
await asyncio.sleep(wait_time)
|
|
187
|
+
else:
|
|
188
|
+
raise
|
|
189
|
+
except Exception:
|
|
190
|
+
raise
|
|
191
|
+
|
|
192
|
+
# Return last response (even if it's an error status)
|
|
193
|
+
if last_response:
|
|
194
|
+
return last_response
|
|
195
|
+
|
|
196
|
+
# Should catch this in loop, but just in case
|
|
197
|
+
if last_exception:
|
|
198
|
+
raise last_exception
|
|
199
|
+
|
|
200
|
+
return last_response
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def get_cert_bundle_path() -> str | None:
|
|
204
|
+
# First check if SSL_CERT_FILE environment variable is set
|
|
205
|
+
ssl_cert_file = os.environ.get("SSL_CERT_FILE")
|
|
206
|
+
if ssl_cert_file and Path(ssl_cert_file).exists():
|
|
207
|
+
return ssl_cert_file
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def create_client(
|
|
211
|
+
timeout: int = 180,
|
|
212
|
+
verify: bool | str = None,
|
|
213
|
+
headers: dict[str, str | None] = None,
|
|
214
|
+
retry_status_codes: tuple = (429, 502, 503, 504),
|
|
215
|
+
) -> httpx.Client:
|
|
216
|
+
if verify is None:
|
|
217
|
+
verify = get_cert_bundle_path()
|
|
218
|
+
|
|
219
|
+
# Check if HTTP/2 is enabled in config
|
|
220
|
+
http2_enabled = get_http2()
|
|
221
|
+
|
|
222
|
+
# If retry components are available, create a client with retry transport
|
|
223
|
+
# Note: TenacityTransport was removed. For now we just return a standard client.
|
|
224
|
+
# Future TODO: Implement RetryingClient(httpx.Client) if needed.
|
|
225
|
+
return httpx.Client(
|
|
226
|
+
verify=verify,
|
|
227
|
+
headers=headers or {},
|
|
228
|
+
timeout=timeout,
|
|
229
|
+
http2=http2_enabled,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def create_async_client(
|
|
234
|
+
timeout: int = 180,
|
|
235
|
+
verify: bool | str = None,
|
|
236
|
+
headers: dict[str, str | None] = None,
|
|
237
|
+
retry_status_codes: tuple = (429, 502, 503, 504),
|
|
238
|
+
model_name: str = "",
|
|
239
|
+
) -> httpx.AsyncClient:
|
|
240
|
+
config = _resolve_proxy_config(verify)
|
|
241
|
+
|
|
242
|
+
if not config.disable_retry:
|
|
243
|
+
return RetryingAsyncClient(
|
|
244
|
+
retry_status_codes=retry_status_codes,
|
|
245
|
+
model_name=model_name,
|
|
246
|
+
proxy=config.proxy_url,
|
|
247
|
+
verify=config.verify,
|
|
248
|
+
headers=headers or {},
|
|
249
|
+
timeout=timeout,
|
|
250
|
+
http2=config.http2_enabled,
|
|
251
|
+
trust_env=config.trust_env,
|
|
252
|
+
)
|
|
253
|
+
else:
|
|
254
|
+
return httpx.AsyncClient(
|
|
255
|
+
proxy=config.proxy_url,
|
|
256
|
+
verify=config.verify,
|
|
257
|
+
headers=headers or {},
|
|
258
|
+
timeout=timeout,
|
|
259
|
+
http2=config.http2_enabled,
|
|
260
|
+
trust_env=config.trust_env,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def create_httpx_client(
|
|
265
|
+
timeout: float = 5.0,
|
|
266
|
+
verify: bool | str = None,
|
|
267
|
+
headers: dict[str, str | None] = None,
|
|
268
|
+
) -> httpx.Client:
|
|
269
|
+
if verify is None:
|
|
270
|
+
verify = get_cert_bundle_path()
|
|
271
|
+
|
|
272
|
+
client = httpx.Client(verify=verify)
|
|
273
|
+
|
|
274
|
+
if headers:
|
|
275
|
+
client.headers.update(headers or {})
|
|
276
|
+
|
|
277
|
+
return client
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def create_auth_headers(
|
|
281
|
+
api_key: str, header_name: str = "Authorization"
|
|
282
|
+
) -> dict[str, str]:
|
|
283
|
+
return {header_name: f"Bearer {api_key}"}
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def resolve_env_var_in_header(headers: dict[str, str]) -> dict[str, str]:
|
|
287
|
+
resolved_headers = {}
|
|
288
|
+
|
|
289
|
+
for key, value in headers.items():
|
|
290
|
+
if isinstance(value, str):
|
|
291
|
+
try:
|
|
292
|
+
expanded = os.path.expandvars(value)
|
|
293
|
+
resolved_headers[key] = expanded
|
|
294
|
+
except Exception:
|
|
295
|
+
resolved_headers[key] = value
|
|
296
|
+
else:
|
|
297
|
+
resolved_headers[key] = value
|
|
298
|
+
|
|
299
|
+
return resolved_headers
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def create_reopenable_async_client(
|
|
303
|
+
timeout: int = 180,
|
|
304
|
+
verify: bool | str = None,
|
|
305
|
+
headers: dict[str, str | None] = None,
|
|
306
|
+
retry_status_codes: tuple = (429, 502, 503, 504),
|
|
307
|
+
model_name: str = "",
|
|
308
|
+
) -> ReopenableAsyncClient | httpx.AsyncClient:
|
|
309
|
+
config = _resolve_proxy_config(verify)
|
|
310
|
+
|
|
311
|
+
base_kwargs = {
|
|
312
|
+
"proxy": config.proxy_url,
|
|
313
|
+
"verify": config.verify,
|
|
314
|
+
"headers": headers or {},
|
|
315
|
+
"timeout": timeout,
|
|
316
|
+
"http2": config.http2_enabled,
|
|
317
|
+
"trust_env": config.trust_env,
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if ReopenableAsyncClient is not None:
|
|
321
|
+
client_class = (
|
|
322
|
+
RetryingAsyncClient if not config.disable_retry else httpx.AsyncClient
|
|
323
|
+
)
|
|
324
|
+
kwargs = {**base_kwargs, "client_class": client_class}
|
|
325
|
+
if not config.disable_retry:
|
|
326
|
+
kwargs["retry_status_codes"] = retry_status_codes
|
|
327
|
+
kwargs["model_name"] = model_name
|
|
328
|
+
return ReopenableAsyncClient(**kwargs)
|
|
329
|
+
else:
|
|
330
|
+
# Fallback to RetryingAsyncClient or plain AsyncClient
|
|
331
|
+
if not config.disable_retry:
|
|
332
|
+
return RetryingAsyncClient(
|
|
333
|
+
retry_status_codes=retry_status_codes,
|
|
334
|
+
model_name=model_name,
|
|
335
|
+
**base_kwargs,
|
|
336
|
+
)
|
|
337
|
+
else:
|
|
338
|
+
return httpx.AsyncClient(**base_kwargs)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def is_cert_bundle_available() -> bool:
|
|
342
|
+
cert_path = get_cert_bundle_path()
|
|
343
|
+
if cert_path is None:
|
|
344
|
+
return False
|
|
345
|
+
return Path(cert_path).exists() and Path(cert_path).is_file()
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def find_available_port(start_port=8090, end_port=9010, host="127.0.0.1"):
|
|
349
|
+
for port in range(start_port, end_port + 1):
|
|
350
|
+
try:
|
|
351
|
+
# Try to bind to the port to check if it's available.
|
|
352
|
+
# Do NOT set SO_REUSEADDR here — it would allow binding to
|
|
353
|
+
# ports already in use, defeating the availability check.
|
|
354
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
355
|
+
sock.bind((host, port))
|
|
356
|
+
return port
|
|
357
|
+
except OSError:
|
|
358
|
+
# Port is in use, try the next one
|
|
359
|
+
continue
|
|
360
|
+
return None
|
code_muse/keymap.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Keymap configuration for Muse.
|
|
2
|
+
|
|
3
|
+
This module handles configurable keyboard shortcuts, starting with the
|
|
4
|
+
cancel_agent_key feature that allows users to override Ctrl+C with a
|
|
5
|
+
different key for cancelling agent tasks.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# Character codes for Ctrl+letter combinations (Ctrl+A = 0x01, Ctrl+Z = 0x1A)
|
|
9
|
+
KEY_CODES: dict[str, str] = {
|
|
10
|
+
"ctrl+a": "\x01",
|
|
11
|
+
"ctrl+b": "\x02",
|
|
12
|
+
"ctrl+c": "\x03",
|
|
13
|
+
"ctrl+d": "\x04",
|
|
14
|
+
"ctrl+e": "\x05",
|
|
15
|
+
"ctrl+f": "\x06",
|
|
16
|
+
"ctrl+g": "\x07",
|
|
17
|
+
"ctrl+h": "\x08",
|
|
18
|
+
"ctrl+i": "\x09",
|
|
19
|
+
"ctrl+j": "\x0a",
|
|
20
|
+
"ctrl+k": "\x0b",
|
|
21
|
+
"ctrl+l": "\x0c",
|
|
22
|
+
"ctrl+m": "\x0d",
|
|
23
|
+
"ctrl+n": "\x0e",
|
|
24
|
+
"ctrl+o": "\x0f",
|
|
25
|
+
"ctrl+p": "\x10",
|
|
26
|
+
"ctrl+q": "\x11",
|
|
27
|
+
"ctrl+r": "\x12",
|
|
28
|
+
"ctrl+s": "\x13",
|
|
29
|
+
"ctrl+t": "\x14",
|
|
30
|
+
"ctrl+u": "\x15",
|
|
31
|
+
"ctrl+v": "\x16",
|
|
32
|
+
"ctrl+w": "\x17",
|
|
33
|
+
"ctrl+x": "\x18",
|
|
34
|
+
"ctrl+y": "\x19",
|
|
35
|
+
"ctrl+z": "\x1a",
|
|
36
|
+
"escape": "\x1b",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# Valid keys for cancel_agent_key configuration
|
|
40
|
+
# NOTE: "escape" is excluded because it conflicts with ANSI escape sequences
|
|
41
|
+
# (arrow keys, F-keys, etc. all start with \x1b)
|
|
42
|
+
VALID_CANCEL_KEYS: set[str] = {
|
|
43
|
+
"ctrl+c",
|
|
44
|
+
"ctrl+k",
|
|
45
|
+
"ctrl+q",
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
DEFAULT_CANCEL_AGENT_KEY: str = "ctrl+c"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class KeymapError(Exception):
|
|
52
|
+
"""Exception raised for keymap configuration errors."""
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_cancel_agent_key() -> str:
|
|
56
|
+
"""Get the configured cancel agent key from config.
|
|
57
|
+
|
|
58
|
+
On Windows when launched via uvx, this automatically returns "ctrl+k"
|
|
59
|
+
to work around uvx capturing Ctrl+C before it reaches Python.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
The key name (e.g., "ctrl+c", "ctrl+k") from config,
|
|
63
|
+
or the default if not configured.
|
|
64
|
+
"""
|
|
65
|
+
from code_muse.config import get_value
|
|
66
|
+
from code_muse.uvx_detection import should_use_alternate_cancel_key
|
|
67
|
+
|
|
68
|
+
# On Windows + uvx, force ctrl+k to bypass uvx's SIGINT capture
|
|
69
|
+
if should_use_alternate_cancel_key():
|
|
70
|
+
return "ctrl+k"
|
|
71
|
+
|
|
72
|
+
key = get_value("cancel_agent_key")
|
|
73
|
+
if key is None or key.strip() == "":
|
|
74
|
+
return DEFAULT_CANCEL_AGENT_KEY
|
|
75
|
+
return key.strip().lower()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def validate_cancel_agent_key() -> None:
|
|
79
|
+
"""Validate the configured cancel agent key.
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
KeymapError: If the configured key is invalid.
|
|
83
|
+
"""
|
|
84
|
+
key = get_cancel_agent_key()
|
|
85
|
+
if key not in VALID_CANCEL_KEYS:
|
|
86
|
+
valid_keys_str = ", ".join(sorted(VALID_CANCEL_KEYS))
|
|
87
|
+
raise KeymapError(
|
|
88
|
+
f"Invalid cancel_agent_key '{key}' in muse.cfg. "
|
|
89
|
+
f"Valid options are: {valid_keys_str}"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def cancel_agent_uses_signal() -> bool:
|
|
94
|
+
"""Check if the cancel agent key uses SIGINT (Ctrl+C).
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
True if the cancel key is ctrl+c (uses SIGINT handler),
|
|
98
|
+
False if it uses keyboard listener approach.
|
|
99
|
+
"""
|
|
100
|
+
return get_cancel_agent_key() == "ctrl+c"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def get_cancel_agent_char_code() -> str:
|
|
104
|
+
"""Get the character code for the cancel agent key.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
The character code (e.g., "\x0b" for ctrl+k).
|
|
108
|
+
|
|
109
|
+
Raises:
|
|
110
|
+
KeymapError: If the key is not found in KEY_CODES.
|
|
111
|
+
"""
|
|
112
|
+
key = get_cancel_agent_key()
|
|
113
|
+
if key not in KEY_CODES:
|
|
114
|
+
raise KeymapError(f"Unknown key '{key}' - no character code mapping found.")
|
|
115
|
+
return KEY_CODES[key]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def get_cancel_agent_display_name() -> str:
|
|
119
|
+
"""Get a human-readable display name for the cancel agent key.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
A formatted display name like "Ctrl+K".
|
|
123
|
+
"""
|
|
124
|
+
key = get_cancel_agent_key()
|
|
125
|
+
if key.startswith("ctrl+"):
|
|
126
|
+
letter = key.split("+")[1].upper()
|
|
127
|
+
return f"Ctrl+{letter}"
|
|
128
|
+
return key.upper()
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Helpers for lightweight case-insensitive list filtering in TUIs."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
_NON_ALNUM_RE = re.compile(r"[^0-9a-z]+")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def normalize_filter_text(text: str) -> str:
|
|
9
|
+
"""Normalize text for forgiving case-insensitive substring matching."""
|
|
10
|
+
normalized = _NON_ALNUM_RE.sub(" ", str(text).casefold()).strip()
|
|
11
|
+
return " ".join(normalized.split())
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def query_matches_text(query: str, *candidates: str) -> bool:
|
|
15
|
+
"""Return True when every query term appears in the candidate text."""
|
|
16
|
+
terms = normalize_filter_text(query).split()
|
|
17
|
+
if not terms:
|
|
18
|
+
return True
|
|
19
|
+
|
|
20
|
+
haystack = " ".join(
|
|
21
|
+
normalize_filter_text(candidate) for candidate in candidates if candidate
|
|
22
|
+
).strip()
|
|
23
|
+
if not haystack:
|
|
24
|
+
return False
|
|
25
|
+
|
|
26
|
+
return all(term in haystack for term in terms)
|
code_muse/main.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Main entry point for Muse CLI.
|
|
2
|
+
|
|
3
|
+
This module re-exports the main_entry function from cli_runner for backwards compatibility.
|
|
4
|
+
All the actual logic lives in cli_runner.py.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from code_muse.cli_runner import main_entry
|
|
8
|
+
|
|
9
|
+
if __name__ == "__main__":
|
|
10
|
+
main_entry()
|