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,68 @@
|
|
|
1
|
+
"""Plan document generation and persistence.
|
|
2
|
+
|
|
3
|
+
Produces markdown plan files with YAML front-matter and saves them to a
|
|
4
|
+
local ``plans/`` directory.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from datetime import UTC, datetime
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def generate_plan_md(
|
|
12
|
+
goal: str,
|
|
13
|
+
research_notes: str,
|
|
14
|
+
discussion: str,
|
|
15
|
+
steps: list[str],
|
|
16
|
+
) -> str:
|
|
17
|
+
"""Generate a markdown plan document with YAML front-matter.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
goal: Short title/summary of the plan.
|
|
21
|
+
research_notes: Analysis and context gathered during planning.
|
|
22
|
+
discussion: Reasoning and trade-offs considered.
|
|
23
|
+
steps: Ordered list of implementation steps.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
A fully formatted markdown string.
|
|
27
|
+
"""
|
|
28
|
+
ts = datetime.now(UTC).isoformat()
|
|
29
|
+
numbered_steps = "\n".join(f"{i + 1}. {step}" for i, step in enumerate(steps))
|
|
30
|
+
return f"""---
|
|
31
|
+
goal: "{goal}"
|
|
32
|
+
created_at: "{ts}"
|
|
33
|
+
status: draft
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
# Plan: {goal}
|
|
37
|
+
|
|
38
|
+
## Analysis
|
|
39
|
+
{research_notes}
|
|
40
|
+
|
|
41
|
+
## Discussion
|
|
42
|
+
{discussion}
|
|
43
|
+
|
|
44
|
+
## Implementation Steps
|
|
45
|
+
{numbered_steps}
|
|
46
|
+
|
|
47
|
+
## Risks
|
|
48
|
+
- (placeholder)
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def save_plan(content: str, plans_dir: Path = Path("plans")) -> Path:
|
|
53
|
+
"""Save plan markdown to ``plans/plan_{timestamp}.md``.
|
|
54
|
+
|
|
55
|
+
Creates the directory if it does not exist.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
content: Markdown content to save.
|
|
59
|
+
plans_dir: Target directory (default: ``plans/`` in cwd).
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
The path to the written file.
|
|
63
|
+
"""
|
|
64
|
+
plans_dir.mkdir(parents=True, exist_ok=True)
|
|
65
|
+
ts = datetime.now(UTC).strftime("%Y%m%d_%H%M%S")
|
|
66
|
+
file_path = plans_dir / f"plan_{ts}.md"
|
|
67
|
+
file_path.write_text(content, encoding="utf-8")
|
|
68
|
+
return file_path
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Plan mode enforcement hook.
|
|
2
|
+
|
|
3
|
+
Registers a ``pre_tool_call`` callback that blocks destructive tools
|
|
4
|
+
according to the active plan mode state.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from code_muse.plugins.plan_mode.plan_mode_tools import PlanModeState, get_current_mode
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
# Tools allowed in PLAN mode (research / read-only / meta)
|
|
15
|
+
_PLAN_ALLOWED_TOOLS: set[str] = {
|
|
16
|
+
"read_file",
|
|
17
|
+
"list_files",
|
|
18
|
+
"grep",
|
|
19
|
+
"ask_user_question",
|
|
20
|
+
"list_or_search_skills",
|
|
21
|
+
"enter_plan_mode",
|
|
22
|
+
"exit_plan_mode",
|
|
23
|
+
"get_plan_mode",
|
|
24
|
+
"approve_plan",
|
|
25
|
+
"cancel_plan",
|
|
26
|
+
"open_plan_in_editor",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# Tools blocked in AUTO_EDIT mode (shell commands only)
|
|
30
|
+
_AUTO_EDIT_BLOCKED_TOOLS: set[str] = {
|
|
31
|
+
"agent_run_shell_command",
|
|
32
|
+
"run_shell_command",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
async def plan_mode_pre_tool_call_hook(
|
|
37
|
+
tool_name: str, tool_args: dict, context: Any = None
|
|
38
|
+
) -> dict | None:
|
|
39
|
+
"""Enforce mode-specific tool restrictions.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
``{"blocked": True, "error_message": "..."}`` if the tool is
|
|
43
|
+
blocked in the current mode, otherwise ``None``.
|
|
44
|
+
"""
|
|
45
|
+
mode = get_current_mode()
|
|
46
|
+
|
|
47
|
+
if mode == PlanModeState.DEFAULT:
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
if mode == PlanModeState.PLAN:
|
|
51
|
+
if tool_name in _PLAN_ALLOWED_TOOLS:
|
|
52
|
+
return None
|
|
53
|
+
logger.info("Blocked tool '%s' during plan mode", tool_name)
|
|
54
|
+
return {
|
|
55
|
+
"blocked": True,
|
|
56
|
+
"error_message": (
|
|
57
|
+
"🚫 Plan mode is active. This tool is blocked during planning. "
|
|
58
|
+
"Use exit_plan_mode to return to normal mode."
|
|
59
|
+
),
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if mode == PlanModeState.AUTO_EDIT:
|
|
63
|
+
if tool_name in _AUTO_EDIT_BLOCKED_TOOLS:
|
|
64
|
+
logger.info("Blocked tool '%s' during auto-edit mode", tool_name)
|
|
65
|
+
return {
|
|
66
|
+
"blocked": True,
|
|
67
|
+
"error_message": (
|
|
68
|
+
"🚫 Auto-edit mode is active. Shell commands are blocked. "
|
|
69
|
+
"Use exit_plan_mode to return to normal mode."
|
|
70
|
+
),
|
|
71
|
+
}
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
return None
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Plan mode state management and tool registration.
|
|
2
|
+
|
|
3
|
+
Tools:
|
|
4
|
+
- enter_plan_mode(goal: str = "")
|
|
5
|
+
- exit_plan_mode()
|
|
6
|
+
- get_plan_mode()
|
|
7
|
+
- approve_plan()
|
|
8
|
+
- open_plan_in_editor()
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import enum
|
|
12
|
+
import os
|
|
13
|
+
import subprocess
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from pydantic_ai import RunContext
|
|
18
|
+
|
|
19
|
+
from code_muse.messaging.bus import get_message_bus
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PlanModeState(enum.Enum):
|
|
23
|
+
"""Three-state mode model."""
|
|
24
|
+
|
|
25
|
+
DEFAULT = "default"
|
|
26
|
+
AUTO_EDIT = "auto_edit"
|
|
27
|
+
PLAN = "plan"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# Module-level mutable state (integration note: if the agent runtime ever
|
|
31
|
+
# exposes a formal ``_mode`` attribute, mirror state there).
|
|
32
|
+
_current_mode: PlanModeState = PlanModeState.DEFAULT
|
|
33
|
+
_plan_goal: str = ""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_current_mode() -> PlanModeState:
|
|
37
|
+
"""Return the active plan mode state."""
|
|
38
|
+
return _current_mode
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_plan_goal() -> str:
|
|
42
|
+
"""Return the current planning goal, if any."""
|
|
43
|
+
return _plan_goal
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def set_plan_mode(mode: PlanModeState, goal: str = "") -> None:
|
|
47
|
+
"""Update the global plan mode state and optional goal."""
|
|
48
|
+
global _current_mode, _plan_goal
|
|
49
|
+
_current_mode = mode
|
|
50
|
+
_plan_goal = goal
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def register_enter_plan_mode(agent: Any) -> None:
|
|
54
|
+
"""Register the ``enter_plan_mode`` tool on *agent*."""
|
|
55
|
+
|
|
56
|
+
@agent.tool
|
|
57
|
+
def enter_plan_mode(context: RunContext, goal: str = "") -> str:
|
|
58
|
+
"""Enter plan mode. Optional goal describes what you are planning.
|
|
59
|
+
|
|
60
|
+
In plan mode, destructive tools (write_file, replace_in_file,
|
|
61
|
+
delete_file, shell commands) are blocked. Research tools
|
|
62
|
+
(read_file, list_files, grep, ask_user_question) remain available.
|
|
63
|
+
"""
|
|
64
|
+
set_plan_mode(PlanModeState.PLAN, goal)
|
|
65
|
+
bus = get_message_bus()
|
|
66
|
+
bus.emit_info(f"📋 Plan mode activated{f' (goal: {goal})' if goal else ''}")
|
|
67
|
+
return f"Plan mode active. Goal: {goal or '(none)'}"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def register_exit_plan_mode(agent: Any) -> None:
|
|
71
|
+
"""Register the ``exit_plan_mode`` tool on *agent*."""
|
|
72
|
+
|
|
73
|
+
@agent.tool
|
|
74
|
+
def exit_plan_mode(context: RunContext) -> str:
|
|
75
|
+
"""Exit plan mode and return to normal operation."""
|
|
76
|
+
set_plan_mode(PlanModeState.DEFAULT)
|
|
77
|
+
bus = get_message_bus()
|
|
78
|
+
bus.emit_info("📋 Plan mode deactivated — normal editing resumed")
|
|
79
|
+
return "Plan mode deactivated. Normal editing resumed."
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def register_get_plan_mode(agent: Any) -> None:
|
|
83
|
+
"""Register the ``get_plan_mode`` tool on *agent*."""
|
|
84
|
+
|
|
85
|
+
@agent.tool
|
|
86
|
+
def get_plan_mode(context: RunContext) -> str:
|
|
87
|
+
"""Return the current plan mode state and goal."""
|
|
88
|
+
mode = get_current_mode()
|
|
89
|
+
goal = get_plan_goal()
|
|
90
|
+
return f"Mode: {mode.value} | Goal: {goal or '(none)'}"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def register_approve_plan(agent: Any) -> None:
|
|
94
|
+
"""Register the ``approve_plan`` tool on *agent*."""
|
|
95
|
+
|
|
96
|
+
@agent.tool
|
|
97
|
+
def approve_plan(context: RunContext) -> str:
|
|
98
|
+
"""Approve the current plan and enter auto-edit mode."""
|
|
99
|
+
set_plan_mode(PlanModeState.AUTO_EDIT)
|
|
100
|
+
bus = get_message_bus()
|
|
101
|
+
bus.emit_info("✅ Plan approved — entering auto-edit mode")
|
|
102
|
+
return "Plan approved. Auto-edit mode active."
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def register_open_plan_in_editor(agent: Any) -> None:
|
|
106
|
+
"""Register the ``open_plan_in_editor`` tool on *agent*."""
|
|
107
|
+
|
|
108
|
+
@agent.tool
|
|
109
|
+
def open_plan_in_editor(context: RunContext) -> str:
|
|
110
|
+
"""Open the most recent plan file in the default editor."""
|
|
111
|
+
plans_dir = Path("plans")
|
|
112
|
+
if not plans_dir.exists():
|
|
113
|
+
return "Error: plans directory not found."
|
|
114
|
+
|
|
115
|
+
plan_files = sorted(
|
|
116
|
+
plans_dir.glob("*.md"),
|
|
117
|
+
key=lambda p: p.stat().st_mtime,
|
|
118
|
+
reverse=True,
|
|
119
|
+
)
|
|
120
|
+
if not plan_files:
|
|
121
|
+
return "Error: no plan files found."
|
|
122
|
+
|
|
123
|
+
plan_path = plan_files[0]
|
|
124
|
+
|
|
125
|
+
candidates = []
|
|
126
|
+
editor = os.environ.get("EDITOR")
|
|
127
|
+
if editor:
|
|
128
|
+
candidates.append(editor)
|
|
129
|
+
candidates.extend(["nvim", "vim", "nano"])
|
|
130
|
+
|
|
131
|
+
for editor in candidates:
|
|
132
|
+
try:
|
|
133
|
+
subprocess.run([editor, str(plan_path)], check=False)
|
|
134
|
+
return f"Opened {plan_path} in {editor}"
|
|
135
|
+
except FileNotFoundError:
|
|
136
|
+
continue
|
|
137
|
+
|
|
138
|
+
return "Error: no suitable editor found (tried $EDITOR, nvim, vim, nano)."
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Callback registration for the Plan Mode plugin.
|
|
2
|
+
|
|
3
|
+
Registers:
|
|
4
|
+
- ``enter_plan_mode``, ``exit_plan_mode``, ``get_plan_mode`` tools
|
|
5
|
+
- ``pre_tool_call`` enforcement hook
|
|
6
|
+
- ``/plan [goal]``, ``/plan exit`` slash commands
|
|
7
|
+
- ``/mode`` slash command (delegated to :mod:`mode_cycling`)
|
|
8
|
+
- Help entries for all slash commands
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from code_muse.callbacks import register_callback
|
|
15
|
+
from code_muse.messaging import emit_info, emit_success
|
|
16
|
+
from code_muse.plugins.plan_mode.mode_cycling import cycle_mode
|
|
17
|
+
from code_muse.plugins.plan_mode.plan_hooks import plan_mode_pre_tool_call_hook
|
|
18
|
+
from code_muse.plugins.plan_mode.plan_mode_tools import (
|
|
19
|
+
PlanModeState,
|
|
20
|
+
get_current_mode,
|
|
21
|
+
register_approve_plan,
|
|
22
|
+
register_enter_plan_mode,
|
|
23
|
+
register_exit_plan_mode,
|
|
24
|
+
register_get_plan_mode,
|
|
25
|
+
register_open_plan_in_editor,
|
|
26
|
+
set_plan_mode,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
# Tool registration callback
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _register_plan_mode_tools() -> list[dict[str, Any]]:
|
|
38
|
+
"""Return tool definitions for the plan mode plugin."""
|
|
39
|
+
return [
|
|
40
|
+
{"name": "enter_plan_mode", "register_func": register_enter_plan_mode},
|
|
41
|
+
{"name": "exit_plan_mode", "register_func": register_exit_plan_mode},
|
|
42
|
+
{"name": "get_plan_mode", "register_func": register_get_plan_mode},
|
|
43
|
+
{"name": "approve_plan", "register_func": register_approve_plan},
|
|
44
|
+
{"name": "open_plan_in_editor", "register_func": register_open_plan_in_editor},
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
# Slash-command handlers
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _on_custom_command(command: str, name: str) -> bool | None:
|
|
54
|
+
"""Handle ``/plan``, ``/plan exit``, and ``/mode`` commands."""
|
|
55
|
+
if name == "mode":
|
|
56
|
+
return _handle_mode_command(command)
|
|
57
|
+
|
|
58
|
+
if name == "plan":
|
|
59
|
+
return _handle_plan_command(command)
|
|
60
|
+
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _handle_plan_command(command: str) -> bool:
|
|
65
|
+
"""Handle ``/plan [goal]`` and ``/plan exit``."""
|
|
66
|
+
parts = command.split(maxsplit=1)
|
|
67
|
+
remainder = parts[1].strip() if len(parts) > 1 else ""
|
|
68
|
+
|
|
69
|
+
if remainder.lower() == "exit":
|
|
70
|
+
set_plan_mode(PlanModeState.DEFAULT)
|
|
71
|
+
emit_success("📋 Plan mode exited — normal editing resumed")
|
|
72
|
+
return True
|
|
73
|
+
|
|
74
|
+
# remainder is the optional goal (may be empty)
|
|
75
|
+
set_plan_mode(PlanModeState.PLAN, remainder)
|
|
76
|
+
if remainder:
|
|
77
|
+
emit_success(f"📋 Plan mode active — goal: {remainder}")
|
|
78
|
+
else:
|
|
79
|
+
emit_success("📋 Plan mode active")
|
|
80
|
+
return True
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _handle_mode_command(command: str) -> bool:
|
|
84
|
+
"""Handle ``/mode`` — cycle through DEFAULT → AUTO_EDIT → PLAN."""
|
|
85
|
+
old_mode = get_current_mode()
|
|
86
|
+
new_mode = cycle_mode()
|
|
87
|
+
mode_names = {
|
|
88
|
+
PlanModeState.DEFAULT: "Default",
|
|
89
|
+
PlanModeState.AUTO_EDIT: "Auto-edit",
|
|
90
|
+
PlanModeState.PLAN: "Plan",
|
|
91
|
+
}
|
|
92
|
+
emit_info(
|
|
93
|
+
f"🔁 Mode changed: {mode_names.get(old_mode, old_mode.value)} → "
|
|
94
|
+
f"{mode_names.get(new_mode, new_mode.value)}"
|
|
95
|
+
)
|
|
96
|
+
return True
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# ---------------------------------------------------------------------------
|
|
100
|
+
# Help entries
|
|
101
|
+
# ---------------------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _on_custom_command_help() -> list[tuple[str, str]]:
|
|
105
|
+
return [
|
|
106
|
+
("plan", "Enter plan mode (optionally with a goal)"),
|
|
107
|
+
("plan exit", "Exit plan mode and resume normal editing"),
|
|
108
|
+
("mode", "Cycle through DEFAULT → AUTO_EDIT → PLAN mode"),
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# ---------------------------------------------------------------------------
|
|
113
|
+
# Register all callbacks
|
|
114
|
+
# ---------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
register_callback("register_tools", _register_plan_mode_tools)
|
|
117
|
+
register_callback("pre_tool_call", plan_mode_pre_tool_call_hook)
|
|
118
|
+
register_callback("custom_command", _on_custom_command)
|
|
119
|
+
register_callback("custom_command_help", _on_custom_command_help)
|
|
120
|
+
|
|
121
|
+
logger.debug("Plan Mode plugin callbacks registered")
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Builtin plugin trust command handler.
|
|
2
|
+
|
|
3
|
+
Implements /plugin list, /plugin trust <name>, /plugin revoke <name>,
|
|
4
|
+
and /plugin help.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
|
|
9
|
+
from code_muse.callbacks import register_callback
|
|
10
|
+
from code_muse.plugins import (
|
|
11
|
+
_PLUGINS_LOADED,
|
|
12
|
+
compute_plugin_hash,
|
|
13
|
+
get_user_plugins_dir,
|
|
14
|
+
is_plugin_trusted,
|
|
15
|
+
record_plugin_trust,
|
|
16
|
+
revoke_plugin_trust,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _plugin_command_help() -> list[tuple[str, str]]:
|
|
23
|
+
"""Return help entries for plugin trust commands."""
|
|
24
|
+
return [
|
|
25
|
+
("plugin list", "List discovered user plugins with trust status"),
|
|
26
|
+
("plugin trust <name>", "Trust a user plugin by content hash"),
|
|
27
|
+
("plugin revoke <name>", "Revoke trust for a user plugin"),
|
|
28
|
+
("plugin help", "Show plugin trust command help"),
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _handle_plugin_command(command: str, name: str) -> str | None:
|
|
33
|
+
"""Handle /plugin subcommands."""
|
|
34
|
+
if name != "plugin":
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
parts = command.strip().split()
|
|
38
|
+
if len(parts) < 2:
|
|
39
|
+
return _plugin_help_text()
|
|
40
|
+
|
|
41
|
+
sub = parts[1].lower()
|
|
42
|
+
|
|
43
|
+
if sub == "help":
|
|
44
|
+
return _plugin_help_text()
|
|
45
|
+
|
|
46
|
+
if sub in ("list", "plugins"):
|
|
47
|
+
return _plugin_list()
|
|
48
|
+
|
|
49
|
+
if sub == "trust":
|
|
50
|
+
if len(parts) < 3:
|
|
51
|
+
return "Usage: /plugin trust <plugin_name>"
|
|
52
|
+
return _plugin_trust(parts[2])
|
|
53
|
+
|
|
54
|
+
if sub == "revoke":
|
|
55
|
+
if len(parts) < 3:
|
|
56
|
+
return "Usage: /plugin revoke <plugin_name>"
|
|
57
|
+
return _plugin_revoke(parts[2])
|
|
58
|
+
|
|
59
|
+
return f"Unknown /plugin subcommand: {sub}. Usage: /plugin trust|revoke|list|help"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _plugin_help_text() -> str:
|
|
63
|
+
return (
|
|
64
|
+
"Plugin trust commands:\n"
|
|
65
|
+
" /plugin list — List discovered user plugins with hash & trust status\n"
|
|
66
|
+
" /plugin trust <name> — Record trust for a user plugin\n"
|
|
67
|
+
" /plugin revoke <name> — Revoke trust for a user plugin\n"
|
|
68
|
+
" /plugin help — Show this help"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _plugin_list() -> str:
|
|
73
|
+
"""Summarise discovered user plugins with hash and trust status."""
|
|
74
|
+
user_dir = get_user_plugins_dir()
|
|
75
|
+
if not user_dir.exists():
|
|
76
|
+
return f"No user plugins directory found at {user_dir}"
|
|
77
|
+
|
|
78
|
+
entries: list[str] = []
|
|
79
|
+
for item in sorted(user_dir.iterdir()):
|
|
80
|
+
if not item.is_dir() or item.name.startswith(".") or item.name.startswith("_"):
|
|
81
|
+
continue
|
|
82
|
+
|
|
83
|
+
has_callbacks = (item / "register_callbacks.py").exists()
|
|
84
|
+
has_init = (item / "__init__.py").exists()
|
|
85
|
+
if not has_callbacks and not has_init:
|
|
86
|
+
continue
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
content_hash = compute_plugin_hash(item)
|
|
90
|
+
except OSError as exc:
|
|
91
|
+
logger.warning("Failed to hash plugin %s: %s", item.name, exc)
|
|
92
|
+
continue
|
|
93
|
+
|
|
94
|
+
trusted = is_plugin_trusted(item.name, content_hash)
|
|
95
|
+
status = "trusted" if trusted else "untrusted"
|
|
96
|
+
entries.append(f" {item.name:20} {status:10} {content_hash[:12]}… {item}")
|
|
97
|
+
|
|
98
|
+
if not entries:
|
|
99
|
+
return f"No user plugins found in {user_dir}"
|
|
100
|
+
|
|
101
|
+
header = f"{'Name':20} {'Status':10} {'Hash':14} Path"
|
|
102
|
+
return "User plugins:\n" + header + "\n" + "\n".join(entries)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _plugin_trust(plugin_name: str) -> str:
|
|
106
|
+
"""Compute current hash and record trust for a user plugin."""
|
|
107
|
+
user_dir = get_user_plugins_dir()
|
|
108
|
+
plugin_dir = user_dir / plugin_name
|
|
109
|
+
|
|
110
|
+
if not plugin_dir.exists() or not plugin_dir.is_dir():
|
|
111
|
+
return f"Plugin '{plugin_name}' not found in {user_dir}"
|
|
112
|
+
|
|
113
|
+
if (
|
|
114
|
+
not (plugin_dir / "register_callbacks.py").exists()
|
|
115
|
+
and not (plugin_dir / "__init__.py").exists()
|
|
116
|
+
):
|
|
117
|
+
return f"Plugin '{plugin_name}' has no register_callbacks.py or __init__.py"
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
content_hash = compute_plugin_hash(plugin_dir)
|
|
121
|
+
except OSError as exc:
|
|
122
|
+
logger.warning("Failed to hash plugin %s: %s", plugin_name, exc)
|
|
123
|
+
return f"Failed to compute hash for plugin '{plugin_name}'"
|
|
124
|
+
|
|
125
|
+
record_plugin_trust(plugin_name, content_hash, str(plugin_dir))
|
|
126
|
+
|
|
127
|
+
msg = f"Trust recorded for plugin '{plugin_name}' (hash: {content_hash[:12]}…)."
|
|
128
|
+
if _PLUGINS_LOADED:
|
|
129
|
+
msg += " Restart Muse to load newly trusted plugins."
|
|
130
|
+
return msg
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _plugin_revoke(plugin_name: str) -> str:
|
|
134
|
+
"""Revoke trust for a user plugin."""
|
|
135
|
+
revoke_plugin_trust(plugin_name)
|
|
136
|
+
return f"Trust revoked for plugin '{plugin_name}'. Restart Muse to unload."
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
register_callback("custom_command", _handle_plugin_command)
|
|
140
|
+
register_callback("custom_command_help", _plugin_command_help)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Policy Engine plugin for Muse.
|
|
2
|
+
|
|
3
|
+
TOML-based rules that control tool execution:
|
|
4
|
+
- allow → auto-approve (skip confirmation)
|
|
5
|
+
- deny → block with message
|
|
6
|
+
- ask_user → show normal confirmation dialog
|
|
7
|
+
|
|
8
|
+
Rules match by toolName (with * wildcard) and optional commandPrefix
|
|
9
|
+
(for shell commands). Rules are loaded from:
|
|
10
|
+
- User tier: ~/.muse/policies/*.toml
|
|
11
|
+
- Project tier: .muse/policies/*.toml
|
|
12
|
+
|
|
13
|
+
Priority resolves conflicts (higher wins).
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from code_muse.plugins.policy_engine.approval_flow_integration import (
|
|
17
|
+
integrate_policy_check,
|
|
18
|
+
)
|
|
19
|
+
from code_muse.plugins.policy_engine.policy_evaluator import (
|
|
20
|
+
evaluate_policy,
|
|
21
|
+
evaluate_tool_policy,
|
|
22
|
+
)
|
|
23
|
+
from code_muse.plugins.policy_engine.policy_file_discovery import (
|
|
24
|
+
clear_policy_cache,
|
|
25
|
+
discover_policy_files,
|
|
26
|
+
load_all_policies,
|
|
27
|
+
)
|
|
28
|
+
from code_muse.plugins.policy_engine.policy_toml_schema import (
|
|
29
|
+
Decision,
|
|
30
|
+
ToolRule,
|
|
31
|
+
parse_policy_toml,
|
|
32
|
+
validate_rules,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
__all__ = [
|
|
36
|
+
"Decision",
|
|
37
|
+
"ToolRule",
|
|
38
|
+
"clear_policy_cache",
|
|
39
|
+
"discover_policy_files",
|
|
40
|
+
"evaluate_policy",
|
|
41
|
+
"evaluate_tool_policy",
|
|
42
|
+
"integrate_policy_check",
|
|
43
|
+
"load_all_policies",
|
|
44
|
+
"parse_policy_toml",
|
|
45
|
+
"validate_rules",
|
|
46
|
+
]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from code_muse.plugins.policy_engine.policy_evaluator import (
|
|
5
|
+
evaluate_policy,
|
|
6
|
+
evaluate_tool_policy,
|
|
7
|
+
)
|
|
8
|
+
from code_muse.plugins.policy_engine.policy_file_discovery import load_all_policies
|
|
9
|
+
from code_muse.plugins.policy_engine.policy_toml_schema import Decision, ToolRule
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def integrate_policy_check(
|
|
15
|
+
tool_name: str,
|
|
16
|
+
command: str | None = None,
|
|
17
|
+
rules: list[ToolRule] | None = None,
|
|
18
|
+
) -> dict[str, Any] | None:
|
|
19
|
+
if rules is None:
|
|
20
|
+
rules = load_all_policies()
|
|
21
|
+
|
|
22
|
+
if command is not None:
|
|
23
|
+
decision, matched_rule = evaluate_policy(tool_name, command, rules)
|
|
24
|
+
else:
|
|
25
|
+
decision, matched_rule = evaluate_tool_policy(tool_name, rules)
|
|
26
|
+
|
|
27
|
+
if decision == Decision.ALLOW:
|
|
28
|
+
if command is not None:
|
|
29
|
+
# Shell command path: signal auto_approval to skip confirmation
|
|
30
|
+
logger.info(
|
|
31
|
+
"Policy auto-approved %s (command=%s, rule=%s)",
|
|
32
|
+
tool_name,
|
|
33
|
+
command,
|
|
34
|
+
matched_rule.description if matched_rule else "default",
|
|
35
|
+
)
|
|
36
|
+
return {"auto_approve": True}
|
|
37
|
+
# Non-shell tool path: auto_approve not relevant, just allow
|
|
38
|
+
logger.debug("Policy allowed %s (no confirmation to skip)", tool_name)
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
if decision == Decision.DENY:
|
|
42
|
+
message = _build_block_message(matched_rule, tool_name)
|
|
43
|
+
logger.warning(
|
|
44
|
+
"Policy blocked %s (command=%s): %s", tool_name, command, message
|
|
45
|
+
)
|
|
46
|
+
return {"blocked": True, "error_message": message}
|
|
47
|
+
|
|
48
|
+
if decision == Decision.ASK_USER:
|
|
49
|
+
logger.debug("Policy ASK_USER for %s (command=%s)", tool_name, command)
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
# Should never reach here, but default to normal flow
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _build_block_message(rule: Any, tool_name: str) -> str:
|
|
57
|
+
if rule and getattr(rule, "description", None):
|
|
58
|
+
return f"🚫 Policy: {rule.description}"
|
|
59
|
+
return f"🚫 Policy: blocked {tool_name}"
|