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,584 @@
|
|
|
1
|
+
"""Utility helpers for the GitHub Copilot auth plugin.
|
|
2
|
+
|
|
3
|
+
Handles browser-based Device Flow authentication, session-token exchange /
|
|
4
|
+
caching, and model registration persistence.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
import time
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
import httpx
|
|
14
|
+
|
|
15
|
+
from .config import (
|
|
16
|
+
COPILOT_AUTH_CONFIG,
|
|
17
|
+
COPILOT_MODEL_CONTEXT_LENGTHS,
|
|
18
|
+
DEFAULT_COPILOT_MODELS,
|
|
19
|
+
DEVICE_FLOW_CONFIG,
|
|
20
|
+
get_copilot_models_path,
|
|
21
|
+
get_device_token_storage_path,
|
|
22
|
+
get_session_cache_path,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
# Token storage — persisted tokens obtained via the Device Flow
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class CopilotToken:
|
|
35
|
+
"""An OAuth token for a GitHub host."""
|
|
36
|
+
|
|
37
|
+
host: str # "github.com" or a GHE hostname
|
|
38
|
+
oauth_token: str
|
|
39
|
+
user: str = ""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_token_for_host(host: str) -> CopilotToken | None:
|
|
43
|
+
"""Return a stored Device Flow token whose host matches *host* exactly.
|
|
44
|
+
|
|
45
|
+
Returns ``None`` if no token for the given host is found.
|
|
46
|
+
"""
|
|
47
|
+
tokens = load_device_tokens()
|
|
48
|
+
for t in tokens:
|
|
49
|
+
if t.host == host:
|
|
50
|
+
return t
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# ---------------------------------------------------------------------------
|
|
55
|
+
# Device Flow — browser-based GitHub OAuth (no IDE required)
|
|
56
|
+
# ---------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def start_device_flow(host: str = "github.com") -> dict[str, Any | None]:
|
|
60
|
+
"""Initiate the GitHub Device Flow and return the device code response.
|
|
61
|
+
|
|
62
|
+
Returns a dict with ``device_code``, ``user_code``, ``verification_uri``,
|
|
63
|
+
``expires_in``, and ``interval`` on success, or ``None`` on failure.
|
|
64
|
+
"""
|
|
65
|
+
url = DEVICE_FLOW_CONFIG["device_code_url"].format(host=host)
|
|
66
|
+
payload = {
|
|
67
|
+
"client_id": DEVICE_FLOW_CONFIG["client_id"],
|
|
68
|
+
"scope": DEVICE_FLOW_CONFIG["scope"],
|
|
69
|
+
}
|
|
70
|
+
headers = {"Accept": "application/json"}
|
|
71
|
+
try:
|
|
72
|
+
resp = httpx.post(url, data=payload, headers=headers, timeout=30)
|
|
73
|
+
if resp.status_code == 200:
|
|
74
|
+
data = resp.json()
|
|
75
|
+
if data.get("device_code") and data.get("user_code"):
|
|
76
|
+
return data
|
|
77
|
+
logger.warning("Device flow response missing required fields: %s", data)
|
|
78
|
+
else:
|
|
79
|
+
logger.warning(
|
|
80
|
+
"Device flow initiation failed: %s %s",
|
|
81
|
+
resp.status_code,
|
|
82
|
+
resp.text[:300],
|
|
83
|
+
)
|
|
84
|
+
except Exception as exc:
|
|
85
|
+
logger.warning("Device flow error: %s", exc)
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def poll_for_token(
|
|
90
|
+
device_code: str,
|
|
91
|
+
host: str = "github.com",
|
|
92
|
+
interval: int = 5,
|
|
93
|
+
expires_in: int = 900,
|
|
94
|
+
) -> str | None:
|
|
95
|
+
"""Poll GitHub until the user completes the Device Flow authorization.
|
|
96
|
+
|
|
97
|
+
Returns the OAuth ``access_token`` on success, or ``None`` on timeout/denial.
|
|
98
|
+
"""
|
|
99
|
+
url = DEVICE_FLOW_CONFIG["access_token_url"].format(host=host)
|
|
100
|
+
payload = {
|
|
101
|
+
"client_id": DEVICE_FLOW_CONFIG["client_id"],
|
|
102
|
+
"device_code": device_code,
|
|
103
|
+
"grant_type": DEVICE_FLOW_CONFIG["grant_type"],
|
|
104
|
+
}
|
|
105
|
+
headers = {"Accept": "application/json"}
|
|
106
|
+
|
|
107
|
+
deadline = time.time() + expires_in
|
|
108
|
+
poll_interval = max(interval, DEVICE_FLOW_CONFIG["default_poll_interval"])
|
|
109
|
+
|
|
110
|
+
while time.time() < deadline:
|
|
111
|
+
time.sleep(poll_interval)
|
|
112
|
+
try:
|
|
113
|
+
resp = httpx.post(url, data=payload, headers=headers, timeout=30)
|
|
114
|
+
data = resp.json() if resp.status_code == 200 else {}
|
|
115
|
+
except Exception as exc:
|
|
116
|
+
logger.warning("Device flow poll error: %s", exc)
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
token = data.get("access_token")
|
|
120
|
+
if token:
|
|
121
|
+
return token
|
|
122
|
+
|
|
123
|
+
error = data.get("error", "")
|
|
124
|
+
if error == "authorization_pending":
|
|
125
|
+
continue
|
|
126
|
+
if error == "slow_down":
|
|
127
|
+
poll_interval += 5
|
|
128
|
+
continue
|
|
129
|
+
if error in ("expired_token", "access_denied", "unsupported_grant_type"):
|
|
130
|
+
logger.warning("Device flow denied or expired: %s", error)
|
|
131
|
+
return None
|
|
132
|
+
# Unknown error — keep trying until deadline
|
|
133
|
+
logger.debug("Device flow poll returned: %s", data)
|
|
134
|
+
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def save_device_token(host: str, oauth_token: str, user: str = "") -> bool:
|
|
139
|
+
"""Persist a token obtained via the Device Flow to disk."""
|
|
140
|
+
try:
|
|
141
|
+
path = get_device_token_storage_path()
|
|
142
|
+
data: dict[str, Any] = {}
|
|
143
|
+
if path.exists():
|
|
144
|
+
with open(path, encoding="utf-8") as fh:
|
|
145
|
+
data = json.load(fh)
|
|
146
|
+
data[host] = {
|
|
147
|
+
"oauth_token": oauth_token,
|
|
148
|
+
"user": user,
|
|
149
|
+
"created_at": time.time(),
|
|
150
|
+
}
|
|
151
|
+
with open(path, "w", encoding="utf-8") as fh:
|
|
152
|
+
json.dump(data, fh, indent=2)
|
|
153
|
+
path.chmod(0o600)
|
|
154
|
+
return True
|
|
155
|
+
except Exception as exc:
|
|
156
|
+
logger.error("Failed to save device token: %s", exc)
|
|
157
|
+
return False
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def load_device_tokens() -> list[CopilotToken]:
|
|
161
|
+
"""Load tokens previously obtained via the Device Flow."""
|
|
162
|
+
tokens: list[CopilotToken] = []
|
|
163
|
+
try:
|
|
164
|
+
path = get_device_token_storage_path()
|
|
165
|
+
if not path.exists():
|
|
166
|
+
return tokens
|
|
167
|
+
with open(path, encoding="utf-8") as fh:
|
|
168
|
+
data = json.load(fh)
|
|
169
|
+
if isinstance(data, dict):
|
|
170
|
+
for host, entry in data.items():
|
|
171
|
+
if not isinstance(entry, dict):
|
|
172
|
+
continue
|
|
173
|
+
oauth_token = entry.get("oauth_token")
|
|
174
|
+
if oauth_token:
|
|
175
|
+
tokens.append(
|
|
176
|
+
CopilotToken(
|
|
177
|
+
host=host,
|
|
178
|
+
oauth_token=oauth_token,
|
|
179
|
+
user=entry.get("user", ""),
|
|
180
|
+
)
|
|
181
|
+
)
|
|
182
|
+
except Exception as exc:
|
|
183
|
+
logger.warning("Failed to load device tokens: %s", exc)
|
|
184
|
+
return tokens
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# ---------------------------------------------------------------------------
|
|
188
|
+
# Session token exchange — converts long-lived OAuth token → short-lived
|
|
189
|
+
# Copilot API bearer token (typically valid ~30 min).
|
|
190
|
+
# ---------------------------------------------------------------------------
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@dataclass
|
|
194
|
+
class SessionToken:
|
|
195
|
+
"""A short-lived Copilot API session token."""
|
|
196
|
+
|
|
197
|
+
token: str
|
|
198
|
+
expires_at: float # Unix timestamp
|
|
199
|
+
api_endpoint: str = (
|
|
200
|
+
"" # API base URL from the token response (may differ per region/host)
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
# Module-level cache keyed by (host, oauth_token[:16])
|
|
205
|
+
_session_cache: dict[str, SessionToken] = {}
|
|
206
|
+
|
|
207
|
+
# Stores the API base URL returned by the most recent session-token exchange
|
|
208
|
+
# per host, so that model registration can use it.
|
|
209
|
+
_host_api_endpoints: dict[str, str] = {}
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _cache_key(oauth_token: str, host: str) -> str:
|
|
213
|
+
return f"{host}:{oauth_token[:16]}"
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _token_endpoint(host: str) -> str:
|
|
217
|
+
"""Return the Copilot session-token endpoint for the given host."""
|
|
218
|
+
if host == "github.com":
|
|
219
|
+
return COPILOT_AUTH_CONFIG["github_token_url"]
|
|
220
|
+
return COPILOT_AUTH_CONFIG["ghe_token_url_template"].format(host=host)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def exchange_for_session_token(
|
|
224
|
+
oauth_token: str, host: str = "github.com"
|
|
225
|
+
) -> SessionToken | None:
|
|
226
|
+
"""Exchange a GitHub OAuth token for a short-lived Copilot session token.
|
|
227
|
+
|
|
228
|
+
The response ``endpoints.api`` value is the correct API base URL for this
|
|
229
|
+
token and may differ from the default (e.g. regional or GHE deployments).
|
|
230
|
+
"""
|
|
231
|
+
url = _token_endpoint(host)
|
|
232
|
+
headers = {
|
|
233
|
+
# TODO: PEP 750 t-string — use templatelib when stable
|
|
234
|
+
"Authorization": f"token {oauth_token}",
|
|
235
|
+
"Accept": "application/json",
|
|
236
|
+
"Editor-Version": COPILOT_AUTH_CONFIG["editor_version"],
|
|
237
|
+
"Editor-Plugin-Version": COPILOT_AUTH_CONFIG["editor_plugin_version"],
|
|
238
|
+
"Copilot-Integration-Id": COPILOT_AUTH_CONFIG["copilot_integration_id"],
|
|
239
|
+
}
|
|
240
|
+
try:
|
|
241
|
+
resp = httpx.get(url, headers=headers, timeout=30)
|
|
242
|
+
if resp.status_code == 200:
|
|
243
|
+
data = resp.json()
|
|
244
|
+
token_str = data.get("token")
|
|
245
|
+
expires_at = data.get("expires_at", 0)
|
|
246
|
+
if token_str:
|
|
247
|
+
# Extract the API endpoint — the token is only valid against
|
|
248
|
+
# this specific URL. Falls back to the default if missing.
|
|
249
|
+
endpoints = data.get("endpoints") or {}
|
|
250
|
+
api_endpoint = (
|
|
251
|
+
endpoints.get("api", "").rstrip("/")
|
|
252
|
+
or COPILOT_AUTH_CONFIG["api_base_url"]
|
|
253
|
+
)
|
|
254
|
+
# Remember this for model registration
|
|
255
|
+
_host_api_endpoints[host] = api_endpoint
|
|
256
|
+
logger.debug(
|
|
257
|
+
"Copilot session for %s: api_endpoint=%s", host, api_endpoint
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
st = SessionToken(
|
|
261
|
+
token=token_str,
|
|
262
|
+
expires_at=float(expires_at),
|
|
263
|
+
api_endpoint=api_endpoint,
|
|
264
|
+
)
|
|
265
|
+
# Persist to disk so we survive process restarts within the window
|
|
266
|
+
_persist_session(st, host, oauth_token)
|
|
267
|
+
return st
|
|
268
|
+
logger.warning("Token endpoint returned 200 but no 'token' field")
|
|
269
|
+
elif resp.status_code == 401:
|
|
270
|
+
logger.warning(
|
|
271
|
+
"Copilot token exchange returned 401 — OAuth token may be revoked."
|
|
272
|
+
)
|
|
273
|
+
else:
|
|
274
|
+
logger.warning(
|
|
275
|
+
"Copilot token exchange failed: %s %s",
|
|
276
|
+
resp.status_code,
|
|
277
|
+
resp.text[:200],
|
|
278
|
+
)
|
|
279
|
+
except httpx.TimeoutException:
|
|
280
|
+
logger.warning("Timeout exchanging Copilot token for host %s", host)
|
|
281
|
+
except Exception as exc:
|
|
282
|
+
logger.warning("Copilot token exchange error: %s", exc)
|
|
283
|
+
return None
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _persist_session(st: SessionToken, host: str, oauth_token: str = "") -> None:
|
|
287
|
+
"""Write session token to disk for reuse after restarts.
|
|
288
|
+
|
|
289
|
+
Stores a fingerprint of the OAuth token so that a persisted session is
|
|
290
|
+
only reused when the same OAuth token is still active.
|
|
291
|
+
"""
|
|
292
|
+
try:
|
|
293
|
+
path = get_session_cache_path()
|
|
294
|
+
data: dict[str, Any] = {}
|
|
295
|
+
if path.exists():
|
|
296
|
+
with open(path, encoding="utf-8") as fh:
|
|
297
|
+
data = json.load(fh)
|
|
298
|
+
data[host] = {
|
|
299
|
+
"token": st.token,
|
|
300
|
+
"expires_at": st.expires_at,
|
|
301
|
+
"api_endpoint": st.api_endpoint,
|
|
302
|
+
"oauth_fingerprint": oauth_token[:16] if oauth_token else "",
|
|
303
|
+
}
|
|
304
|
+
with open(path, "w", encoding="utf-8") as fh:
|
|
305
|
+
json.dump(data, fh, indent=2)
|
|
306
|
+
path.chmod(0o600)
|
|
307
|
+
except Exception as exc:
|
|
308
|
+
logger.debug("Could not persist session token: %s", exc)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _load_persisted_session(host: str, oauth_token: str = "") -> SessionToken | None:
|
|
312
|
+
"""Load a previously persisted session token from disk.
|
|
313
|
+
|
|
314
|
+
If *oauth_token* is provided, the persisted entry is only returned when
|
|
315
|
+
its stored fingerprint matches ``oauth_token[:16]``, preventing
|
|
316
|
+
cross-account reuse after a re-login.
|
|
317
|
+
"""
|
|
318
|
+
try:
|
|
319
|
+
path = get_session_cache_path()
|
|
320
|
+
if not path.exists():
|
|
321
|
+
return None
|
|
322
|
+
with open(path, encoding="utf-8") as fh:
|
|
323
|
+
data = json.load(fh)
|
|
324
|
+
entry = data.get(host)
|
|
325
|
+
if entry:
|
|
326
|
+
# Verify the stored fingerprint matches the current OAuth token
|
|
327
|
+
stored_fp = entry.get("oauth_fingerprint", "")
|
|
328
|
+
if oauth_token and stored_fp and stored_fp != oauth_token[:16]:
|
|
329
|
+
logger.debug(
|
|
330
|
+
"Persisted session fingerprint mismatch for %s — skipping", host
|
|
331
|
+
)
|
|
332
|
+
return None
|
|
333
|
+
|
|
334
|
+
api_endpoint = entry.get(
|
|
335
|
+
"api_endpoint", COPILOT_AUTH_CONFIG["api_base_url"]
|
|
336
|
+
)
|
|
337
|
+
# Restore the host→endpoint mapping
|
|
338
|
+
if api_endpoint:
|
|
339
|
+
_host_api_endpoints[host] = api_endpoint
|
|
340
|
+
return SessionToken(
|
|
341
|
+
token=entry["token"],
|
|
342
|
+
expires_at=float(entry["expires_at"]),
|
|
343
|
+
api_endpoint=api_endpoint,
|
|
344
|
+
)
|
|
345
|
+
except Exception as exc:
|
|
346
|
+
logger.debug("Could not load persisted session: %s", exc)
|
|
347
|
+
return None
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def get_valid_session_token(oauth_token: str, host: str = "github.com") -> str | None:
|
|
351
|
+
"""Return a valid Copilot session token, refreshing if needed.
|
|
352
|
+
|
|
353
|
+
Checks in-memory cache → on-disk cache → exchanges for a new one.
|
|
354
|
+
Returns the raw bearer token string or ``None``.
|
|
355
|
+
"""
|
|
356
|
+
key = _cache_key(oauth_token, host)
|
|
357
|
+
|
|
358
|
+
# 1) In-memory cache
|
|
359
|
+
cached = _session_cache.get(key)
|
|
360
|
+
if cached and cached.expires_at > time.time() + 60:
|
|
361
|
+
# Ensure host→endpoint mapping is populated
|
|
362
|
+
if cached.api_endpoint:
|
|
363
|
+
_host_api_endpoints[host] = cached.api_endpoint
|
|
364
|
+
return cached.token
|
|
365
|
+
|
|
366
|
+
# 2) On-disk cache (with fingerprint verification)
|
|
367
|
+
persisted = _load_persisted_session(host, oauth_token)
|
|
368
|
+
if persisted and persisted.expires_at > time.time() + 60:
|
|
369
|
+
_session_cache[key] = persisted
|
|
370
|
+
return persisted.token
|
|
371
|
+
|
|
372
|
+
# 3) Exchange
|
|
373
|
+
new_token = exchange_for_session_token(oauth_token, host)
|
|
374
|
+
if new_token:
|
|
375
|
+
_session_cache[key] = new_token
|
|
376
|
+
return new_token.token
|
|
377
|
+
|
|
378
|
+
return None
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def get_api_endpoint_for_host(host: str) -> str:
|
|
382
|
+
"""Return the Copilot API base URL for the given host.
|
|
383
|
+
|
|
384
|
+
The correct endpoint is discovered during session-token exchange
|
|
385
|
+
(the ``endpoints.api`` field in the response). Falls back to the
|
|
386
|
+
default ``api.githubcopilot.com`` if no exchange has happened yet.
|
|
387
|
+
"""
|
|
388
|
+
return _host_api_endpoints.get(host, COPILOT_AUTH_CONFIG["api_base_url"])
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def clear_caches() -> None:
|
|
392
|
+
"""Reset all in-memory session and endpoint caches.
|
|
393
|
+
|
|
394
|
+
Called by ``/copilot-logout`` to ensure no stale Copilot state remains.
|
|
395
|
+
"""
|
|
396
|
+
_session_cache.clear()
|
|
397
|
+
_host_api_endpoints.clear()
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
# ---------------------------------------------------------------------------
|
|
401
|
+
# Model persistence — copilot_models.json
|
|
402
|
+
# ---------------------------------------------------------------------------
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def load_copilot_models() -> dict[str, Any]:
|
|
406
|
+
"""Load registered Copilot models from copilot_models.json."""
|
|
407
|
+
try:
|
|
408
|
+
path = get_copilot_models_path()
|
|
409
|
+
if path.exists():
|
|
410
|
+
with open(path, encoding="utf-8") as fh:
|
|
411
|
+
data = json.load(fh)
|
|
412
|
+
if isinstance(data, dict):
|
|
413
|
+
return data
|
|
414
|
+
logger.warning("copilot_models.json is not a JSON object — ignoring")
|
|
415
|
+
except Exception as exc:
|
|
416
|
+
logger.error("Failed to load Copilot models: %s", exc)
|
|
417
|
+
return {}
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def save_copilot_models(models: dict[str, Any]) -> bool:
|
|
421
|
+
"""Persist Copilot models to copilot_models.json."""
|
|
422
|
+
try:
|
|
423
|
+
path = get_copilot_models_path()
|
|
424
|
+
with open(path, "w", encoding="utf-8") as fh:
|
|
425
|
+
json.dump(models, fh, indent=2)
|
|
426
|
+
return True
|
|
427
|
+
except Exception as exc:
|
|
428
|
+
logger.error("Failed to save Copilot models: %s", exc)
|
|
429
|
+
return False
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def remove_copilot_models() -> int:
|
|
433
|
+
"""Remove all Copilot-sourced models from copilot_models.json."""
|
|
434
|
+
try:
|
|
435
|
+
models = load_copilot_models()
|
|
436
|
+
to_remove = [
|
|
437
|
+
name
|
|
438
|
+
for name, cfg in models.items()
|
|
439
|
+
if cfg.get("oauth_source") == "copilot-auth-plugin"
|
|
440
|
+
]
|
|
441
|
+
for name in to_remove:
|
|
442
|
+
models.pop(name, None)
|
|
443
|
+
if save_copilot_models(models):
|
|
444
|
+
return len(to_remove)
|
|
445
|
+
except Exception as exc:
|
|
446
|
+
logger.error("Error removing Copilot models: %s", exc)
|
|
447
|
+
return 0
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def fetch_copilot_models(session_token: str, host: str = "github.com") -> list[str]:
|
|
451
|
+
"""Try to fetch the model catalogue from the Copilot API.
|
|
452
|
+
|
|
453
|
+
Falls back to ``DEFAULT_COPILOT_MODELS`` if the endpoint is unavailable.
|
|
454
|
+
Uses the host-specific API endpoint discovered during token exchange.
|
|
455
|
+
"""
|
|
456
|
+
api_base = get_api_endpoint_for_host(host)
|
|
457
|
+
url = f"{api_base}/models"
|
|
458
|
+
headers = {
|
|
459
|
+
"Authorization": f"Bearer {session_token}",
|
|
460
|
+
"Accept": "application/json",
|
|
461
|
+
"Editor-Version": COPILOT_AUTH_CONFIG["editor_version"],
|
|
462
|
+
"Editor-Plugin-Version": COPILOT_AUTH_CONFIG["editor_plugin_version"],
|
|
463
|
+
"Copilot-Integration-Id": COPILOT_AUTH_CONFIG["copilot_integration_id"],
|
|
464
|
+
"Openai-Intent": COPILOT_AUTH_CONFIG["openai_intent"],
|
|
465
|
+
}
|
|
466
|
+
try:
|
|
467
|
+
resp = httpx.get(url, headers=headers, timeout=15)
|
|
468
|
+
if resp.status_code == 200:
|
|
469
|
+
data = resp.json()
|
|
470
|
+
model_list = data.get("data") or data.get("models") or []
|
|
471
|
+
if isinstance(model_list, list):
|
|
472
|
+
ids = []
|
|
473
|
+
for m in model_list:
|
|
474
|
+
if isinstance(m, dict):
|
|
475
|
+
mid = m.get("id") or m.get("name")
|
|
476
|
+
if mid:
|
|
477
|
+
ids.append(mid)
|
|
478
|
+
elif isinstance(m, str):
|
|
479
|
+
ids.append(m)
|
|
480
|
+
if ids:
|
|
481
|
+
logger.info("Fetched %d models from Copilot API", len(ids))
|
|
482
|
+
return ids
|
|
483
|
+
except Exception as exc:
|
|
484
|
+
logger.debug("Could not fetch Copilot model list: %s", exc)
|
|
485
|
+
|
|
486
|
+
logger.info("Using default Copilot model list")
|
|
487
|
+
return DEFAULT_COPILOT_MODELS
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def _is_claude_model(model_name: str) -> bool:
|
|
491
|
+
"""Check if a model name refers to a Claude/Anthropic model."""
|
|
492
|
+
return model_name.lower().startswith("claude-")
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def _is_openai_model(model_name: str) -> bool:
|
|
496
|
+
"""Check if a model name refers to an OpenAI/GPT model."""
|
|
497
|
+
lower = model_name.lower()
|
|
498
|
+
return lower.startswith("gpt-") or lower.startswith("o3") or lower.startswith("o4")
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def _build_claude_model_settings(model_name: str) -> dict[str, Any]:
|
|
502
|
+
"""Build model_settings fields for a Claude model.
|
|
503
|
+
|
|
504
|
+
Mirrors the conventions in the claude_code_oauth plugin's
|
|
505
|
+
``_build_model_entry``.
|
|
506
|
+
"""
|
|
507
|
+
supported_settings = [
|
|
508
|
+
"temperature",
|
|
509
|
+
"extended_thinking",
|
|
510
|
+
"budget_tokens",
|
|
511
|
+
"interleaved_thinking",
|
|
512
|
+
]
|
|
513
|
+
|
|
514
|
+
# Opus 4-6 (e.g. claude-opus-4.6) supports the effort setting,
|
|
515
|
+
# same as the claude_code_oauth plugin.
|
|
516
|
+
lower = model_name.lower()
|
|
517
|
+
if "opus-4.6" in lower or "opus-4-6" in lower or "4-6-opus" in lower:
|
|
518
|
+
supported_settings.append("effort")
|
|
519
|
+
|
|
520
|
+
return {"supported_settings": supported_settings}
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def _build_openai_model_settings(model_name: str) -> dict[str, Any]:
|
|
524
|
+
"""Build model_settings fields for an OpenAI/GPT model behind Copilot.
|
|
525
|
+
|
|
526
|
+
The Copilot API only proxies a subset of OpenAI features. Currently
|
|
527
|
+
none of the GPT models exposed through Copilot support
|
|
528
|
+
``reasoning_effort``, ``summary``, or ``verbosity`` — sending those
|
|
529
|
+
parameters results in a 400 Bad Request. Only basic ``temperature``
|
|
530
|
+
is safe.
|
|
531
|
+
"""
|
|
532
|
+
return {"supported_settings": ["temperature"]}
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
def _model_settings_for(model_name: str) -> dict[str, Any]:
|
|
536
|
+
"""Return family-specific config fields for a Copilot model.
|
|
537
|
+
|
|
538
|
+
Claude models get extended_thinking/effort/etc.; OpenAI models get
|
|
539
|
+
reasoning_effort/summary/verbosity; everything else gets temperature.
|
|
540
|
+
"""
|
|
541
|
+
if _is_claude_model(model_name):
|
|
542
|
+
return _build_claude_model_settings(model_name)
|
|
543
|
+
if _is_openai_model(model_name):
|
|
544
|
+
return _build_openai_model_settings(model_name)
|
|
545
|
+
# Fallback for other providers (Gemini, etc.)
|
|
546
|
+
return {"supported_settings": ["temperature"]}
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def add_models_to_config(
|
|
550
|
+
models: list[str],
|
|
551
|
+
host: str = "github.com",
|
|
552
|
+
) -> bool:
|
|
553
|
+
"""Register Copilot models in copilot_models.json."""
|
|
554
|
+
try:
|
|
555
|
+
copilot_models = load_copilot_models()
|
|
556
|
+
added = 0
|
|
557
|
+
prefix = COPILOT_AUTH_CONFIG["prefix"]
|
|
558
|
+
# Use the API endpoint from session-token exchange (critical for GHE).
|
|
559
|
+
api_url = get_api_endpoint_for_host(host)
|
|
560
|
+
for model_name in models:
|
|
561
|
+
prefixed = f"{prefix}{model_name}"
|
|
562
|
+
entry: dict[str, Any] = {
|
|
563
|
+
"type": "copilot",
|
|
564
|
+
"name": model_name,
|
|
565
|
+
"custom_endpoint": {
|
|
566
|
+
"url": api_url,
|
|
567
|
+
},
|
|
568
|
+
"context_length": COPILOT_MODEL_CONTEXT_LENGTHS.get(
|
|
569
|
+
model_name,
|
|
570
|
+
COPILOT_AUTH_CONFIG["default_context_length"],
|
|
571
|
+
),
|
|
572
|
+
"copilot_host": host,
|
|
573
|
+
"oauth_source": "copilot-auth-plugin",
|
|
574
|
+
}
|
|
575
|
+
# Merge family-specific settings (supported_settings, etc.)
|
|
576
|
+
entry.update(_model_settings_for(model_name))
|
|
577
|
+
copilot_models[prefixed] = entry
|
|
578
|
+
added += 1
|
|
579
|
+
if save_copilot_models(copilot_models):
|
|
580
|
+
logger.info("Registered %d Copilot models (api: %s)", added, api_url)
|
|
581
|
+
return True
|
|
582
|
+
except Exception as exc:
|
|
583
|
+
logger.error("Error adding Copilot models to config: %s", exc)
|
|
584
|
+
return False
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Custom commands plugin — TOML-defined shortcuts with {{args}} injection."""
|
|
2
|
+
|
|
3
|
+
from code_muse.plugins.custom_commands.command_discovery import (
|
|
4
|
+
CommandDef as CommandDef,
|
|
5
|
+
)
|
|
6
|
+
from code_muse.plugins.custom_commands.command_discovery import (
|
|
7
|
+
discover_commands as discover_commands,
|
|
8
|
+
)
|
|
9
|
+
from code_muse.plugins.custom_commands.command_toml_schema import (
|
|
10
|
+
parse_command_toml as parse_command_toml,
|
|
11
|
+
)
|
|
12
|
+
from code_muse.plugins.custom_commands.register_callbacks import (
|
|
13
|
+
CustomCommandResult as CustomCommandResult,
|
|
14
|
+
)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
_ARGS_PLACEHOLDER = "{{args}}"
|
|
4
|
+
|
|
5
|
+
_SHELL_BLOCK_RE = re.compile(r"```(?:bash|shell)", re.IGNORECASE)
|
|
6
|
+
_RUN_SHELL_CMD_RE = re.compile(r"agent_run_shell_command", re.IGNORECASE)
|
|
7
|
+
|
|
8
|
+
# Mapping of command prefix → flag to auto-append
|
|
9
|
+
_AUTO_FLAGS: list[tuple[list[str], str]] = [
|
|
10
|
+
(["npm", "install"], "--silent"),
|
|
11
|
+
(["git"], "--no-pager"),
|
|
12
|
+
(["pnpm"], "--silent"),
|
|
13
|
+
(["cargo"], "--quiet"),
|
|
14
|
+
(["pip", "install"], "--quiet"),
|
|
15
|
+
(["yarn"], "--silent"),
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
# Flags that indicate a command already has an efficiency flag set
|
|
19
|
+
_EFFICIENCY_FLAGS = {"--silent", "--quiet", "--no-pager"}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def inject_args(prompt: str, args: str) -> str:
|
|
23
|
+
"""Replace every occurrence of ``{{args}}`` with *args*.
|
|
24
|
+
|
|
25
|
+
If *args* is empty, ``{{args}}`` is replaced with an empty string.
|
|
26
|
+
"""
|
|
27
|
+
return prompt.replace(_ARGS_PLACEHOLDER, args)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def detect_shell_blocks(prompt: str) -> bool:
|
|
31
|
+
"""Return ``True`` if *prompt* contains shell-related constructs."""
|
|
32
|
+
return bool(_SHELL_BLOCK_RE.search(prompt) or _RUN_SHELL_CMD_RE.search(prompt))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def auto_flag_shell_command(command: str) -> str:
|
|
36
|
+
"""Append efficiency flags to known shell commands when missing.
|
|
37
|
+
|
|
38
|
+
Only appends a flag if the command is recognized and the flag is
|
|
39
|
+
not already present anywhere in the command string.
|
|
40
|
+
"""
|
|
41
|
+
stripped = command.strip()
|
|
42
|
+
if not stripped:
|
|
43
|
+
return command
|
|
44
|
+
|
|
45
|
+
# If any efficiency flag is already present, leave the command alone
|
|
46
|
+
if any(flag in stripped for flag in _EFFICIENCY_FLAGS):
|
|
47
|
+
return command
|
|
48
|
+
|
|
49
|
+
parts = stripped.split()
|
|
50
|
+
if not parts:
|
|
51
|
+
return command
|
|
52
|
+
|
|
53
|
+
for prefixes, flag in _AUTO_FLAGS:
|
|
54
|
+
if len(parts) >= len(prefixes) and parts[: len(prefixes)] == prefixes:
|
|
55
|
+
return stripped + " " + flag
|
|
56
|
+
|
|
57
|
+
return command
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
_FENCED_BLOCK_RE = re.compile(
|
|
61
|
+
r"(```(?:bash|shell)\n)(.*?)(\n```)",
|
|
62
|
+
re.IGNORECASE | re.DOTALL,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _process_block(match: re.Match[str]) -> str:
|
|
67
|
+
"""Process a single fenced shell block: apply flags to each line."""
|
|
68
|
+
prefix = match.group(1)
|
|
69
|
+
body = match.group(2)
|
|
70
|
+
suffix = match.group(3)
|
|
71
|
+
|
|
72
|
+
lines = body.split("\n")
|
|
73
|
+
processed = [auto_flag_shell_command(line) for line in lines]
|
|
74
|
+
return prefix + "\n".join(processed) + suffix
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def apply_shell_flags(prompt: str) -> str:
|
|
78
|
+
"""Find fenced shell blocks and apply efficiency flags to each line.
|
|
79
|
+
|
|
80
|
+
Returns the prompt with shell commands updated in-place.
|
|
81
|
+
"""
|
|
82
|
+
return _FENCED_BLOCK_RE.sub(_process_block, prompt)
|