alysis-code 0.13.0__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.
- alysis_code/__init__.py +3 -0
- alysis_code/__main__.py +4 -0
- alysis_code/_build_info.py +14 -0
- alysis_code/account_login.py +468 -0
- alysis_code/agent/README.md +35 -0
- alysis_code/agent/__init__.py +11 -0
- alysis_code/agent/acceptance_contract.py +2217 -0
- alysis_code/agent/blast_radius.py +1403 -0
- alysis_code/agent/cache_keepalive.py +227 -0
- alysis_code/agent/completion_certificate.py +366 -0
- alysis_code/agent/completion_gate.py +306 -0
- alysis_code/agent/empty_response_stall.py +403 -0
- alysis_code/agent/errors.py +28 -0
- alysis_code/agent/llm_calls.py +475 -0
- alysis_code/agent/mutation_classification.py +227 -0
- alysis_code/agent/prompt_context.py +2508 -0
- alysis_code/agent/read_ledger.py +253 -0
- alysis_code/agent/regression_baseline.py +642 -0
- alysis_code/agent/reproduction_first.py +610 -0
- alysis_code/agent/sensitive_output.py +629 -0
- alysis_code/agent/session.py +3218 -0
- alysis_code/agent/steering.py +191 -0
- alysis_code/agent/subagent_execution.py +5177 -0
- alysis_code/agent/subagent_workspace.py +666 -0
- alysis_code/agent/tools_assembly.py +4728 -0
- alysis_code/agent/turn/__init__.py +101 -0
- alysis_code/agent/turn/core.py +8483 -0
- alysis_code/agent/turn/events.py +113 -0
- alysis_code/agent/turn/exploration.py +590 -0
- alysis_code/agent/turn/interventions.py +65 -0
- alysis_code/agent/turn/read_cache.py +420 -0
- alysis_code/agent/turn/snapshot.py +179 -0
- alysis_code/agent/turn_contract.py +661 -0
- alysis_code/agent/turn_path.py +129 -0
- alysis_code/agent/verification.py +2885 -0
- alysis_code/agent/verification_commands.py +512 -0
- alysis_code/agent/verification_evidence.py +738 -0
- alysis_code/agent_loop.py +693 -0
- alysis_code/agent_runtimes/__init__.py +51 -0
- alysis_code/agent_runtimes/base.py +114 -0
- alysis_code/agent_runtimes/builtins.py +129 -0
- alysis_code/agent_runtimes/codex_cli.py +664 -0
- alysis_code/agent_runtimes/host.py +263 -0
- alysis_code/agent_runtimes/registry.py +64 -0
- alysis_code/agent_runtimes/service.py +150 -0
- alysis_code/agentbox_client.py +416 -0
- alysis_code/agentbox_integration.py +310 -0
- alysis_code/alysis_cloud.py +152 -0
- alysis_code/approval_scope.py +276 -0
- alysis_code/assets/README.md +33 -0
- alysis_code/assets/__init__.py +126 -0
- alysis_code/assets/asset_read_core.py +281 -0
- alysis_code/assets/budget_allocator.py +456 -0
- alysis_code/assets/comprehender.py +759 -0
- alysis_code/assets/index.py +654 -0
- alysis_code/assets/ingestion.py +275 -0
- alysis_code/assets/legacy_migration.py +413 -0
- alysis_code/assets/models.py +263 -0
- alysis_code/assets/ocr.py +239 -0
- alysis_code/assets/owl/ascii/f-000.txt +13 -0
- alysis_code/assets/owl/ascii/f-001.txt +13 -0
- alysis_code/assets/owl/ascii/f-002.txt +13 -0
- alysis_code/assets/owl/ascii/f-003.txt +13 -0
- alysis_code/assets/owl/ascii/f-004.txt +13 -0
- alysis_code/assets/owl/ascii/f-005.txt +13 -0
- alysis_code/assets/owl/ascii/f-006.txt +13 -0
- alysis_code/assets/owl/ascii/f-007.txt +13 -0
- alysis_code/assets/owl/ascii/f-008.txt +13 -0
- alysis_code/assets/owl/ascii/f-009.txt +13 -0
- alysis_code/assets/owl/ascii/f-010.txt +13 -0
- alysis_code/assets/owl/ascii/f-011.txt +13 -0
- alysis_code/assets/owl/ascii/f-012.txt +13 -0
- alysis_code/assets/owl/ascii/f-013.txt +13 -0
- alysis_code/assets/owl/ascii/f-014.txt +13 -0
- alysis_code/assets/owl/ascii/f-015.txt +13 -0
- alysis_code/assets/owl/ascii/f-016.txt +13 -0
- alysis_code/assets/owl/ascii/f-017.txt +13 -0
- alysis_code/assets/owl/ascii/f-018.txt +13 -0
- alysis_code/assets/owl/ascii/f-019.txt +13 -0
- alysis_code/assets/owl/ascii/f-020.txt +13 -0
- alysis_code/assets/owl/index.html +98 -0
- alysis_code/assets/owl/show-owl.sh +761 -0
- alysis_code/assets/paths.py +49 -0
- alysis_code/assets/plan_binding.py +326 -0
- alysis_code/assets/planner_context.py +466 -0
- alysis_code/assets/planner_tools.py +184 -0
- alysis_code/assets/prompts.py +101 -0
- alysis_code/assets/replanner_context.py +239 -0
- alysis_code/assets/surface.py +521 -0
- alysis_code/assets/untrusted_content.py +48 -0
- alysis_code/assets/usage_logger.py +94 -0
- alysis_code/assets/worker_mirror.py +428 -0
- alysis_code/assets/worker_section.py +303 -0
- alysis_code/assets/worker_tools.py +468 -0
- alysis_code/atomic_io.py +83 -0
- alysis_code/auth_diagnostics.py +272 -0
- alysis_code/background_runner.py +366 -0
- alysis_code/branding.py +270 -0
- alysis_code/budget_policy.py +390 -0
- alysis_code/build_identity.py +465 -0
- alysis_code/builtin_hooks/__init__.py +7 -0
- alysis_code/builtin_hooks/notify_done_windows.py +65 -0
- alysis_code/bwrap_etc.py +76 -0
- alysis_code/cancellation.py +41 -0
- alysis_code/capabilities.py +137 -0
- alysis_code/chatgpt_codex_static_provider.py +133 -0
- alysis_code/cli.py +51 -0
- alysis_code/cli_impl/__init__.py +1 -0
- alysis_code/cli_impl/assets_cli.py +537 -0
- alysis_code/cli_impl/assets_modal.py +412 -0
- alysis_code/cli_impl/chat/__init__.py +156 -0
- alysis_code/cli_impl/chat/commands.py +2616 -0
- alysis_code/cli_impl/chat/loop.py +4508 -0
- alysis_code/cli_impl/chat/mid_turn_policy.py +125 -0
- alysis_code/cli_impl/chat/rendering.py +444 -0
- alysis_code/cli_impl/chat/state.py +124 -0
- alysis_code/cli_impl/chat_resume.py +830 -0
- alysis_code/cli_impl/chat_slash_completer.py +258 -0
- alysis_code/cli_impl/commands/__init__.py +11 -0
- alysis_code/cli_impl/commands/_shared.py +89 -0
- alysis_code/cli_impl/commands/auth.py +623 -0
- alysis_code/cli_impl/commands/chat_resume_helpers.py +1531 -0
- alysis_code/cli_impl/commands/chat_state.py +158 -0
- alysis_code/cli_impl/commands/chat_status.py +1248 -0
- alysis_code/cli_impl/commands/chat_terminal.py +942 -0
- alysis_code/cli_impl/commands/chat_tui_panels.py +1018 -0
- alysis_code/cli_impl/commands/cli_common.py +1223 -0
- alysis_code/cli_impl/commands/cli_surface.py +77 -0
- alysis_code/cli_impl/commands/config.py +131 -0
- alysis_code/cli_impl/commands/conventions.py +85 -0
- alysis_code/cli_impl/commands/execution_helpers.py +350 -0
- alysis_code/cli_impl/commands/extensions.py +401 -0
- alysis_code/cli_impl/commands/forge.py +1282 -0
- alysis_code/cli_impl/commands/forge_asset_view.py +121 -0
- alysis_code/cli_impl/commands/forge_helpers.py +1215 -0
- alysis_code/cli_impl/commands/hooks.py +737 -0
- alysis_code/cli_impl/commands/ide_bridge.py +31 -0
- alysis_code/cli_impl/commands/mcp.py +700 -0
- alysis_code/cli_impl/commands/profile.py +453 -0
- alysis_code/cli_impl/commands/prompt_helpers.py +307 -0
- alysis_code/cli_impl/commands/report.py +88 -0
- alysis_code/cli_impl/commands/root.py +1118 -0
- alysis_code/cli_impl/commands/sandbox.py +184 -0
- alysis_code/cli_impl/commands/server.py +54 -0
- alysis_code/cli_impl/commands/sessions.py +252 -0
- alysis_code/cli_impl/commands/skills.py +404 -0
- alysis_code/cli_impl/commands/startup.py +946 -0
- alysis_code/cli_impl/commands/tools.py +335 -0
- alysis_code/cli_impl/commands/update.py +364 -0
- alysis_code/cli_impl/commands/welcome.py +972 -0
- alysis_code/cli_impl/config_menu.py +3882 -0
- alysis_code/cli_impl/forge.py +4509 -0
- alysis_code/cli_impl/forge_recovery.py +485 -0
- alysis_code/cli_impl/setup_wizard.py +2409 -0
- alysis_code/cli_impl/tui/__init__.py +58 -0
- alysis_code/cli_impl/tui/app.py +4551 -0
- alysis_code/cli_impl/tui/config.py +32 -0
- alysis_code/cli_impl/tui/config_flow.py +2754 -0
- alysis_code/cli_impl/tui/config_overlay.py +566 -0
- alysis_code/cli_impl/tui/content.py +78 -0
- alysis_code/cli_impl/tui/footer.py +218 -0
- alysis_code/cli_impl/tui/forge_status.py +136 -0
- alysis_code/cli_impl/tui/markdown.py +244 -0
- alysis_code/cli_impl/tui/owl.py +109 -0
- alysis_code/cli_impl/tui/plan_meta.py +477 -0
- alysis_code/cli_impl/tui/setup_app.py +519 -0
- alysis_code/cli_impl/tui/setup_flow.py +1622 -0
- alysis_code/cli_impl/tui/state.py +101 -0
- alysis_code/cli_impl/tui/subagent_identity.py +66 -0
- alysis_code/cli_impl/tui/subagent_panel.py +186 -0
- alysis_code/cli_impl/tui/surface.py +796 -0
- alysis_code/cli_impl/tui/transcript.py +514 -0
- alysis_code/cli_impl/tui/update_prompt.py +79 -0
- alysis_code/cli_impl/tui/workspace_guard.py +384 -0
- alysis_code/clipboard.py +172 -0
- alysis_code/code_review.py +1211 -0
- alysis_code/compaction/__init__.py +28 -0
- alysis_code/compaction/conversation_compactor.py +2932 -0
- alysis_code/compaction/importance.py +177 -0
- alysis_code/compaction/settings.py +297 -0
- alysis_code/compaction/tool_output_offload.py +447 -0
- alysis_code/config.py +3509 -0
- alysis_code/conflict_auto_resolver.py +895 -0
- alysis_code/context/__init__.py +1 -0
- alysis_code/context/tool_schema_budgeter.py +220 -0
- alysis_code/crash_diagnostics.py +282 -0
- alysis_code/custom_tools/README.md +34 -0
- alysis_code/custom_tools/__init__.py +43 -0
- alysis_code/custom_tools/discovery.py +903 -0
- alysis_code/custom_tools/runtime.py +1516 -0
- alysis_code/custom_tools/session.py +227 -0
- alysis_code/custom_tools/trust.py +232 -0
- alysis_code/diff_paths.py +113 -0
- alysis_code/direction_change.py +293 -0
- alysis_code/dispatch_timing.py +306 -0
- alysis_code/durable_service_manager.py +1236 -0
- alysis_code/edit_discipline.py +659 -0
- alysis_code/error_text.py +73 -0
- alysis_code/execution_budget.py +411 -0
- alysis_code/execution_context.py +915 -0
- alysis_code/execution_deadline.py +1065 -0
- alysis_code/execution_shared.py +1904 -0
- alysis_code/extensions/README.md +30 -0
- alysis_code/extensions/__init__.py +93 -0
- alysis_code/extensions/activation.py +138 -0
- alysis_code/extensions/install.py +1436 -0
- alysis_code/extensions/manifest.py +487 -0
- alysis_code/extensions/models.py +74 -0
- alysis_code/extensions/paths.py +56 -0
- alysis_code/extensions/registry.json +4 -0
- alysis_code/extensions/registry.py +52 -0
- alysis_code/extensions/state.py +83 -0
- alysis_code/extensions/workspace_trust.py +101 -0
- alysis_code/failed_task_evidence.py +369 -0
- alysis_code/failure_category.py +315 -0
- alysis_code/feedback_report.py +1647 -0
- alysis_code/file_classification.py +485 -0
- alysis_code/forge.py +2064 -0
- alysis_code/forge_completion.py +362 -0
- alysis_code/forge_events.py +475 -0
- alysis_code/frontmatter_utils.py +95 -0
- alysis_code/git_evidence.py +1181 -0
- alysis_code/git_ops.py +560 -0
- alysis_code/git_safe.py +62 -0
- alysis_code/git_worktrees.py +190 -0
- alysis_code/hooks/README.md +33 -0
- alysis_code/hooks/__init__.py +67 -0
- alysis_code/hooks/audit.py +171 -0
- alysis_code/hooks/config.py +225 -0
- alysis_code/hooks/dispatcher.py +1110 -0
- alysis_code/hooks/models.py +447 -0
- alysis_code/hooks/trust.py +202 -0
- alysis_code/host_actions.py +543 -0
- alysis_code/host_browser.py +103 -0
- alysis_code/ide/__init__.py +5 -0
- alysis_code/ide/activity_events.py +399 -0
- alysis_code/ide/approvals.py +337 -0
- alysis_code/ide/artifacts.py +153 -0
- alysis_code/ide/browser_egress_proxy.py +1076 -0
- alysis_code/ide/cdp_websocket_transport.py +1192 -0
- alysis_code/ide/change_ledger.py +1721 -0
- alysis_code/ide/context_blocks.py +979 -0
- alysis_code/ide/event_stream.py +531 -0
- alysis_code/ide/forge_protocol.py +3112 -0
- alysis_code/ide/forge_request_ledger.py +737 -0
- alysis_code/ide/health.py +965 -0
- alysis_code/ide/managed_browser.py +2251 -0
- alysis_code/ide/management_protocol.py +3414 -0
- alysis_code/ide/mcp_oauth_coordinator.py +744 -0
- alysis_code/ide/mcp_oauth_lifecycle.py +1504 -0
- alysis_code/ide/prompt_queue.py +1070 -0
- alysis_code/ide/protocol.py +191 -0
- alysis_code/ide/resumable_swarm.py +1543 -0
- alysis_code/ide/session_search.py +295 -0
- alysis_code/ide/stdio_bridge.py +9935 -0
- alysis_code/ide/structured_state.py +1579 -0
- alysis_code/ide/swarm_protocol.py +816 -0
- alysis_code/integration_gate.py +506 -0
- alysis_code/interactive_input_guard.py +39 -0
- alysis_code/interactive_plan_mode.py +26 -0
- alysis_code/internal_artifacts.py +179 -0
- alysis_code/knowledge_base.py +1409 -0
- alysis_code/knowledge_capture.py +1190 -0
- alysis_code/knowledge_librarian.py +605 -0
- alysis_code/language_policy.py +34 -0
- alysis_code/litellm_static_provider.py +535 -0
- alysis_code/llm/__init__.py +1 -0
- alysis_code/llm/anthropic_messages.py +2288 -0
- alysis_code/llm/base.py +71 -0
- alysis_code/llm/cache_capabilities.py +985 -0
- alysis_code/llm/cache_control_blocks.py +244 -0
- alysis_code/llm/cache_policy.py +388 -0
- alysis_code/llm/factory.py +373 -0
- alysis_code/llm/gemini_generate_content.py +2652 -0
- alysis_code/llm/gemini_interactions.py +739 -0
- alysis_code/llm/metadata.py +450 -0
- alysis_code/llm/openai_compat.py +2947 -0
- alysis_code/llm/openai_responses.py +2604 -0
- alysis_code/llm/protocols.py +609 -0
- alysis_code/llm/provider_limits.py +525 -0
- alysis_code/llm/request_plan.py +389 -0
- alysis_code/llm/request_shape.py +238 -0
- alysis_code/llm/streaming.py +108 -0
- alysis_code/llm/temperature_compat.py +78 -0
- alysis_code/llm/types.py +195 -0
- alysis_code/llm/usage_normalization.py +222 -0
- alysis_code/llm_error_display.py +315 -0
- alysis_code/logging_redaction.py +326 -0
- alysis_code/managed_host_deadline.py +196 -0
- alysis_code/mcp/README.md +33 -0
- alysis_code/mcp/__init__.py +24 -0
- alysis_code/mcp/client.py +1137 -0
- alysis_code/mcp/config.py +597 -0
- alysis_code/mcp/errors.py +113 -0
- alysis_code/mcp/forge_scope.py +154 -0
- alysis_code/mcp/jsonrpc.py +214 -0
- alysis_code/mcp/manager.py +2308 -0
- alysis_code/mcp/models.py +666 -0
- alysis_code/mcp/oauth.py +972 -0
- alysis_code/mcp/oauth_runtime.py +310 -0
- alysis_code/mcp/oauth_store.py +276 -0
- alysis_code/mcp/prompts.py +329 -0
- alysis_code/mcp/resources.py +295 -0
- alysis_code/mcp/roots.py +106 -0
- alysis_code/mcp/server_requests.py +75 -0
- alysis_code/mcp/token_store.py +859 -0
- alysis_code/mcp/transport_http.py +1338 -0
- alysis_code/mcp/transport_stdio.py +1267 -0
- alysis_code/mcp/untrusted_content.py +119 -0
- alysis_code/merge_conflict_reviewer.py +729 -0
- alysis_code/model_catalog/__init__.py +1 -0
- alysis_code/model_catalog/chatgpt_codex_subscription_snapshot.json +186 -0
- alysis_code/model_catalog/litellm_model_prices_snapshot.json +44715 -0
- alysis_code/model_catalog/litellm_model_prices_snapshot.meta.json +17 -0
- alysis_code/model_metadata_policy.py +223 -0
- alysis_code/model_metadata_utils.py +103 -0
- alysis_code/model_registry.py +1420 -0
- alysis_code/model_router.py +147 -0
- alysis_code/permission_policy.py +1016 -0
- alysis_code/personas.py +451 -0
- alysis_code/pipeline_facts.py +233 -0
- alysis_code/plan_assistant.py +4763 -0
- alysis_code/plan_mode.py +393 -0
- alysis_code/plan_reconciliation.py +1228 -0
- alysis_code/plan_repair.py +652 -0
- alysis_code/plan_validation.py +1099 -0
- alysis_code/planning_constraints.py +904 -0
- alysis_code/policy.py +95 -0
- alysis_code/preview_server.py +457 -0
- alysis_code/process_reaping.py +566 -0
- alysis_code/profile_presets.py +1834 -0
- alysis_code/profiles.py +666 -0
- alysis_code/provider_auth/__init__.py +29 -0
- alysis_code/provider_auth/base.py +99 -0
- alysis_code/provider_auth/openai_codex.py +951 -0
- alysis_code/provider_auth/registry.py +76 -0
- alysis_code/provider_auth/store.py +125 -0
- alysis_code/provider_diagnostics.py +1209 -0
- alysis_code/provider_model_catalog.py +685 -0
- alysis_code/provider_telemetry.py +1699 -0
- alysis_code/provider_url.py +75 -0
- alysis_code/reasoning_contracts.py +911 -0
- alysis_code/remote_sync.py +350 -0
- alysis_code/replanning.py +1195 -0
- alysis_code/repo_scan.py +1152 -0
- alysis_code/request_estimation.py +296 -0
- alysis_code/review_gate.py +617 -0
- alysis_code/run_lock.py +1141 -0
- alysis_code/run_outcome.py +58 -0
- alysis_code/run_provenance.py +774 -0
- alysis_code/run_state.py +445 -0
- alysis_code/runtime_artifacts.py +116 -0
- alysis_code/runtime_context_features.py +78 -0
- alysis_code/runtime_kind.py +52 -0
- alysis_code/safety/__init__.py +11 -0
- alysis_code/safety/mcp_sanitize.py +29 -0
- alysis_code/safety/safe_http.py +297 -0
- alysis_code/safety/subagent_report.py +184 -0
- alysis_code/sandbox_doctor.py +682 -0
- alysis_code/sandbox_runner.py +1025 -0
- alysis_code/sandbox_settings.py +423 -0
- alysis_code/serialized_paths.py +355 -0
- alysis_code/server/__init__.py +3 -0
- alysis_code/server/app.py +367 -0
- alysis_code/server/auth.py +34 -0
- alysis_code/server/job_config.py +30 -0
- alysis_code/server/settings.py +215 -0
- alysis_code/server/store.py +193 -0
- alysis_code/server/worker_runner.py +657 -0
- alysis_code/service_persistence.py +355 -0
- alysis_code/session_artifacts.py +108 -0
- alysis_code/session_metrics.py +331 -0
- alysis_code/session_store.py +624 -0
- alysis_code/skills/README.md +34 -0
- alysis_code/skills/__init__.py +104 -0
- alysis_code/skills/conventions.py +84 -0
- alysis_code/skills/discovery.py +176 -0
- alysis_code/skills/eval_models.py +232 -0
- alysis_code/skills/eval_runner.py +372 -0
- alysis_code/skills/evals.py +1344 -0
- alysis_code/skills/install.py +293 -0
- alysis_code/skills/loader.py +118 -0
- alysis_code/skills/matching.py +103 -0
- alysis_code/skills/models.py +71 -0
- alysis_code/skills/paths.py +56 -0
- alysis_code/skills/prompting.py +500 -0
- alysis_code/skills/scaffold.py +142 -0
- alysis_code/skills/state.py +441 -0
- alysis_code/skills/transactions.py +125 -0
- alysis_code/skills/validation.py +304 -0
- alysis_code/step_budget.py +238 -0
- alysis_code/subagent_labels.py +49 -0
- alysis_code/subagents.py +1072 -0
- alysis_code/surface/__init__.py +80 -0
- alysis_code/surface/base.py +305 -0
- alysis_code/surface/console.py +387 -0
- alysis_code/surface/events.py +372 -0
- alysis_code/surface/hidden_surface.py +529 -0
- alysis_code/surface/noop_surface.py +219 -0
- alysis_code/surface/rich_surface.py +1555 -0
- alysis_code/surface/styles.py +67 -0
- alysis_code/surface/theme.py +455 -0
- alysis_code/surface/types.py +100 -0
- alysis_code/swarm_backend.py +926 -0
- alysis_code/swarm_orchestrator.py +4020 -0
- alysis_code/swarm_scheduler.py +441 -0
- alysis_code/swarm_trace.py +429 -0
- alysis_code/swarm_worker.py +2119 -0
- alysis_code/swarm_write_guard.py +348 -0
- alysis_code/task_dependencies.py +170 -0
- alysis_code/task_readiness.py +992 -0
- alysis_code/task_scope.py +2148 -0
- alysis_code/terminal_manager.py +762 -0
- alysis_code/terminal_ownership.py +460 -0
- alysis_code/text_normalization.py +30 -0
- alysis_code/token_budget.py +97 -0
- alysis_code/tools/README.md +34 -0
- alysis_code/tools/__init__.py +1 -0
- alysis_code/tools/artifacts.py +127 -0
- alysis_code/tools/availability.py +188 -0
- alysis_code/tools/fs.py +1456 -0
- alysis_code/tools/git.py +461 -0
- alysis_code/tools/history.py +229 -0
- alysis_code/tools/http_timeout.py +78 -0
- alysis_code/tools/image_generation.py +552 -0
- alysis_code/tools/registry.py +2936 -0
- alysis_code/tools/repo_map.py +476 -0
- alysis_code/tools/search.py +563 -0
- alysis_code/tools/shell.py +135 -0
- alysis_code/tools/symbols.py +1350 -0
- alysis_code/tools/test_discovery.py +643 -0
- alysis_code/tools/web.py +482 -0
- alysis_code/tools/web_search.py +2012 -0
- alysis_code/tools/web_search_dashscope.py +557 -0
- alysis_code/tools/web_search_ddgs.py +221 -0
- alysis_code/tools/web_search_provider_adapters.py +1429 -0
- alysis_code/tools/web_search_tavily.py +194 -0
- alysis_code/updates.py +933 -0
- alysis_code/usage_tracker.py +1990 -0
- alysis_code/verification_command_analysis.py +1004 -0
- alysis_code/verification_contract.py +574 -0
- alysis_code/verification_failure_summary.py +273 -0
- alysis_code/verification_repair.py +385 -0
- alysis_code/verify_gate.py +3129 -0
- alysis_code/web_research.py +1872 -0
- alysis_code/web_search_adapters.py +66 -0
- alysis_code/web_search_policy.py +27 -0
- alysis_code/workspace_binding.py +389 -0
- alysis_code/workspace_binding_ui.py +408 -0
- alysis_code/workspace_context.py +273 -0
- alysis_code/workspace_isolation.py +138 -0
- alysis_code/workspace_provisioning.py +455 -0
- alysis_code-0.13.0.dist-info/METADATA +507 -0
- alysis_code-0.13.0.dist-info/RECORD +458 -0
- alysis_code-0.13.0.dist-info/WHEEL +4 -0
- alysis_code-0.13.0.dist-info/entry_points.txt +3 -0
- alysis_code-0.13.0.dist-info/licenses/LICENSE +176 -0
- alysis_code-0.13.0.dist-info/licenses/NOTICE +4 -0
|
@@ -0,0 +1,700 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from datetime import UTC, datetime
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from ...config import ConfigError
|
|
11
|
+
from ...mcp.config import load_resolved_mcp_config
|
|
12
|
+
from ...mcp.manager import McpManager, create_mcp_manager
|
|
13
|
+
from ...mcp.models import ResolvedMcpServer
|
|
14
|
+
from ...mcp.oauth import (
|
|
15
|
+
McpOAuthError,
|
|
16
|
+
discover_authorization_server_metadata,
|
|
17
|
+
discover_authorization_server_metadata_from_url,
|
|
18
|
+
discover_protected_resource_metadata,
|
|
19
|
+
resolve_requested_scopes,
|
|
20
|
+
)
|
|
21
|
+
from ...mcp.oauth_runtime import perform_authorization_code_login
|
|
22
|
+
from ...mcp.oauth_store import (
|
|
23
|
+
McpOAuthTokenStoreError,
|
|
24
|
+
delete_oauth_token_record,
|
|
25
|
+
load_oauth_token_record,
|
|
26
|
+
)
|
|
27
|
+
from ...runtime_kind import RuntimeKind, normalize_runtime_kind
|
|
28
|
+
from ._shared import _console, _resolve_tool_workspace_root, _Table
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from rich.console import Console
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
mcp_app = typer.Typer(add_completion=False, help="Manual MCP inspection commands.")
|
|
35
|
+
mcp_prompts_app = typer.Typer(add_completion=False, help="Manual MCP prompt commands.")
|
|
36
|
+
mcp_auth_app = typer.Typer(add_completion=False, help="Manual MCP OAuth commands.")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
_MANUAL_MCP_PROMPT_RUNTIME_KINDS = (
|
|
40
|
+
RuntimeKind.INTERACTIVE_CHAT,
|
|
41
|
+
RuntimeKind.ONE_SHOT,
|
|
42
|
+
RuntimeKind.FORGE_EXEC,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _parse_manual_mcp_prompt_runtime(value: RuntimeKind | str) -> RuntimeKind:
|
|
47
|
+
try:
|
|
48
|
+
runtime_kind = normalize_runtime_kind(value)
|
|
49
|
+
except ConfigError as exc:
|
|
50
|
+
raise typer.BadParameter(str(exc)) from exc
|
|
51
|
+
if runtime_kind not in _MANUAL_MCP_PROMPT_RUNTIME_KINDS:
|
|
52
|
+
allowed = ", ".join(kind.value for kind in _MANUAL_MCP_PROMPT_RUNTIME_KINDS)
|
|
53
|
+
raise typer.BadParameter(
|
|
54
|
+
f"manual MCP prompt access supports only these runtime profiles: {allowed}."
|
|
55
|
+
)
|
|
56
|
+
return runtime_kind
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _manual_mcp_manager_for_path(*, path: Path, runtime_kind: RuntimeKind | str) -> McpManager:
|
|
60
|
+
workspace_root = _resolve_tool_workspace_root(path=path)
|
|
61
|
+
return create_mcp_manager(
|
|
62
|
+
workspace_root=workspace_root,
|
|
63
|
+
runtime_kind=_parse_manual_mcp_prompt_runtime(runtime_kind),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _manual_mcp_resolved_config_for_path(*, path: Path) -> Any:
|
|
68
|
+
workspace_root = _resolve_tool_workspace_root(path=path)
|
|
69
|
+
return load_resolved_mcp_config(workspace_root=workspace_root)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _resolve_mcp_server_by_id(*, resolved_config: Any, server_id: str) -> ResolvedMcpServer:
|
|
73
|
+
normalized_server_id = str(server_id or "").strip().lower()
|
|
74
|
+
for server in resolved_config.servers:
|
|
75
|
+
if server.id == normalized_server_id:
|
|
76
|
+
return server
|
|
77
|
+
raise typer.BadParameter(f"Unknown MCP server id: {server_id}")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _require_http_oauth_server_for_auth(
|
|
81
|
+
*, server: ResolvedMcpServer, command_name: str
|
|
82
|
+
) -> ResolvedMcpServer:
|
|
83
|
+
if server.transport != "http":
|
|
84
|
+
raise typer.BadParameter(
|
|
85
|
+
f"MCP auth {command_name} requires an HTTP MCP server: {server.id}"
|
|
86
|
+
)
|
|
87
|
+
if server.oauth is None:
|
|
88
|
+
raise typer.BadParameter(
|
|
89
|
+
f"MCP server '{server.id}' does not have an oauth block in user config."
|
|
90
|
+
)
|
|
91
|
+
return server
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _format_mcp_oauth_timestamp(value: datetime | None) -> str:
|
|
95
|
+
if value is None:
|
|
96
|
+
return "-"
|
|
97
|
+
return value.astimezone(UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _mcp_oauth_expiry_summary(record: Any | None) -> str:
|
|
101
|
+
if record is None:
|
|
102
|
+
return "-"
|
|
103
|
+
expires_text = _format_mcp_oauth_timestamp(record.expires_at)
|
|
104
|
+
if record.expires_at <= datetime.now(UTC):
|
|
105
|
+
return f"expired ({expires_text})"
|
|
106
|
+
return expires_text
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _mcp_auth_status_table(*, rows: list[dict[str, str]]) -> Any:
|
|
110
|
+
table = _Table(title=f"MCP OAuth Status ({len(rows)})")
|
|
111
|
+
table.add_column("server_id", no_wrap=True)
|
|
112
|
+
table.add_column("auth_enabled", no_wrap=True)
|
|
113
|
+
table.add_column("token", no_wrap=True)
|
|
114
|
+
table.add_column("expires")
|
|
115
|
+
table.add_column("granted_scopes")
|
|
116
|
+
for row in rows:
|
|
117
|
+
table.add_row(
|
|
118
|
+
row["server_id"],
|
|
119
|
+
row["auth_enabled"],
|
|
120
|
+
row["token"],
|
|
121
|
+
row["expires"],
|
|
122
|
+
row["granted_scopes"],
|
|
123
|
+
)
|
|
124
|
+
return table
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _build_mcp_auth_status_rows(
|
|
128
|
+
*, resolved_config: Any, server_id: str | None
|
|
129
|
+
) -> list[dict[str, str]]:
|
|
130
|
+
selected_servers: list[ResolvedMcpServer]
|
|
131
|
+
if server_id is None:
|
|
132
|
+
selected_servers = [
|
|
133
|
+
server
|
|
134
|
+
for server in resolved_config.servers
|
|
135
|
+
if server.transport == "http" and server.oauth is not None
|
|
136
|
+
]
|
|
137
|
+
else:
|
|
138
|
+
selected_servers = [
|
|
139
|
+
_resolve_mcp_server_by_id(resolved_config=resolved_config, server_id=server_id)
|
|
140
|
+
]
|
|
141
|
+
rows: list[dict[str, str]] = []
|
|
142
|
+
for server in selected_servers:
|
|
143
|
+
token_record = None
|
|
144
|
+
if server.transport == "http" and server.oauth is not None:
|
|
145
|
+
token_record = load_oauth_token_record(server.id)
|
|
146
|
+
rows.append(
|
|
147
|
+
{
|
|
148
|
+
"server_id": server.id,
|
|
149
|
+
"auth_enabled": "yes"
|
|
150
|
+
if server.transport == "http" and server.oauth is not None
|
|
151
|
+
else "no",
|
|
152
|
+
"token": "present" if token_record is not None else "absent",
|
|
153
|
+
"expires": _mcp_oauth_expiry_summary(token_record),
|
|
154
|
+
"granted_scopes": (
|
|
155
|
+
", ".join(token_record.granted_scopes)
|
|
156
|
+
if token_record and token_record.granted_scopes
|
|
157
|
+
else "-"
|
|
158
|
+
),
|
|
159
|
+
}
|
|
160
|
+
)
|
|
161
|
+
return rows
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _parse_prompt_cli_arguments(values: list[str]) -> dict[str, str]:
|
|
165
|
+
parsed: dict[str, str] = {}
|
|
166
|
+
for raw_item in values:
|
|
167
|
+
item = str(raw_item or "")
|
|
168
|
+
if "=" not in item:
|
|
169
|
+
raise typer.BadParameter("--arg values must use key=value.")
|
|
170
|
+
raw_key, raw_value = item.split("=", 1)
|
|
171
|
+
key = raw_key.strip()
|
|
172
|
+
if not key:
|
|
173
|
+
raise typer.BadParameter("--arg keys cannot be empty.")
|
|
174
|
+
if key in parsed:
|
|
175
|
+
raise typer.BadParameter(f"duplicate --arg key: {key}")
|
|
176
|
+
parsed[key] = raw_value
|
|
177
|
+
return parsed
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _print_json_payload(*, console: Console, payload: dict[str, Any]) -> None:
|
|
181
|
+
console.file.write(json.dumps(payload, ensure_ascii=True, indent=2, sort_keys=True) + "\n")
|
|
182
|
+
console.file.flush()
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _mcp_prompt_argument_label(arguments: list[dict[str, Any]]) -> str:
|
|
186
|
+
parts: list[str] = []
|
|
187
|
+
for raw_item in arguments:
|
|
188
|
+
if not isinstance(raw_item, dict):
|
|
189
|
+
continue
|
|
190
|
+
name = str(raw_item.get("name") or "").strip()
|
|
191
|
+
if not name:
|
|
192
|
+
continue
|
|
193
|
+
required = bool(raw_item.get("required"))
|
|
194
|
+
parts.append(f"{name}{'*' if required else ''}")
|
|
195
|
+
return ", ".join(parts) if parts else "-"
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _mcp_prompt_list_table(*, prompts: list[dict[str, Any]]) -> Any:
|
|
199
|
+
table = _Table(title=f"MCP Prompts ({len(prompts)})")
|
|
200
|
+
table.add_column("server_id", no_wrap=True)
|
|
201
|
+
table.add_column("name", no_wrap=True)
|
|
202
|
+
table.add_column("title", no_wrap=True)
|
|
203
|
+
table.add_column("arguments")
|
|
204
|
+
table.add_column("description")
|
|
205
|
+
for prompt in prompts:
|
|
206
|
+
title = str(prompt.get("title") or "-").strip() or "-"
|
|
207
|
+
description = str(prompt.get("description") or "-").strip() or "-"
|
|
208
|
+
table.add_row(
|
|
209
|
+
str(prompt.get("server_id") or "").strip(),
|
|
210
|
+
str(prompt.get("name") or "").strip(),
|
|
211
|
+
title,
|
|
212
|
+
_mcp_prompt_argument_label(list(prompt.get("arguments") or [])),
|
|
213
|
+
description,
|
|
214
|
+
)
|
|
215
|
+
return table
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _mcp_status_table(*, rows: list[dict[str, str]]) -> Any:
|
|
219
|
+
table = _Table(title=f"MCP Status ({len(rows)})")
|
|
220
|
+
table.add_column("server_id", no_wrap=True)
|
|
221
|
+
table.add_column("transport", no_wrap=True)
|
|
222
|
+
table.add_column("tools")
|
|
223
|
+
table.add_column("resources")
|
|
224
|
+
table.add_column("prompts")
|
|
225
|
+
table.add_column("operator_action")
|
|
226
|
+
for row in rows:
|
|
227
|
+
table.add_row(
|
|
228
|
+
row["server_id"],
|
|
229
|
+
row["transport"],
|
|
230
|
+
row["tools"],
|
|
231
|
+
row["resources"],
|
|
232
|
+
row["prompts"],
|
|
233
|
+
row["operator_action"],
|
|
234
|
+
)
|
|
235
|
+
return table
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _mcp_status_surface_label(*, loaded: bool, count: int | None, stale: bool) -> str:
|
|
239
|
+
if loaded:
|
|
240
|
+
count_label = str(count if count is not None else 0)
|
|
241
|
+
label = f"loaded:{count_label}"
|
|
242
|
+
else:
|
|
243
|
+
label = "not_loaded"
|
|
244
|
+
if stale:
|
|
245
|
+
label += " stale:yes"
|
|
246
|
+
return label
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _mcp_status_payload(
|
|
250
|
+
*,
|
|
251
|
+
manager: McpManager,
|
|
252
|
+
bootstrap_errors: list[str],
|
|
253
|
+
prompt_errors: dict[str, str],
|
|
254
|
+
) -> dict[str, Any]:
|
|
255
|
+
metadata = manager.catalog_snapshot_metadata()
|
|
256
|
+
server_catalogs = {
|
|
257
|
+
str(entry.get("server_id") or ""): entry
|
|
258
|
+
for entry in metadata.get("server_catalogs") or []
|
|
259
|
+
if isinstance(entry, dict)
|
|
260
|
+
}
|
|
261
|
+
prompt_catalogs = {
|
|
262
|
+
str(entry.get("server_id") or ""): entry
|
|
263
|
+
for entry in metadata.get("prompt_server_catalogs") or []
|
|
264
|
+
if isinstance(entry, dict)
|
|
265
|
+
}
|
|
266
|
+
prompt_enabled_ids = {
|
|
267
|
+
str(server_id) for server_id in metadata.get("prompt_enabled_server_ids") or []
|
|
268
|
+
}
|
|
269
|
+
tool_stale_ids = {str(server_id) for server_id in metadata.get("tool_stale_server_ids") or []}
|
|
270
|
+
resource_stale_ids = {
|
|
271
|
+
str(server_id) for server_id in metadata.get("resource_stale_server_ids") or []
|
|
272
|
+
}
|
|
273
|
+
prompt_stale_ids = {
|
|
274
|
+
str(server_id) for server_id in metadata.get("prompt_stale_server_ids") or []
|
|
275
|
+
}
|
|
276
|
+
rows: list[dict[str, str]] = []
|
|
277
|
+
json_rows: list[dict[str, Any]] = []
|
|
278
|
+
for server in manager.active_servers:
|
|
279
|
+
server_catalog = server_catalogs.get(server.id)
|
|
280
|
+
prompt_catalog = prompt_catalogs.get(server.id)
|
|
281
|
+
tool_loaded = server_catalog is not None
|
|
282
|
+
tool_count = (
|
|
283
|
+
int(server_catalog.get("exposed_tool_count") or 0)
|
|
284
|
+
if server_catalog is not None
|
|
285
|
+
else None
|
|
286
|
+
)
|
|
287
|
+
resource_loaded = bool(
|
|
288
|
+
server_catalog is not None and server_catalog.get("resources_snapshot_loaded")
|
|
289
|
+
)
|
|
290
|
+
resource_count = (
|
|
291
|
+
int(server_catalog.get("snapshotted_resource_count") or 0)
|
|
292
|
+
if resource_loaded and server_catalog is not None
|
|
293
|
+
else None
|
|
294
|
+
)
|
|
295
|
+
prompt_loaded = bool(prompt_catalog and prompt_catalog.get("prompt_snapshot_loaded"))
|
|
296
|
+
prompt_failed = bool(prompt_catalog and prompt_catalog.get("prompt_snapshot_failed"))
|
|
297
|
+
prompt_count = (
|
|
298
|
+
int(prompt_catalog.get("snapshotted_prompt_count") or 0)
|
|
299
|
+
if prompt_catalog is not None
|
|
300
|
+
else None
|
|
301
|
+
)
|
|
302
|
+
prompt_stale = server.id in prompt_stale_ids
|
|
303
|
+
tool_stale = server.id in tool_stale_ids
|
|
304
|
+
resource_stale = server.id in resource_stale_ids
|
|
305
|
+
actions: list[str] = []
|
|
306
|
+
if tool_stale:
|
|
307
|
+
actions.append("new session for tools")
|
|
308
|
+
if resource_stale:
|
|
309
|
+
actions.append("new session for resources")
|
|
310
|
+
if prompt_stale:
|
|
311
|
+
actions.append("mcp prompts --refresh")
|
|
312
|
+
if server.id in prompt_errors:
|
|
313
|
+
actions.append("prompt snapshot error")
|
|
314
|
+
action_label = "; ".join(actions) if actions else "-"
|
|
315
|
+
if prompt_failed:
|
|
316
|
+
prompt_label = "failed"
|
|
317
|
+
elif server.id in prompt_enabled_ids:
|
|
318
|
+
prompt_label = _mcp_status_surface_label(
|
|
319
|
+
loaded=prompt_loaded,
|
|
320
|
+
count=prompt_count,
|
|
321
|
+
stale=prompt_stale,
|
|
322
|
+
)
|
|
323
|
+
prompt_label += " refresh:yes"
|
|
324
|
+
else:
|
|
325
|
+
prompt_label = "disabled"
|
|
326
|
+
row = {
|
|
327
|
+
"server_id": server.id,
|
|
328
|
+
"transport": server.transport,
|
|
329
|
+
"tools": _mcp_status_surface_label(
|
|
330
|
+
loaded=tool_loaded,
|
|
331
|
+
count=tool_count,
|
|
332
|
+
stale=tool_stale,
|
|
333
|
+
),
|
|
334
|
+
"resources": _mcp_status_surface_label(
|
|
335
|
+
loaded=resource_loaded,
|
|
336
|
+
count=resource_count,
|
|
337
|
+
stale=resource_stale,
|
|
338
|
+
),
|
|
339
|
+
"prompts": prompt_label,
|
|
340
|
+
"operator_action": action_label,
|
|
341
|
+
}
|
|
342
|
+
rows.append(row)
|
|
343
|
+
json_rows.append(
|
|
344
|
+
{
|
|
345
|
+
"server_id": server.id,
|
|
346
|
+
"transport": server.transport,
|
|
347
|
+
"tools_snapshot_loaded": tool_loaded,
|
|
348
|
+
"tools_snapshot_count": tool_count,
|
|
349
|
+
"tools_snapshot_stale": tool_stale,
|
|
350
|
+
"resources_snapshot_loaded": resource_loaded,
|
|
351
|
+
"resources_snapshot_count": resource_count,
|
|
352
|
+
"resources_snapshot_stale": resource_stale,
|
|
353
|
+
"prompts_enabled": server.id in prompt_enabled_ids,
|
|
354
|
+
"prompts_snapshot_loaded": prompt_loaded,
|
|
355
|
+
"prompts_snapshot_count": prompt_count,
|
|
356
|
+
"prompts_snapshot_stale": prompt_stale,
|
|
357
|
+
"manual_prompt_refresh_available": server.id in prompt_enabled_ids,
|
|
358
|
+
"operator_action": actions,
|
|
359
|
+
"prompt_error": prompt_errors.get(server.id),
|
|
360
|
+
}
|
|
361
|
+
)
|
|
362
|
+
return {
|
|
363
|
+
"rows": json_rows,
|
|
364
|
+
"table_rows": rows,
|
|
365
|
+
"bootstrap_errors": bootstrap_errors,
|
|
366
|
+
"prompt_errors": prompt_errors,
|
|
367
|
+
"metadata": metadata,
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def _print_prompt_get_payload(*, console: Console, payload: dict[str, Any]) -> None:
|
|
372
|
+
from rich.text import Text
|
|
373
|
+
|
|
374
|
+
metadata = _Table(title=f"MCP Prompt: {payload['server_id']}/{payload['name']}")
|
|
375
|
+
metadata.add_column("field")
|
|
376
|
+
metadata.add_column("value")
|
|
377
|
+
metadata.add_row("server_id", str(payload.get("server_id") or ""))
|
|
378
|
+
metadata.add_row("name", str(payload.get("name") or ""))
|
|
379
|
+
metadata.add_row("title", str(payload.get("title") or "-"))
|
|
380
|
+
metadata.add_row("description", str(payload.get("description") or "-"))
|
|
381
|
+
metadata.add_row(
|
|
382
|
+
"arguments",
|
|
383
|
+
_mcp_prompt_argument_label(list(payload.get("arguments") or [])),
|
|
384
|
+
)
|
|
385
|
+
metadata.add_row("message_count", str(payload.get("message_count") or 0))
|
|
386
|
+
metadata.add_row("content_summary", str(payload.get("content_summary") or "-"))
|
|
387
|
+
if payload.get("refresh_performed"):
|
|
388
|
+
metadata.add_row("refresh_performed", "yes")
|
|
389
|
+
if payload.get("snapshot_stale"):
|
|
390
|
+
metadata.add_row("snapshot_stale", "yes")
|
|
391
|
+
if isinstance(payload.get("applied_arguments"), dict):
|
|
392
|
+
metadata.add_row(
|
|
393
|
+
"applied_arguments",
|
|
394
|
+
json.dumps(payload["applied_arguments"], ensure_ascii=True, sort_keys=True),
|
|
395
|
+
)
|
|
396
|
+
console.print(metadata)
|
|
397
|
+
|
|
398
|
+
message_rows = list(payload.get("messages") or [])
|
|
399
|
+
if not message_rows:
|
|
400
|
+
return
|
|
401
|
+
messages_table = _Table(title="Prompt Messages")
|
|
402
|
+
messages_table.add_column("#", no_wrap=True)
|
|
403
|
+
messages_table.add_column("role", no_wrap=True)
|
|
404
|
+
messages_table.add_column("summary")
|
|
405
|
+
messages_table.add_column("text")
|
|
406
|
+
for index, raw_message in enumerate(message_rows, start=1):
|
|
407
|
+
if not isinstance(raw_message, dict):
|
|
408
|
+
continue
|
|
409
|
+
messages_table.add_row(
|
|
410
|
+
str(index),
|
|
411
|
+
str(raw_message.get("role") or "").strip(),
|
|
412
|
+
str(raw_message.get("content_summary") or "-"),
|
|
413
|
+
Text(str(raw_message.get("text") or "-")),
|
|
414
|
+
)
|
|
415
|
+
console.print(messages_table)
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
@mcp_auth_app.command("login")
|
|
419
|
+
def mcp_auth_login(
|
|
420
|
+
server_id: str = typer.Argument(..., help="Exact MCP server id."),
|
|
421
|
+
path: Path = typer.Option(Path("."), "--path", help="Workspace or repository path."),
|
|
422
|
+
) -> None:
|
|
423
|
+
console = _console()
|
|
424
|
+
try:
|
|
425
|
+
resolved_config = _manual_mcp_resolved_config_for_path(path=path)
|
|
426
|
+
server = _require_http_oauth_server_for_auth(
|
|
427
|
+
server=_resolve_mcp_server_by_id(resolved_config=resolved_config, server_id=server_id),
|
|
428
|
+
command_name="login",
|
|
429
|
+
)
|
|
430
|
+
assert server.url is not None
|
|
431
|
+
assert server.oauth is not None
|
|
432
|
+
configured_scopes = tuple(server.oauth.scopes or ())
|
|
433
|
+
protected_metadata = None
|
|
434
|
+
if not configured_scopes or server.oauth.authorization_server_url is None:
|
|
435
|
+
protected_metadata = discover_protected_resource_metadata(
|
|
436
|
+
server_id=server.id,
|
|
437
|
+
resource_server_url=server.url,
|
|
438
|
+
)
|
|
439
|
+
if server.oauth.authorization_server_url is not None:
|
|
440
|
+
metadata = discover_authorization_server_metadata_from_url(
|
|
441
|
+
server_id=server.id,
|
|
442
|
+
authorization_server_url=server.oauth.authorization_server_url,
|
|
443
|
+
)
|
|
444
|
+
elif protected_metadata is not None:
|
|
445
|
+
metadata = discover_authorization_server_metadata_from_url(
|
|
446
|
+
server_id=server.id,
|
|
447
|
+
authorization_server_url=protected_metadata.authorization_servers[0],
|
|
448
|
+
)
|
|
449
|
+
else:
|
|
450
|
+
metadata = discover_authorization_server_metadata(
|
|
451
|
+
server_id=server.id,
|
|
452
|
+
resource_server_url=server.url,
|
|
453
|
+
)
|
|
454
|
+
requested_scopes = resolve_requested_scopes(
|
|
455
|
+
configured_scopes=configured_scopes,
|
|
456
|
+
challenge_scope=None,
|
|
457
|
+
metadata_scopes_supported=(
|
|
458
|
+
protected_metadata.scopes_supported if protected_metadata is not None else None
|
|
459
|
+
),
|
|
460
|
+
existing_granted_scopes=None,
|
|
461
|
+
purpose="login",
|
|
462
|
+
)
|
|
463
|
+
result = perform_authorization_code_login(
|
|
464
|
+
server_id=server.id,
|
|
465
|
+
authorization_server_metadata=metadata,
|
|
466
|
+
resource_server_url=server.url,
|
|
467
|
+
client_id=server.oauth.client_id,
|
|
468
|
+
scopes=requested_scopes,
|
|
469
|
+
redirect_host=server.oauth.redirect_host,
|
|
470
|
+
redirect_port=server.oauth.redirect_port,
|
|
471
|
+
output_write=lambda message: console.print(message, highlight=False),
|
|
472
|
+
)
|
|
473
|
+
scopes_text = (
|
|
474
|
+
", ".join(result.token_record.granted_scopes)
|
|
475
|
+
if result.token_record.granted_scopes
|
|
476
|
+
else "-"
|
|
477
|
+
)
|
|
478
|
+
console.print(f"OAuth login succeeded for MCP server '{server.id}'.")
|
|
479
|
+
console.print(f"Expires: {_format_mcp_oauth_timestamp(result.token_record.expires_at)}")
|
|
480
|
+
console.print(f"Granted scopes: {scopes_text}")
|
|
481
|
+
except (ConfigError, McpOAuthError, McpOAuthTokenStoreError, typer.BadParameter) as exc:
|
|
482
|
+
message = str(exc).replace("\n", " ")
|
|
483
|
+
console.print(f"[red]{message}[/red]", soft_wrap=True)
|
|
484
|
+
raise typer.Exit(code=1) from exc
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
@mcp_auth_app.command("status")
|
|
488
|
+
def mcp_auth_status(
|
|
489
|
+
server: str | None = typer.Option(
|
|
490
|
+
None, "--server", help="Optional exact MCP server id filter."
|
|
491
|
+
),
|
|
492
|
+
path: Path = typer.Option(Path("."), "--path", help="Workspace or repository path."),
|
|
493
|
+
) -> None:
|
|
494
|
+
console = _console()
|
|
495
|
+
try:
|
|
496
|
+
resolved_config = _manual_mcp_resolved_config_for_path(path=path)
|
|
497
|
+
rows = _build_mcp_auth_status_rows(resolved_config=resolved_config, server_id=server)
|
|
498
|
+
if not rows:
|
|
499
|
+
console.print("No OAuth-enabled HTTP MCP servers configured.")
|
|
500
|
+
return
|
|
501
|
+
console.print(_mcp_auth_status_table(rows=rows))
|
|
502
|
+
except (ConfigError, McpOAuthTokenStoreError, typer.BadParameter) as exc:
|
|
503
|
+
console.print(f"[red]{exc}[/red]")
|
|
504
|
+
raise typer.Exit(code=1) from exc
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
@mcp_auth_app.command("logout")
|
|
508
|
+
def mcp_auth_logout(
|
|
509
|
+
server_id: str = typer.Argument(..., help="Exact MCP server id."),
|
|
510
|
+
path: Path = typer.Option(Path("."), "--path", help="Workspace or repository path."),
|
|
511
|
+
) -> None:
|
|
512
|
+
console = _console()
|
|
513
|
+
try:
|
|
514
|
+
resolved_config = _manual_mcp_resolved_config_for_path(path=path)
|
|
515
|
+
server = _require_http_oauth_server_for_auth(
|
|
516
|
+
server=_resolve_mcp_server_by_id(resolved_config=resolved_config, server_id=server_id),
|
|
517
|
+
command_name="logout",
|
|
518
|
+
)
|
|
519
|
+
removed = delete_oauth_token_record(server.id)
|
|
520
|
+
if removed:
|
|
521
|
+
console.print(f"Cleared stored OAuth tokens for MCP server '{server.id}'.")
|
|
522
|
+
return
|
|
523
|
+
console.print(f"No stored OAuth tokens found for MCP server '{server.id}'.")
|
|
524
|
+
except (ConfigError, McpOAuthTokenStoreError, typer.BadParameter) as exc:
|
|
525
|
+
console.print(f"[red]{exc}[/red]")
|
|
526
|
+
raise typer.Exit(code=1) from exc
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
@mcp_app.command("status")
|
|
530
|
+
def mcp_status(
|
|
531
|
+
path: Path = typer.Option(Path("."), "--path", help="Workspace or repository path."),
|
|
532
|
+
runtime: str = typer.Option(
|
|
533
|
+
RuntimeKind.INTERACTIVE_CHAT.value,
|
|
534
|
+
"--runtime",
|
|
535
|
+
help=(
|
|
536
|
+
"Runtime profile used to resolve enabled MCP servers: "
|
|
537
|
+
"interactive_chat, one_shot, or forge_exec."
|
|
538
|
+
),
|
|
539
|
+
),
|
|
540
|
+
as_json: bool = typer.Option(False, "--json", help="Emit deterministic JSON output."),
|
|
541
|
+
) -> None:
|
|
542
|
+
console = _console()
|
|
543
|
+
manager: McpManager | None = None
|
|
544
|
+
bootstrap_errors: list[str] = []
|
|
545
|
+
prompt_errors: dict[str, str] = {}
|
|
546
|
+
try:
|
|
547
|
+
manager = _manual_mcp_manager_for_path(path=path, runtime_kind=runtime)
|
|
548
|
+
try:
|
|
549
|
+
_ = manager.tool_bindings
|
|
550
|
+
except (ConfigError, RuntimeError) as exc:
|
|
551
|
+
bootstrap_errors.append(str(exc))
|
|
552
|
+
prompt_enabled_ids = [
|
|
553
|
+
str(server_id)
|
|
554
|
+
for server_id in manager.catalog_snapshot_metadata().get("prompt_enabled_server_ids")
|
|
555
|
+
or []
|
|
556
|
+
]
|
|
557
|
+
for prompt_server_id in prompt_enabled_ids:
|
|
558
|
+
try:
|
|
559
|
+
manager.list_prompts(server_id=prompt_server_id, limit=1)
|
|
560
|
+
except (ConfigError, RuntimeError) as exc:
|
|
561
|
+
prompt_errors[prompt_server_id] = str(exc)
|
|
562
|
+
payload = _mcp_status_payload(
|
|
563
|
+
manager=manager,
|
|
564
|
+
bootstrap_errors=bootstrap_errors,
|
|
565
|
+
prompt_errors=prompt_errors,
|
|
566
|
+
)
|
|
567
|
+
if as_json:
|
|
568
|
+
_print_json_payload(
|
|
569
|
+
console=console,
|
|
570
|
+
payload={key: value for key, value in payload.items() if key != "table_rows"},
|
|
571
|
+
)
|
|
572
|
+
return
|
|
573
|
+
table_rows = list(payload.get("table_rows") or [])
|
|
574
|
+
if not table_rows:
|
|
575
|
+
console.print("No active MCP servers for this runtime.")
|
|
576
|
+
return
|
|
577
|
+
console.print(_mcp_status_table(rows=table_rows))
|
|
578
|
+
for error in bootstrap_errors:
|
|
579
|
+
console.print(f"[yellow]Snapshot warning:[/yellow] {error}")
|
|
580
|
+
for server_id, error in prompt_errors.items():
|
|
581
|
+
console.print(f"[yellow]Prompt warning for {server_id}:[/yellow] {error}")
|
|
582
|
+
except ConfigError as exc:
|
|
583
|
+
console.print(f"[red]{exc}[/red]")
|
|
584
|
+
raise typer.Exit(code=1) from exc
|
|
585
|
+
finally:
|
|
586
|
+
if manager is not None:
|
|
587
|
+
manager.close()
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
@mcp_prompts_app.command("list")
|
|
591
|
+
def mcp_prompts_list(
|
|
592
|
+
path: Path = typer.Option(Path("."), "--path", help="Workspace or repository path."),
|
|
593
|
+
runtime: str = typer.Option(
|
|
594
|
+
RuntimeKind.INTERACTIVE_CHAT.value,
|
|
595
|
+
"--runtime",
|
|
596
|
+
help=(
|
|
597
|
+
"Runtime profile used to resolve enabled MCP servers: "
|
|
598
|
+
"interactive_chat, one_shot, or forge_exec."
|
|
599
|
+
),
|
|
600
|
+
),
|
|
601
|
+
server: str | None = typer.Option(
|
|
602
|
+
None, "--server", help="Optional exact MCP server id filter."
|
|
603
|
+
),
|
|
604
|
+
query: str | None = typer.Option(None, "--query", help="Optional text filter."),
|
|
605
|
+
limit: int = typer.Option(20, "--limit", min=1, max=50, help="Maximum prompts to display."),
|
|
606
|
+
refresh: bool = typer.Option(
|
|
607
|
+
False,
|
|
608
|
+
"--refresh",
|
|
609
|
+
help="Explicitly refresh the targeted prompt snapshot before listing.",
|
|
610
|
+
),
|
|
611
|
+
as_json: bool = typer.Option(False, "--json", help="Emit deterministic JSON output."),
|
|
612
|
+
) -> None:
|
|
613
|
+
console = _console()
|
|
614
|
+
manager: McpManager | None = None
|
|
615
|
+
try:
|
|
616
|
+
manager = _manual_mcp_manager_for_path(path=path, runtime_kind=runtime)
|
|
617
|
+
try:
|
|
618
|
+
payload = manager.list_prompts(
|
|
619
|
+
server_id=server,
|
|
620
|
+
query=query,
|
|
621
|
+
limit=limit,
|
|
622
|
+
refresh=refresh,
|
|
623
|
+
)
|
|
624
|
+
except (ConfigError, RuntimeError) as exc:
|
|
625
|
+
console.print(f"[red]{exc}[/red]")
|
|
626
|
+
raise typer.Exit(code=1) from exc
|
|
627
|
+
prompts = list(payload.get("prompts") or [])
|
|
628
|
+
if as_json:
|
|
629
|
+
_print_json_payload(console=console, payload=payload)
|
|
630
|
+
return
|
|
631
|
+
if not prompts:
|
|
632
|
+
console.print("No MCP prompts available.")
|
|
633
|
+
return
|
|
634
|
+
console.print(_mcp_prompt_list_table(prompts=prompts))
|
|
635
|
+
console.print(
|
|
636
|
+
f"[dim]Returned:[/dim] {payload['returned_count']} "
|
|
637
|
+
f"[dim]Matching:[/dim] {payload['matching_count']} "
|
|
638
|
+
f"[dim]Snapshot:[/dim] {payload['total_snapshot_count']}"
|
|
639
|
+
)
|
|
640
|
+
if payload.get("refresh_performed"):
|
|
641
|
+
console.print("[dim]Prompt snapshot refreshed explicitly.[/dim]")
|
|
642
|
+
stale_server_ids = payload.get("stale_server_ids")
|
|
643
|
+
if stale_server_ids:
|
|
644
|
+
console.print(
|
|
645
|
+
"[yellow]Stale prompt snapshot:[/yellow] "
|
|
646
|
+
f"{', '.join(str(item) for item in stale_server_ids)}"
|
|
647
|
+
)
|
|
648
|
+
except ConfigError as exc:
|
|
649
|
+
console.print(f"[red]{exc}[/red]")
|
|
650
|
+
raise typer.Exit(code=1) from exc
|
|
651
|
+
finally:
|
|
652
|
+
if manager is not None:
|
|
653
|
+
manager.close()
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
@mcp_prompts_app.command("get")
|
|
657
|
+
def mcp_prompts_get(
|
|
658
|
+
server_id: str = typer.Argument(..., help="Exact MCP server id."),
|
|
659
|
+
prompt_name: str = typer.Argument(..., help="Exact MCP prompt name."),
|
|
660
|
+
path: Path = typer.Option(Path("."), "--path", help="Workspace or repository path."),
|
|
661
|
+
runtime: str = typer.Option(
|
|
662
|
+
RuntimeKind.INTERACTIVE_CHAT.value,
|
|
663
|
+
"--runtime",
|
|
664
|
+
help=(
|
|
665
|
+
"Runtime profile used to resolve enabled MCP servers: "
|
|
666
|
+
"interactive_chat, one_shot, or forge_exec."
|
|
667
|
+
),
|
|
668
|
+
),
|
|
669
|
+
arg: list[str] = typer.Option(None, "--arg", help="Prompt string argument as key=value."),
|
|
670
|
+
refresh: bool = typer.Option(
|
|
671
|
+
False,
|
|
672
|
+
"--refresh",
|
|
673
|
+
help="Explicitly refresh this server's prompt snapshot before fetching.",
|
|
674
|
+
),
|
|
675
|
+
as_json: bool = typer.Option(False, "--json", help="Emit deterministic JSON output."),
|
|
676
|
+
) -> None:
|
|
677
|
+
console = _console()
|
|
678
|
+
manager: McpManager | None = None
|
|
679
|
+
try:
|
|
680
|
+
manager = _manual_mcp_manager_for_path(path=path, runtime_kind=runtime)
|
|
681
|
+
try:
|
|
682
|
+
payload = manager.get_prompt(
|
|
683
|
+
server_id=server_id,
|
|
684
|
+
prompt_name=prompt_name,
|
|
685
|
+
arguments=_parse_prompt_cli_arguments(list(arg or [])),
|
|
686
|
+
refresh=refresh,
|
|
687
|
+
)
|
|
688
|
+
except (ConfigError, RuntimeError) as exc:
|
|
689
|
+
console.print(f"[red]{exc}[/red]")
|
|
690
|
+
raise typer.Exit(code=1) from exc
|
|
691
|
+
if as_json:
|
|
692
|
+
_print_json_payload(console=console, payload=payload)
|
|
693
|
+
return
|
|
694
|
+
_print_prompt_get_payload(console=console, payload=payload)
|
|
695
|
+
except ConfigError as exc:
|
|
696
|
+
console.print(f"[red]{exc}[/red]")
|
|
697
|
+
raise typer.Exit(code=1) from exc
|
|
698
|
+
finally:
|
|
699
|
+
if manager is not None:
|
|
700
|
+
manager.close()
|