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,740 @@
|
|
|
1
|
+
"""Command handlers for Muse - CORE commands.
|
|
2
|
+
|
|
3
|
+
This module contains @register_command decorated handlers that are automatically
|
|
4
|
+
discovered by the command registry system.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from code_muse.command_line.agent_menu import interactive_agent_picker
|
|
11
|
+
from code_muse.command_line.command_registry import register_command
|
|
12
|
+
from code_muse.command_line.model_picker_completion import (
|
|
13
|
+
interactive_model_picker,
|
|
14
|
+
update_model_in_input,
|
|
15
|
+
)
|
|
16
|
+
from code_muse.command_line.utils import make_directory_table
|
|
17
|
+
from code_muse.config import finalize_autosave_session
|
|
18
|
+
from code_muse.messaging import emit_error, emit_info
|
|
19
|
+
from code_muse.tools.tools_content import tools_content
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Import get_commands_help from command_handler to avoid circular imports
|
|
23
|
+
# This will be defined in command_handler.py
|
|
24
|
+
def get_commands_help():
|
|
25
|
+
"""Lazy import to avoid circular dependency."""
|
|
26
|
+
from code_muse.command_line.command_handler import get_commands_help as _gch
|
|
27
|
+
|
|
28
|
+
return _gch()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@register_command(
|
|
32
|
+
name="help",
|
|
33
|
+
description="Show this help message",
|
|
34
|
+
usage="/help, /h",
|
|
35
|
+
aliases=["h"],
|
|
36
|
+
category="core",
|
|
37
|
+
)
|
|
38
|
+
def handle_help_command(command: str) -> bool:
|
|
39
|
+
"""Show commands help."""
|
|
40
|
+
import uuid
|
|
41
|
+
|
|
42
|
+
from code_muse.messaging import emit_info
|
|
43
|
+
|
|
44
|
+
group_id = str(uuid.uuid4())
|
|
45
|
+
help_text = get_commands_help()
|
|
46
|
+
emit_info(help_text, message_group_id=group_id)
|
|
47
|
+
return True
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@register_command(
|
|
51
|
+
name="cd",
|
|
52
|
+
description="Change directory or show directories",
|
|
53
|
+
usage="/cd <dir>",
|
|
54
|
+
category="core",
|
|
55
|
+
)
|
|
56
|
+
def handle_cd_command(command: str) -> bool:
|
|
57
|
+
"""Change directory or list current directory."""
|
|
58
|
+
import shlex
|
|
59
|
+
|
|
60
|
+
from code_muse.messaging import emit_error, emit_info, emit_success, emit_warning
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
if os.name == "nt":
|
|
64
|
+
# Windows paths commonly use backslashes; POSIX shlex treats them as
|
|
65
|
+
# escape characters and corrupts valid paths (e.g., C:\foo\bar).
|
|
66
|
+
lexer = shlex.shlex(command, posix=False)
|
|
67
|
+
lexer.whitespace_split = True
|
|
68
|
+
lexer.commenters = ""
|
|
69
|
+
tokens = list(lexer)
|
|
70
|
+
else:
|
|
71
|
+
tokens = shlex.split(command)
|
|
72
|
+
except ValueError:
|
|
73
|
+
# Keep remaining text as one argument for better resilience.
|
|
74
|
+
tokens = command.split(maxsplit=1)
|
|
75
|
+
|
|
76
|
+
if len(tokens) == 1:
|
|
77
|
+
try:
|
|
78
|
+
table = make_directory_table()
|
|
79
|
+
emit_info(table)
|
|
80
|
+
except Exception as e:
|
|
81
|
+
emit_error(f"Error listing directory: {e}")
|
|
82
|
+
return True
|
|
83
|
+
|
|
84
|
+
if len(tokens) >= 2:
|
|
85
|
+
# /cd takes one path argument; if tokenizer split extra whitespace,
|
|
86
|
+
# rejoin it so unquoted paths with spaces still have a chance.
|
|
87
|
+
dirname = " ".join(tokens[1:]).strip().strip("\"'")
|
|
88
|
+
target = Path(dirname).expanduser()
|
|
89
|
+
if not target.is_absolute():
|
|
90
|
+
target = Path.cwd() / target
|
|
91
|
+
if target.is_dir():
|
|
92
|
+
os.chdir(target)
|
|
93
|
+
emit_success(f"Changed directory to: {target}")
|
|
94
|
+
# Reload the agent so the system prompt and project-local
|
|
95
|
+
# AGENT.md rules reflect the new working directory. Without
|
|
96
|
+
# this, the LLM keeps receiving stale path information for the
|
|
97
|
+
# remainder of the session (the PydanticAgent instructions are
|
|
98
|
+
# baked in at construction time and never refreshed otherwise).
|
|
99
|
+
try:
|
|
100
|
+
from code_muse.agents.agent_manager import get_current_agent
|
|
101
|
+
|
|
102
|
+
get_current_agent().reload_code_generation_agent()
|
|
103
|
+
except Exception as e:
|
|
104
|
+
emit_warning(
|
|
105
|
+
f"Directory changed, but agent reload failed: {e}. "
|
|
106
|
+
"You may need to run /agent or /model to force a refresh."
|
|
107
|
+
)
|
|
108
|
+
else:
|
|
109
|
+
emit_error(f"Not a directory: {dirname}")
|
|
110
|
+
return True
|
|
111
|
+
|
|
112
|
+
return True
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@register_command(
|
|
116
|
+
name="tools",
|
|
117
|
+
description="Show available tools and capabilities",
|
|
118
|
+
usage="/tools",
|
|
119
|
+
category="core",
|
|
120
|
+
)
|
|
121
|
+
def handle_tools_command(command: str) -> bool:
|
|
122
|
+
"""Display available tools."""
|
|
123
|
+
from rich.markdown import Markdown
|
|
124
|
+
|
|
125
|
+
from code_muse.messaging import emit_info
|
|
126
|
+
|
|
127
|
+
markdown_content = Markdown(tools_content)
|
|
128
|
+
emit_info(markdown_content)
|
|
129
|
+
return True
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@register_command(
|
|
133
|
+
name="paste",
|
|
134
|
+
description="Paste image from clipboard (same as F3, or Ctrl+V with image)",
|
|
135
|
+
usage="/paste, /clipboard, /cb",
|
|
136
|
+
aliases=["clipboard", "cb"],
|
|
137
|
+
category="core",
|
|
138
|
+
)
|
|
139
|
+
def handle_paste_command(command: str) -> bool:
|
|
140
|
+
"""Paste an image from the clipboard into the pending attachments."""
|
|
141
|
+
from code_muse.command_line.clipboard import (
|
|
142
|
+
capture_clipboard_image_to_pending,
|
|
143
|
+
get_clipboard_manager,
|
|
144
|
+
has_image_in_clipboard,
|
|
145
|
+
)
|
|
146
|
+
from code_muse.messaging import emit_info, emit_success, emit_warning
|
|
147
|
+
|
|
148
|
+
if not has_image_in_clipboard():
|
|
149
|
+
emit_warning("No image found in clipboard")
|
|
150
|
+
emit_info("Copy an image (screenshot, from browser, etc.) and try again")
|
|
151
|
+
return True
|
|
152
|
+
|
|
153
|
+
placeholder = capture_clipboard_image_to_pending()
|
|
154
|
+
if placeholder:
|
|
155
|
+
manager = get_clipboard_manager()
|
|
156
|
+
count = manager.get_pending_count()
|
|
157
|
+
emit_success(f"📋 {placeholder}")
|
|
158
|
+
emit_info(f"Total pending clipboard images: {count}")
|
|
159
|
+
emit_info("Type your prompt and press Enter to send with the image(s)")
|
|
160
|
+
else:
|
|
161
|
+
emit_warning("Failed to capture clipboard image")
|
|
162
|
+
|
|
163
|
+
return True
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@register_command(
|
|
167
|
+
name="tutorial",
|
|
168
|
+
description="Run the interactive tutorial wizard",
|
|
169
|
+
usage="/tutorial",
|
|
170
|
+
category="core",
|
|
171
|
+
)
|
|
172
|
+
def handle_tutorial_command(command: str) -> bool:
|
|
173
|
+
"""Run the interactive tutorial wizard.
|
|
174
|
+
|
|
175
|
+
Usage:
|
|
176
|
+
/tutorial - Run the tutorial (can be run anytime)
|
|
177
|
+
"""
|
|
178
|
+
import asyncio
|
|
179
|
+
import concurrent.futures
|
|
180
|
+
|
|
181
|
+
from code_muse.command_line.onboarding_wizard import (
|
|
182
|
+
reset_onboarding,
|
|
183
|
+
run_onboarding_wizard,
|
|
184
|
+
)
|
|
185
|
+
from code_muse.model_switching import set_model_and_reload_agent
|
|
186
|
+
|
|
187
|
+
# Always reset so user can re-run the tutorial anytime
|
|
188
|
+
reset_onboarding()
|
|
189
|
+
|
|
190
|
+
# Run the async wizard in a thread pool (same pattern as agent picker).
|
|
191
|
+
# FREE-THREADED: ThreadPoolExecutor is compatible with free-threaded Python 3.14.
|
|
192
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
193
|
+
future = executor.submit(lambda: asyncio.run(run_onboarding_wizard()))
|
|
194
|
+
result = future.result(timeout=300) # 5 min timeout
|
|
195
|
+
|
|
196
|
+
if result == "chatgpt":
|
|
197
|
+
emit_info("🔐 Starting ChatGPT OAuth flow...")
|
|
198
|
+
from code_muse.plugins.chatgpt_oauth.oauth_flow import run_oauth_flow
|
|
199
|
+
|
|
200
|
+
run_oauth_flow()
|
|
201
|
+
set_model_and_reload_agent("chatgpt-gpt-5.4")
|
|
202
|
+
elif result == "claude":
|
|
203
|
+
emit_info("🔐 Starting Claude Code OAuth flow...")
|
|
204
|
+
from code_muse.plugins.claude_code_oauth.register_callbacks import (
|
|
205
|
+
_perform_authentication,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
_perform_authentication()
|
|
209
|
+
set_model_and_reload_agent("claude-code-claude-opus-4-7")
|
|
210
|
+
elif result == "completed":
|
|
211
|
+
emit_info("🎉 Tutorial complete! Happy coding!")
|
|
212
|
+
elif result == "skipped":
|
|
213
|
+
emit_info("⏭️ Tutorial skipped. Run /tutorial anytime!")
|
|
214
|
+
|
|
215
|
+
return True
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@register_command(
|
|
219
|
+
name="exit",
|
|
220
|
+
description="Exit interactive mode",
|
|
221
|
+
usage="/exit, /quit",
|
|
222
|
+
aliases=["quit"],
|
|
223
|
+
category="core",
|
|
224
|
+
)
|
|
225
|
+
def handle_exit_command(command: str) -> bool:
|
|
226
|
+
"""Exit the interactive session."""
|
|
227
|
+
from code_muse.messaging import emit_success
|
|
228
|
+
|
|
229
|
+
try:
|
|
230
|
+
emit_success("Goodbye!")
|
|
231
|
+
except Exception:
|
|
232
|
+
# Handle emit errors gracefully
|
|
233
|
+
pass
|
|
234
|
+
# Signal to the main app that we want to exit
|
|
235
|
+
# The actual exit handling is done in main.py
|
|
236
|
+
return True
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@register_command(
|
|
240
|
+
name="agent",
|
|
241
|
+
description="Switch to a different agent or show available agents",
|
|
242
|
+
usage="/agent <name>, /a <name>",
|
|
243
|
+
aliases=["a"],
|
|
244
|
+
category="core",
|
|
245
|
+
)
|
|
246
|
+
def handle_agent_command(command: str) -> bool:
|
|
247
|
+
"""Handle agent switching."""
|
|
248
|
+
from rich.text import Text
|
|
249
|
+
|
|
250
|
+
from code_muse.agents import (
|
|
251
|
+
get_agent_descriptions,
|
|
252
|
+
get_available_agents,
|
|
253
|
+
get_current_agent,
|
|
254
|
+
set_current_agent,
|
|
255
|
+
)
|
|
256
|
+
from code_muse.messaging import emit_error, emit_info, emit_success, emit_warning
|
|
257
|
+
|
|
258
|
+
tokens = command.split()
|
|
259
|
+
|
|
260
|
+
if len(tokens) == 1:
|
|
261
|
+
# Show interactive agent picker
|
|
262
|
+
try:
|
|
263
|
+
# Run the async picker using asyncio utilities
|
|
264
|
+
# Since we're called from an async context but this function is sync,
|
|
265
|
+
# we need to carefully schedule and wait for the coroutine
|
|
266
|
+
import asyncio
|
|
267
|
+
import concurrent.futures
|
|
268
|
+
import uuid
|
|
269
|
+
|
|
270
|
+
# Create a new event loop in a thread and run the picker there.
|
|
271
|
+
# FREE-THREADED: ThreadPoolExecutor is compatible with free-threaded Python 3.14.
|
|
272
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
273
|
+
future = executor.submit(
|
|
274
|
+
lambda: asyncio.run(interactive_agent_picker())
|
|
275
|
+
)
|
|
276
|
+
selected_agent = future.result(timeout=300) # 5 min timeout
|
|
277
|
+
|
|
278
|
+
if selected_agent:
|
|
279
|
+
current_agent = get_current_agent()
|
|
280
|
+
# Check if we're already using this agent
|
|
281
|
+
if current_agent.name == selected_agent:
|
|
282
|
+
group_id = str(uuid.uuid4())
|
|
283
|
+
emit_info(
|
|
284
|
+
f"Already using agent: {current_agent.display_name}",
|
|
285
|
+
message_group=group_id,
|
|
286
|
+
)
|
|
287
|
+
return True
|
|
288
|
+
|
|
289
|
+
# Switch to the new agent
|
|
290
|
+
group_id = str(uuid.uuid4())
|
|
291
|
+
new_session_id = finalize_autosave_session()
|
|
292
|
+
if not set_current_agent(selected_agent):
|
|
293
|
+
emit_warning(
|
|
294
|
+
"Agent switch failed after autosave rotation. Your context was preserved.",
|
|
295
|
+
message_group=group_id,
|
|
296
|
+
)
|
|
297
|
+
return True
|
|
298
|
+
|
|
299
|
+
new_agent = get_current_agent()
|
|
300
|
+
new_agent.reload_code_generation_agent()
|
|
301
|
+
emit_success(
|
|
302
|
+
f"Switched to agent: {new_agent.display_name}",
|
|
303
|
+
message_group=group_id,
|
|
304
|
+
)
|
|
305
|
+
emit_info(f"{new_agent.description}", message_group=group_id)
|
|
306
|
+
emit_info(
|
|
307
|
+
Text.from_markup(
|
|
308
|
+
f"[dim]Auto-save session rotated to: {new_session_id}[/dim]"
|
|
309
|
+
),
|
|
310
|
+
message_group=group_id,
|
|
311
|
+
)
|
|
312
|
+
else:
|
|
313
|
+
emit_warning("Agent selection cancelled")
|
|
314
|
+
return True
|
|
315
|
+
except Exception as e:
|
|
316
|
+
# Fallback to old behavior if picker fails
|
|
317
|
+
import traceback
|
|
318
|
+
import uuid
|
|
319
|
+
|
|
320
|
+
emit_warning(f"Interactive picker failed: {e}")
|
|
321
|
+
emit_warning(f"Traceback: {traceback.format_exc()}")
|
|
322
|
+
|
|
323
|
+
# Show current agent and available agents
|
|
324
|
+
current_agent = get_current_agent()
|
|
325
|
+
available_agents = get_available_agents()
|
|
326
|
+
descriptions = get_agent_descriptions()
|
|
327
|
+
|
|
328
|
+
# Generate a group ID for all messages in this command
|
|
329
|
+
group_id = str(uuid.uuid4())
|
|
330
|
+
|
|
331
|
+
emit_info(
|
|
332
|
+
Text.from_markup(
|
|
333
|
+
f"[bold green]Current Agent:[/bold green] {current_agent.display_name}"
|
|
334
|
+
),
|
|
335
|
+
message_group=group_id,
|
|
336
|
+
)
|
|
337
|
+
emit_info(
|
|
338
|
+
Text.from_markup(f"[dim]{current_agent.description}[/dim]\n"),
|
|
339
|
+
message_group=group_id,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
emit_info(
|
|
343
|
+
Text.from_markup("[bold magenta]Available Agents:[/bold magenta]"),
|
|
344
|
+
message_group=group_id,
|
|
345
|
+
)
|
|
346
|
+
for name, display_name in available_agents.items():
|
|
347
|
+
description = descriptions.get(name, "No description")
|
|
348
|
+
current_marker = (
|
|
349
|
+
" [green]← current[/green]" if name == current_agent.name else ""
|
|
350
|
+
)
|
|
351
|
+
emit_info(
|
|
352
|
+
Text.from_markup(
|
|
353
|
+
f" [cyan]{name:<12}[/cyan] {display_name}{current_marker}"
|
|
354
|
+
),
|
|
355
|
+
message_group=group_id,
|
|
356
|
+
)
|
|
357
|
+
emit_info(f" {description}", message_group=group_id)
|
|
358
|
+
|
|
359
|
+
emit_info(
|
|
360
|
+
Text.from_markup("\n[yellow]Usage:[/yellow] /agent <agent-name>"),
|
|
361
|
+
message_group=group_id,
|
|
362
|
+
)
|
|
363
|
+
return True
|
|
364
|
+
|
|
365
|
+
elif len(tokens) == 2:
|
|
366
|
+
agent_name = tokens[1].lower()
|
|
367
|
+
|
|
368
|
+
# Generate a group ID for all messages in this command
|
|
369
|
+
import uuid
|
|
370
|
+
|
|
371
|
+
group_id = str(uuid.uuid4())
|
|
372
|
+
available_agents = get_available_agents()
|
|
373
|
+
|
|
374
|
+
if agent_name not in available_agents:
|
|
375
|
+
emit_error(f"Agent '{agent_name}' not found", message_group=group_id)
|
|
376
|
+
emit_warning(
|
|
377
|
+
f"Available agents: {', '.join(available_agents.keys())}",
|
|
378
|
+
message_group=group_id,
|
|
379
|
+
)
|
|
380
|
+
return True
|
|
381
|
+
|
|
382
|
+
current_agent = get_current_agent()
|
|
383
|
+
if current_agent.name == agent_name:
|
|
384
|
+
emit_info(
|
|
385
|
+
f"Already using agent: {current_agent.display_name}",
|
|
386
|
+
message_group=group_id,
|
|
387
|
+
)
|
|
388
|
+
return True
|
|
389
|
+
|
|
390
|
+
new_session_id = finalize_autosave_session()
|
|
391
|
+
if not set_current_agent(agent_name):
|
|
392
|
+
emit_warning(
|
|
393
|
+
"Agent switch failed after autosave rotation. Your context was preserved.",
|
|
394
|
+
message_group=group_id,
|
|
395
|
+
)
|
|
396
|
+
return True
|
|
397
|
+
|
|
398
|
+
new_agent = get_current_agent()
|
|
399
|
+
new_agent.reload_code_generation_agent()
|
|
400
|
+
emit_success(
|
|
401
|
+
f"Switched to agent: {new_agent.display_name}",
|
|
402
|
+
message_group=group_id,
|
|
403
|
+
)
|
|
404
|
+
emit_info(f"{new_agent.description}", message_group=group_id)
|
|
405
|
+
emit_info(
|
|
406
|
+
Text.from_markup(
|
|
407
|
+
f"[dim]Auto-save session rotated to: {new_session_id}[/dim]"
|
|
408
|
+
),
|
|
409
|
+
message_group=group_id,
|
|
410
|
+
)
|
|
411
|
+
return True
|
|
412
|
+
else:
|
|
413
|
+
emit_warning("Usage: /agent [agent-name]")
|
|
414
|
+
return True
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
@register_command(
|
|
418
|
+
name="model",
|
|
419
|
+
description="Set active model",
|
|
420
|
+
usage="/model, /m <model>",
|
|
421
|
+
aliases=["m"],
|
|
422
|
+
category="core",
|
|
423
|
+
)
|
|
424
|
+
def handle_model_command(command: str) -> bool:
|
|
425
|
+
"""Set the active model."""
|
|
426
|
+
import asyncio
|
|
427
|
+
|
|
428
|
+
from code_muse.command_line.model_picker_completion import (
|
|
429
|
+
get_active_model,
|
|
430
|
+
load_model_names,
|
|
431
|
+
set_active_model,
|
|
432
|
+
)
|
|
433
|
+
from code_muse.messaging import emit_success, emit_warning
|
|
434
|
+
|
|
435
|
+
tokens = command.split()
|
|
436
|
+
|
|
437
|
+
# If just /model or /m with no args, show interactive picker
|
|
438
|
+
if len(tokens) == 1:
|
|
439
|
+
try:
|
|
440
|
+
# Run the async picker using asyncio utilities
|
|
441
|
+
# Since we're called from an async context but this function is sync,
|
|
442
|
+
# we need to carefully schedule and wait for the coroutine
|
|
443
|
+
import concurrent.futures
|
|
444
|
+
|
|
445
|
+
# Create a new event loop in a thread and run the picker there.
|
|
446
|
+
# FREE-THREADED: ThreadPoolExecutor is compatible with free-threaded Python 3.14.
|
|
447
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
448
|
+
future = executor.submit(
|
|
449
|
+
lambda: asyncio.run(interactive_model_picker())
|
|
450
|
+
)
|
|
451
|
+
selected_model = future.result(timeout=300) # 5 min timeout
|
|
452
|
+
|
|
453
|
+
if selected_model:
|
|
454
|
+
set_active_model(selected_model)
|
|
455
|
+
emit_success(f"Active model set and loaded: {selected_model}")
|
|
456
|
+
else:
|
|
457
|
+
emit_warning("Model selection cancelled")
|
|
458
|
+
return True
|
|
459
|
+
except Exception as e:
|
|
460
|
+
# Fallback to old behavior if picker fails
|
|
461
|
+
import traceback
|
|
462
|
+
|
|
463
|
+
emit_warning(f"Interactive picker failed: {e}")
|
|
464
|
+
emit_warning(f"Traceback: {traceback.format_exc()}")
|
|
465
|
+
model_names = load_model_names()
|
|
466
|
+
emit_warning("Usage: /model <model-name> or /m <model-name>")
|
|
467
|
+
emit_warning(f"Available models: {', '.join(model_names)}")
|
|
468
|
+
return True
|
|
469
|
+
|
|
470
|
+
# Handle both /model and /m for backward compatibility
|
|
471
|
+
model_command = command
|
|
472
|
+
if command.startswith("/model"):
|
|
473
|
+
# Convert /model to /m for internal processing
|
|
474
|
+
model_command = command.replace("/model", "/m", 1)
|
|
475
|
+
|
|
476
|
+
# If model matched, set it
|
|
477
|
+
new_input = update_model_in_input(model_command)
|
|
478
|
+
if new_input is not None:
|
|
479
|
+
model = get_active_model()
|
|
480
|
+
emit_success(f"Active model set and loaded: {model}")
|
|
481
|
+
return True
|
|
482
|
+
|
|
483
|
+
# If no model matched, show error
|
|
484
|
+
model_names = load_model_names()
|
|
485
|
+
emit_warning("Usage: /model <model-name> or /m <model-name>")
|
|
486
|
+
emit_warning(f"Available models: {', '.join(model_names)}")
|
|
487
|
+
return True
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
@register_command(
|
|
491
|
+
name="add_model",
|
|
492
|
+
description="Browse and add models from models.dev catalog",
|
|
493
|
+
usage="/add_model",
|
|
494
|
+
category="core",
|
|
495
|
+
)
|
|
496
|
+
def handle_add_model_command(command: str) -> bool:
|
|
497
|
+
"""Launch interactive model browser TUI."""
|
|
498
|
+
from code_muse.command_line.add_model_menu import interactive_model_picker
|
|
499
|
+
from code_muse.tools.command_runner import set_awaiting_user_input
|
|
500
|
+
|
|
501
|
+
set_awaiting_user_input(True)
|
|
502
|
+
try:
|
|
503
|
+
# interactive_model_picker is now synchronous - no async complications!
|
|
504
|
+
result = interactive_model_picker()
|
|
505
|
+
|
|
506
|
+
if result:
|
|
507
|
+
emit_info("Successfully added model configuration")
|
|
508
|
+
return True
|
|
509
|
+
except KeyboardInterrupt:
|
|
510
|
+
# User cancelled - this is expected behavior
|
|
511
|
+
return True
|
|
512
|
+
except Exception as e:
|
|
513
|
+
emit_error(f"Failed to launch model browser: {e}")
|
|
514
|
+
return False
|
|
515
|
+
finally:
|
|
516
|
+
set_awaiting_user_input(False)
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
@register_command(
|
|
520
|
+
name="remove_model",
|
|
521
|
+
description="Remove a model from extra_models.json",
|
|
522
|
+
usage="/remove_model <model_name>",
|
|
523
|
+
category="config",
|
|
524
|
+
)
|
|
525
|
+
def handle_remove_model_command(command: str) -> bool:
|
|
526
|
+
"""Remove a model from extra_models.json by key."""
|
|
527
|
+
import json
|
|
528
|
+
import pathlib
|
|
529
|
+
|
|
530
|
+
from code_muse.config import EXTRA_MODELS_FILE, clear_model_cache
|
|
531
|
+
from code_muse.messaging import emit_success, emit_warning
|
|
532
|
+
|
|
533
|
+
tokens = command.split()
|
|
534
|
+
if len(tokens) < 2:
|
|
535
|
+
emit_warning("Usage: /remove_model <model_name>")
|
|
536
|
+
return False
|
|
537
|
+
|
|
538
|
+
model_name = tokens[1]
|
|
539
|
+
extra_models_path = pathlib.Path(EXTRA_MODELS_FILE)
|
|
540
|
+
extra_models: dict = {}
|
|
541
|
+
|
|
542
|
+
if extra_models_path.exists():
|
|
543
|
+
try:
|
|
544
|
+
with open(extra_models_path, encoding="utf-8") as f:
|
|
545
|
+
extra_models = json.load(f)
|
|
546
|
+
if not isinstance(extra_models, dict):
|
|
547
|
+
emit_warning("extra_models.json must be a dictionary")
|
|
548
|
+
return False
|
|
549
|
+
except json.JSONDecodeError as e:
|
|
550
|
+
emit_warning(f"Error parsing extra_models.json: {e}")
|
|
551
|
+
return False
|
|
552
|
+
|
|
553
|
+
if model_name not in extra_models:
|
|
554
|
+
available = list(extra_models.keys())
|
|
555
|
+
if available:
|
|
556
|
+
emit_warning(
|
|
557
|
+
f"Model '{model_name}' not found in extra_models.json. "
|
|
558
|
+
f"Available extra models: {', '.join(available)}"
|
|
559
|
+
)
|
|
560
|
+
else:
|
|
561
|
+
emit_warning(f"Model '{model_name}' not found. extra_models.json is empty.")
|
|
562
|
+
return False
|
|
563
|
+
|
|
564
|
+
del extra_models[model_name]
|
|
565
|
+
|
|
566
|
+
# Atomic write
|
|
567
|
+
extra_models_path.parent.mkdir(parents=True, exist_ok=True)
|
|
568
|
+
temp_path = extra_models_path.with_suffix(".tmp")
|
|
569
|
+
with open(temp_path, "w", encoding="utf-8") as f:
|
|
570
|
+
json.dump(extra_models, f, indent=4, ensure_ascii=False)
|
|
571
|
+
temp_path.replace(extra_models_path)
|
|
572
|
+
|
|
573
|
+
clear_model_cache()
|
|
574
|
+
emit_success(f"Removed '{model_name}' from extra_models.json")
|
|
575
|
+
return True
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
@register_command(
|
|
579
|
+
name="model_settings",
|
|
580
|
+
description="Configure per-model settings (temperature, seed, etc.)",
|
|
581
|
+
usage="/model_settings [--show [model_name]]",
|
|
582
|
+
aliases=["ms"],
|
|
583
|
+
category="config",
|
|
584
|
+
)
|
|
585
|
+
def handle_model_settings_command(command: str) -> bool:
|
|
586
|
+
"""Launch interactive model settings TUI.
|
|
587
|
+
|
|
588
|
+
Opens a TUI showing all available models. Select a model to configure
|
|
589
|
+
its settings (temperature, seed, etc.). ESC closes the TUI.
|
|
590
|
+
|
|
591
|
+
Use --show [model_name] to display current settings without the TUI.
|
|
592
|
+
"""
|
|
593
|
+
from code_muse.command_line.model_settings_menu import (
|
|
594
|
+
interactive_model_settings,
|
|
595
|
+
show_model_settings_summary,
|
|
596
|
+
)
|
|
597
|
+
from code_muse.messaging import emit_error, emit_info, emit_success, emit_warning
|
|
598
|
+
from code_muse.tools.command_runner import set_awaiting_user_input
|
|
599
|
+
|
|
600
|
+
tokens = command.split()
|
|
601
|
+
|
|
602
|
+
# Check for --show flag to just display current settings
|
|
603
|
+
if "--show" in tokens:
|
|
604
|
+
model_name = None
|
|
605
|
+
for t in tokens[1:]:
|
|
606
|
+
if not t.startswith("--"):
|
|
607
|
+
model_name = t
|
|
608
|
+
break
|
|
609
|
+
show_model_settings_summary(model_name)
|
|
610
|
+
return True
|
|
611
|
+
|
|
612
|
+
set_awaiting_user_input(True)
|
|
613
|
+
try:
|
|
614
|
+
result = interactive_model_settings()
|
|
615
|
+
|
|
616
|
+
if result:
|
|
617
|
+
emit_success("Model settings updated successfully")
|
|
618
|
+
|
|
619
|
+
# Always reload the active agent so settings take effect
|
|
620
|
+
from code_muse.agents import get_current_agent
|
|
621
|
+
|
|
622
|
+
try:
|
|
623
|
+
current_agent = get_current_agent()
|
|
624
|
+
current_agent.reload_code_generation_agent()
|
|
625
|
+
emit_info("Active agent reloaded")
|
|
626
|
+
except Exception as reload_error:
|
|
627
|
+
emit_warning(f"Agent reload failed: {reload_error}")
|
|
628
|
+
|
|
629
|
+
return True
|
|
630
|
+
except KeyboardInterrupt:
|
|
631
|
+
return True
|
|
632
|
+
except Exception as e:
|
|
633
|
+
emit_error(f"Failed to launch model settings: {e}")
|
|
634
|
+
return False
|
|
635
|
+
finally:
|
|
636
|
+
set_awaiting_user_input(False)
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
@register_command(
|
|
640
|
+
name="generate-pr-description",
|
|
641
|
+
description="Generate comprehensive PR description",
|
|
642
|
+
usage="/generate-pr-description [@dir]",
|
|
643
|
+
category="core",
|
|
644
|
+
)
|
|
645
|
+
def handle_generate_pr_description_command(command: str) -> str:
|
|
646
|
+
"""Generate a PR description."""
|
|
647
|
+
# Parse directory argument (e.g., /generate-pr-description @some/dir)
|
|
648
|
+
tokens = command.split()
|
|
649
|
+
directory_context = ""
|
|
650
|
+
for t in tokens:
|
|
651
|
+
if t.startswith("@"):
|
|
652
|
+
directory_context = f" Please work in the directory: {t[1:]}"
|
|
653
|
+
break
|
|
654
|
+
|
|
655
|
+
# Hard-coded prompt from user requirements
|
|
656
|
+
pr_prompt = f"""Generate a comprehensive PR description for my current branch changes. Follow these steps:
|
|
657
|
+
|
|
658
|
+
1 Discover the changes: Use git CLI to find the base branch (usually main/master/develop) and get the list of changed files, commits, and diffs.
|
|
659
|
+
2 Analyze the code: Read and analyze all modified files to understand:
|
|
660
|
+
• What functionality was added/changed/removed
|
|
661
|
+
• The technical approach and implementation details
|
|
662
|
+
• Any architectural or design pattern changes
|
|
663
|
+
• Dependencies added/removed/updated
|
|
664
|
+
3 Generate a structured PR description with these sections:
|
|
665
|
+
• Title: Concise, descriptive title (50 chars max)
|
|
666
|
+
• Summary: Brief overview of what this PR accomplishes
|
|
667
|
+
• Changes Made: Detailed bullet points of specific changes
|
|
668
|
+
• Technical Details: Implementation approach, design decisions, patterns used
|
|
669
|
+
• Files Modified: List of key files with brief description of changes
|
|
670
|
+
• Testing: What was tested and how (if applicable)
|
|
671
|
+
• Breaking Changes: Any breaking changes (if applicable)
|
|
672
|
+
• Additional Notes: Any other relevant information
|
|
673
|
+
4 Create a markdown file: Generate a PR_DESCRIPTION.md file with proper GitHub markdown formatting that I can directly copy-paste into GitHub's PR
|
|
674
|
+
description field. Use proper markdown syntax with headers, bullet points, code blocks, and formatting.
|
|
675
|
+
5 Make it review-ready: Ensure the description helps reviewers understand the context, approach, and impact of the changes.
|
|
676
|
+
6. If you have Github CLI integration, or gh cli is installed and authenticated then find the PR for the branch we analyzed and update the PR description there and then delete the PR_DESCRIPTION.md file. (If you have a better name (title) for the PR, go ahead and update the title too.{directory_context}"""
|
|
677
|
+
|
|
678
|
+
# Return the prompt to be processed by the main chat system
|
|
679
|
+
return pr_prompt
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
@register_command(
|
|
683
|
+
name="wiggum",
|
|
684
|
+
description="Loop mode: re-run the same prompt when agent finishes (like Wiggum chasing donuts 🍩)",
|
|
685
|
+
usage="/wiggum <prompt>",
|
|
686
|
+
category="core",
|
|
687
|
+
)
|
|
688
|
+
def handle_wiggum_command(command: str) -> str | bool:
|
|
689
|
+
"""Start wiggum loop mode.
|
|
690
|
+
|
|
691
|
+
When active, the agent will automatically re-run the same prompt
|
|
692
|
+
after completing, resetting context each time. Use Ctrl+C to stop.
|
|
693
|
+
|
|
694
|
+
Example:
|
|
695
|
+
/wiggum say hello world
|
|
696
|
+
"""
|
|
697
|
+
from code_muse.command_line.wiggum_state import start_wiggum
|
|
698
|
+
from code_muse.messaging import emit_info, emit_success, emit_warning
|
|
699
|
+
|
|
700
|
+
# Extract the prompt after /wiggum
|
|
701
|
+
parts = command.split(maxsplit=1)
|
|
702
|
+
if len(parts) < 2 or not parts[1].strip():
|
|
703
|
+
emit_warning("Usage: /wiggum <prompt>")
|
|
704
|
+
emit_info("Example: /wiggum say hello world")
|
|
705
|
+
emit_info("This will repeatedly run 'say hello world' after each completion.")
|
|
706
|
+
emit_info("Press Ctrl+C to stop the loop.")
|
|
707
|
+
return True
|
|
708
|
+
|
|
709
|
+
prompt = parts[1].strip()
|
|
710
|
+
|
|
711
|
+
# Start wiggum mode
|
|
712
|
+
start_wiggum(prompt)
|
|
713
|
+
emit_success("🍩 WIGGUM MODE ACTIVATED!")
|
|
714
|
+
emit_info(f"Prompt: {prompt}")
|
|
715
|
+
emit_info("The agent will re-loop this prompt after each completion.")
|
|
716
|
+
emit_info("Press Ctrl+C to stop the wiggum loop.")
|
|
717
|
+
|
|
718
|
+
# Return the prompt to execute immediately
|
|
719
|
+
return prompt
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
@register_command(
|
|
723
|
+
name="wiggum_stop",
|
|
724
|
+
description="Stop wiggum loop mode",
|
|
725
|
+
usage="/wiggum_stop",
|
|
726
|
+
aliases=["stopwiggum", "ws"],
|
|
727
|
+
category="core",
|
|
728
|
+
)
|
|
729
|
+
def handle_wiggum_stop_command(command: str) -> bool:
|
|
730
|
+
"""Stop wiggum loop mode."""
|
|
731
|
+
from code_muse.command_line.wiggum_state import is_wiggum_active, stop_wiggum
|
|
732
|
+
from code_muse.messaging import emit_info, emit_success
|
|
733
|
+
|
|
734
|
+
if is_wiggum_active():
|
|
735
|
+
stop_wiggum()
|
|
736
|
+
emit_success("🍩 Wiggum mode stopped!")
|
|
737
|
+
else:
|
|
738
|
+
emit_info("Wiggum mode is not active.")
|
|
739
|
+
|
|
740
|
+
return True
|