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,73 @@
|
|
|
1
|
+
import glob
|
|
2
|
+
import os
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from prompt_toolkit.completion import Completer, Completion
|
|
7
|
+
from prompt_toolkit.document import Document
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FilePathCompleter(Completer):
|
|
11
|
+
"""A simple file path completer that works with a trigger symbol."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, symbol: str = "@"):
|
|
14
|
+
self.symbol = symbol
|
|
15
|
+
|
|
16
|
+
def get_completions(
|
|
17
|
+
self, document: Document, complete_event
|
|
18
|
+
) -> Iterable[Completion]:
|
|
19
|
+
text = document.text
|
|
20
|
+
cursor_position = document.cursor_position
|
|
21
|
+
text_before_cursor = text[:cursor_position]
|
|
22
|
+
if self.symbol not in text_before_cursor:
|
|
23
|
+
return
|
|
24
|
+
symbol_pos = text_before_cursor.rfind(self.symbol)
|
|
25
|
+
text_after_symbol = text_before_cursor[symbol_pos + len(self.symbol) :]
|
|
26
|
+
start_position = -(len(text_after_symbol))
|
|
27
|
+
try:
|
|
28
|
+
pattern = text_after_symbol + "*"
|
|
29
|
+
if not pattern.strip("*") or pattern.strip("*").endswith("/"):
|
|
30
|
+
base_path = pattern.strip("*")
|
|
31
|
+
if not base_path:
|
|
32
|
+
base_path = "."
|
|
33
|
+
if base_path.startswith("~"):
|
|
34
|
+
base_path = Path(base_path).expanduser()
|
|
35
|
+
if Path(base_path).is_dir():
|
|
36
|
+
paths = [
|
|
37
|
+
str(Path(base_path) / f)
|
|
38
|
+
for f in os.listdir(base_path)
|
|
39
|
+
if not f.startswith(".") or text_after_symbol.endswith(".")
|
|
40
|
+
]
|
|
41
|
+
else:
|
|
42
|
+
paths = []
|
|
43
|
+
else:
|
|
44
|
+
paths = glob.glob(pattern)
|
|
45
|
+
if not pattern.startswith(".") and not pattern.startswith("*/."):
|
|
46
|
+
paths = [p for p in paths if not Path(p).name.startswith(".")]
|
|
47
|
+
paths.sort()
|
|
48
|
+
for path in paths:
|
|
49
|
+
p = Path(path)
|
|
50
|
+
is_dir = p.is_dir()
|
|
51
|
+
display = p.name
|
|
52
|
+
if p.is_absolute():
|
|
53
|
+
display_path = path
|
|
54
|
+
else:
|
|
55
|
+
if text_after_symbol.startswith("/"):
|
|
56
|
+
display_path = str(p.resolve())
|
|
57
|
+
elif text_after_symbol.startswith("~"):
|
|
58
|
+
home = Path.home()
|
|
59
|
+
if path.startswith(str(home)):
|
|
60
|
+
display_path = "~" + path[len(str(home)) :]
|
|
61
|
+
else:
|
|
62
|
+
display_path = path
|
|
63
|
+
else:
|
|
64
|
+
display_path = path
|
|
65
|
+
display_meta = "Directory" if is_dir else "File"
|
|
66
|
+
yield Completion(
|
|
67
|
+
display_path,
|
|
68
|
+
start_position=start_position,
|
|
69
|
+
display=display,
|
|
70
|
+
display_meta=display_meta,
|
|
71
|
+
)
|
|
72
|
+
except OSError, RuntimeError:
|
|
73
|
+
pass
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from prompt_toolkit.completion import Completer, Completion
|
|
4
|
+
|
|
5
|
+
from code_muse.config import CONFIG_DIR
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LoadContextCompleter(Completer):
|
|
9
|
+
def __init__(self, trigger: str = "/load_context"):
|
|
10
|
+
self.trigger = trigger
|
|
11
|
+
|
|
12
|
+
def get_completions(self, document, complete_event):
|
|
13
|
+
cursor_position = document.cursor_position
|
|
14
|
+
text_before_cursor = document.text_before_cursor
|
|
15
|
+
stripped_text_for_trigger_check = text_before_cursor.lstrip()
|
|
16
|
+
|
|
17
|
+
# If user types just /load_context (no space), suggest adding a space
|
|
18
|
+
if stripped_text_for_trigger_check == self.trigger:
|
|
19
|
+
yield Completion(
|
|
20
|
+
self.trigger + " ",
|
|
21
|
+
start_position=-len(self.trigger),
|
|
22
|
+
display=self.trigger + " ",
|
|
23
|
+
display_meta="load saved context",
|
|
24
|
+
)
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
# Require a space after /load_context before showing completions (consistency with other completers)
|
|
28
|
+
if not stripped_text_for_trigger_check.startswith(self.trigger + " "):
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
# Extract the session name after /load_context and space (up to cursor)
|
|
32
|
+
actual_trigger_pos = text_before_cursor.find(self.trigger)
|
|
33
|
+
trigger_end = actual_trigger_pos + len(self.trigger) + 1 # +1 for the space
|
|
34
|
+
session_filter = text_before_cursor[trigger_end:cursor_position].lstrip()
|
|
35
|
+
start_position = -(len(session_filter))
|
|
36
|
+
|
|
37
|
+
# Get available context files (both .json and .pkl)
|
|
38
|
+
try:
|
|
39
|
+
contexts_dir = Path(CONFIG_DIR) / "contexts"
|
|
40
|
+
if contexts_dir.exists():
|
|
41
|
+
seen: set[str] = set()
|
|
42
|
+
for ext in ("*.json", "*.pkl"):
|
|
43
|
+
for context_file in contexts_dir.glob(ext):
|
|
44
|
+
session_name = context_file.stem
|
|
45
|
+
if session_name in seen:
|
|
46
|
+
continue
|
|
47
|
+
seen.add(session_name)
|
|
48
|
+
if session_name.startswith(session_filter):
|
|
49
|
+
yield Completion(
|
|
50
|
+
session_name,
|
|
51
|
+
start_position=start_position,
|
|
52
|
+
display=session_name,
|
|
53
|
+
display_meta="saved context session",
|
|
54
|
+
)
|
|
55
|
+
except Exception:
|
|
56
|
+
# Silently ignore errors (e.g., permission issues, non-existent dir)
|
|
57
|
+
pass
|
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
from collections.abc import Iterable
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from prompt_toolkit import Application, PromptSession
|
|
5
|
+
from prompt_toolkit.completion import Completer, Completion
|
|
6
|
+
from prompt_toolkit.document import Document
|
|
7
|
+
from prompt_toolkit.history import FileHistory
|
|
8
|
+
from prompt_toolkit.key_binding import KeyBindings
|
|
9
|
+
from prompt_toolkit.layout import Layout, Window
|
|
10
|
+
from prompt_toolkit.layout.controls import FormattedTextControl
|
|
11
|
+
|
|
12
|
+
from code_muse.command_line.pagination import (
|
|
13
|
+
ensure_visible_page,
|
|
14
|
+
get_page_bounds,
|
|
15
|
+
get_page_for_index,
|
|
16
|
+
get_total_pages,
|
|
17
|
+
)
|
|
18
|
+
from code_muse.config import get_global_model_name
|
|
19
|
+
from code_muse.list_filtering import query_matches_text
|
|
20
|
+
from code_muse.model_switching import set_model_and_reload_agent
|
|
21
|
+
|
|
22
|
+
MODEL_PICKER_PAGE_SIZE = 15
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def load_model_names():
|
|
26
|
+
"""Load model names from the config that's fetched from the endpoint."""
|
|
27
|
+
from code_muse.model_factory import ModelFactory
|
|
28
|
+
|
|
29
|
+
models_config = ModelFactory.load_config()
|
|
30
|
+
return list(models_config.keys())
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_active_model():
|
|
34
|
+
"""
|
|
35
|
+
Returns the active model from the config using get_model_name().
|
|
36
|
+
This ensures consistency across the codebase by always using the config value.
|
|
37
|
+
"""
|
|
38
|
+
return get_global_model_name()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def set_active_model(model_name: str):
|
|
42
|
+
"""
|
|
43
|
+
Sets the active model name by updating the config (for persistence).
|
|
44
|
+
"""
|
|
45
|
+
set_model_and_reload_agent(model_name)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ModelNameCompleter(Completer):
|
|
49
|
+
"""
|
|
50
|
+
A completer that triggers on '/model' to show available models from models.json.
|
|
51
|
+
Only '/model' (not just '/') will trigger the dropdown.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(self, trigger: str = "/model"):
|
|
55
|
+
self.trigger = trigger
|
|
56
|
+
self.model_names = load_model_names()
|
|
57
|
+
|
|
58
|
+
def get_completions(
|
|
59
|
+
self, document: Document, complete_event
|
|
60
|
+
) -> Iterable[Completion]:
|
|
61
|
+
text = document.text
|
|
62
|
+
cursor_position = document.cursor_position
|
|
63
|
+
text_before_cursor = text[:cursor_position]
|
|
64
|
+
|
|
65
|
+
# Only trigger if /model is at the very beginning of the line and has a space after it
|
|
66
|
+
stripped_text = text_before_cursor.lstrip()
|
|
67
|
+
if not stripped_text.startswith(self.trigger + " "):
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
# Find where /model actually starts (after any leading whitespace)
|
|
71
|
+
symbol_pos = text_before_cursor.find(self.trigger)
|
|
72
|
+
text_after_trigger = text_before_cursor[
|
|
73
|
+
symbol_pos + len(self.trigger) + 1 :
|
|
74
|
+
].lstrip()
|
|
75
|
+
start_position = -(len(text_after_trigger))
|
|
76
|
+
|
|
77
|
+
# Filter model names based on what's typed after /model (case-insensitive)
|
|
78
|
+
for model_name in self.model_names:
|
|
79
|
+
if text_after_trigger and not query_matches_text(
|
|
80
|
+
text_after_trigger, model_name
|
|
81
|
+
):
|
|
82
|
+
continue # Skip models that don't match the typed text
|
|
83
|
+
|
|
84
|
+
meta = (
|
|
85
|
+
"Model (selected)"
|
|
86
|
+
if model_name.lower() == get_active_model().lower()
|
|
87
|
+
else "Model"
|
|
88
|
+
)
|
|
89
|
+
yield Completion(
|
|
90
|
+
model_name,
|
|
91
|
+
start_position=start_position,
|
|
92
|
+
display=model_name,
|
|
93
|
+
display_meta=meta,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _find_matching_model(rest: str, model_names: list[str]) -> str | None:
|
|
98
|
+
"""
|
|
99
|
+
Find the best matching model for the given input.
|
|
100
|
+
|
|
101
|
+
Priority:
|
|
102
|
+
1. Exact match (case-insensitive)
|
|
103
|
+
2. Input starts with a model name (longest/most specific wins)
|
|
104
|
+
3. Model starts with input (prefix/completion match, longest wins)
|
|
105
|
+
"""
|
|
106
|
+
rest_lower = rest.lower()
|
|
107
|
+
|
|
108
|
+
# First check for exact match
|
|
109
|
+
for model in model_names:
|
|
110
|
+
if rest_lower == model.lower():
|
|
111
|
+
return model
|
|
112
|
+
|
|
113
|
+
# Sort by length (longest first) so more specific matches win
|
|
114
|
+
sorted_models = sorted(model_names, key=len, reverse=True)
|
|
115
|
+
|
|
116
|
+
# Check if input starts with a model name (e.g. "gpt-5 tell me a joke")
|
|
117
|
+
for model in sorted_models:
|
|
118
|
+
model_lower = model.lower()
|
|
119
|
+
if rest_lower.startswith(model_lower) and (
|
|
120
|
+
len(rest_lower) == len(model_lower) or rest_lower[len(model_lower)] == " "
|
|
121
|
+
):
|
|
122
|
+
return model
|
|
123
|
+
|
|
124
|
+
# Check for prefix/completion match (input is partial model name)
|
|
125
|
+
for model in sorted_models:
|
|
126
|
+
if model.lower().startswith(rest_lower):
|
|
127
|
+
return model
|
|
128
|
+
|
|
129
|
+
# Fall back to the same fuzzy matcher used by the completer.
|
|
130
|
+
for model in sorted_models:
|
|
131
|
+
if query_matches_text(rest, model):
|
|
132
|
+
return model
|
|
133
|
+
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def update_model_in_input(text: str) -> str | None:
|
|
138
|
+
# If input starts with /model or /m and a model name, set model and strip it out
|
|
139
|
+
content = text.strip()
|
|
140
|
+
model_names = load_model_names()
|
|
141
|
+
|
|
142
|
+
# Check for /model command (require space after /model, case-insensitive)
|
|
143
|
+
if content.lower().startswith("/model "):
|
|
144
|
+
# Find the actual /model command (case-insensitive)
|
|
145
|
+
model_cmd = content.split(" ", 1)[0] # Get the command part
|
|
146
|
+
rest = content[len(model_cmd) :].strip() # Remove the actual command
|
|
147
|
+
|
|
148
|
+
# Find the best matching model
|
|
149
|
+
model = _find_matching_model(rest, model_names)
|
|
150
|
+
if model:
|
|
151
|
+
# Found a matching model - now extract it properly
|
|
152
|
+
set_active_model(model)
|
|
153
|
+
|
|
154
|
+
# Find the actual model name in the original text (preserving case)
|
|
155
|
+
# We need to find where the model ends in the original rest string
|
|
156
|
+
model_end_idx = len(model)
|
|
157
|
+
|
|
158
|
+
# Build the full command+model part to remove
|
|
159
|
+
cmd_and_model_pattern = model_cmd + " " + rest[:model_end_idx]
|
|
160
|
+
idx = text.find(cmd_and_model_pattern)
|
|
161
|
+
if idx != -1:
|
|
162
|
+
new_text = (
|
|
163
|
+
text[:idx] + text[idx + len(cmd_and_model_pattern) :]
|
|
164
|
+
).strip()
|
|
165
|
+
return new_text
|
|
166
|
+
return None
|
|
167
|
+
|
|
168
|
+
# Check for /m command (case-insensitive)
|
|
169
|
+
elif content.lower().startswith("/m ") and not content.lower().startswith(
|
|
170
|
+
"/model "
|
|
171
|
+
):
|
|
172
|
+
# Find the actual /m command (case-insensitive)
|
|
173
|
+
m_cmd = content.split(" ", 1)[0] # Get the command part
|
|
174
|
+
rest = content[len(m_cmd) :].strip() # Remove the actual command
|
|
175
|
+
|
|
176
|
+
# Find the best matching model
|
|
177
|
+
model = _find_matching_model(rest, model_names)
|
|
178
|
+
if model:
|
|
179
|
+
# Found a matching model - now extract it properly
|
|
180
|
+
set_active_model(model)
|
|
181
|
+
|
|
182
|
+
# Find the actual model name in the original text (preserving case)
|
|
183
|
+
# We need to find where the model ends in the original rest string
|
|
184
|
+
model_end_idx = len(model)
|
|
185
|
+
|
|
186
|
+
# Build the full command+model part to remove
|
|
187
|
+
# Handle space variations in the original text
|
|
188
|
+
cmd_and_model_pattern = m_cmd + " " + rest[:model_end_idx]
|
|
189
|
+
idx = text.find(cmd_and_model_pattern)
|
|
190
|
+
if idx != -1:
|
|
191
|
+
new_text = (
|
|
192
|
+
text[:idx] + text[idx + len(cmd_and_model_pattern) :]
|
|
193
|
+
).strip()
|
|
194
|
+
return new_text
|
|
195
|
+
return None
|
|
196
|
+
|
|
197
|
+
return None
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class ModelSelectionMenu:
|
|
201
|
+
"""Paginated interactive model picker for the /model command."""
|
|
202
|
+
|
|
203
|
+
def __init__(self, model_names: list[str | None] = None):
|
|
204
|
+
self.model_names = (
|
|
205
|
+
list(model_names) if model_names is not None else load_model_names()
|
|
206
|
+
)
|
|
207
|
+
self.current_model = get_active_model()
|
|
208
|
+
self.filter_text = ""
|
|
209
|
+
self.selected_index = 0
|
|
210
|
+
self.page = 0
|
|
211
|
+
self.page_size = MODEL_PICKER_PAGE_SIZE
|
|
212
|
+
self.result: str | None = None
|
|
213
|
+
|
|
214
|
+
if self.current_model in self.visible_model_names:
|
|
215
|
+
self.selected_index = self.visible_model_names.index(self.current_model)
|
|
216
|
+
self.page = get_page_for_index(self.selected_index, self.page_size)
|
|
217
|
+
|
|
218
|
+
@property
|
|
219
|
+
def total_pages(self) -> int:
|
|
220
|
+
return get_total_pages(len(self.visible_model_names), self.page_size)
|
|
221
|
+
|
|
222
|
+
@property
|
|
223
|
+
def page_start(self) -> int:
|
|
224
|
+
start, _ = get_page_bounds(
|
|
225
|
+
self.page, len(self.visible_model_names), self.page_size
|
|
226
|
+
)
|
|
227
|
+
return start
|
|
228
|
+
|
|
229
|
+
@property
|
|
230
|
+
def page_end(self) -> int:
|
|
231
|
+
_, end = get_page_bounds(
|
|
232
|
+
self.page, len(self.visible_model_names), self.page_size
|
|
233
|
+
)
|
|
234
|
+
return end
|
|
235
|
+
|
|
236
|
+
@property
|
|
237
|
+
def models_on_page(self) -> list[str]:
|
|
238
|
+
return self.visible_model_names[self.page_start : self.page_end]
|
|
239
|
+
|
|
240
|
+
@property
|
|
241
|
+
def visible_model_names(self) -> list[str]:
|
|
242
|
+
if not self.filter_text:
|
|
243
|
+
return self.model_names
|
|
244
|
+
return [
|
|
245
|
+
model_name
|
|
246
|
+
for model_name in self.model_names
|
|
247
|
+
if query_matches_text(self.filter_text, model_name)
|
|
248
|
+
]
|
|
249
|
+
|
|
250
|
+
def _get_selected_model_name(self) -> str | None:
|
|
251
|
+
if 0 <= self.selected_index < len(self.visible_model_names):
|
|
252
|
+
return self.visible_model_names[self.selected_index]
|
|
253
|
+
return None
|
|
254
|
+
|
|
255
|
+
def _ensure_selection_visible(self) -> None:
|
|
256
|
+
self.page = ensure_visible_page(
|
|
257
|
+
self.selected_index,
|
|
258
|
+
self.page,
|
|
259
|
+
len(self.visible_model_names),
|
|
260
|
+
self.page_size,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
def _set_filter_text(self, value: str) -> None:
|
|
264
|
+
selected_model = self._get_selected_model_name()
|
|
265
|
+
self.filter_text = value
|
|
266
|
+
visible_models = self.visible_model_names
|
|
267
|
+
if not visible_models:
|
|
268
|
+
self.selected_index = 0
|
|
269
|
+
self.page = 0
|
|
270
|
+
return
|
|
271
|
+
|
|
272
|
+
if selected_model and selected_model in visible_models:
|
|
273
|
+
self.selected_index = visible_models.index(selected_model)
|
|
274
|
+
elif self.current_model in visible_models:
|
|
275
|
+
self.selected_index = visible_models.index(self.current_model)
|
|
276
|
+
else:
|
|
277
|
+
self.selected_index = 0
|
|
278
|
+
self._ensure_selection_visible()
|
|
279
|
+
|
|
280
|
+
def _append_filter_char(self, value: str) -> None:
|
|
281
|
+
self._set_filter_text(self.filter_text + value)
|
|
282
|
+
|
|
283
|
+
def _delete_filter_char(self) -> None:
|
|
284
|
+
if self.filter_text:
|
|
285
|
+
self._set_filter_text(self.filter_text[:-1])
|
|
286
|
+
|
|
287
|
+
def _accept_selection(self) -> bool:
|
|
288
|
+
"""Store the currently selected visible model if one is available."""
|
|
289
|
+
selected_model = self._get_selected_model_name()
|
|
290
|
+
if selected_model is None:
|
|
291
|
+
return False
|
|
292
|
+
self.result = selected_model
|
|
293
|
+
return True
|
|
294
|
+
|
|
295
|
+
def _move_up(self) -> None:
|
|
296
|
+
if self.selected_index > 0:
|
|
297
|
+
self.selected_index -= 1
|
|
298
|
+
self._ensure_selection_visible()
|
|
299
|
+
|
|
300
|
+
def _move_down(self) -> None:
|
|
301
|
+
if self.selected_index < len(self.visible_model_names) - 1:
|
|
302
|
+
self.selected_index += 1
|
|
303
|
+
self._ensure_selection_visible()
|
|
304
|
+
|
|
305
|
+
def _page_up(self) -> None:
|
|
306
|
+
if self.page > 0:
|
|
307
|
+
self.page -= 1
|
|
308
|
+
self.selected_index = self.page_start
|
|
309
|
+
|
|
310
|
+
def _page_down(self) -> None:
|
|
311
|
+
if self.page < self.total_pages - 1:
|
|
312
|
+
self.page += 1
|
|
313
|
+
self.selected_index = self.page_start
|
|
314
|
+
|
|
315
|
+
def _render(self):
|
|
316
|
+
lines = [("bold cyan", " 🤖 Select Active Model")]
|
|
317
|
+
filter_label = self.filter_text or "type to filter"
|
|
318
|
+
lines.append(("fg:ansibrightblack", f"\n Filter: {filter_label}"))
|
|
319
|
+
if self.total_pages > 1:
|
|
320
|
+
lines.append(
|
|
321
|
+
("fg:ansibrightblack", f" (Page {self.page + 1}/{self.total_pages})")
|
|
322
|
+
)
|
|
323
|
+
lines.append(("", "\n"))
|
|
324
|
+
|
|
325
|
+
if not self.visible_model_names:
|
|
326
|
+
empty_message = (
|
|
327
|
+
"No models match the current filter."
|
|
328
|
+
if self.filter_text
|
|
329
|
+
else "No models available."
|
|
330
|
+
)
|
|
331
|
+
lines.append(("fg:ansiyellow", f"\n {empty_message}\n"))
|
|
332
|
+
lines.append(("fg:ansibrightblack", " Type "))
|
|
333
|
+
lines.append(("", "Adjust filter\n"))
|
|
334
|
+
lines.append(("fg:ansibrightblack", " Backspace "))
|
|
335
|
+
lines.append(("", "Delete filter char\n"))
|
|
336
|
+
if self.filter_text:
|
|
337
|
+
lines.append(("fg:ansibrightblack", " Ctrl+U "))
|
|
338
|
+
lines.append(("", "Clear filter\n"))
|
|
339
|
+
lines.append(("fg:ansiyellow", " Esc "))
|
|
340
|
+
lines.append(("", "Exit\n"))
|
|
341
|
+
return lines
|
|
342
|
+
|
|
343
|
+
lines.append(("fg:ansibrightblack", f"\n Current: {self.current_model}\n\n"))
|
|
344
|
+
|
|
345
|
+
for offset, model_name in enumerate(self.models_on_page):
|
|
346
|
+
absolute_index = self.page_start + offset
|
|
347
|
+
is_selected = absolute_index == self.selected_index
|
|
348
|
+
is_current = model_name == self.current_model
|
|
349
|
+
|
|
350
|
+
prefix = " › " if is_selected else " "
|
|
351
|
+
style = "fg:ansiwhite bold" if is_selected else "fg:ansibrightblack"
|
|
352
|
+
lines.append((style, f"{prefix}{model_name}"))
|
|
353
|
+
if is_current:
|
|
354
|
+
lines.append(("fg:ansigreen", " (active)"))
|
|
355
|
+
lines.append(("", "\n"))
|
|
356
|
+
|
|
357
|
+
lines.append(("", "\n"))
|
|
358
|
+
lines.append(("fg:ansibrightblack", " ↑/↓ "))
|
|
359
|
+
lines.append(("", "Navigate\n"))
|
|
360
|
+
if self.total_pages > 1:
|
|
361
|
+
lines.append(("fg:ansibrightblack", " PgUp/PgDn "))
|
|
362
|
+
lines.append(("", "Change page\n"))
|
|
363
|
+
lines.append(("fg:ansibrightblack", " Type "))
|
|
364
|
+
lines.append(("", "Filter models\n"))
|
|
365
|
+
lines.append(("fg:ansibrightblack", " Backspace "))
|
|
366
|
+
lines.append(("", "Delete filter char\n"))
|
|
367
|
+
lines.append(("fg:ansibrightblack", " Ctrl+U "))
|
|
368
|
+
lines.append(("", "Clear filter\n"))
|
|
369
|
+
lines.append(("fg:ansigreen", " Enter "))
|
|
370
|
+
lines.append(("", "Select model\n"))
|
|
371
|
+
lines.append(("fg:ansiyellow", " Esc "))
|
|
372
|
+
lines.append(("", "Cancel\n"))
|
|
373
|
+
return lines
|
|
374
|
+
|
|
375
|
+
async def run_async(self) -> str | None:
|
|
376
|
+
control = FormattedTextControl(lambda: self._render())
|
|
377
|
+
kb = KeyBindings()
|
|
378
|
+
|
|
379
|
+
def refresh() -> None:
|
|
380
|
+
control.text = self._render()
|
|
381
|
+
|
|
382
|
+
@kb.add("up")
|
|
383
|
+
@kb.add("c-p")
|
|
384
|
+
def _(event):
|
|
385
|
+
self._move_up()
|
|
386
|
+
refresh()
|
|
387
|
+
event.app.invalidate()
|
|
388
|
+
|
|
389
|
+
@kb.add("down")
|
|
390
|
+
@kb.add("c-n")
|
|
391
|
+
def _(event):
|
|
392
|
+
self._move_down()
|
|
393
|
+
refresh()
|
|
394
|
+
event.app.invalidate()
|
|
395
|
+
|
|
396
|
+
@kb.add("pageup")
|
|
397
|
+
@kb.add("left")
|
|
398
|
+
def _(event):
|
|
399
|
+
self._page_up()
|
|
400
|
+
refresh()
|
|
401
|
+
event.app.invalidate()
|
|
402
|
+
|
|
403
|
+
@kb.add("pagedown")
|
|
404
|
+
@kb.add("right")
|
|
405
|
+
def _(event):
|
|
406
|
+
self._page_down()
|
|
407
|
+
refresh()
|
|
408
|
+
event.app.invalidate()
|
|
409
|
+
|
|
410
|
+
@kb.add("backspace")
|
|
411
|
+
def _(event):
|
|
412
|
+
if not self.filter_text:
|
|
413
|
+
return
|
|
414
|
+
self._delete_filter_char()
|
|
415
|
+
refresh()
|
|
416
|
+
event.app.invalidate()
|
|
417
|
+
|
|
418
|
+
@kb.add("c-u")
|
|
419
|
+
def _(event):
|
|
420
|
+
if not self.filter_text:
|
|
421
|
+
return
|
|
422
|
+
self._set_filter_text("")
|
|
423
|
+
refresh()
|
|
424
|
+
event.app.invalidate()
|
|
425
|
+
|
|
426
|
+
@kb.add("<any>")
|
|
427
|
+
def _(event):
|
|
428
|
+
if not event.data or not event.data.isprintable():
|
|
429
|
+
return
|
|
430
|
+
self._append_filter_char(event.data)
|
|
431
|
+
refresh()
|
|
432
|
+
event.app.invalidate()
|
|
433
|
+
|
|
434
|
+
@kb.add("enter")
|
|
435
|
+
def _(event):
|
|
436
|
+
if not self._accept_selection():
|
|
437
|
+
return
|
|
438
|
+
event.app.exit()
|
|
439
|
+
|
|
440
|
+
@kb.add("escape")
|
|
441
|
+
@kb.add("c-c")
|
|
442
|
+
def _(event):
|
|
443
|
+
self.result = None
|
|
444
|
+
event.app.exit()
|
|
445
|
+
|
|
446
|
+
app = Application(
|
|
447
|
+
layout=Layout(Window(content=control, wrap_lines=True)),
|
|
448
|
+
key_bindings=kb,
|
|
449
|
+
full_screen=False,
|
|
450
|
+
)
|
|
451
|
+
await app.run_async()
|
|
452
|
+
return self.result
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def _build_legacy_picker_choices(
|
|
456
|
+
model_names: list[str], current_model: str
|
|
457
|
+
) -> list[str]:
|
|
458
|
+
"""Build simple picker labels for test and non-interactive fallback paths."""
|
|
459
|
+
choices = []
|
|
460
|
+
for model_name in model_names:
|
|
461
|
+
suffix = " (current)" if model_name == current_model else ""
|
|
462
|
+
choices.append(f"{model_name}{suffix}")
|
|
463
|
+
return choices
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def _normalize_legacy_picker_choice(choice: str) -> str:
|
|
467
|
+
"""Extract the model name from a legacy picker label."""
|
|
468
|
+
return choice.removesuffix(" (current)")
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
async def interactive_model_picker() -> str | None:
|
|
472
|
+
"""Run the paginated interactive model picker used by /model."""
|
|
473
|
+
from code_muse.tools.command_runner import set_awaiting_user_input
|
|
474
|
+
|
|
475
|
+
set_awaiting_user_input(True)
|
|
476
|
+
try:
|
|
477
|
+
try:
|
|
478
|
+
return await ModelSelectionMenu().run_async()
|
|
479
|
+
except EOFError:
|
|
480
|
+
model_names = load_model_names()
|
|
481
|
+
current_model = get_active_model()
|
|
482
|
+
choices = _build_legacy_picker_choices(model_names, current_model)
|
|
483
|
+
if not choices:
|
|
484
|
+
return None
|
|
485
|
+
|
|
486
|
+
from code_muse.tools.common import arrow_select_async
|
|
487
|
+
|
|
488
|
+
try:
|
|
489
|
+
selected = await arrow_select_async("Select Active Model", choices)
|
|
490
|
+
except KeyboardInterrupt:
|
|
491
|
+
return None
|
|
492
|
+
return _normalize_legacy_picker_choice(selected)
|
|
493
|
+
finally:
|
|
494
|
+
set_awaiting_user_input(False)
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
async def get_input_with_model_completion(
|
|
498
|
+
prompt_str: str = ">>> ",
|
|
499
|
+
trigger: str = "/model",
|
|
500
|
+
history_file: str | None = None,
|
|
501
|
+
) -> str:
|
|
502
|
+
history = FileHistory(Path(history_file).expanduser()) if history_file else None
|
|
503
|
+
session = PromptSession(
|
|
504
|
+
completer=ModelNameCompleter(trigger),
|
|
505
|
+
history=history,
|
|
506
|
+
complete_while_typing=True,
|
|
507
|
+
)
|
|
508
|
+
text = await session.prompt_async(prompt_str)
|
|
509
|
+
possibly_stripped = update_model_in_input(text)
|
|
510
|
+
if possibly_stripped is not None:
|
|
511
|
+
return possibly_stripped
|
|
512
|
+
return text
|